Hermes Decision Trace

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。

🛠️
落地方式

已实现 /home/ht/code/anyrouter_codex_adapter.py,已创建并启用 anyrouter-codex-adapter.service,已在 CPA config.yaml 加入 alias ANY-5.5,并补齐工具调用桥接,避免 terminalskill_viewweb_search 等工具参数 JSON 泄漏成正文。

证据摘要

  • 直连 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。

行动清单

继续把 ANY-5.5 当备用 provider 观察;若要作为主模型,需要压测多工具、多轮、长上下文和流式 usage;如需系统级托管,由涛哥执行 sudo/系统级 unit 安装。

边界 / 风险

依赖 AnyRouter 对 Codex-shape 请求的兼容

AnyRouter 若改变对 Codex CLI headers/body 的校验,adapter 可能需要同步调整。

必须走 7890 代理

当前直连 TLS 握手失败。若 7890 不在,service 虽然启动,调用会上游失败。

流式 usage 仍不完整

非流式 usage 可记录 token;流式 CPA-Manager usage 仍可能显示 total_tokens=0,因为 Chat SSE chunk 未完整回传 usage。

工具 JSON 兜底不能无限激进

当前策略是高置信才转换:必须能匹配 tools schema / wrapper / tool_choice / 常见字段。低置信 JSON 会保留正文,避免误把正常 JSON 回答变成工具调用。

完整记录

AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥的排障复盘

背景

用户提供 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-8
  • X-Tengine-Error: denied by http_custom
  • tokens.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 request
  • invalid codex request

抓取 Codex CLI 0.130.0 真实请求体后确认关键形态:

  • /v1/responses
  • stream: true
  • store: false
  • include: ["reasoning.encrypted_content"]
  • prompt_cache_key
  • text: {"verbosity":"low"}
  • client_metadata.x-codex-installation-id
  • tool_choice: auto
  • parallel_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 文件

/home/ht/code/anyrouter_codex_adapter.py

能力:

  • GET /health
  • GET /v1/models
  • POST /v1/chat/completions
  • POST /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

当前已创建:

/home/ht/.config/systemd/user/anyrouter-codex-adapter.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=/home/ht/.config/anyrouter-codex-adapter/env WorkingDirectory=/home/ht/code ExecStart=/usr/bin/python3 /home/ht/code/anyrouter_codex_adapter.py 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 文件

/home/ht/.config/anyrouter-codex-adapter/env

权限:

600

包含:

  • ANYROUTER_KEY=<redacted>
  • ANYROUTER_ADAPTER_HOST=127.0.0.1
  • ANYROUTER_ADAPTER_PORT=8331
  • ANYROUTER_BASE=https://anyrouter.top/v1
  • ANYROUTER_DEFAULT_MODEL=gpt-5.5
  • ANYROUTER_MODELS=gpt-5.5
  • ANYROUTER_TIMEOUT=240
  • ANYROUTER_REASONING_EFFORT=medium
  • HTTPS_PROXY=http://127.0.0.1:7890
  • HTTP_PROXY=http://127.0.0.1:7890

CPA 配置

已备份:

/home/ht/cli-proxy-api/config.yaml.bak-anyrouter-adapter-20260604-133106

新增 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

已补通用规范化层,覆盖:

  1. 裸参数 JSON:{"command":"date"}{"name":"hermes-agent","file_path":""}{"query":"Hermes Agent"}
  2. Markdown fenced JSON
  3. OpenAI wrapper:{"name":"terminal","arguments":{...}}
  4. tool_calls 数组
  5. 多工具调用数组
  6. tool_choice 强制指定
  7. Responses 原生 function_call
  8. 流式 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 泄漏

离线回归测试

新增:

/home/ht/code/test_anyrouter_codex_adapter_tools.py

测试全部通过:

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=/home/ht/.config/anyrouter-codex-adapter/env WorkingDirectory=/home/ht/code ExecStart=/usr/bin/python3 /home/ht/code/anyrouter_codex_adapter.py 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 回答变成工具调用。

后续建议

  1. ANY-5.5 先作为实验/备用 provider,不替换 CPA 原生 Codex OAuth 主路。
  2. 如果要长期跑,建议补 loginctl enable-linger ht 或迁移系统级 unit。
  3. 后续继续观察工具调用:如出现新的 JSON 泄漏形态,优先扩展 test_anyrouter_codex_adapter_tools.py,再 patch adapter。
  4. 如果要用于 Hermes 主模型,建议再压测:多轮工具调用、tool result 回灌、并行工具、长上下文、流式工具调用与真实 WebUI/Feishu 两端表现。