AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥
- HTML: https://decision.ht1072.top/2026-06-04-anyrouter-codex-adapter-cpa-tool-bridge.html
- Local HTML:
[已移除本地路径] - Generated: 2026-06-04T14:33:59+08:00
AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥的排障复盘
页面摘要区
核心结论: AnyRouter 不能直接作为普通 OpenAI-compatible upstream 挂入 CPA;必须通过本地 Codex-shape adapter 模拟 Codex CLI /v1/responses 请求,才能稳定把 gpt-5.5 暴露为 CPA alias ANY-5.5。
推荐路径: 保留 CPA 原生 Codex OAuth 作为主路,ANY-5.5 作为实验/备用 provider;adapter 继续本地监听 127.0.0.1:8331,CPA 通过 openai-compatibility 接入。
关键依据: 直连 AnyRouter TLS 会 SSLV3_ALERT_HANDSHAKE_FAILURE,必须走 127.0.0.1:7890;普通 Chat/Responses 调用会出现 HTML 假 200 或 invalid codex request;Codex-shape body + headers 后,CPA 非流式和流式都能返回 OK,并且非流式 usage 可记录 token。
落地方式: 已实现 [已移除本地路径],已创建并启用 anyrouter-codex-adapter.service,已在 CPA config.yaml 加入 alias ANY-5.5,并补齐工具调用桥接,避免 terminal、skill_view、web_search 等工具参数 JSON 泄漏成正文。
风险与边界: 当前是 systemd --user 服务,不是 /etc/systemd/system 系统级 unit;user linger 当前为 no,如果需要开机无人登录自动启动,应额外执行 loginctl enable-linger ht 或迁移为 system unit。AnyRouter adapter 仍依赖上游对 Codex-shape 请求的兼容和本机 7890 代理。
行动清单: 继续把 ANY-5.5 当备用 provider 观察;若要作为主模型,需要压测多工具、多轮、长上下文和流式 usage;如需系统级托管,由涛哥执行 sudo/系统级 unit 安装。
背景
用户提供 AnyRouter:
- 地址:
https://anyrouter.top - 模型:
gpt-5.5 - 目标:写入 CPA 后虽然显示通了,但实际不能稳定用,尤其工具调用异常。
最初 CPA 侧出现过 “HTTP 200 成功” 记录,但深入检查后发现它们不是模型成功,而是 HTML/边缘防护页或空 token 记录。后续围绕 AnyRouter、CPA、Codex CLI 请求形态和工具桥接做了完整排障。
关键发现
1. CPA 里的早期 AnyRouter 成功是“假成功”
CPA-Manager usage 中有 provider=any 的记录,表面:
- HTTP 200
failed=false- path 包含
/v1/chat/completions或/v1/responses
但 raw evidence 显示:
Content-Type: text/html; charset=utf-8X-Tengine-Error: denied by http_customtokens.total = 0
结论:这是边缘/WAF 返回的 HTML,不是模型 JSON/SSE 响应。
2. 真正成功的 23:36 附近调用是 CPA 原生 Codex OAuth
当时 request_id 6a103d71:
- provider:
codex - executor_type:
CodexExecutor - model:
gpt-5.5-high - resolved_model:
gpt-5.5 - total_tokens:
119639
这条与 AnyRouter 无关。
3. AnyRouter 直连不能省代理
A/B 测试:
- direct no proxy:
SSLV3_ALERT_HANDSHAKE_FAILURE - via
http://127.0.0.1:7890:可进入 API/SSE 层
因此 adapter service 当前必须保留:
HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
4. 普通 Chat→Responses adapter 不够,必须 Codex-shape
普通 adapter 请求会被 AnyRouter 判定:
must be stream requestinvalid codex request
抓取 Codex CLI 0.130.0 真实请求体后确认关键形态:
/v1/responsesstream: truestore: falseinclude: ["reasoning.encrypted_content"]prompt_cache_keytext: {"verbosity":"low"}client_metadata.x-codex-installation-idtool_choice: autoparallel_tool_calls: true- Codex-style tools schema
- headers:
originator,session_id,x-codex-*,x-client-request-id, Codex User-Agent
补齐后,AnyRouter 返回真实 SSE,并可转换成 OpenAI Chat Completions。
已落地内容
Adapter 文件
[已移除本地路径]
能力:
GET /healthGET /v1/modelsPOST /v1/chat/completionsPOST /v1/responses- Chat messages → Codex-shaped Responses input
- OpenAI tools → Responses tools
- Responses SSE → Chat Completions JSON / SSE
- 工具调用 JSON 兜底 → Chat
tool_calls
systemd --user service
当前已创建:
[已移除本地路径]
状态:
enabled
active
listen: 127.0.0.1:8331
unit 内容:
[Unit]
Description=AnyRouter Codex-shape Chat adapter
After=network-online.target
[Service]
Type=simple
EnvironmentFile=[已移除本地路径]
WorkingDirectory=[已移除本地路径]
ExecStart=/usr/bin/python3 [已移除本地路径]
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=default.target
注意:这是用户级 systemd。当前:
loginctl show-user ht -p Linger
Linger=no
如果要无人登录自动启动,应执行:
sudo loginctl enable-linger ht
或者迁移为系统级 unit。
Env 文件
[已移除本地路径]
权限:
600
包含:
ANYROUTER_KEY=<redacted>ANYROUTER_ADAPTER_HOST=127.0.0.1ANYROUTER_ADAPTER_PORT=8331ANYROUTER_BASE=https://anyrouter.top/v1ANYROUTER_DEFAULT_MODEL=gpt-5.5ANYROUTER_MODELS=gpt-5.5ANYROUTER_TIMEOUT=240ANYROUTER_REASONING_EFFORT=mediumHTTPS_PROXY=http://127.0.0.1:7890HTTP_PROXY=http://127.0.0.1:7890
CPA 配置
已备份:
[已移除本地路径]
新增 provider:
- name: AnyRouter-Adapter
base-url: http://127.0.0.1:8331/v1
api-key-entries:
- api-key: dummy
models:
- name: gpt-5.5
alias: ANY-5.5
CPA 已热加载,无需成功重启即可在 /v1/models 看到:
ANY-5.5
验证记录
本地 adapter health
GET http://127.0.0.1:8331/health
返回:
{"ok":true,"upstream":"https://anyrouter.top/v1","models":["gpt-5.5"]}
CPA 非流式 smoke
请求:
POST http://127.0.0.1:8317/v1/chat/completions
model: ANY-5.5
stream: false
返回:
{
"message": {"role":"assistant", "content":"OK"},
"usage": {"prompt_tokens":182,"completion_tokens":5,"total_tokens":187}
}
CPA 流式 smoke
请求:
model: ANY-5.5
stream: true
返回:
delta.role = assistant
delta.content = OK
finish_reason = stop
[DONE]
多轮历史修复
失败原因:adapter 把历史 assistant 消息转成了 input_text。
AnyRouter 报:
{
"param":"input[1].content[0]",
"code":"invalid_value"
}
修复:assistant 历史用 output_text。
验证:user → assistant → user 多轮请求成功:
total_tokens: 215
failed: 0
工具调用桥接修复
用户观察到正文出现:
{"background":false,"command":"date",...}
以及:
{"file_path":"","name":"hermes-agent"}
根因:AnyRouter/Codex 有时把工具参数 JSON 作为普通文本输出,adapter 之前没有转回 OpenAI Chat tool_calls。
已补通用规范化层,覆盖:
- 裸参数 JSON:
{"command":"date"}、{"name":"hermes-agent","file_path":""}、{"query":"Hermes Agent"} - Markdown fenced JSON
- OpenAI wrapper:
{"name":"terminal","arguments":{...}} tool_calls数组- 多工具调用数组
tool_choice强制指定- Responses 原生
function_call - 流式
delta.tool_calls
验证通过:
- terminal nonstream →
message.tool_calls[0].function.name=terminal - skill_view nonstream →
skill_view - web_search nonstream →
web_search - skill_view stream →
delta.tool_calls,无正文 JSON 泄漏
离线回归测试
新增:
[已移除本地路径]
测试全部通过:
ok test_fenced_json_is_unwrapped
ok test_forced_tool_choice_disambiguates_overlap
ok test_openai_tool_calls_array_is_flattened
ok test_plain_json_without_matching_schema_is_not_tool_call
ok test_response_native_function_call_done_with_arguments
ok test_response_native_function_call_maps_to_chat_tool_call
ok test_skill_view_bare_json_infers_by_schema
ok test_terminal_bare_json_infers_by_required_command
ok test_top_level_array_multi_tool_calls
ok test_wrapper_name_arguments_is_respected
系统级 unit 建议
当前用户无权限直接写 /etc/systemd/system。如果要做成系统级 systemd,可执行:
sudo tee /etc/systemd/system/anyrouter-codex-adapter.service >/dev/null <<'EOF'
[Unit]
Description=AnyRouter Codex-shape Chat adapter
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ht
EnvironmentFile=[已移除本地路径]
WorkingDirectory=[已移除本地路径]
ExecStart=/usr/bin/python3 [已移除本地路径]
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now anyrouter-codex-adapter.service
sudo systemctl status anyrouter-codex-adapter.service
如果保留 user service,只需要补:
sudo loginctl enable-linger ht
风险与边界
风险 1:依赖 AnyRouter 对 Codex-shape 请求的兼容
AnyRouter 若改变对 Codex CLI headers/body 的校验,adapter 可能需要同步调整。
风险 2:必须走 7890 代理
当前直连 TLS 握手失败。若 7890 不在,service 虽然启动,调用会上游失败。
风险 3:流式 usage 仍不完整
非流式 usage 可记录 token;流式 CPA-Manager usage 仍可能显示 total_tokens=0,因为 Chat SSE chunk 未完整回传 usage。
风险 4:工具 JSON 兜底不能无限激进
当前策略是高置信才转换:必须能匹配 tools schema / wrapper / tool_choice / 常见字段。低置信 JSON 会保留正文,避免误把正常 JSON 回答变成工具调用。
后续建议
ANY-5.5先作为实验/备用 provider,不替换 CPA 原生 Codex OAuth 主路。- 如果要长期跑,建议补
loginctl enable-linger ht或迁移系统级 unit。 - 后续继续观察工具调用:如出现新的 JSON 泄漏形态,优先扩展
test_anyrouter_codex_adapter_tools.py,再 patch adapter。 - 如果要用于 Hermes 主模型,建议再压测:多轮工具调用、tool result 回灌、并行工具、长上下文、流式工具调用与真实 WebUI/Feishu 两端表现。