← 核心插件深度解析索引 / 整体架构解析
@deepseek-ai/dsh-tools · packages/core/tools

dsh-tools — 工具注册表与守卫执行管线

工具插件注册 schema 与执行器;agent loop 通过 tools/pre-execute → guards → execute → post-execute 执行每次调用。 注册表同时决定工具如何呈现给模型:原生 function calling / Code Mode / 两者。

ctx key: ctx.tools 10 个 src 文件 · 5800 行(仓库最大核心包) Service 类插件(默认导出 ToolRuntime) Config: mode(native|code|both) · maxParallelSubCalls

01包定位与入口

内容
ServiceToolRuntime extends Service,super(ctx, 'tools');static inject = ['systemPrompt'] —— 自动把工具 schema 喂进提示词组装(index.ts:788,832)
Configmode: 模型呈现方式(native 默认 | code 只送 run_code+SDK | both);maxParallelSubCalls: Code Mode 程序内并发子调用上限(默认 10,1 = 严格串行)
内部接口TOOL_RUNTIME_SCHEDULER(unique symbol):agent-loop 并行调度器专用 —— prepare/dispatch/finalize/finish 四段
错误码UNKNOWN_TOOL · INVALID_TOOL_OUTPUT · INVALID_ARGS · UNSUPPORTED_SCHEMA · CODE_RUN_FAILED · ABORTED · ABORTED_BEFORE_DISPATCH

02核心接口

ToolRuntime 公开 API(全部返回精确 disposer)

register(definition: ToolDefinition): () => void
presentAs(mode: ToolPresentationMode): () => void   // 仅 scoped ctx
restrict(filter: ToolRestriction): () => void           // 仅 scoped ctx
get(name, scope?): ToolDefinition | undefined
schemas(scope?): ToolSchema[]                          // 提示词组装喂给模型
guard(guard: ToolGuard): () => void
execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>

ToolDefinition(index.ts:222-288)

extends ToolSchema  // name / description / parameters
output: ToolOutputDefinition            // 强制:{ schema, render(args, value), presentationMeta? }
execute(args, exec): Promise<unknown>    // 只返回 canonical lossless-JSON 值
finalizeContent?(exec, result)            // 同步、总函数、只许替换 content
timeoutMs?: number                    // 声明式协作超时预算(永不上模型)
isConcurrencySafe?(args): boolean      // 仅精确 true 才并发
presentCall?(args): ToolCallView | undefined
presentResult?(args, result): ToolResultView | undefined  // 纯函数,直播与回放都会调用

执行视图类型族

03事件:三个可变换瀑布 + 观察者

事件mode签名与 next() 语义
tools/pre-executewaterfall可重排的 allow/deny/ask 门;next() 委托为 allow;ask 缺 approval 服务时退化为 deny
tools/executewaterfallaround-dispatch 包装(超时/重试/指标);next() 返回规范化结果;wrapper 只能换 exec.signal
tools/post-executewaterfallaccept / 换 content 或 value / block(反馈变无值失败)/ 附上下文;抛出的工具失败也到达此瀑布
tools/code-dispatch-logwaterfall只改 run_code 子分派的持久化日志副本;监听器抛错被包含,回退原样记录
tools/resultemit观察冻结的最终结果;live 事件,别与持久化 tool/result 混淆
tools/changeemit注册/注销/scope 限制变化;故意不 scope 过滤的全局通知

全部经 ctx.waterfall(scopeTarget(this, exec.agent), …) 分派 —— agent-scoped 监听器只收到该 agent 的调用。持久化会话事件(tool/code-dispatch-start / tool/code-dispatch)在 src/types.ts,deriveMessages() 忽略它们 → 子调用永不重入模型上下文。

04关键机制深度解析

execute 完整流程(index.ts:1342)

createExecution
铸 token · snapshotJsonValue(arguments) + deepFreeze · collapse 判定(mode='code' 可见工具 → 直接拒绝:只许 run_code)
tools/pre-execute 瀑布
默认 allow;ask → 机会式消费 ctx.get('approval'),缺服务 → deny
▼ guard:global 层先查,再沿 agent 链从远到近;首个 reason 拒绝(单调,不可翻案)
tools/execute 瀑布 → dispatchToolBody
caller signal 与 wrapper 替换的信号熔合(fuseToolSignals);工具 deferred 上下文在决定上下文之前合并
tools/post-execute 瀑布
block → 无值失败;content 与 value 同给 → TypeError;换 value → 重新规范化+重渲染
materializeFinalResult → finalizeContent → notifyResult
lossless 快照+深冻结;Object.freeze(exec) 后 emit tools/result;返回与观察者相同的冻结快照
◆ 取消是协作式、静默的 — 每个调用有 ToolCancellationState = { callerSignal, bodyInvoked }。body 前 → ABORTED_BEFORE_DISPATCH; body 后成功结果 → ABORTED;denial/失败/超时保持更具体的结果。从不 abandon 已启动的 promise。 预中止入口:物化+冻结参数后跳过一切政策与分派。

注册 / 可见性解析:scope 层与影子(index.ts:1037-1193)

四个 schema/codegen 模块的分工

模块职责
schema.ts作者面向的 DSL:ValueSchemaSpec(9 种节点)+ ParameterSchemaSpec(隐式开放对象根)+ defineTool(类型推断 + 执行前校验,非法 → ToolArgsError)+ InferValue/InferArgs(16 层容器后回退 JsonValue)
json-schema.ts强制 JSON Schema 子集(工具输出、Code Mode、subagent、workflow 共用);不支持的关键词拒绝而非忽略;校验是总函数、返回路径化违规
ts-types.tsschema → TypeScript 类型文本;生成完整 tools:sdk 节;任何构造退化 unknown,绝不 throw
py-types.tsschema → Python 类型文本(TypedDict / Protocol);退化 Any

defineTool 的三处软校验:execute 前硬校验(ToolArgsError);presentCall/presentResult/isConcurrencySafe旧日志回放参数软校验、失败回退 undefined/false 而非崩溃(schema.ts:594-615)。

code-mode:run_code 保留传输(code-mode.ts)

presentation:UI 渲染意图词汇(presentation.ts)

调用视图 ToolCallView 与结果视图 ToolResultViewcard 标签联合:generic | terminal | diff | search | read | web(+locations)。 presenter 是纯函数,UI 在直播与日志回放两处调用;结构信息(读文件行、web 来源)经 output.presentationMeta(args, value) 投影成 JSON, 随 tool/result 持久化、回到 presentResult 读回 —— canonical value 本身永不重放。 工具的 UI 渲染意图是设计的一部分,前置决定,别事后补。

invariant 断言(invariant.ts)

监听 internal/dispatch 拦截所有事件分发:阶段单调性(pre-execute 不重复、execute 必跟在 pre 后、post 必跟在 pre/execute 后、result 收尾);tools/result 时 exec/result/content 必须 frozen;code-dispatch 必须落在开放 turn 内且 subCallId 归属稳定;既有会话在 seed 时全量回放验证。

05自定制插件指南

(a) 注册一个新模型面对工具

  1. 选接口:一等插件作者用 defineTool(参数/返回值类型推断 + 自动校验);或裸 ctx.tools.register(rawDefinition)(自己校验输入,output 声明与校验仍是注册表强制的)
  2. 写 schema:参数用 ParameterSchemaSpec(隐式开放对象根,required: true 按属性标注);输出用 ValueSchemaSpec;必写 output.render(args, value) 纯投影
  3. execute 契约:返回 output.schema 声明的 canonical lossless-JSON 值;必须观察/转发 exec.signal;可选 timeoutMs(正有限)、isConcurrencySafe(精确 true 才并发)
  4. UI 呈现一并决定:presentCall/presentResult 返回 card 视图,不返回则 generic 回退(标题=工具名)
  5. 测试与快照:单元(注册→schemas() 投影→execute 管线)+ keyless 快照(每个非平凡模型可见行为变化,同 PR 通过真实可运行例子)+ REAL 组合测试(经 Loader boot 的 cordis.yml);参考 docs/cookbook/adding-a-tool.md

(b) 在工具执行前后拦截 / 改造

需求机制约束
允许 / 拒绝 / 询问tools/pre-execute 瀑布 或 ctx.tools.guard()guard 单调(返回 reason 即拒绝,无法翻案);不能重写 exec.arguments(会与日志/渲染脱节)
超时 / 重试 / 指标tools/execute 瀑布 wrapper只能换 exec.signal;调用身份不可变;wrapper 自产结果按该调用的 output 声明重新规范化
改呈现 / 换值 / 附上下文tools/post-executeaccept 换 content value(二选一);block 把反馈变无值失败;上下文顺序:工具 deferred 在前、决定上下文在后
最后一道 content 不变量ToolDefinition.finalizeContent同步、总函数、每结果恰好一次(含绕过 post-execute 的失败)、只换 content
只观察tools/result只读冻结快照;监听器抛错被包含
改 Code Mode 子分派的日志副本tools/code-dispatch-log程序值/模型结果不受影响;抛错回退原样

(c) 给某 agent 单独限定工具集

(d) 定制工具的 UI 呈现

  1. 决定卡型并返回 ToolCallView(presentCall)与 ToolResultView(presentResult);参考 dsh-tool-bash(terminal 卡)、dsh-tool-fs(diff/read 卡)
  2. presenter 只依赖 args 与持久化结果(直播+回放双路径),不 throw(defineTool 已对旧参数软校验回退)
  3. 结构信息经 output.presentationMeta 投影成 JSON 持久化,再在 presentResult 读回

06关键陷阱与约定