// Package tools 定义工具系统的核心接口和注册表. // // 工具系统的类型定义和接口. // 原项目的工具系统有以下问题: // - Tool 定义和 UI 渲染耦合(renderToolResultMessage 直接返回 JSX) // - 并发安全性通过每个工具的 isConcurrencySafe() 方法判断,散落各处 // - 权限检查混在工具内部和外部两层 // // Go 版本的设计: // - Tool 接口只关心执行逻辑,不关心 UI // - 并发安全性和只读性作为元数据声明,由编排器统一调度 // - 权限检查完全由 PermissionEngine 负责,工具不参与 package tools import ( "context" "encoding/json" ) // Tool 是所有工具必须实现的接口. // 对应原项目中 Tool 泛型类型. // // Go 版本简化为统一的 JSON 输入/输出,避免泛型复杂度. // 类型安全由每个工具内部的 json.Unmarshal 保证. // // Shape: synchronous callback. Engine dispatches via Execute with a // JSON input and blocks for *Result; custom tool authors inject via // engine.Config.Tools. Description / InputSchema / Name are pull-side // metadata read by engine at registration / prompt assembly time. // // 形态: 同步回调. 引擎经 Execute 传 JSON 输入阻塞等 *Result; 自定义工具 // 作者经 engine.Config.Tools 注入. Description / InputSchema / Name 是 // pull 侧元数据, 引擎在注册 / 拼 prompt 时读. type Tool interface { // Name 返回工具的唯一标识符. Name() string // Description 返回工具的描述(提供给模型的说明). // 可以是动态的,根据当前上下文返回不同内容. Description(ctx context.Context) string // InputSchema 返回工具的 JSON Schema 输入定义. // 模型根据此 schema 生成工具调用参数. InputSchema() json.RawMessage // Execute 执行工具逻辑. // input 是模型生成的 JSON 参数(已通过 schema 验证). // 返回 Result 和可能的错误. // // ctx 携带取消信号,对应原 TS 项目的 AbortController. // progress 回调用于报告长时间任务的进度. Execute(ctx context.Context, input json.RawMessage, progress ProgressFunc) (*Result, error) } // Metadata 是工具的元数据声明. // 工具可以选择实现 MetadataProvider 接口来声明这些属性. // 如果不实现,编排器使用保守默认值(不可并发,非只读,非破坏性). // // 对应原项目中 isConcurrencySafe(),isReadOnly(),isDestructive() 等方法. type Metadata struct { // ConcurrencySafe 表示该工具是否可以与其他工具并行执行. // true: Glob,Grep,FileRead 等只读工具 // false: FileEdit,FileWrite,Bash(可能有副作用)等 ConcurrencySafe bool // ReadOnly declares this tool has no side effects (pure queries / // read-only FS access). Informational metadata: external SDK / TUI / // audit consumers render a "read-only" label; evolve's tool generator // may auto-set it. // // Not part of permission decisions -- permission path is driven by // PermissionClass (above). No in-tree consumer reads this field for // permission logic today. // // ReadOnly 声明该工具不产生副作用 (纯查询 / 只读 FS 访问). // Informational 元数据: 外部 SDK / TUI / audit 消费层据此渲染 "只读" // 标签, evolve 工具生成器也可据此自动设置. // // 不参与权限决策 -- 权限路径由 PermissionClass 驱动 (见上方). // 本字段当前无 in-tree 消费者读取做权限判断. ReadOnly bool // Destructive declares the tool may cause irreversible damage (rm -rf, // git push --force, DROP TABLE). Informational metadata: audit / UI // render warning icons; reports count "N destructive calls this session". // // Not part of permission decisions -- same constraint as ReadOnly. // // Destructive 声明该工具可能造成不可逆破坏 (rm -rf / git push --force / // DROP TABLE 等). Informational 元数据: audit / UI 据此渲染警告图标, // 审计报表统计 "本 session 破坏性调用 N 次". // // 不参与权限决策 -- 约束同 ReadOnly. Destructive bool // Aliases 工具的别名列表(向后兼容用). Aliases []string // SearchHint 搜索提示词(3-10 字),用于 ToolSearch 延迟加载时的匹配. SearchHint string // PermissionClass 声明工具的权限检查类型. // 权限引擎据此分派正确的安全检查逻辑,避免权限系统硬编码工具名. // // 升华改进(ELEVATED): P0-2 修复核心--工具自描述权限语义, // 第三方工具注册时 tools.Registry 自动调用 permission.RegisterToolClass. // 有效取值(见 permission 包中的 PermClass* 常量): // "bash" - 命令执行类(子命令分割 + 危险命令检测) // "file" - 文件操作类(路径权限 + 危险路径检测) // "webfetch" - Web 请求类(域名权限检查) // "readonly" - 只读类(直接放行) // "generic" - 通用(默认,规则匹配后 Ask) // "" - 未声明,权限系统使用内置 fallback(向后兼容) PermissionClass string // AuditOperation 声明工具的审计操作类型. // 用于 AuditObserver 将工具事件翻译为 AuditEntry.Operation 字段. // // 升华改进(ELEVATED): P1 L1191 修复 - 与 PermissionClass 同源模式, 工具自描述 // 跨切面语义 (权限 + 审计), 消费者 (flysafe / flyto-platform-common / 新工具作者) // 注册自定义工具时不需要 fork 引擎修改 audit_observer.operationFromTool 的 // switch.例如第三方 "DatabaseWrite" / "SlackSend" / "K8sApply" 工具通过 // Metadata{AuditOperation: "write"} 声明, 审计记录立即正确. // // 常用取值 (任意自定义字符串也合法, 由消费者与其审计后端约定): // "write" - 写入 (文件写, DB 写, 消息发送) // "edit" - 修改 (文件编辑, DB 更新) // "read" - 读取 (文件读, DB 查询, glob, grep) // "execute" - 命令执行 (bash, 子进程) // "delete" - 删除 (文件删, DB 删) // "invoke" - 通用调用 (默认值, 无副作用) // "" - 未声明, AuditObserver 回退到启发式 (基于工具名匹配) // // 替代方案 A: <保留硬编码 switch, 不加 Metadata 字段> - 否决: // 违反开闭原则, 消费者加新工具必须 fork 引擎. // 替代方案 B: <新增全局 RegisterAuditOperation(name, op) 函数> - 否决: // 引入平行于 Metadata 的第二种注册路径, 与既有 PermissionClass 风格不一致. // 替代方案 C: - 否决: // 消费者要事先知道所有工具名, 插件动态注册场景不适用. AuditOperation string // RequiresCheckpoint 表示该工具执行前需要等待外部确认. // // 升华改进(ELEVATED): 早期实现 中"Human checkpoint"只是提示词里的文字建议, // 模型自己决定要不要暂停--不可靠(模型可能忽略). // 我们在工具声明层强制暂停:RequiresCheckpoint=true 的工具, // 引擎在执行前必须经过 CheckpointHandlerFn 放行,handler 未注册默认拒绝. // // 适用场景(不可逆操作): // - 发送消息(邮件,IM,短信) // - 删除数据(DROP TABLE,rm -rf) // - 生产部署(kubectl apply,terraform apply) // - 资金操作(转账,支付) // // 替代方案:<在 prompt 中写"执行前问用户"> // - 否决:依赖模型遵守,不可靠;且无法在 SDK 嵌入模式下触发程序级回调. RequiresCheckpoint bool // RequiresReverseThinking flags this tool as one whose call benefits // from a reverse-thinking pass before execution: producing a // counterfactual.Deliverable (hidden assumptions / failure scenarios / // verdict / verdict_reason) that is persisted alongside the call for // audit and evolve.Reflector replay. // // Boundary with RequiresCheckpoint: RequiresCheckpoint asks "should a // human approve this call?"; RequiresReverseThinking asks "should the // agent challenge its own recommendation before this call?". Both can // be true simultaneously (high-stakes calls deserve both); core does // not impose ordering -- the consuming hook chain (e.g. // platform/common/safetychain) decides whether reverse-think runs // before / after / instead-of the human checkpoint. // // Suitable when (non-trivial design space, not every irreversible op): // - Tool selects between competing strategies (route planning, // carrier choice, policy parameter) // - Tool encodes a decision whose failure mode is non-obvious // (settlement matching, fraud judgment) // - Tool calls a downstream system whose schema may shift // (third-party API, partner integration) // // Avoid when: the tool is a pure mechanical operation (file read, // arithmetic, well-defined CRUD); reverse-think on these is overhead // without insight. // // Default false. When false, no reverse-thinking pass is invoked // regardless of any hooks layered on; opt-in is per-tool. // // ELEVATED rationale: rule of two with RequiresCheckpoint -- both are // "self-describing tool flags consumed by hooks / handlers above core", // adding one alongside the existing one is cheaper than introducing a // new metadata category. // // 替代方案 (已否决): // A. 引擎级 ReverseThinkingGate 接口 (TODO L569 原方案 8 模块完整版) // -- 否决: 与 hooks / permission / validator / staging / RequiresCheckpoint // 5 套现有 gate 重叠, 业界零先例 (调研: Claude Agent SDK / OpenAI // Assistants / Vertex ADK / LangGraph / AutoGen / CrewAI / Semantic // Kernel / Cline 全部走 prompt-level + tool desc + 开发者自填策略). // B. 仅写 prompt 层引导, 不加字段 // -- 否决: prompt 易丢, 无法在 SDK 嵌入模式下程序级判定; 且 // Deliverable 持久化无锚点字段. // // RequiresReverseThinking 标记本工具调用前应走反向思维 pass: 产 counterfactual // .Deliverable (hidden_assumptions / failure_scenarios / verdict / // verdict_reason) 与调用一同持久化供审计和 evolve.Reflector 重放. // // 与 RequiresCheckpoint 边界: RequiresCheckpoint 问 "需要人确认吗?"; // RequiresReverseThinking 问 "agent 应不应该挑战自己的建议?". 两者可同时 // true (高风险调用值得两道); core 不规定顺序 -- 消费 hook 链 // (例如 platform/common/safetychain) 决定 reverse-think 在 checkpoint // 之前 / 之后 / 替代. // // 适用场景 (非平凡设计空间, 不是所有不可逆操作): // - 工具在多个候选策略中选择 (路由规划 / 承运商选择 / 策略参数) // - 工具编码失败模式不直观的决策 (账单匹配 / 反欺诈判定) // - 工具调下游 schema 可能漂移的系统 (三方 API / 合作方集成) // // 不适用: 纯机械操作 (文件读 / 算术 / 良定义 CRUD); 反向思维只是开销 // 没有洞察. // // 默认 false. false 时无论上层挂多少 hook 都不触发反向思维; 按工具 opt-in. // // 升华理由 (ELEVATED): 与 RequiresCheckpoint 走 rule of two -- 两者都是 // "core 之上 hook / handler 消费的自描述工具 flag", 在已有字段旁加同源字段 // 比新增 metadata 类别更便宜. RequiresReverseThinking bool } // MetadataProvider 是工具的可选接口. // 实现此接口的工具可以声明并发安全性等元数据. // // Shape: pull. Engine reads Metadata() at orchestration decisions // (can run in parallel? is destructive? requires checkpoint?). // // 形态: 调取 (pull). 引擎在编排决策时读 Metadata() (能否并发? 是否破坏性? // 要不要 checkpoint?). type MetadataProvider interface { Metadata() Metadata } // Result 是工具执行的结果. type Result struct { // Output 是工具的文本输出. Output string // IsError 标记输出是否为错误信息. IsError bool // Data 是结构化数据输出(可选). // 用于工具间传递结构化信息. Data any // UndoInfo 撤销信息(由 Reversible 工具填充). // 升华改进(ELEVATED): 撤销信息嵌入 Result 而非单独返回-- // 这样编排器在收集工具结果时自然就拿到了撤销信息,不需要额外调用. // 替代方案:在 Orchestrator 中单独调用 GenerateUndo(多一次调用,时序更复杂). UndoInfo *UndoInfo } // ProgressFunc 是进度回调函数类型. type ProgressFunc func(progress float64, detail string) // GetMetadata 安全获取工具的元数据. // 如果工具未实现 MetadataProvider,返回保守默认值. func GetMetadata(t Tool) Metadata { if mp, ok := t.(MetadataProvider); ok { return mp.Metadata() } // 保守默认值:假设不可并发,非只读,非破坏性 return Metadata{ ConcurrencySafe: false, ReadOnly: false, Destructive: false, } }