package tools import ( "context" "encoding/json" "fmt" "os" "strings" "agent-native/runtime/codeorch" ) type searchTool struct{ reg *Registry } func (t *searchTool) Name() string { return "tool_search" } func (t *searchTool) Description() string { return "按名称或描述查找延迟暴露的工具,并以文本返回它们的 schema" } func (t *searchTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object","properties":{"query":{"type":"string"}},"required":["query"]}`) } func (t *searchTool) Execute(_ context.Context, args json.RawMessage) (Result, error) { var q struct { Query string `json:"query"` } if len(args) > 0 { _ = json.Unmarshal(args, &q) } return Result{Content: t.reg.Search(q.Query)}, nil } func (t *searchTool) Replay() string { return ReplaySafe } type callTool struct{ reg *Registry } func (t *callTool) Name() string { return "call_tool" } func (t *callTool) Description() string { return "调用一个延迟暴露的工具。参数 schema 固定,不随工具清单变化" } func (t *callTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"},"arguments":{"type":"object"}},"required":["name"]}`) } func (t *callTool) Execute(ctx context.Context, args json.RawMessage) (Result, error) { var req struct { Name string `json:"name"` Arguments json.RawMessage `json:"arguments"` } if err := json.Unmarshal(args, &req); err != nil { return Result{Content: err.Error(), IsError: true}, nil } if req.Name == "" || req.Name == "call_tool" || req.Name == "tool_search" || codeorch.Forbidden(req.Name) { return Result{Content: "不能调用 " + req.Name, IsError: true}, nil } res := t.reg.Execute(ctx, req.Name, req.Arguments) if !res.IsError { t.reg.markActivated(req.Name) } return res, nil } func (r *Registry) ensureMetaLocked() { if _, ok := r.tools["tool_search"]; ok { return } r.tools["tool_search"] = &searchTool{reg: r} r.order = append(r.order, "tool_search") r.tools["call_tool"] = &callTool{reg: r} r.order = append(r.order, "call_tool") } // Search 把名称或描述命中的延迟工具 schema 写成文本。 func (r *Registry) Search(query string) string { r.mu.Lock() defer r.mu.Unlock() q := strings.ToLower(strings.TrimSpace(query)) var b strings.Builder for _, name := range r.order { t := r.tools[name] if ExposureOf(t) != ExposureDeferred { continue } blob := strings.ToLower(t.Name() + " " + t.Description()) if q != "" && !strings.Contains(blob, q) { continue } fmt.Fprintf(&b, "name: %s\ndescription: %s\nparameters: %s\n\n", t.Name(), t.Description(), t.Parameters()) } if b.Len() == 0 { return "没有匹配的延迟工具" } return strings.TrimSpace(b.String()) } // SetActivationFile 指定已激活工具的持久文件。 func (r *Registry) SetActivationFile(path string) { r.mu.Lock() r.activatePath = path r.mu.Unlock() } // RestoreActivated 读回上次激活的延迟工具名。清单仍然不包含它们。 func (r *Registry) RestoreActivated() error { r.mu.Lock() path := r.activatePath r.mu.Unlock() if path == "" { return nil } raw, err := os.ReadFile(path) if os.IsNotExist(err) { return nil } if err != nil { return err } var names []string if err := json.Unmarshal(raw, &names); err != nil { return err } r.mu.Lock() defer r.mu.Unlock() if r.activated == nil { r.activated = map[string]bool{} } for _, name := range names { r.activated[name] = true } return nil } // Activated 返回已恢复或本次调用过的延迟工具名。 func (r *Registry) Activated() []string { r.mu.Lock() defer r.mu.Unlock() var out []string for name := range r.activated { out = append(out, name) } return out } func (r *Registry) markActivated(name string) { r.mu.Lock() defer r.mu.Unlock() t, ok := r.tools[name] if !ok || ExposureOf(t) != ExposureDeferred { return } if r.activated == nil { r.activated = map[string]bool{} } r.activated[name] = true if r.activatePath == "" { return } var names []string for n := range r.activated { names = append(names, n) } raw, err := json.Marshal(names) if err != nil { return } _ = os.WriteFile(r.activatePath, raw, 0o644) }