尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Antigravity-Manager 请求转换全解析:Codex /v1/responses 到 Gemini v1internal 的协议映射与稳定前缀优化

Antigravity-Manager 请求转换全解析:Codex /v1/responses 到 Gemini v1internal 的协议映射与稳定前缀优化 Antigravity-Manager 请求转换全解析Codex /v1/responses 到 Gemini v1internal 的协议映射与稳定前缀优化【免费下载链接】Antigravity-ManagerProfessional Antigravity Account Manager Switcher. One-click seamless account switching for Antigravity Tools. Built with Tauri v2 React (Rust).专业的 Antigravity 账号管理与切换工具。为 Antigravity 提供一键无缝账号切换功能。项目地址: https://gitcode.com/gh_mirrors/an/Antigravity-Manager本篇文章基于 Antigravity-Manager 仓库中的 request_transform.md 文档完整剖析该项目代理层中最核心的一条数据通路将 OpenAI Codex 风格的/v1/responses请求转换为 Google Gemini 原生v1internal协议请求。文章不仅继承原文档的完整转换链路图还结合 模型路由源码、会话派生实现、OpenAI 请求映射器 与 Gemini 请求包装器 逐项印证底层原理。读完本文你将掌握model / instructions / input[] / tools[] / 采样参数各自如何被映射与清洗、为什么systemInstruction与tools要构成稳定前缀约 17.5K tokens 的系统提示 约 5K tokens 的工具声明、以及sessionId的 FNV-1a 派生与轮换机制如何避免上游会话膨胀报错。一、为什么需要请求转换两条协议之间的鸿沟Antigravity-Manager 是一个基于 Tauri v2 ReactRust 后端的专业 Antigravity 账号管理与切换工具其核心能力之一是内置代理层让各种第三方客户端Codex、Claude Code、OpenCode、Cherry Studio 等通过统一入口使用 Gemini / Claude 等上游模型能力。其中最具挑战性的一条链路是Codex → GeminiCodex 客户端发出的是 OpenAI 官方/v1/responses协议 JSON字段风格为model、instructions、input[]含message、function_call、function_call_output、local_shell_call、web_search_call等 item 类型、tools[]、prompt_cache_key上游 Gemini 侧则是v1internal原生协议字段风格为model、request.systemInstruction、request.tools[].functionDeclarations、request.contents[]、request.generationConfig、request.sessionId等。两种协议在字段命名、消息角色模型、工具描述结构、采样参数名称上全部不同必须由代理层做一次请求变换request transform并在变换过程中同时解决缓存命中率、会话稳定性、身份注入、参数合法性等工程问题。从源码结构看这条转换链路由 handlers/gemini.rs 作为入口负责方法分派、账号轮换、重试由 mappers/openai/request.rs 完成 Codex 侧解析与清洗由 mappers/gemini/wrapper.rs 完成 Gemini 侧组装与字段排序。三者协同构成下图所示的完整数据流。二、转换全貌一张图看懂字段流向原文档 request_transform.md 用一张对照图完整勾勒了两侧请求体的映射关系这里是完整保留并整理的版本Codex /v1/responses JSON Gemini v1internal JSON ═════════════════════════════ ═════════════════════════ outer body: outer body: ┌─ model ────────────────────────→ resolve_model_route() ──→ model ├─ instructions (string) ─┐ ┌─ request: │ ├─ sanitize ────────────→│ ├─ systemInstruction ← ╗ │ │ Antigravity identity │ │ {role:user, ║ │ │ global system prompt │ │ parts:[{text}…]} ║ ~17.5K tokens │ └────────────────────────│ │ ║ 稳定前缀 │ │ ├─ tools ────── ╗ ║ ├─ tools (Codex schema) ── flatten ── sort ──────→│ │ {functionDeclarations:[…]} ║ │ {type,function,…} clean uppercase │ │ ║ ║ │ │ ├─ toolConfig ║ ║ │ │ ├─ generationConfig ← context params │ │ ├─ sessionId ← FNV-1a(account_id) │ │ └─ contents ← ═══════════════╝ │ │ ↕ (按 role 转换) ├─ input[] │ │ ├─ {type:message, role, content} │ {role: user/model, │ │ └─ text / input_image ─────────────────────→│ parts:[{text/inlineData}]} │ │ │ │ ├─ {type:function_call, name, arguments, id} │ {role: model, │ │ └─ name → shell/apply_patch/… ────────────→│ parts:[{functionCall:{name,args,id}}]} │ │ │ │ ├─ {type:function_call_output, call_id, output}│ {role: user, │ │ └─ output → {result} ──────────────────────→│ parts:[{functionResponse:{name,response,id}}]} │ │ │ │ └─ {type:local_shell_call / web_search_call}→│ 同上特殊 name 映射 │ ├─ temperature ─────────────────────────→ generationConfig.temperature ├─ max_tokens ─────────────────────────→ generationConfig.maxOutputTokens ├─ top_p ─────────────────────────→ generationConfig.topP ├─ thinking ─────────────────────────→ generationConfig.thinkingConfig {thinkingBudget} ├─ stream ── handler 控制 ──────────→ streamGenerateContent / generateContent │ └─ prompt_cache_key (Codex)── 未使用 outer body (续): ├─ project ├─ userAgent: antigravity └─ requestId ← [末尾]这张图的核心价值在于它把一次看似简单的协议翻译拆解成了稳定前缀与动态内容两个相互独立的部分这正是上游缓存命中率优化的关键。原文档用三句话做了精炼总结这里完整保留流向转换Codexinstructions→ developer message →sanitize()清洗动态值 → 加 Antigravity 身份 →systemInstruction.parts[].text稳定前缀的核心 (~17.5K tokens)Codexinput[]→ 逐 item 映射message→contents[role],function_call→functionCall,function_call_output→functionResponse动态内容 (~1.1M tokens)Codextools[]→flatten展平 namespace →sort按 name 排序 → clean schema →tools[].functionDeclarations稳定前缀的一部分 (~5K tokens)下面逐条深入每条转换路径的实现细节。三、模型路由resolve_model_route 的四级优先级Codex 请求中的model字段并不会被原样透传而是先经过模型路由解析再写入 Gemini 请求的model。入口位于 handlers/gemini.rslet initial_mapped_model crate::proxy::common::model_mapping::resolve_model_route( model_name, *state.custom_mapping.read().await, );路由引擎resolve_model_route定义在 model_mapping.rs其匹配优先级为API 热更新废弃模型转发最高物理优先级DYNAMIC_MODEL_FORWARDING_RULES动态表当官方下发了某模型被移除的 fallback path 时强制纠正。对应源码中的update_dynamic_forwarding_rules(old_model, new_model)注册逻辑。精确匹配查用户自定义映射表custom_mapping命中直接返回目标模型。通配符匹配按特异性择优遍历custom_mapping中含*的模式用wildcard_match做多通配符匹配取非通配符字符数最多specificity 最高的模式为胜者。源码注释特别提醒当多个模式特异性相同时结果由 HashMap 迭代顺序决定非确定用户应通过提高模式特异性来规避例如gpt-4*specificity 5永远赢过gpt*specificity 3。系统默认映射map_claude_model_to_gemini查询内置CLAUDE_TO_GEMINI静态表。该表内置了大量 Claude/OpenAI 别名到 Gemini 的映射例如claude-opus-4→claude-opus-4-6-thinkingIssue #1743 重定向、gpt-4o→gemini-2.5-flash对gemini-*前缀与含thinking的模型直接透传未知模型 ID 也不再强制 fallback而是直接透传给 Google API让用户通过自定义映射体验未发布模型。model_mapping.rs内置了详尽的单元测试验证这些优先级例如test_wildcard_priority验证gpt-4*比gpt*更具体时胜出test_multi_wildcard_support验证claude-*-sonnet-*能命中版本化 Sonnet 模型这些测试可以直接在仓库中查看。四、instructions → systemInstruction稳定前缀的基石4.1 收集与身份注入Codex 请求的instructions字符串通常承载 developer message与消息列表中所有role: system/role: developer的文本块会被collect_system_instruction_blocks收集为一个文本块列表request.rs。随后在 Gemini 侧组装为systemInstructionsystemInstruction: { role: user, parts: [{ text: … }] }组装前还会追加 Antigravity 身份声明与全局系统提示词global system prompt使每条请求带上统一的身份前缀。4.2 sanitize剥离动态值保障前缀稳定这是稳定前缀策略中最重要的一个环节。sanitize_system_instruction_for_cacherequest.rs会系统性地剥离/冻结系统提示中的动态内容时间戳Current date/time is: ...、Today is: ...、Date: YYYY-MM-DD ...等行直接删除环境 XML 标签current_date、timezone、cwd、shell的值替换为[DATE_FROZEN]、[TZ_FROZEN]、[WORKSPACE_FROZEN]、[SHELL_FROZEN]占位符——Codex 每次请求都会把当前环境注入这些标签其值随时变化插件版本号/26.609.41114/这类路径中嵌入的动态版本号统一替换为/[VERSION_FROZEN]/UUID标准 8-4-4-4-12 格式统一替换为{uuid}随机 IDreq_xxx、sid-xxx、trace_xxx替换为{id}空白归一3 个以上连续换行合并为 2 个去除首尾空白。清洗结果还会经system_instruction_dedupe_key做去重键归一分词后重新拼接并配合多层级缓存Layer 1 缓存 sanitized 结果跨 session 复用——这些处理的目的只有一个让同一会话内每条请求的 systemInstruction 字节级一致从而让上游能复用已经计算过的系统提示缓存。源码注释将其标为最大的静态块~17,500 tokens。4.3 去重与保留策略sanitize_system_instruction_for_cache还配套了system_instruction_dedupe_key在去重键维度上剔除内容完全相同的系统提示块。同时system_instructions.retain(...)会过滤掉空块避免向 Gemini 发送空的 systemInstruction part。五、input[] → contents[]按角色逐项映射Codexinput[]是整条链路中体量最大可达 ~1.1M tokens的动态部分会被逐 item 映射为 Geminirequest.contents[]数组。映射规则由responses_message_parts、push_responses_content_part等函数实现request.rsCodex input[] item 类型Gemini contents part说明{type:message, role, content}{role: user/model, parts:[{text}]}纯文本消息按 role 直接转换message中的input_imageparts:[{inlineData}]图片转为 base64inlineData块{type:function_call, name, arguments, id}{role: model, parts:[{functionCall:{name,args,id}}]}工具调用回填为 model 侧的 functionCallshell/apply_patch等保留原名{type:function_call_output, call_id, output}{role: user, parts:[{functionResponse:{name,response,id}}]}工具结果包装为{result}形式回填 user 侧{type:local_shell_call / web_search_call}同上特殊 name 映射特殊工具类型复用 functionCall/functionResponse 机制5.1 图片与多模态数据的边界校验值得注意的是映射并不是无脑搬运。validate_responses_input_image_limits/validate_responses_image_data_url会对 input 中的图片做双重校验request.rs图片数量上限Too many input images: maximum is {}解码后总字节数上限Total input image data is too large: maximum decoded size is {} bytes。parse_generation_input_images则要求每张图片必须是 base64 的data:imageURL否则直接报错——这在源头上拦截了畸形或超限的多模态输入避免把脏数据转发到上游。5.2 输出侧的对称转换请求侧做完转换后Gemini 的响应也需要对称映射回 Codex 格式convert_chat_response_to_responses负责把 OpenAI 风格 chat 响应转换为/v1/responses格式含usage.input_tokens_details.cached_tokens等缓存统计字段保证 Codex 客户端拿到的永远是它认识的协议。六、tools[] → functionDeclarations展平、排序、清洗、大写工具声明是稳定前缀的第二大组成部分~5K tokens转换管线为flatten → sort → clean → uppercaseflatten 展平 namespaceflatten_toolsrequest.rs递归展开嵌套的工具结构。由于 Codex 可能以 namespace 形式嵌套声明工具而 GeminifunctionDeclarations要求扁平数组因此需要递归展平。sort 按 name 排序function_declarations.sort_by(...)对声明按函数名排序保证同一组工具在多次请求中的排列顺序恒定——这与 systemInstruction 的稳定化目标一致共同服务前缀缓存。clean 清洗 schema清理不符合 Gemini 规范的字段过滤googleSearch等会与客户端工具派发冲突的声明。wrapper 侧同样会处理当存在functionDeclarations时移除混入的 Google Search见 wrapper.rs 的测试断言 v1internal should avoid mixed Google Search when functionDeclarations present避免客户端工具与模型内置检索双路触发。uppercase 类型大写wrapper 中会对 schema 的type字段做to_uppercase()规范化如object→OBJECT、string→STRING以符合 Gemini 的 JSON Schema 类型枚举要求。最终组装为tools: [{ functionDeclarations: [ … ] }]七、采样参数映射与 generationConfig 的合法性修复Codex 的顶层采样参数按如下规则映射到generationConfigCodex 字段Gemini 字段temperaturegenerationConfig.temperaturemax_tokensgenerationConfig.maxOutputTokenstop_pgenerationConfig.topPthinkinggenerationConfig.thinkingConfig { thinkingBudget }stream由 handler 控制选择streamGenerateContent/generateContent映射之外wrapper.rs 还会对generationConfig做三项合法性修复thinkingConfig 自动注入对需要思考能力的模型自动补thinkingConfig含thinkingBudget并确保includeThoughts: falsemaxOutputTokens 与 thinkingBudget 的约束Googlev1internal要求maxOutputTokens thinkingBudget若不满足则自动把maxOutputTokens提升到min_required_max最低保证 131072避免上游 400 报错对应源码注释[FIX #1747]按模型三层限额Dynamic Static Default 65535的封顶逻辑防止极端配置突破模型输出上限。此外针对不同请求类型如image_genwrapper 会做差异化清理图片生成不支持系统提示时移除systemInstruction并清理responseMimeType、responseModalities等不适用字段。八、sessionIdFNV-1a 派生与会话膨胀自愈8.1 从 account_id 到稳定负整数Gemini 官方客户端习惯用大负整数作为sessionId。Antigravity-Manager 在 session.rs 中实现了与之完全一致的 FNV-1a 哈希pub fn derive_session_id(account_id: str) - String { let mut hash: i64 -3750763034362895579_i64; // FNV offset basis for byte in account_id.bytes() { hash hash.wrapping_mul(1099511628211_i64); hash ^ byte as i64; } hash.to_string() }同一账号总是派生出同一个sessionId从而在会话内保持请求的粘性服务端缓存命中同时不同账号天然隔离。8.2 bump突破 1M token 会话上限源码注释[FIX session-1M]揭示了一个关键工程问题上游按 sessionId 在服务端累积对话输入一个驱动大量工具循环的会话可能把累积输入推到 1M tokens 以上此后该 sessionId 的每个请求都会以400 The input token count exceeds the maximum number of tokens allowed 1048576失败直到上游会话过期。解决方案是引入会话代数generation概念SESSION_BUMPS以(account_id, conversation fingerprint)为键维护单调递增计数器bump_session每触发一次就推进代数derive_session_scoped(account_id, fingerprint, generation)把三者拼入哈希输入生成全新的 sessionId——这强制上游开启全新会话透明地恢复对话能力而同一会话代数内 sessionId 保持稳定不破坏缓存命中见 session.rs 的test_derive_session_scoped测试。8.3 请求体中的注入位置wrapper 在组装 Gemini 请求时注入inner_request[sessionId]且把sessionId安排在稳定字段区systemInstruction → tools → toolConfig → generationConfig → sessionId → contents见 wrapper.rs 的字段重排逻辑。九、requestId 与 userAgent仿真官方客户端的最后拼图为了让上游服务把代理流量识别为官方客户端wrapper 还会做两项仿真requestId 深度对齐按官方格式agent/{timestamp_ms}/{random_hex_8bytes}生成且源码注释特别标注[CACHE] requestId 移到末尾避免动态值破坏前缀字节一致性——因为 requestId 是动态值如果放在请求体前部会导致前缀每次请求都变化、缓存全部失效因此必须放到 JSON 对象末尾动态 userAgent 仿真userAgent: antigravity是基础标识同时支持按客户端适配如 jetski动态切换仿真 UA。结合 wrapper.rs 的注释Gemini 侧最终请求体的字段顺序被固定为systemInstruction稳定~17.5K tokenstools稳定~5K tokenstoolConfig稳定generationConfig稳定sanitize 后一致sessionId稳定基于 account hashcontents动态~1.1M tokensproject、userAgent、requestIdrequestId 置于最末这套顺序在 openai/request.rs 的 Gemini 组装分支中同样被遵守前缀顺序: systemInstruction → tools → toolConfig → generationConfig → safetySettings → sessionId → contents。十、稳定性验证测试用例与调试手段这条转换链路不是一次性实现的仓库中留下了大量回归测试与调试设施可作为理解与验证的入口模型路由测试model_mapping.rs 覆盖精确匹配、通配符特异性、多通配符、catch-all、图片模型归一化gemini-3-pro-image-4k-16x9等后缀归并等场景会话派生测试session.rs 验证同一输入派生稳定、不同指纹/代数派生不同、bump 隔离性转换器测试openai/request.rs 内置prompt_log_identity_cleanup_only_changes_system_instructions确认清洗只影响系统提示、responses_*系列测试responses_routing_identity_follows_the_response_chain验证会话链身份跟随以及 wrapper.rs 的 thinkingConfig 注入、maxOutputTokens 抬升、functionDeclarations 与 Google Search 互斥等断言调试日志debug_logger::write_debug_payloadhandlers/gemini.rs会以original_request形式落盘原始 Codex 请求配合 trace_id 可逐阶段核对转换结果。十一、小结将 Codex/v1/responses平稳转换为 Geminiv1internal远不止字段改名那么简单。Antigravity-Manager 的实践给出了三个关键设计原则前缀稳定化通过sanitize()冻结系统提示中的时间戳、环境标签、UUID 等动态值再对工具声明做展平 排序 清洗 大写把请求体前部打造成字节级稳定的静态前缀最大化上游缓存命中动态区隔离把真正随对话变化的contents~1.1M tokens放在固定字段之后动态的requestId沉到 JSON 末尾避免动态字节污染前缀会话生命周期管理用 FNV-1a 从账号派生稳定sessionId同时用 bump 代数机制在会话膨胀越过 1M token 时自动换新会话兼顾缓存与可用性。对于需要在自己项目中实现多协议中转的开发者request_transform.md 提供了一张可直接照抄的字段映射表而上述源码文件则给出了每个映射步骤的工业级实现细节是理解协议适配层设计的上佳范本。【免费下载链接】Antigravity-ManagerProfessional Antigravity Account Manager Switcher. One-click seamless account switching for Antigravity Tools. Built with Tauri v2 React (Rust).专业的 Antigravity 账号管理与切换工具。为 Antigravity 提供一键无缝账号切换功能。项目地址: https://gitcode.com/gh_mirrors/an/Antigravity-Manager创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表