Skip to content

feat: add OpenAI Responses native passthrough - #312

Open
WingI9B1 wants to merge 1 commit into
bestruirui:devfrom
WingI9B1:agent/codex-native-passthrough
Open

feat: add OpenAI Responses native passthrough#312
WingI9B1 wants to merge 1 commit into
bestruirui:devfrom
WingI9B1:agent/codex-native-passthrough

Conversation

@WingI9B1

Copy link
Copy Markdown

背景

Octopus 当前使用 AxonHub llm transformer 处理 OpenAI Responses 请求:

OpenAI Responses JSON
→ InboundTransformer
→ llm.Request
→ OutboundTransformer
→ 上游协议

这套流程适合 OpenAI Responses 与 Anthropic 等协议之间的转换,但在客户端和上游都使用 OpenAI Responses/Codex 协议时,会完整解析并重新构造请求和响应。

Codex 当前使用了一些 Responses 协议扩展能力,包括:

  • Remote Compaction V2
  • compaction_trigger
  • compaction
  • encrypted_content
  • Standalone Web Search
  • /v1/alpha/search
  • Codex 专用 Responses item
  • 未来可能增加的未知 Responses 字段和 SSE event

Remote Compaction V2 会在普通 POST /v1/responses 请求的 input 末尾加入:

{
  "type": "compaction_trigger"
}

上游随后通过 Responses SSE 返回包含以下内容的 item:

{
  "type": "compaction",
  "encrypted_content": "..."
}

在现有 transformer 流程中,compaction_triggercompactionencrypted_content 或未知 SSE event 可能无法完整保留。实际表现可能是请求返回 HTTP 200,但最终得到普通 assistant 文本回复,Codex 无法获得所需的 Compaction item。

Octopus 此前也没有注册:

POST /v1/alpha/search
POST /v1/responses/compact

因此 Codex 的 standalone web search 和旧版 remote compact endpoint 无法通过 Octopus 调用。

目标

当客户端使用 OpenAI Responses/Codex 协议,并且最终选中的上游 Channel 同样使用 OpenAI Responses 协议时,允许 Channel 启用原生透传。

原生透传保留 Octopus 的以下能力:

  • API Key 鉴权
  • 模型识别
  • 模型映射
  • Group 和 Channel 路由
  • Channel 优先级
  • Failover
  • Channel 隔离
  • 请求日志
  • Token 用量统计
  • 计费

该改动不会在 Octopus 内实现远程压缩或搜索算法。Octopus 负责将客户端请求透明转发给支持这些能力的上游,并将上游响应透明返回给 Codex。

跨协议调用继续使用现有 AxonHub transformer,例如:

Responses → Anthropic
Anthropic → Responses

实现内容

1. 通用 Native Passthrough Relay

新增通用原生透传实现:

internal/relay/passthrough.go

透传处理器只进行路由所需的最低限度解析:

  1. 读取原始 JSON body
  2. 提取 model
  3. 使用现有 Group、Balancer 和 Channel 逻辑选择上游
  4. 应用模型映射
  5. 只修改 JSON 顶层的 model
  6. 保留其余未知字段和嵌套结构
  7. 将请求发送到上游
  8. 将上游 JSON 或 SSE 响应透明返回客户端

需要保留的内容包括:

  • 未知顶层 JSON 字段
  • 未知 input item
  • compaction_trigger
  • compaction
  • encrypted_content
  • metadata
  • tools
  • reasoning
  • prompt_cache_key
  • 未来新增的 Responses 字段

2. Responses 到 Responses 自动进入透传路径

当以下条件同时满足时进入 Native Passthrough:

  • 客户端 API Format 为 OpenAI Responses
  • 上游 Channel API Format 为 OpenAI Responses
  • Channel 已开启 OpenAI/Codex 原生透传

其他情况继续进入现有 transformer 流程。

该能力使用 Channel 级显式开关,现有 Channel 默认行为保持不变。

3. Responses SSE 透明转发

Native Passthrough 不会把 SSE 解析为 llm.Response 后重新生成。

上游 SSE 数据会直接转发给客户端,以保留:

  • response.created
  • response.in_progress
  • response.output_item.added
  • response.output_item.done
  • response.completed
  • sequence_number
  • response id
  • item id
  • created_at
  • metadata
  • compaction
  • encrypted_content
  • 未知 SSE event
  • 未来新增的 SSE 字段

4. Usage 旁路观察

透明转发期间增加只读 Usage Observer。

Observer 监听:

response.completed.response.usage

并提取:

  • input_tokens
  • output_tokens
  • cached_tokens
  • reasoning_tokens

Observer 只读取 SSE frame,不修改发送给客户端的数据。

普通 JSON 响应也会在不改变响应内容的情况下旁路读取 usage。

5. Failover 边界

原生透传继续支持现有 Channel Failover,但遵循流式响应边界:

  • 上游尚未向客户端发送响应 body 时,允许切换到下一个 Channel
  • 已经向客户端发送任何 SSE 数据后,禁止切换 Channel

该限制用于避免将两个上游的 Responses SSE 拼接到同一个客户端响应中。

6. Codex Standalone Web Search

新增:

POST /v1/alpha/search

该 endpoint 使用相同的 Native Passthrough Relay:

  1. 从原始 JSON 提取 model
  2. 使用 Octopus 现有路由选择 Channel
  3. 应用模型映射
  4. 转发到上游 /v1/alpha/search
  5. 原样返回上游 JSON 响应

Octopus 不解析 standalone search 的 commandssettingsresults 或未来新增字段。

7. 旧版 Remote Compact 兼容

新增:

POST /v1/responses/compact

该 endpoint 同样使用 Native Passthrough。

Codex 当前 Remote Compaction V2 仍通过:

POST /v1/responses
input: [..., {"type":"compaction_trigger"}]

完成远程压缩。/v1/responses/compact 主要用于旧 Codex 和其他兼容客户端,不会改变 Remote Compaction V2 的处理逻辑。

8. Headers

原生透传尽可能保留客户端请求 headers,包括:

  • x-openai-*
  • openai-*
  • user-agent
  • trace headers
  • metadata headers
  • 其他端到端 headers

同时过滤 Hop-by-Hop headers,并重新设置上游鉴权及请求传输相关字段。

Channel 配置

Channel 设置中新增:

OpenAI/Codex 原生透传

建议仅为确认支持 OpenAI Responses/Codex 原生能力的上游开启。

开启后,在客户端和上游均使用 OpenAI Responses 协议时,将保留原始请求字段和 SSE event,只执行鉴权、模型映射、路由、Failover、日志、用量统计和计费。

适用能力包括:

  • Remote Compaction V2
  • Standalone Web Search
  • encrypted_content
  • Codex 专用 Responses item
  • 未来 OpenAI Responses 扩展

自动化测试

新增测试文件:

internal/relay/passthrough_test.go

覆盖以下场景:

  • TestNativePassthroughOrdinaryResponsesPreservesUnknownFieldsAndMapsModel
  • TestNativePassthroughRemoteCompactionV2AndUnknownSSEEvent
  • TestNativePassthroughAlphaSearch
  • TestNativePassthroughLegacyResponsesCompact
  • TestNativePassthroughFailoverBeforeStreamStarts
  • TestNativePassthroughDoesNotFailoverAfterStreamStarts
  • TestNativePassthroughRequiresResponsesChannelOptIn
  • TestNativePassthroughUnknownResponseItemIsPreserved
  • TestNativePassthroughUnknownSSEEventIsByteTransparent
  • TestNativePassthroughUsageObserverRecordsCachedAndReasoningTokens

后端测试结果:

go test -modfile=.codex-test.mod -mod=mod ./...

结果:

PASS

前端构建结果:

pnpm build

结果:

PASS

当前 pnpm lint 会报告上游已有的 24 个全量 ESLint 错误,报错文件没有出现在本 PR 的修改文件中。本 PR 没有混入无关的 lint 修复。

手动测试前提

测试 Channel 需要满足:

  1. API Format 设置为 OpenAI Responses
  2. 开启“OpenAI/Codex 原生透传”
  3. 上游真实支持对应 Codex 能力
  4. Octopus API Key 有权访问测试模型
  5. 根据实际配置替换模型名和 API Key

手动测试 1:Remote Compaction V2

curl -N -i http://127.0.0.1:8080/v1/responses \
  -H 'Authorization: Bearer sk-octopus-XXXX' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-5.6-luna",
    "store": false,
    "stream": true,
    "input": [
      {
        "role": "user",
        "content": "今天的天气不怎么好,有点要下雨的可能。"
      },
      {
        "type": "compaction_trigger"
      }
    ]
  }'

预期结果:

  1. HTTP 状态码为 200
  2. Content-Type 为 text/event-stream
  3. 上游收到完整的 compaction_trigger
  4. 客户端收到包含 Compaction item 的 SSE
  5. Compaction item 中包含非空 encrypted_content
  6. Octopus 不生成普通 assistant 文本代替 Compaction item

响应中应能观察到类似内容:

{
  "type": "compaction",
  "encrypted_content": "..."
}

手动测试 2:Codex Standalone Web Search

该请求格式与当前 Codex SearchRequest 保持一致:

curl -sS -i http://127.0.0.1:8080/v1/alpha/search \
  -H 'Authorization: Bearer sk-octopus-XXXX' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "manual-search-test",
    "model": "gpt-5.6-luna",
    "commands": {
      "search_query": [
        {
          "q": "OpenAI Codex remote compaction"
        }
      ]
    },
    "settings": {
      "allowed_callers": [
        "direct"
      ],
      "external_web_access": true
    }
  }'

预期结果:

  1. HTTP 状态码为 200
  2. 请求被转发到上游 /v1/alpha/search
  3. 上游收到相同的 idcommandssettings
  4. 如果发生模型映射,上游收到映射后的 model
  5. 客户端收到上游原始 JSON 响应
  6. 响应中的未知 results 类型和字段不会被 Octopus 删除

响应格式由上游决定,当前 Codex 支持的结构包括:

{
  "encrypted_output": "...",
  "output": "...",
  "results": []
}

其中 encrypted_outputresults 可以由上游省略或返回 null

手动测试 3:旧版 Remote Compact

curl -sS -i http://127.0.0.1:8080/v1/responses/compact \
  -H 'Authorization: Bearer sk-octopus-XXXX' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-5.6-luna",
    "input": [
      {
        "role": "user",
        "content": "请记住代号 A17。"
      },
      {
        "role": "assistant",
        "content": "好的,我记住了代号 A17。"
      },
      {
        "role": "user",
        "content": "继续对话。"
      }
    ]
  }'

预期结果:

  1. endpoint 不再返回 404
  2. 请求转发到上游 /v1/responses/compact
  3. 请求 body 中除模型映射外的字段保持不变
  4. 客户端收到上游原始 JSON 响应

安全与兼容性

  • 原生透传需要在 Channel 上显式开启
  • 现有 Channel 默认行为保持不变
  • 跨协议转换继续使用 AxonHub transformer
  • 模型映射只修改顶层 model
  • 上游 Authorization 会使用 Channel 配置替换
  • Hop-by-Hop headers 不会转发
  • SSE 开始后不会进行跨 Channel Failover
  • Usage Observer 不修改客户端收到的响应

贡献检查

  • 本 PR 仅包含 OpenAI Responses/Codex 原生透传一个变更主题
  • Remote Compaction V2 已通过自动化测试和手动测试
  • /v1/alpha/search 已通过自动化测试和手动测试
  • 普通 Responses 已完成回归测试
  • Failover 边界已通过自动化测试
  • AI 参与内容已经完成人工审查

@bestruirui

Copy link
Copy Markdown
Owner

感谢,你说的这个功能已经做好了,正在本地测试

@WingI9B1

Copy link
Copy Markdown
Author

@bestruirui 大佬辛苦😂,我自己比较需要,然后看 issue 好像没人提,就自己本地修改测试完提交 PR 了😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants