这个包是什么What this package ispi-codemode 让模型写一段 JavaScript 来调用工具,而不是一次调用一个工具。脚本跑在 worker 线程里的 QuickJS 虚拟机(独立的 wasm 实例)中,唯一的能力就是宿主注入的工具与 globals——没有计时器、网络、文件系统或模块。包里还有把 JSON Schema 渲染成 TypeScript 声明的工具,好让模型知道怎么调用。pi-codemode lets the model write a piece of JavaScript that calls tools, instead of calling one tool at a time. The script runs in a QuickJS VM (a separate wasm instance) inside a worker thread, and its only capability is the tools and globals the host injects — no timers, network, filesystem or modules. The package also renders JSON Schemas as TypeScript declarations so the model knows how to call them.
共 1,837 行 TypeScript,唯一的运行时依赖是 quickjs-wasi;不依赖其他 pi 包。pi 内只有 coding-agent 的 codemode 工具(1,358 行)使用它,而它又是 MCP 工具的默认调用方式。1,837 lines of TypeScript; the only runtime dependency is quickjs-wasi, and no other pi package. Inside pi, only coding-agent's codemode tool (1,358 lines) uses it — which in turn is the default way MCP tools are called.
模块结构Module structure#
一句话结论Takeaway三块:宿主侧(runtime/host.ts 的 CodemodeSandbox 与 Execution、消息协议、wasm 加载)、沙箱内(runtime/worker.ts 与在 VM 里执行的 prelude-source.ts)、给模型看的文本(declarations.ts、source.ts、identifier.ts)。四个导入路径分别对应整体、声明渲染、源码解析与 worker 入口。Three blocks: the host side (CodemodeSandbox and Execution in runtime/host.ts, the message protocol, wasm loading), the sandbox (runtime/worker.ts and prelude-source.ts, which runs inside the VM), and model-facing text (declarations.ts, source.ts, identifier.ts). Four import paths cover the whole package, declaration rendering, source parsing and the worker entry.
要点Key points
- 最大的文件是
runtime/prelude-source.ts:它是一段字符串形式的 JavaScript,在 VM 里搭出tools、text()、store()等全部脚本 API。The largest file isruntime/prelude-source.ts: JavaScript held in a string that builds the whole script API —tools,text(),store()and the rest — inside the VM. ./declarations与./source不加载 wasm,宿主可以只用它们生成工具描述或校验脚本,而不启动沙箱。./declarationsand./sourcedo not load wasm, so a host can generate tool descriptions or validate scripts without starting a sandbox../worker单独导出,是为了打包场景:打包后的宿主把它作为独立入口,再通过workerUrl传给沙箱。./workeris exported separately for bundling: a bundled host makes it a separate entry and passes it to the sandbox asworkerUrl.
工作原理How it works
new CodemodeSandbox({ tools, globals, timeoutMs, memoryLimitBytes })。new CodemodeSandbox({ tools, globals, timeoutMs, memoryLimitBytes }).renderDeclarations({ tools, globals })生成放进工具描述的 TypeScript。renderDeclarations({ tools, globals })produces the TypeScript that goes into the tool description.parseCodemodeSource(input)拆出可选的// @options:行,再sandbox.execute(code, { signal, store })。parseCodemodeSource(input)splits off an optional// @options:line, thensandbox.execute(code, { signal, store }).
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/index.tsL13 | export … | 包入口package entry |
| packages/codemode/src/runtime/host.tsL340 | CodemodeSandbox | 沙箱the sandbox |
| packages/codemode/src/runtime/worker.tsL52 | worker main() | worker 入口worker entry |
| packages/codemode/src/runtime/prelude-source.tsL46 | PRELUDE_SOURCE | VM 内的 APIthe in-VM API |
| packages/codemode/src/wasm.tsL21 | loadQuickJSWasm() | wasm 加载wasm loading |
公共 APIPublic API#
一句话结论Takeaway4 个导入路径。根入口从 7 个模块重新导出 19 个值和 16 个类型;核心只有一个类 CodemodeSandbox,其余是声明渲染、源码解析、限额常量和类型。4 import paths. The root re-exports 19 values and 16 types from 7 modules; the core is a single class, CodemodeSandbox, and the rest is declaration rendering, source parsing, limit constants and types.
| 导入路径Import path | 目标Target | 说明Notes |
|---|---|---|
@earendil-works/pi-codemode | ./dist/index.js | 沙箱、类型、声明渲染、源码解析与限额the sandbox, types, declaration rendering, source parsing and limits |
@earendil-works/pi-codemode/declarations | ./dist/declarations.js | 只有声明渲染,不加载 wasmdeclaration rendering only, no wasm |
@earendil-works/pi-codemode/source | ./dist/source.js | 只有源码解析与语法source parsing and grammar only |
@earendil-works/pi-codemode/worker | ./dist/runtime/worker.js | worker 入口,供打包时单独引用the worker entry, referenced separately when bundling |
常用导出Key exports
| 模块Module | 常用导出Key exports | 用途Use |
|---|---|---|
| packages/codemode/src/runtime/host.ts | CodemodeSandbox | 沙箱:execute(code)the sandbox: execute(code) |
| packages/codemode/src/types.ts | CodemodeTool CodemodeResult | 工具与结果tools and results |
| packages/codemode/src/declarations.ts | renderDeclarations renderToolSignature schemaToType | 给模型的 TypeScriptTypeScript for the model |
| packages/codemode/src/source.ts | parseCodemodeSource CODEMODE_SOURCE_GRAMMAR | @options 与 Lark 语法@options and the Lark grammar |
| packages/codemode/src/identifier.ts | toCodemodeIdentifier | 工具名 → 标识符tool name → identifier |
| packages/codemode/src/runtime/prelude-source.ts | MAX_OUTPUT_CHARS MAX_STORE_VALUE_CHARS | 脚本限额script limits |
index.ts 的全部导出(按来源模块)Every index.ts export, by source module
展开全部 7 个来源模块Show all 7 source modules19 values16 types
| 来源模块Source module | 值导出Value exports | 类型导出Type exports |
|---|---|---|
| packages/codemode/src/declarations.ts | DEFAULT_INPUT_SCHEMA_MAX_CHARS MCP_TYPESCRIPT_PREAMBLE mcpStructuredContentSchema renderDeclarations renderToolOutputType renderToolSample renderToolSignature schemaToType | RenderDeclarationsOptions |
| packages/codemode/src/identifier.ts | toCodemodeIdentifier | — |
| packages/codemode/src/runtime/host.ts | CodemodeSandbox | — |
| packages/codemode/src/runtime/prelude-source.ts | MAX_OUTPUT_CHARS MAX_OUTPUT_ITEMS MAX_STORE_TOTAL_CHARS MAX_STORE_VALUE_CHARS | — |
| packages/codemode/src/source.ts | CODEMODE_OPTIONS_PREFIX CODEMODE_SOURCE_GRAMMAR CodemodeSourceError parseCodemodeSource | CodemodeSourceOptions ParsedCodemodeSource |
| packages/codemode/src/types.ts | — | CodemodeCall CodemodeCallStatus CodemodeError CodemodeErrorKind CodemodeExecuteOptions CodemodeJsonSchema CodemodeOutputItem CodemodeResult CodemodeSandboxOptions CodemodeStoreWrites +2 |
| packages/codemode/src/wasm.ts | loadQuickJSWasm | CodemodeWasmModule |
由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.
三层隔离Three layers of isolation#
一句话结论Takeaway宿主线程只持有工具表;每次 execute() 新建一个 worker 线程,worker 里再创建一个 QuickJS 虚拟机。VM 是独立的 wasm 实例,和宿主的 JavaScript 世界没有共享对象——脚本能做的唯一一件事,就是通过 prelude 闭包里的 bridge 请求宿主调用某个工具。The host thread holds only the tool table; each execute() creates a worker thread, and the worker creates a QuickJS VM. The VM is a separate wasm instance with no objects shared with the host's JavaScript world — the only thing a script can do is ask the host, through the bridge hidden in the prelude's closure, to call a tool.
要点Key points
- worker 的存在是为了不让空转的脚本卡住宿主线程;每次执行都用新 worker,失控的脚本直接被
terminate(),不会影响下一次。The worker exists so that a spinning script never blocks the host thread; a fresh worker per run means a runaway script is simplyterminate()d and cannot affect the next run. - 结束时宿主先把共享的 4 字节
interrupt置 1,再terminate():VM 的中断处理器会轮询它,因为 Bun 的worker.terminate()停不住在 wasm 里空转的线程(见runtime/protocol.ts)。On finish the host sets the shared 4-byteinterruptto 1 beforeterminate(): the VM's interrupt handler polls it, because Bun'sworker.terminate()cannot stop a thread spinning in wasm (seeruntime/protocol.ts). maxStackSize让深递归变成可捕获的RangeError而不是 wasm 陷阱;QuickJS 自己写到 fd 1/2 的诊断被丢弃,不会弄乱宿主的 TUI。maxStackSizeturns deep recursion into a catchableRangeErrorinstead of a wasm trap; QuickJS's own diagnostics on fd 1/2 are discarded so they never garble the host's TUI.- prelude 在脚本运行前冻结所有内建对象,因为它和脚本共用这些对象:被篡改的
toJSON否则可能让 prelude 向宿主发出错误的数据。The prelude freezes every built-in before the script runs because it shares them with the script: a patchedtoJSONcould otherwise make the prelude send the host malformed data.
工作原理How it works
- 宿主:
new Worker(workerUrl, { workerData }),workerData 带上编译好的 wasm 模块(结构化克隆时共享代码)。Host:new Worker(workerUrl, { workerData }); workerData carries the compiled wasm module (structured clone shares the code). - worker:
QuickJS.create({ memoryLimit, maxStackSize, interruptHandler, wasi })→vm.newFunction("bridge")→ 求值 prelude。Worker:QuickJS.create({ memoryLimit, maxStackSize, interruptHandler, wasi })→vm.newFunction("bridge")→ evaluate the prelude. - prelude 返回
{ settle, run, stalled },worker 用它们驱动脚本。The prelude returns{ settle, run, stalled }, which the worker uses to drive the script.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/runtime/host.tsL125 | Execution | 一次执行one run |
| packages/codemode/src/runtime/host.tsL323 | interrupt | 中断标志interrupt flag |
| packages/codemode/src/runtime/worker.tsL54 | QuickJS.create() | 创建 VMcreating the VM |
| packages/codemode/src/runtime/worker.tsL32 | discardOutput() | 丢弃引擎输出discarding engine output |
| packages/codemode/src/runtime/prelude-source.tsL56 | lockdown() | 冻结内建对象freezing built-ins |
一次执行One execution#
一句话结论Takeawayexecute(code) 复制当前的工具表,启动计时器(默认 300,000 ms,包括工具耗时),加载 wasm,再启动 worker。worker 创建 VM、求值 prelude、把脚本包进 async 函数后运行。结局只有五种:成功,或 script、timeout、aborted、sandbox 四类错误;finish() 只执行一次。execute(code) copies the current tool table, starts the timer (default 300,000 ms, tool time included), loads wasm and starts the worker. The worker creates the VM, evaluates the prelude, wraps the script in an async function and runs it. There are only five endings: success, or one of four errors — script, timeout, aborted, sandbox; finish() runs exactly once.
要点Key points
- VM 里没有计时器和 I/O:如果脚本没结束、又没有挂起的工具调用,它就永远不会结束——
stalled()在每次 drain 后检查这种情况并立即判失败。The VM has no timers or I/O: if the script has not finished and no tool call is pending, it never will —stalled()checks for this after every drain and fails it at once. - 脚本包装成
(async (tools, console) => {<code>\n}),前缀和脚本同在第一行,所以报错的行号与脚本原文一致。The script is wrapped as(async (tools, console) => {<code>\n}), with the prefix on the script's first line so reported line numbers match the script as written. execute()不会因为脚本失败而 reject,失败以{ ok: false }返回,并保留失败前的 output 与 calls。execute()never rejects for script failures; they come back as{ ok: false }with the output and calls up to the failure.loadQuickJSWasm()每个路径只读取并编译一次;加载失败的 Promise 会被移出缓存,下次重试。loadQuickJSWasm()reads and compiles once per path; a failed load is evicted from the cache and retried next time.
工作原理How it works
execute(code, { signal, timeoutMs, store })→new Execution(…):计时器、abort 监听、等待 wasm。execute(code, { signal, timeoutMs, store })→new Execution(…): timer, abort listener, wait for wasm.start()→new Worker();监听 message / error / exit。start()→new Worker(); listen for message / error / exit.- 收到
done后先解码值与 store 写入,再finish():中止 pending 调用 → 置 interrupt → terminate → resolve。Ondone, decode the value and store writes first, thenfinish(): abort pending calls → set interrupt → terminate → resolve.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/runtime/host.tsL394 | execute() | 入口entry |
| packages/codemode/src/runtime/host.tsL22 | DEFAULT_TIMEOUT_MS | 默认截止时间default deadline |
| packages/codemode/src/runtime/host.tsL297 | finish() | 唯一的结束点the single exit |
| packages/codemode/src/runtime/worker.tsL148 | async wrapper | 脚本包装script wrapper |
| packages/codemode/src/runtime/prelude-source.tsL499 | stalled() | 死等检测stall detection |
| packages/codemode/src/types.tsL59 | CodemodeErrorKind | 4 类错误four error kinds |
一次工具调用的往返One tool call round trip#
一句话结论Takeawayawait tools.x(args) 在 VM 里变成 bridge("call", id, name, argsJson),worker 把它转成 postMessage,宿主的 Execution 查到工具、执行、把结果以 result 消息送回,worker 调用 settle(id) 并运行挂起的任务,脚本从 await 处继续。两边之间只有 JSON 字符串。await tools.x(args) becomes bridge("call", id, name, argsJson) in the VM; the worker turns it into a postMessage; the host's Execution finds the tool, runs it and sends the result back as a result message; the worker calls settle(id) and runs pending jobs, and the script resumes after await. Only JSON strings cross between them.
要点Key points
- 工具抛出的错误在脚本里变成同一条消息的
Error;返回值必须可 JSON 序列化。An error thrown by a tool becomes anErrorwith the same message in the script; return values must be JSON-serializable. result.calls记录每次工具调用:先记为cancelled,完成后改成ok或error并记下耗时——脚本结束时仍未完成的调用保持cancelled。result.callsrecords every tool call: it starts ascancelledand becomesokorerrorwith its duration — calls still running when the script ends staycancelled.tools是一个代理:访问不存在的工具名会抛TypeError,列出相近的名字(或在不超过 20 个工具时列出全部)。toolsis a proxy: an unknown tool name throws aTypeErrorthat lists near misses (or, with 20 tools or fewer, all of them).- globals(如
searchTools)走同一条路径,只是target: "global",且不记入calls。Globals (such assearchTools) take the same path withtarget: "global"and are not recorded incalls.
工作原理How it works
- prelude:
caller("call", name)生成函数 → JSON.stringify 参数 →bridge()→ 返回挂进pending的 Promise。Prelude:caller("call", name)builds the function → JSON.stringify the args →bridge()→ return a Promise kept inpending. - 宿主:
handleCall()→tool.execute(JSON.parse(args), { signal })→post({ type: "result", id, ok, payload })。Host:handleCall()→tool.execute(JSON.parse(args), { signal })→post({ type: "result", id, ok, payload }). - worker:
settle(id, ok, payload)→drain()。Worker:settle(id, ok, payload)→drain().
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/runtime/protocol.tsL30 | WorkerToHostMessage | worker → 宿主worker → host |
| packages/codemode/src/runtime/protocol.tsL39 | HostToWorkerMessage | 宿主 → workerhost → worker |
| packages/codemode/src/runtime/host.tsL265 | handleCall() | 执行工具running the tool |
| packages/codemode/src/runtime/worker.tsL65 | bridge | VM 内的桥the in-VM bridge |
| packages/codemode/src/runtime/prelude-source.tsL457 | settle() | 恢复脚本resuming the script |
脚本 API 与限额The script API and its limits#
一句话结论Takeaway脚本能用的只有:tools 与 ALL_TOOLS、输出函数 text() / image() / console.*、exit()、同步的 store() / load()、宿主配置的 globals,以及 return。输出、store 都有硬限额,超出时在脚本里抛错——宿主会一直保存输出直到脚本结束,没有限额的循环打印会拖垮宿主内存。All a script gets: tools and ALL_TOOLS, the output functions text() / image() / console.*, exit(), synchronous store() / load(), host-configured globals, and return. Output and the store have hard limits that throw inside the script — the host keeps all output until the script ends, so an unbounded print loop would exhaust host memory.
要点Key points
image()接受 base64 的data:URL、{ image_url }对象或 MCP 的ImageContent块,并且只认 PNG、JPEG、GIF、WebP。image()accepts a base64data:URL, an{ image_url }object or an MCPImageContentblock, and only takes PNG, JPEG, GIF or WebP.store()/load()作用于传入的 JSON 快照;成功的执行把写入作为storeWrites交还宿主,持久化由宿主决定。store()/load()work on the JSON snapshot passed in; successful runs hand writes back asstoreWrites, and persisting them is up to the host.- 冻结内建对象会破坏“在实例上覆盖原型属性”的普通写法,所以
name、message、toJSON等常见属性被改成访问器,this.name = …依然可用。Freezing built-ins would break ordinary code that overrides prototype properties on instances, so common ones such asname,messageandtoJSONbecome accessors andthis.name = …still works.
工作原理How it works
- 宿主注册 globals 时,名字必须是标识符或
ns.member,且不能与保留名冲突。When the host registers globals, names must be identifiers orns.member, and may not clash with the reserved names. ns.member形式的 globals 被收进一个冻结的ns对象。Globals namedns.memberare grouped into a frozennsobject.spread: true的 global 收到全部参数组成的数组,而不是第一个参数。Aspread: trueglobal receives all arguments as an array instead of the first one.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/runtime/prelude-source.tsL40 | MAX_OUTPUT_CHARS | 输出上限output limit |
| packages/codemode/src/runtime/prelude-source.tsL32 | MAX_STORE_VALUE_CHARS | store 上限store limit |
| packages/codemode/src/runtime/prelude-source.tsL401 | image() | 图片输出image output |
| packages/codemode/src/runtime/prelude-source.tsL284 | store() | storestore |
| packages/codemode/src/runtime/host.tsL24 | RESERVED_GLOBALS | 保留名reserved names |
公共类型与源码格式Public types and the source format#
一句话结论TakeawayCodemodeTool 只要 name 和 execute(args, { signal });schema 只用来渲染声明,值不会被校验。CodemodeResult 是一个按 ok 区分的联合:成功带 value 与 storeWrites,失败带 CodemodeError;两者都带 output 与 calls。脚本源码可以以一行 // @options: 开头。CodemodeTool needs only name and execute(args, { signal }); schemas are used to render declarations, values are not validated against them. CodemodeResult is a union on ok: success carries value and storeWrites, failure a CodemodeError; both carry output and calls. Script source may start with a // @options: line.
要点Key points
@options只认max_output_tokens与timeout_ms两个字段;该行被替换为空行,行号不变。@optionsaccepts onlymax_output_tokensandtimeout_ms; the line is replaced with a blank one so line numbers stay put.CODEMODE_SOURCE_GRAMMAR是一份 Lark 语法,给支持语法约束采样的模型:脚本可以直接按原文生成,而不是转义成 JSON 字符串。CODEMODE_SOURCE_GRAMMARis a Lark grammar for models with grammar-constrained sampling: the script is generated as raw text instead of an escaped JSON string.- 工具的
signal在脚本结束(包括没被 await 的调用)、超时、调用方中止或沙箱关闭时触发。A tool'ssignalfires when the script finishes (including unawaited calls), times out, the caller aborts, or the sandbox closes.
工作原理How it works
parseCodemodeSource(input)→{ code, options };坏的 options →CodemodeSourceError。parseCodemodeSource(input)→{ code, options }; bad options →CodemodeSourceError.- 把
options.timeoutMs传给execute(),maxOutputTokens由宿主自己执行截断。Passoptions.timeoutMstoexecute();maxOutputTokensis enforced by the host's own truncation. - 检查
result.ok,再读value/error、output与calls。Checkresult.ok, then readvalue/error,outputandcalls.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/types.tsL14 | CodemodeTool | 工具a tool |
| packages/codemode/src/types.tsL84 | CodemodeResult | 结果the result |
| packages/codemode/src/types.tsL94 | CodemodeSandboxOptions | 沙箱选项sandbox options |
| packages/codemode/src/source.tsL100 | parseCodemodeSource() | 源码解析source parsing |
| packages/codemode/src/source.tsL22 | CODEMODE_SOURCE_GRAMMAR | Lark 语法Lark grammar |
TypeScript 声明TypeScript declarations#
一句话结论Takeaway模型通过 TypeScript 声明学会调用工具:renderDeclarations() 把每个工具渲染成 declare const tools 的一个成员 name(args: T): Promise<R>;,描述变成文档注释,schemaToType() 把 JSON Schema 转成类型。MCP 风格的输出 schema 渲染成 CallToolResult<T>。The model learns to call tools from TypeScript declarations: renderDeclarations() renders each tool as a member name(args: T): Promise<R>; of declare const tools, descriptions become doc comments, and schemaToType() turns JSON Schema into types. MCP-style output schemas render as CallToolResult<T>.
要点Key points
- 工具名中不合法的标识符字符变成
_(toCodemodeIdentifier),所以my-tool在脚本里是tools.my_tool;原名tools["my-tool"]同样可用。Characters that are not valid in identifiers become_(toCodemodeIdentifier), somy-toolistools.my_toolin scripts; the originaltools["my-tool"]works too. - 渲染有上限:输入类型超过 16,000 个字符整个变成
unknown;本地$ref每个 schema 最多展开 32 次,递归引用变成unknown。Rendering is bounded: an input type over 16,000 characters becomesunknown; local$refs expand at most 32 times per schema, and recursive references becomeunknown. - 用到
CallToolResult<T>时要把MCP_TYPESCRIPT_PREAMBLE(MCP 结果类型的定义)一起放进描述。WhenCallToolResult<T>is used,MCP_TYPESCRIPT_PREAMBLE(the MCP result type definitions) must go into the description too.
工作原理How it works
renderToolSignature(tool):输入类型 +renderToolOutputType(outputSchema)。renderToolSignature(tool): the input type +renderToolOutputType(outputSchema).mcpStructuredContentSchema()识别 MCP 结果 schema(content 数组、布尔 isError、对象 _meta)。mcpStructuredContentSchema()recognises MCP result schemas (a content array, boolean isError, object _meta).renderToolSample()输出“描述 + 单个工具的声明”,用于工具列表与ALL_TOOLS条目。renderToolSample()outputs “description + that tool's declaration”, used for tool listings andALL_TOOLSentries.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/codemode/src/declarations.tsL105 | renderDeclarations() | 整体声明all declarations |
| packages/codemode/src/declarations.tsL138 | renderToolSignature() | 单个工具one tool |
| packages/codemode/src/declarations.tsL228 | schemaToType() | Schema → 类型schema → type |
| packages/codemode/src/declarations.tsL18 | MCP_TYPESCRIPT_PREAMBLE | MCP 类型MCP types |
| packages/codemode/src/identifier.tsL5 | toCodemodeIdentifier() | 名字 → 标识符name → identifier |
在 pi 中的使用How pi uses it#
一句话结论Takeawaycoding-agent 把它包装成 codemode 工具:模型写脚本,脚本里的每次嵌套调用都经过 agent 循环的工具管线(校验、钩子、权限),而只有脚本自己的输出回到模型。pi 还注入 searchTools()、describeTool()、describeNamespace() 与 models.*,并把 store() 写入保存为会话条目。coding-agent wraps it as the codemode tool: the model writes a script, every nested call goes through the agent loop's tool pipeline (validation, hooks, permissions), and only the script's own output returns to the model. pi also injects searchTools(), describeTool(), describeNamespace() and models.*, and saves store() writes as session entries.
要点Key points
- 脚本可调用 loadout 中所有可调用的工具(包括 deferred 与 codemode 曝光的工具——MCP 工具默认在此),只有
codemode自己除外。它是model-only,脚本不能再启动脚本。Scripts can call every callable tool in the loadout (deferred and codemode-exposure tools included — where MCP tools go by default), exceptcodemodeitself. It ismodel-only: scripts cannot start scripts. - 嵌套结果交给脚本时:有
outputSchema的工具给structuredContent(带isError的 MCP 结果也一样),其他工具给文本,失败则 reject。Nested results reach the script as:structuredContentfor tools with anoutputSchema(MCP results withisErrortoo), text for other tools, and a rejection on failure. store()写入成为codemode-store自定义条目;下次执行前从根到当前叶子重放,所以每个分支只看到自己路径上的值。store()writes becomecodemode-storecustom entries; before the next run they are replayed from root to the current leaf, so each branch sees only values from its own path.- 沙箱在第一次调用时才加载(worker 与 wasm),启动时没有成本;超时只来自脚本的
timeout_ms,否则不限。The sandbox (worker and wasm) loads on the first call, so start-up pays nothing; the only timeout is the script'stimeout_ms, otherwise none.
工作原理How it works
createCodemodeExtension()注册不激活的codemode工具;--tools、defaultTools、setActiveTools()或 MCP 扩展激活它。createCodemodeExtension()registers the inactivecodemodetool;--tools,defaultTools,setActiveTools()or the MCP extension activate it.- 激活时
prepareCodemodeLoadout()用可调用工具的声明替换描述(inlineBudget控制内联多少;mode: "only"列出全部可调用工具,并从请求中去掉激活的 direct 工具的声明)。On activation,prepareCodemodeLoadout()replaces the description with the callable tools' declarations (inlineBudgetlimits how much is inlined;mode: "only"lists every callable tool and leaves active direct tools' declarations out of requests). executeCodemode():解析源码 →new CodemodeSandbox({ tools, globals })→execute(code, { signal, store })→ 截断输出、追加 store 条目。executeCodemode(): parse the source →new CodemodeSandbox({ tools, globals })→execute(code, { signal, store })→ truncate output, append store entries.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/extensions/codemode/index.tsL31 | createCodemodeExtension() | 扩展入口extension entry |
| packages/coding-agent/src/extensions/codemode/tool.tsL380 | createCodemodeToolDefinition() | 工具定义tool definition |
| packages/coding-agent/src/extensions/codemode/tool.tsL342 | prepareCodemodeLoadout() | 描述与模式description and modes |
| packages/coding-agent/src/extensions/codemode/execute.tsL462 | ctx.executeTool() | 嵌套调用nested calls |
| packages/coding-agent/src/extensions/codemode/execute.tsL554 | createDiscoveryGlobals() | searchTools 等searchTools and friends |
| packages/coding-agent/src/extensions/codemode/tool.tsL53 | CODEMODE_STORE_ENTRY_TYPE | store 条目store entries |
扩展点Extension points#
CodemodeToolname + execute(args, { signal }):脚本里的 tools.<id>。name + execute(args, { signal }): tools.<id> in scripts.globals顶层函数或 ns.member,如 pi 的 searchTools、models.*。Top-level functions or ns.member, like pi's searchTools and models.*.execute(code, { store })传入 load() 的快照,取回 storeWrites 自行持久化。Pass the load() snapshot in, take storeWrites out and persist them yourself.renderDeclarations() · renderToolSample()自己组织给模型的工具描述。Build your own tool descriptions for the model.CODEMODE_SOURCE_GRAMMAR给支持语法约束的 provider,让脚本按原文生成。For providers with grammar sampling, so scripts come out as raw text.wasm · workerUrl打包或 Bun 编译时自带 wasm 与 worker 入口。Ship your own wasm and worker entry when bundling or compiling with Bun.timeoutMs · memoryLimitBytes每个沙箱或每次执行的资源上限。Resource limits per sandbox or per run.测试Tests#
一句话结论Takeaway三个测试文件:沙箱(真实的 worker 与 QuickJS,覆盖工具调用、超时、中止、限额与锁定)、声明渲染与源码解析。Three test files: the sandbox (a real worker and QuickJS, covering tool calls, timeouts, aborts, limits and lockdown), declaration rendering and source parsing.
| 测试文件Test file | 用例cases |
|---|---|
| packages/codemode/test/sandbox.test.ts | 52 |
| packages/codemode/test/declarations.test.ts | 12 |
| packages/codemode/test/source.test.ts | 4 |
用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).