
opencodex 原生 web_search_call 桥接让 Codex 界面显示 Sidecar 搜索活动的实现与验证【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址: https://gitcode.com/gh_mirrors/ope/opencodex导读opencodex 作为 OpenAI Codex / Claude Code 的通用 Provider 代理允许把任意 LLMClaude、Gemini、Grok、DeepSeek、Ollama 等接入 Codex CLI、App 与 SDK。当被路由的非 OpenAI 模型发起联网搜索时搜索实际由 opencodex 的 web-search sidecar 执行但结果只以 tool_result 文本喂回模型Codex 客户端不会出现原生的 Searched the web 活动条目。本文基于devlog/_fin/260630_native-sidecar-parity/20_phase1_verification.md及同目录研究/计划文档完整还原 Phase 1 如何通过桥接层补发web_search_call输出项让 Codex 界面原生呈现 sidecar 搜索活动并给出源码级实现证据与验证记录。读完后你将掌握codex-rs 原生搜索项的 wire 形状、opencodex 桥接层的实现落点、以及这一改动的测试与回归验收方法。背景sidecar 搜索为何在 Codex 界面隐身opencodex 对非 OpenAI 模型的联网搜索采用sidecar架构其完整链路见 src/web-search/loop.ts为路由模型产生一个合成的web_search工具调用opencodex 拦截该调用scanEventsForWebSearch不再把它转发给 Codexsrc/web-search/executor.ts 通过 ChatGPT forward/responses后端或 anthropic/xai/gemini/exa 等其他 backend执行真实托管搜索结果经 src/web-search/parse.ts 提取文本与 URL 引用作为 tool_result 注入回路由模型的对话上下文。问题在于合成调用被拦截后Codex 客户端从未收到任何表示发生了搜索的事件因此界面只显示一个普通的回答文本用户看不到搜索活动。原研究文档 00_research.md 把这一现象概括为sidecar 只把搜索结果喂回路由模型当纯文本而没有让路由 Provider 在 Codex UI 中看起来像原生。codex-rs 消费端原生能力已经存在研究阶段A gate 审计对象确认codex-rsCodex 的 Rust 客户端/服务端本身已经理解原生的 web-search 活动项codex-rs/protocol/src/models.rsResponseItem::WebSearchCallwire 类型web_search_call字段为id、status、actionWebSearchAction::{Search, OpenPage, FindInPage, Other}其中action: { type: search, query }是合法形状codex-rs/core/src/event_mapping.rs把ResponseItem::WebSearchCall映射为TurnItem::WebSearchcodex-rs/core/src/session/turn.rsresponse.output_item.added表示条目开始response.output_item.done表示条目完成codex-rs/tui/src/history_cell/search.rs渲染搜索历史单元格。关键 wire 细节决定桥接形状审计时逐条确认SSE 反序列化时event.item直接映射为ResponseItem::WebSearchCallid 字段是id而非call_id且反序列化时读取该字段skip_serializing只影响 codex-rs 自身再序列化不影响 SSE 消费者读取status需要in_progress/completed失败场景可视为failed同时发出added与done与原生生命周期一致单独一个done也可工作但 addeddone 能驱动搜索中的 spinner。研究文档给出了最小事件形状added 帧携带status: in_progressdone 帧携带status: completed与action: { type: search, query }两帧共享同一id。这意味着codex-rs 无需任何客户端改动即可解析并渲染parity 工作完全落在 opencodex 桥接层。Phase 1 实现方案四个改动点P 阶段设计计划文档 10_phase1_websearch-native-ui.md 将改动边界严格限定为四个文件文件改动src/types.tsAdapterEvent联合类型新增web_search_call变体src/bridge.tsstreaming 路径bridgeToResponsesSSE与非 streaming 路径buildResponseJSON各加一个 case补发自包含的web_search_call输出项src/web-search/loop.ts记录真实执行的搜索而非空查询/超限/重复占位符在最终回答之前前置web_search_call事件tests/bridge.test.tstests/web-search.test.ts覆盖 streaming、非 streaming 与 loop 行为计划明确 OUT 的边界不改 adapters、sidecar executor、parse.ts不改已提交的 kiro/timeout 工作vision sidecar 不在此阶段范围内研究结论是原生view_image工具已有原生 UI用户附件图片本就渲染在用户消息中sidecar 的图片描述属于请求预处理不应伪造ImageView事件此阶段也不转发 sidecarsources作为 citations/annotations原生单元格只需要action.queryannotations 留待后续阶段。当前源码中的实际形态begin/end 双事件演进文档记录的是单事件web_search_call的初始设计在当前仓库中该设计已演进为web_search_call_begin/web_search_call_end双事件见 src/types/request.ts| { type: web_search_call_begin; id: string } | { type: web_search_call_end; id: string; queries: string[]; status?: completed | failed; sources?: OcxUrlCitation[] }这一演进与 codex-rs 的 added/done 生命周期一一对应begin打开搜索单元格并立即发出response.output_item.addedstatus: in_progress让 Codex 在 sidecar 实际执行期间显示 Searching the web 的 spinnerend关闭单元格并发出response.output_item.donestatus: completed或failedaction携带全部查询。这也解决了计划文档中输出索引与 finishedItems 一致性的风险点。桥接 streaming 路径src/bridge/sse.ts在bridgeToResponsesSSE的事件分发中web_search_call_begin分支src/bridge/sse.ts先关闭任何打开的 message / reasoning / tool call 条目分配一个新的output_index随后发出{ type: response.output_item.added, output_index: N, item: { type: web_search_call, id: ws_..., status: in_progress } }web_search_call_end分支src/bridge/sse.ts完成单元格若begin未打开防御性场景会先合成 added 帧以保证 done 有匹配项再调用closeCurrentWebSearch发出 done 帧。核心收尾逻辑closeCurrentWebSearchsrc/bridge/sse.ts会以同一itemId发出response.output_item.done携带status与action把完成的 item 记入finishedItems供终端response.completed快照使用递增outputIndex保证后续 message 的索引单调若存在sources以附加字段方式挂在 done item 上codex-rs serde 忽略未知字段供下游 translators如 claude outbound填充web_search_tool_result内容——这是后续 Phase 3 sources 能力的地基。防御性收尾同样重要当流以 error/incomplete 终止时src/bridge/sse.ts仍在飞行中的搜索会以failed状态关闭确保 Codex 界面的 Searching the web spinner 永远不会无限旋转。桥接非 streaming 路径src/bridge/response-json.ts非 streaming 的buildResponseJSON同步处理web_search_call_begin/endsrc/bridge/response-json.ts先冲刷打开的文本/reasoning 缓冲区再压入一个status: completed的web_search_call项。计划文档对此的注释值得保留当前 web-search loop 只走 streaming 路径但保持 JSON 构建器同步可避免未来某个非 streaming 路径喂入该变体时事件被静默丢弃。搜索 loop 侧只记录真实搜索src/web-search/loop.tssrc/web-search/loop.ts 的runSearchCall是记录点它对每个查询执行 sidecar 搜索后递增searchesExecuted与executedSearchCount并只在真实执行了搜索时发出web_search_call_begin/web_search_call_end。以下三种占位场景绝不产生搜索单元格空查询模型调用了web_search但既无query也无queries重复失败查询本轮已失败过的查询failedQueries去重直接短回路超限searchesExecuted maxSearches时的限额提示。这一记录真实搜索的决策正是验证文档强调的runWebSearch分支之外的空/限额/重复占位符不得渲染为已完成的原生搜索避免界面出现虚假的搜索活动。此外批量查询nativeaction.search.queries复数形式会被配对为一个assistant toolCall 一个聚合 toolResult因此界面上也只会出现一个搜索单元格但queries数组携带全部尝试过的查询测试中专门断言了EXACTLY ONE web_search_call cellahead of the messagecarrying both queries。事件顺序搜索在前回答在后无论初始单事件设计还是当前 begin/end 形态排序决策一致所有搜索活动项先行最终回答项在后。这符合原生流程先搜索、后作答而且因为 loop 在产出finalEvents之前已经执行完所有搜索前置顺序实现简单且确定。流式场景下runSearchCall在真实 sidecar 执行期间 yield 出web_search_call_beginspinner 随真实搜索时序出现而最终回答的 passthrough 事件最后到达。验证记录本文档核心A gate 审计与 C gate 检查A gate独立审计gpt-5.5 独立评审对 Phase 1 给出APPROVE无 P0/P1并对照 codex-rs 源码逐条确认SSE 将event.item直接反序列化为ResponseItem::WebSearchCallid 字段是id而非call_id且在反序列化时读取protocol/src/models.rs中的skip_serializing不影响此读取action: { type: search, query }是合法形状added done 双帧匹配原生生命周期单 done 也可工作评审折叠进的三处精化使用event.id而非新生成的 uuid、只在真实runWebSearch分支记录、增加 loop 级测试。C gate全新证据验证文档给出了完整的本地验证矩阵检查项命令结果类型检查bun x tsc --noEmitexit 0隐私扫描bun run privacy:scanPrivacy scan passedexit 0定向测试bun test tests/bridge.test.ts tests/web-search.test.ts tests/sidecar-abort.test.ts29 pass / 0 fail含 4 个新测试全量测试bun test tests1563 pass / 71 fail / 13 errors文档对全量数字做了严谨的归因71 个失败与 13 个错误均为预先存在且属环境性cursor-agent 路径测试、logger 文件测试、本地缺失opencode-ai/plugin/tool模块在干净 stash 上的基线完全相同71/13且 pass 数从 1559 升至 1563恰好是 4 个新测试。CI全新--frozen-lockfile安装不出现这些本地环境失败。未引入任何新失败——这是回归验收的关键证据。范围遵守验证记录明确改动仅限src/types.ts、src/bridge.ts、src/web-search/loop.ts与两个测试文件kiro/timeout 提交未触碰vision sidecar 无改动sidecarsources/citations 本阶段不转发单元格只需queryannotations 留待后续阶段。测试证据如何在仓库中复现验证当前仓库的测试已经覆盖并超越了验证文档所列的验收标准。tests/web-search/web-search.test.ts中有专门的 describe 块 web-search sidecar native web_search_call emissiontests/web-search/web-search.test.ts关键断言包括执行的搜索产生单元格且先于回答output.map(item item.type)等于[web_search_call, message]且单元格的action为{ type: search, query: current docs }L1750 起空查询与限额占位符不产生单元格output.some(item item.type web_search_call)为 falseL1783 起流式时序added(in_progress)帧必须在 sidecar 执行期间到达L1960 起批量查询单单元格恰好一个web_search_call单元格、先于消息、携带两个查询原生复数标签L2040 起。桥接侧的 streaming 非 streaming 形状覆盖在tests/adapters/bridge.test.ts与tests/adapters/bridge-nonstreaming-terminal.test.ts中与文档新增 4 个测试的验收标准added→done 顺序、同item.id、action.query相等、终端response.completed有效一致。风险与边界实现时必须守住的四条线计划文档与验证记录共同沉淀出四条实现纪律对后续维护者同样适用真实性truthfulness只对真实命中 sidecar 的搜索发单元格空查询、失败重复、限额占位符绝不渲染为已完成搜索避免界面欺骗wire 形状精确性id而非call_idadded/done 共享同一 idstatus语义准确in_progress/completed/failed输出索引一致性搜索项占用一个output_index后续 message 索引保持单调finishedItems与response.completed快照一致流被截断时以failed关闭在途搜索杜绝永久 spinner不越界搜索侧是桥接层改动无需动 sidecar executor 与解析器vision sidecar 保持原状原生view_image已有 UI附件图片已在用户消息中渲染sidecar 描述属内部预处理。总结Phase 1 验证记录展示了一条清晰的原生 parity实施路径消费端codex-rs原生能力早已存在缺口只在代理桥接层。通过web_search_call_begin/end双事件、streaming 与 non-streaming 双路径同步实现、以及 loop 侧只记录真实搜索的纪律opencodex 让任何被路由的非 OpenAI 模型在 Codex 界面中呈现与原生一致的搜索活动而无需修改 Codex 客户端。A gate 独立审计 APPROVE、C gate 全量测试无新增失败、范围严格收敛于四个文件——这既是该功能可落地的依据也为后续 Phase 2搜索保真度与 Phase 3sources/citations 转发保留了清晰的演进接口。【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址: https://gitcode.com/gh_mirrors/ope/opencodex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考