mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-05-18 00:45:37 +08:00
116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"mayfly-go/internal/ai/imsg"
|
|
"mayfly-go/internal/ai/session"
|
|
"mayfly-go/pkg/i18n"
|
|
"mayfly-go/pkg/utils/jsonx"
|
|
|
|
"github.com/cloudwego/eino/components/tool"
|
|
"github.com/cloudwego/eino/compose"
|
|
"github.com/cloudwego/eino/schema"
|
|
)
|
|
|
|
type ApprovalInfo struct {
|
|
BaseInterruptInfo
|
|
}
|
|
|
|
var _ InterruptMetadata = (*ApprovalInfo)(nil)
|
|
|
|
func NewArrpovalInfo(ctx context.Context, toolInfo *schema.ToolInfo, arguments string) *ApprovalInfo {
|
|
ti := &ToolInfo{
|
|
Name: toolInfo.Name,
|
|
Desc: toolInfo.Desc,
|
|
}
|
|
toolJsonSchema, err := toolInfo.ParamsOneOf.ToJSONSchema()
|
|
if err != nil {
|
|
ti.JsonSchema = jsonx.ToStr(toolJsonSchema)
|
|
}
|
|
|
|
ai := &ApprovalInfo{
|
|
BaseInterruptInfo: BaseInterruptInfo{
|
|
Type: InterruptTypeApproval,
|
|
ToolInfo: ti,
|
|
ToolCallId: compose.GetToolCallID(ctx),
|
|
Arguments: arguments,
|
|
Description: i18n.T(imsg.ApprovalDesc),
|
|
Title: i18n.T(imsg.ApprovalTitle),
|
|
}}
|
|
return ai
|
|
}
|
|
|
|
func InterruptOrResumeApproval(ctx context.Context, toolDesc string, arguments any, reason string) error {
|
|
isApprovalResume, err := ResumeApproval(ctx, toolDesc)
|
|
if !isApprovalResume {
|
|
return InterruptApproval(ctx, toolDesc, arguments, reason)
|
|
}
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return NewToolError(err, RecoverRetry)
|
|
}
|
|
|
|
func InterruptApproval(ctx context.Context, toolDesc string, arguments any, reason string) error {
|
|
argumentsInJSON := jsonx.ToStr(arguments)
|
|
ai := &ApprovalInfo{
|
|
BaseInterruptInfo: BaseInterruptInfo{
|
|
Type: InterruptTypeApproval,
|
|
ToolCallId: compose.GetToolCallID(ctx),
|
|
Arguments: argumentsInJSON,
|
|
Description: reason,
|
|
ToolInfo: &ToolInfo{Name: toolDesc},
|
|
Title: i18n.T(imsg.ApprovalTitle),
|
|
}}
|
|
return tool.StatefulInterrupt(ctx, ai, argumentsInJSON)
|
|
}
|
|
|
|
func ResumeApproval(ctx context.Context, toolDesc string) (bool, error) {
|
|
// 首先检查是否已审批过
|
|
messages, _ := session.DefaultSessionStore.GetMessage(ctx, &session.MessageQuery{MessageType: string(InterruptTypeApproval), ToolCallId: compose.GetToolCallID(ctx)})
|
|
if len(messages) > 0 {
|
|
for _, msg := range messages {
|
|
var resumeInfo ApprovalResume
|
|
if err := msg.Extra.Unmarshal("resumeInfo", &resumeInfo); err != nil {
|
|
continue
|
|
}
|
|
return true, handleApprovalResult(ctx, toolDesc, &resumeInfo)
|
|
}
|
|
}
|
|
|
|
wasInterrupted, _, _ := tool.GetInterruptState[string](ctx)
|
|
if !wasInterrupted {
|
|
return false, nil
|
|
}
|
|
|
|
isResumeTarget, hasData, data := tool.GetResumeContext[*ApprovalResume](ctx)
|
|
if !isResumeTarget || !hasData {
|
|
return false, nil
|
|
}
|
|
|
|
AppendResumeInfo(ctx, data.InterruptId, data)
|
|
return true, handleApprovalResult(ctx, toolDesc, data)
|
|
}
|
|
|
|
func handleApprovalResult(ctx context.Context, toolDesc string, data *ApprovalResume) error {
|
|
if data.Action == "approve" {
|
|
return nil
|
|
}
|
|
|
|
if data.Action == "reject" {
|
|
reason := cmp.Or(data.Payload.GetStr("reason"), i18n.T(imsg.RejectReasonDefault))
|
|
// 构建更清晰的拒绝消息
|
|
msg := fmt.Sprintf(
|
|
"[OPERATION_REJECTED] The tool '%s' was explicitly rejected by the user.\nReason: %s\nPlease do not retry this action automatically. Ask the user for further instructions if needed.",
|
|
toolDesc,
|
|
reason,
|
|
)
|
|
return errors.New(msg)
|
|
}
|
|
|
|
return fmt.Errorf("[OPERATION_CANCELLED] The tool '%s' execution was cancelled due to invalid action: %s", toolDesc, data.Action)
|
|
}
|