Files
Agent-Native/runtime/tools/deferred.go
T
weichen 6ddd3da8df Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:36:10 +00:00

165 lines
4.2 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/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)
}