chore: import clean Agent Desk snapshot (2/7)

This commit is contained in:
2026-10-16 11:57:21 +00:00
parent 79176541c7
commit 89c5231c4c
461 changed files with 60092 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
package tools
import (
"context"
"agent-desk/internal/ai/runtime/graphs"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type AnalyzeConversationTool struct {
conversation models.Conversation
}
func NewAnalyzeConversationTool() *AnalyzeConversationTool {
return &AnalyzeConversationTool{}
}
func (t *AnalyzeConversationTool) Spec() toolx.ToolSpec {
return toolx.GraphAnalyzeConversation
}
func (t *AnalyzeConversationTool) Name() string {
return toolx.GraphAnalyzeConversation.Name
}
func (t *AnalyzeConversationTool) Code() string {
return toolx.GraphAnalyzeConversation.Code
}
func (t *AnalyzeConversationTool) Enabled(ctx registry.Context) bool {
return true
}
func (t *AnalyzeConversationTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &AnalyzeConversationTool{conversation: ctx.Conversation}, nil
}
func (t *AnalyzeConversationTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.GraphAnalyzeConversation.Name,
Desc: i18nx.Get("tool.graph.analyzeConversation.info"),
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "goal",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.analyzeConversation.param.goal"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "observedIssue",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.analyzeConversation.param.observedIssue"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "needTicket",
Value: &einojsonschema.Schema{
Type: "boolean",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.needTicket"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "needHumanHandoff",
Value: &einojsonschema.Schema{
Type: "boolean",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.needHumanHandoff"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "needQualityCheck",
Value: &einojsonschema.Schema{
Type: "boolean",
Description: i18nx.Get("tool.graph.analyzeConversation.param.needQualityCheck"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "additionalContext",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.analyzeConversation.param.additionalContext"),
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.GraphAnalyzeConversation.Code,
"sourceType": toolx.GraphAnalyzeConversation.SourceType,
},
}, nil
}
func (t *AnalyzeConversationTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
return graphs.NewAnalyzeConversationGraph(t.conversation).Run(ctx, argumentsInJSON)
}

View File

@@ -0,0 +1,90 @@
package tools
import (
"context"
"agent-desk/internal/ai/runtime/graphs"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type CreateTicketGraphTool struct {
conversation models.Conversation
aiAgent models.AIAgent
}
func NewCreateTicketGraphTool() *CreateTicketGraphTool {
return &CreateTicketGraphTool{}
}
func (t *CreateTicketGraphTool) Spec() toolx.ToolSpec {
return toolx.GraphCreateTicketConfirm
}
func (t *CreateTicketGraphTool) Name() string {
return toolx.GraphCreateTicketConfirm.Name
}
func (t *CreateTicketGraphTool) Code() string {
return toolx.GraphCreateTicketConfirm.Code
}
func (t *CreateTicketGraphTool) Enabled(ctx registry.Context) bool {
return true
}
func (t *CreateTicketGraphTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &CreateTicketGraphTool{
conversation: ctx.Conversation,
aiAgent: ctx.AIAgent,
}, nil
}
func (t *CreateTicketGraphTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.GraphCreateTicketConfirm.Name,
Desc: i18nx.Get("tool.graph.createTicketConfirm.info"),
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Required: []string{
"title",
"description",
},
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "title",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.createTicketConfirm.param.title"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "description",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.createTicketConfirm.param.description"),
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.GraphCreateTicketConfirm.Code,
"sourceType": "graph",
},
}, nil
}
func (t *CreateTicketGraphTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
return graphs.NewCreateTicketGraph(t.conversation, t.aiAgent).Run(ctx, argumentsInJSON)
}

View File

@@ -0,0 +1,79 @@
package tools
import (
"context"
"agent-desk/internal/ai/runtime/graphs"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type HandoffGraphTool struct {
conversation models.Conversation
aiAgent models.AIAgent
}
func NewHandoffGraphTool() *HandoffGraphTool {
return &HandoffGraphTool{}
}
func (t *HandoffGraphTool) Spec() toolx.ToolSpec {
return toolx.GraphHandoffConversation
}
func (t *HandoffGraphTool) Name() string {
return toolx.GraphHandoffConversation.Name
}
func (t *HandoffGraphTool) Code() string {
return toolx.GraphHandoffConversation.Code
}
func (t *HandoffGraphTool) Enabled(ctx registry.Context) bool {
return true
}
func (t *HandoffGraphTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &HandoffGraphTool{
conversation: ctx.Conversation,
aiAgent: ctx.AIAgent,
}, nil
}
func (t *HandoffGraphTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.GraphHandoffConversation.Name,
Desc: i18nx.Get("tool.graph.handoffConversation.info"),
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "reason",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.handoffConversation.param.reason"),
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.GraphHandoffConversation.Code,
"sourceType": toolx.GraphHandoffConversation.SourceType,
},
}, nil
}
func (t *HandoffGraphTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
return graphs.NewHandoffGraph(t.conversation, t.aiAgent).Run(ctx, argumentsInJSON)
}

View File

@@ -0,0 +1,52 @@
package tools
import (
"strings"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/pkg/toolx"
)
type Decision string
const (
DecisionConfirm Decision = "confirm"
DecisionCancel Decision = "cancel"
)
func ParseConfirmationDecision(value string) Decision {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return ""
}
confirmWords := []string{"确认", "是", "好的", "可以", "ok", "yes", "继续", "同意"}
for _, item := range confirmWords {
if strings.Contains(value, item) {
return DecisionConfirm
}
}
cancelWords := []string{"取消", "不用", "不需要", "算了", "no"}
for _, item := range cancelWords {
if strings.Contains(value, item) {
return DecisionCancel
}
}
return ""
}
func NewRuntimeStaticTool(toolCode string) registry.Tool {
switch toolx.NormalizeToolCodeAlias(strings.TrimSpace(toolCode)) {
case toolx.GraphTriageServiceRequest.Code:
return NewTriageServiceRequestTool()
case toolx.GraphAnalyzeConversation.Code:
return NewAnalyzeConversationTool()
case toolx.GraphPrepareTicketDraft.Code:
return NewPrepareTicketDraftTool()
case toolx.GraphCreateTicketConfirm.Code:
return NewCreateTicketGraphTool()
case toolx.GraphHandoffConversation.Code:
return NewHandoffGraphTool()
default:
return nil
}
}

View File

@@ -0,0 +1,32 @@
package tools
import (
"testing"
"agent-desk/internal/pkg/toolx"
)
func TestNewRuntimeStaticTool(t *testing.T) {
items := []string{
toolx.GraphTriageServiceRequest.Code,
toolx.GraphAnalyzeConversation.Code,
toolx.GraphPrepareTicketDraft.Code,
toolx.GraphCreateTicketConfirm.Code,
toolx.GraphHandoffConversation.Code,
}
for _, item := range items {
tool := NewRuntimeStaticTool(item)
if tool == nil {
t.Fatalf("expected runtime static tool for %s", item)
}
if tool.Spec().Code != item {
t.Fatalf("unexpected tool code for %s: %s", item, tool.Spec().Code)
}
}
}
func TestNewRuntimeStaticToolReturnsNilForUnknownTool(t *testing.T) {
if tool := NewRuntimeStaticTool("builtin/unknown_tool"); tool != nil {
t.Fatalf("expected nil tool for unknown tool code")
}
}

View File

@@ -0,0 +1,110 @@
package tools
import (
"context"
"agent-desk/internal/ai/runtime/graphs"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type PrepareTicketDraftTool struct {
conversation models.Conversation
}
func NewPrepareTicketDraftTool() *PrepareTicketDraftTool {
return &PrepareTicketDraftTool{}
}
func (t *PrepareTicketDraftTool) Spec() toolx.ToolSpec {
return toolx.GraphPrepareTicketDraft
}
func (t *PrepareTicketDraftTool) Name() string {
return toolx.GraphPrepareTicketDraft.Name
}
func (t *PrepareTicketDraftTool) Code() string {
return toolx.GraphPrepareTicketDraft.Code
}
func (t *PrepareTicketDraftTool) Enabled(ctx registry.Context) bool {
return true
}
func (t *PrepareTicketDraftTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &PrepareTicketDraftTool{conversation: ctx.Conversation}, nil
}
func (t *PrepareTicketDraftTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.GraphPrepareTicketDraft.Name,
Desc: i18nx.Get("tool.graph.prepareTicketDraft.info"),
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "title",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.title"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "description",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.description"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "issue",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.issue"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "impact",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.impact"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "expectedOutcome",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.expectedOutcome"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "currentAttempt",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.prepareTicketDraft.param.currentAttempt"),
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.GraphPrepareTicketDraft.Code,
"sourceType": "graph",
},
}, nil
}
func (t *PrepareTicketDraftTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
return graphs.NewPrepareTicketDraftGraph(t.conversation).Run(ctx, argumentsInJSON)
}

View File

@@ -0,0 +1,287 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"slices"
"strings"
"agent-desk/internal/ai/mcps"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/ai/runtime/tooling"
aitooling "agent-desk/internal/ai/tooling"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type ToolSearchTool struct {
allowedToolCodes []string
}
func NewToolSearchTool() *ToolSearchTool {
return &ToolSearchTool{}
}
func (t *ToolSearchTool) Spec() toolx.ToolSpec {
return toolx.BuiltinToolSearch
}
func (t *ToolSearchTool) Name() string {
return toolx.BuiltinToolSearch.Name
}
func (t *ToolSearchTool) Code() string {
return toolx.BuiltinToolSearch.Code
}
func (t *ToolSearchTool) Enabled(ctx registry.Context) bool {
return len(filterAllowedMCPToolCodes(ctx.AllowedToolCodes)) > 0
}
func (t *ToolSearchTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &ToolSearchTool{
allowedToolCodes: filterAllowedMCPToolCodes(ctx.AllowedToolCodes),
}, nil
}
func (t *ToolSearchTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.BuiltinToolSearch.Name,
Desc: "Search and execute allowed MCP tools. Call this tool with a query to find suitable tools, or provide toolCode and arguments to execute a specific tool.",
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "query",
Value: &einojsonschema.Schema{
Type: "string",
Description: "Query or keyword describing the desired capability or tool intent; used when searching for candidate tools.",
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "toolCode",
Value: &einojsonschema.Schema{
Type: "string",
Description: "The exact tool code to invoke (e.g. mcp_server/tool_name).",
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "arguments",
Value: &einojsonschema.Schema{
Type: "object",
Description: "Arguments object passed to the target tool.",
AdditionalProperties: &einojsonschema.Schema{},
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.BuiltinToolSearch.Code,
},
}, nil
}
func (t *ToolSearchTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
if t == nil {
return "", fmt.Errorf("tool search tool is nil")
}
req, err := parseToolSearchRequest(argumentsInJSON)
if err != nil {
return "", err
}
if req.ToolCode != "" {
return t.invokeTargetTool(ctx, req.ToolCode, req.Arguments)
}
return t.searchCandidates(ctx, req.Query)
}
type toolSearchRequest struct {
Query string `json:"query"`
ToolCode string `json:"toolCode"`
Arguments map[string]any `json:"arguments"`
}
type toolSearchCandidate struct {
ToolCode string `json:"toolCode"`
ServerCode string `json:"serverCode"`
ToolName string `json:"toolName"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
}
func parseToolSearchRequest(argumentsInJSON string) (*toolSearchRequest, error) {
argumentsInJSON = strings.TrimSpace(argumentsInJSON)
if argumentsInJSON == "" {
return &toolSearchRequest{}, nil
}
var req toolSearchRequest
if err := json.Unmarshal([]byte(argumentsInJSON), &req); err != nil {
return nil, fmt.Errorf("invalid tool_search arguments: %w", err)
}
req.Query = strings.TrimSpace(req.Query)
req.ToolCode = strings.TrimSpace(req.ToolCode)
if req.Arguments == nil {
req.Arguments = map[string]any{}
}
return &req, nil
}
func (t *ToolSearchTool) searchCandidates(ctx context.Context, query string) (string, error) {
candidates, err := t.loadAllowedCandidates(ctx)
if err != nil {
return "", err
}
matched := filterCandidatesByQuery(candidates, query)
if len(matched) == 0 {
return "未找到匹配的动态工具,请换个关键词,或继续向用户追问后再搜索。", nil
}
if len(matched) > 8 {
matched = matched[:8]
}
buf, err := json.Marshal(map[string]any{
"query": strings.TrimSpace(query),
"total": len(matched),
"candidates": matched,
})
if err != nil {
return "", err
}
return string(buf), nil
}
func (t *ToolSearchTool) invokeTargetTool(ctx context.Context, toolCode string, arguments map[string]any) (string, error) {
toolCode = strings.TrimSpace(toolCode)
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
if serverCode == "" || toolName == "" {
return "", i18nx.Errorf("error.e0077")
}
if !containsToolCode(t.allowedToolCodes, toolCode) {
return "", i18nx.Errorf("error.e0279")
}
// The published Agent allow-list is the approval boundary for MCP tools.
// The registry still enforces call limits and safety metadata.
_, result, err := aitooling.DefaultMCPExecutor.Execute(ctx, toolCode, arguments, aitooling.Policy{
AllowedToolCodes: t.allowedToolCodes,
Confirmed: true,
})
if err != nil {
return "", err
}
return aitooling.SanitizePreview(buildToolCallResultSummary(result)), nil
}
func (t *ToolSearchTool) loadAllowedCandidates(ctx context.Context) ([]toolSearchCandidate, error) {
serverToToolCodes := make(map[string]map[string]struct{})
for _, toolCode := range t.allowedToolCodes {
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
if serverCode == "" || toolName == "" {
continue
}
if _, ok := serverToToolCodes[serverCode]; !ok {
serverToToolCodes[serverCode] = make(map[string]struct{})
}
serverToToolCodes[serverCode][toolCode] = struct{}{}
}
serverCodes := make([]string, 0, len(serverToToolCodes))
for serverCode := range serverToToolCodes {
serverCodes = append(serverCodes, serverCode)
}
slices.Sort(serverCodes)
ret := make([]toolSearchCandidate, 0)
for _, serverCode := range serverCodes {
tools, err := mcps.Runtime.ListTools(ctx, serverCode)
if err != nil {
return nil, err
}
allowed := serverToToolCodes[serverCode]
for _, item := range tools {
toolCode := toolx.BuildMCPToolCode(serverCode, item.Name)
if _, ok := allowed[toolCode]; !ok {
continue
}
ret = append(ret, toolSearchCandidate{
ToolCode: toolCode,
ServerCode: serverCode,
ToolName: strings.TrimSpace(item.Name),
Title: strings.TrimSpace(item.Title),
Description: strings.TrimSpace(item.Description),
})
}
}
return ret, nil
}
func filterAllowedMCPToolCodes(input []string) []string {
if len(input) == 0 {
return nil
}
ret := make([]string, 0, len(input))
for _, item := range input {
item = strings.TrimSpace(item)
serverCode, toolName := toolx.SplitMCPToolCode(item)
if serverCode == "" || toolName == "" {
continue
}
ret = append(ret, item)
}
return ret
}
func containsToolCode(items []string, target string) bool {
target = strings.TrimSpace(target)
if target == "" {
return false
}
for _, item := range items {
if strings.TrimSpace(item) == target {
return true
}
}
return false
}
func filterCandidatesByQuery(candidates []toolSearchCandidate, query string) []toolSearchCandidate {
query = strings.TrimSpace(strings.ToLower(query))
if query == "" {
return candidates
}
ret := make([]toolSearchCandidate, 0, len(candidates))
for _, item := range candidates {
searchText := strings.ToLower(strings.Join([]string{
item.ToolCode,
item.ServerCode,
item.ToolName,
item.Title,
item.Description,
}, "\n"))
if strings.Contains(searchText, query) {
ret = append(ret, item)
}
}
return ret
}
func cloneArguments(input map[string]any) map[string]any {
if len(input) == 0 {
return map[string]any{}
}
ret := make(map[string]any, len(input))
for key, value := range input {
ret[key] = value
}
return ret
}
func buildToolCallResultSummary(result *mcps.ToolCallResult) string {
return tooling.BuildReducedToolResultSummary(result)
}

View File

@@ -0,0 +1,32 @@
package tools
import "testing"
func TestParseToolSearchRequest(t *testing.T) {
req, err := parseToolSearchRequest(`{"query":" search docs ","toolCode":" mcp_server/search ","arguments":{"q":"hello"}}`)
if err != nil {
t.Fatalf("parseToolSearchRequest returned error: %v", err)
}
if req.Query != "search docs" {
t.Fatalf("unexpected query: %q", req.Query)
}
if req.ToolCode != "mcp_server/search" {
t.Fatalf("unexpected toolCode: %q", req.ToolCode)
}
if req.Arguments["q"] != "hello" {
t.Fatalf("unexpected arguments: %#v", req.Arguments)
}
}
func TestParseToolSearchRequestDefaultsArguments(t *testing.T) {
req, err := parseToolSearchRequest(`{"query":"list"}`)
if err != nil {
t.Fatalf("parseToolSearchRequest returned error: %v", err)
}
if req.Arguments == nil {
t.Fatalf("expected non-nil arguments map")
}
if len(req.Arguments) != 0 {
t.Fatalf("expected empty arguments map, got %#v", req.Arguments)
}
}

View File

@@ -0,0 +1,103 @@
package tools
import (
"context"
"agent-desk/internal/ai/runtime/graphs"
"agent-desk/internal/ai/runtime/registry"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
"agent-desk/internal/pkg/toolx"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
einojsonschema "github.com/eino-contrib/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
type TriageServiceRequestTool struct {
conversation models.Conversation
}
func NewTriageServiceRequestTool() *TriageServiceRequestTool {
return &TriageServiceRequestTool{}
}
func (t *TriageServiceRequestTool) Spec() toolx.ToolSpec {
return toolx.GraphTriageServiceRequest
}
func (t *TriageServiceRequestTool) Name() string {
return toolx.GraphTriageServiceRequest.Name
}
func (t *TriageServiceRequestTool) Code() string {
return toolx.GraphTriageServiceRequest.Code
}
func (t *TriageServiceRequestTool) Enabled(ctx registry.Context) bool {
return true
}
func (t *TriageServiceRequestTool) Build(ctx registry.Context) (einotool.BaseTool, error) {
if !t.Enabled(ctx) {
return nil, nil
}
return &TriageServiceRequestTool{conversation: ctx.Conversation}, nil
}
func (t *TriageServiceRequestTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: toolx.GraphTriageServiceRequest.Name,
Desc: i18nx.Get("tool.graph.triageServiceRequest.info"),
ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
Properties: orderedmap.New[string, *einojsonschema.Schema](orderedmap.WithInitialData(
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "goal",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.goal"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "observedIssue",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.observedIssue"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "needTicket",
Value: &einojsonschema.Schema{
Type: "boolean",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.needTicket"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "needHumanHandoff",
Value: &einojsonschema.Schema{
Type: "boolean",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.needHumanHandoff"),
},
},
orderedmap.Pair[string, *einojsonschema.Schema]{
Key: "additionalContext",
Value: &einojsonschema.Schema{
Type: "string",
Description: i18nx.Get("tool.graph.triageServiceRequest.param.additionalContext"),
},
},
)),
}),
Extra: map[string]any{
"toolCode": toolx.GraphTriageServiceRequest.Code,
"sourceType": "graph",
},
}, nil
}
func (t *TriageServiceRequestTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
return graphs.NewTriageServiceRequestGraph(t.conversation).Run(ctx, argumentsInJSON)
}