oh-my-pi Codex Code Mode 解析:eval 工具如何接管受限直接工具面并动态桥接全部会话工具
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
导读
本文聚焦 oh-my-pi 项目 coding-agent 包中的 eval-code-mode.md 提示模板,深入剖析 Codex Code Mode(code_mode_only模型专用模式)下 eval 工具如何成为模型的"主工作表面":直接工具面被折叠,普通会话工具改经 eval cell 内的tool.*桥接调用,同时由宿主进程动态生成 TypeScript 工具声明块注入提示词。读完本文,你将掌握该模式的完整触发条件、cell 编排规范、工具声明生成机制及其背后的配置项与源码实现路径。
一、背景:什么是 Codex Code Mode,以及 eval 为何成为主工作表面
1.1 模式定位与触发条件
在 oh-my-pi 中,Codex Code Mode 是为code_mode_only类型的模型(如 GPT-5.6 系列)设计的执行形态,其核心思路记录在 code-mode.ts 文件头注释中:collapse the direct tool surface for code_mode_only models,即为这类模型折叠直接工具面,把绝大部分工具调用收拢到 eval 代码执行环境里完成。
触发由配置项providers.openai-codex.codeMode控制,定义于 settings-schema.ts:
- 取值
enum:off/on/auto,默认off; on:强制将code_mode_only模型路由到 eval;auto:跟随模型目录(catalog)中的code_mode_only标记自动启用;- 语义说明原文:Route Codex code_mode_only models (GPT-5.6) through eval,mirrors codex-rs Code Mode。
1.2 直接工具面被折叠:默认只保留七个直接工具
模式激活后,模型能够直接调用的工具被缩减为一组白名单,见 settings-schema.ts:
The standard direct tools are eval, ask, todo, yield, think, checkpoint, and rewind.
即默认直接工具为:eval、ask、todo、yield、think、checkpoint、rewind。其余所有会话工具(read、write、grep、bash 等)都不再对模型直接暴露,而是必须通过 eval cell 内的桥接调用。
如需额外放行某些工具,可通过数组配置项providers.openai-codex.codeModeDirectTools追加。在 session-tools.ts 的挂载逻辑中,模式激活时先清空默认挂载集,再依据该配置重组directToolNames,最终只有落在白名单内的工具被应用到模型侧。
二、eval-code-mode.md 模板全文解读:模型在 Code Mode 下的工作守则
eval-code-mode.md 是一份 Handlebars 模板,当 Codex Code Mode 激活时由EvalTool渲染并拼接进 eval 工具的 description,作为模型看到的系统提示追加内容。其正文共三个层次,含义如下。
2.1 第一层:工作表面声明(模板第 3 行)
Codex Code Mode is active: this tool is your primary work surface and the direct tool surface is restricted.
这句话向模型宣告两层事实:
- eval 是主工作表面:一切代码执行、文件读写、工具调用都应经由 eval cell 完成;
- 直接工具面受限:模型不应再试图调用白名单之外的工具(即便其"记得"这些工具存在),它们已被宿主侧摘除。
2.2 第二层:cell 编排规范(模板第 4~6 行)
模板给出了三条明确的编排纪律:
- 单 cell 多操作:只要后续步骤已知,就把多个操作规划进同一个 cell,通过
await tool.<name>(args)调用会话工具; - 并发不等待:相互独立的调用直接 spawn(不逐个 await),随后用
await Promise.all([…])汇合; - 管线优先:优先使用
tool.*调用而非裸写Bun.file/fs 操作,理由是让所有操作流经 session 工具管线(tool pipeline),从而获得会话统一的审批、流式输出、追踪与工件记录能力; - 保留检查性 cell:需要检查先前结果的步骤单独拆分 cell,避免盲接。
这些规则与主提示 eval.md 中"Work incrementally: imports → define → test → use, each its own cell"的思路互补:宏观上分步推进,微观上一个 cell 内聚合已知的多步操作。
2.3 第三层:exec 工具声明块(模板第 8~17 行)
模板末尾是注入 TypeScript 声明的位置:
declare const tool: { {{declarations}} }; {{#if preludeDeclarations}} {{{preludeDeclarations}}} {{/if}}两个模板变量分别承担:
{{declarations}}:由宿主进程生成的全部桥接工具的方法签名;{{preludeDeclarations}}:当前会话启用的 eval prelude 附加声明(见第四节)。
这段声明块是模型在 Code Mode 下"知道"自己还能调用哪些工具的唯一天花板:声明里有的工具模型才可以await tool.<name>(args)。
三、源码级原理:declarations 是如何动态生成的
3.1 生成器:code-mode-declarations.ts
code-mode-declarations.ts 是声明的核心生成器,其文件头注释明确说明职责:生成"advertising eval-bridged tools under Codex Code Mode"的 TypeScript 方法签名,并注明镜像了 codex-rs 的augment_tool_spec_for_code_mode行为。
其类型映射逻辑(tsType函数,见 code-mode-declarations.ts)将每个工具的 ArkType 参数 schema 转换为 TS 类型字面量:
| schema 形态 | 生成的 TS 类型 |
|---|---|
enum且全为字符串 | 字符串字面量联合,如"a" \| "b" |
string | string |
number/integer | number |
boolean | boolean |
array | T[](联合元素自动加括号,如("a" \| "b")[],防止"a" \| "b"[]的解析歧义) |
object含properties | 对象字面量类型,必填字段无?,可选字段带? |
| 其他 / 深度超限(>2 层) | unknown(fail-safe) |
每个工具最终生成一行方法签名:
<name>(args: <参数类型>): Promise<unknown>;其中<name>若为合法 TS 标识符则原样输出,否则用 JSON 字符串形式包裹。注意两个细节:所有工具统一返回Promise<unknown>,所有参数统一收拢为单个args对象——这正对应 eval.md prelude 中tool.<name>(args)的调用约定,即"args = its parameter object"。
3.2 装配点:EvalTool 的 #codeModeDescription
生成器被 eval.ts 中的#codeModeDescription方法消费,其关键保证有三点(源码注释原文亦如此说明):
- 每次读取时重新计算:声明"pulled from the session's applied direct partition on every read",因此声明内容永远与当前模型、当前工具注册表保持同步,不会过期漂移;
- 绝不广告可直接调用的工具:遍历
getEvalBridgeToolNames()(无桥接名单时退化为整个toolRegistry)时,凡命中getCodeModeDirectToolNames()白名单的工具一律filter掉——模型已经能直接调用的工具,不会再出现在tool声明里,避免重复暴露与语义冲突(如 Code Mode 下write会作为直接工具注入时,它就不会被桥接); - 与渲染管线打通:最终经
prompt.render(evalCodeModeDescription, { baseDescription, declarations, preludeDeclarations })渲染,baseDescription即 eval 工具的基础描述文本。
3.3 与工具暴露状态的联动
getCodeModeDirectToolNames与getEvalBridgeToolNames的会话侧实现在 agent-session.ts 与 session-tools.ts。此外,EvalTool.supportsCodeModeTransport()只在会话启用了 JavaScript 后端时返回true(见 eval.ts),因为 Code Mode 下的await tool.*、Promise.all等编排语法依赖 JS VM 的异步语义。
四、preludeDeclarations:扩展能力如何接入声明块
eval prelude 是"跑在语言 VM 内、但特权处理留在宿主进程"的能力片段,其类型定义在 preludes.ts。EvalPreludeDefinition中与本文直接相关的是可选字段:
/** Optional declarations appended to code-mode TypeScript context while enabled. */ codeModeDeclarations?: string;启用中的 prelude 会贡献两部分内容(见 eval.ts):
documentation追加进 eval 工具描述(仅在启用时展示);codeModeDeclarations经拼接后注入preludeDeclarations模板变量,落到declare const tool之后的上下文里。
这使扩展作者可以为自己的 prelude 补充独立的 TypeScript 类型声明(如自定义全局函数签名),且与工具声明一样遵循"启用才注入"的纪律:getEnabledEvalPreludes会先过滤掉enabled?.() === false的定义(见 preludes.ts)。
五、Code Mode 下模型可用的桥接面:eval prelude 全貌
虽然 Code Mode 折叠了直接工具面,但通过tool.*桥接与 prelude,模型在 eval cell 内仍拥有完整的工作能力。eval 工具主提示 eval.md 中声明的 prelude 函数即为桥接面的主体(注意{{#if js}}分支在 JS 后端下这些调用需await,且 JS 采用"单个尾随对象字面量"传参,禁止位置参数):
| 能力 | 调用形态 | 说明 |
|---|---|---|
| 输出 | display(value)/print(...) | 将值回传给模型 |
| 文件 | read(path, offset?, limit?)/write(path, content) | 经tool.*之外的 prelude 直通宿主 |
| 环境 | env(key?, value?) | 读写环境变量 |
| 工件 | output(*ids, format?, query?, offset?, limit?) | 读取会话工件 |
| 工具桥接 | await tool.<name>(args) | 调用任意会话工具,args 即其参数对象 |
| 单次补全 | completion(prompt, model?, system?, schema?) | 无状态一次性补全,.wait()取结果 |
| 子代理 | agent(prompt, agent?, label?, schema?, ...) | 后台子代理,返回 handle |
| 汇聚 | wait(handles, timeout?, raise_errors?) | handle 屏障,结果按输入顺序返回 |
| 工作池 | workpool(agent?, name?, context?, ...) | 批量独立任务的常驻工作池 |
| 自定义工具 | @tool/tool(fn, ...) | 在 kernel 内定义可被 task 引用的工具 |
| 日志/预算 | log(msg)/phase(title)/budget | 进度上报与预算查询 |
其中agent、workpool、@tool等能力还受eval.tools.enabled、spawn 策略等配置门控,是否出现在 prelude 文本中由模板条件变量控制——这保证了提示词与运行时能力永远一一对应。
六、实战:一个典型的 Code Mode cell 编排示例
综合原模板规则与 prelude 能力,一个符合规范的编排示例(JS 后端)大致如下:
// cell 1:一次性初始化(后续 cell 复用,绝不重复 import) const cfg = JSON.parse(await tool.read({ path: "package.json" })); const depNames = Object.keys(cfg.dependencies ?? {}); // cell 2:独立操作不逐个 await,spawn 后统一汇合 const p1 = tool.grep({ pattern: "TODO", path: "src" }); const p2 = tool.glob({ pattern: "**/*.test.ts" }); const [todoHits, testFiles] = await Promise.all([p1, p2]); // cell 3:依赖前一步结果,单独成 cell display({ depCount: depNames.length, testCount: testFiles.length, todos: todoHits });对应地,Python 后端走同步形态:data = tool.read({ path: "package.json" }),异步调用需显式await tool.read({...})。
七、结语:从提示模板到宿主实现的闭环
eval-code-mode.md虽仅十余行,却是一整套执行架构的"对外契约":折叠直接工具面 → eval 为主工作表面 → 宿主动态生成工具声明 → 模型在 cell 内经tool.*桥接完成全部工作。其正确性依赖 code-mode-declarations.ts 的类型生成、eval.ts 的"每次读取重算 + 白名单去重"装配、settings-schema.ts 的模式配置,以及 preludes.ts 的能力声明注入——四者共同保证了模型看到的声明面与运行时实际暴露的工具严格一致,既不遗漏也不重复。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考