用于 Codex / OpenAI Responses 兼容 API 的“继续思考”中间件。
本项目是一个轻量 Starlette 代理,部署在编码代理和上游 Responses 接口之间。它会检测一种已知的推理截断指纹:usage.output_tokens_details.reasoning_tokens == 518 * n - 2。检测到后,中间件会在后台让模型继续思考,并把多轮上游流式响应折叠成一个连贯的下游 SSE 响应。
编码代理 -> CodexCont -> Codex / Responses API
用 AI Agent 安装? 把
INSTALL-GUIDE-AGENT/AGENT.md交给你的 Agent —— 这是一份专为 AI Agent 在你机器上逐步执行而写的安装手册。
本项目是对已观察到的 OpenAI Codex 推理截断机制的明确绕过。若使用本中间件的行为被视为滥用、违反服务条款、导致费用异常增加,或造成其他不良后果,均由使用者自行承担责任。
- 实时向下游转发 reasoning 项,保留“正在思考”的体验。
- 在上游 terminal event 出现前,缓存暂定的最终输出(
message和function_call)。 - 可选在折叠期间发送无副作用的 SSE 心跳事件,避免长时间缓存输出导致下游 idle 超时。
- 如果本轮被判定为截断,丢弃暂定输出,并携带已产生的 reasoning 打开下一轮续写请求。
- 如果本轮自然完成或触发安全上限,冲刷最终一轮的输出,并发出一个重构后的 terminal response。
- 对不符合条件的请求透明透传。
默认续写方式是隐藏的 phase: "commentary" assistant 消息("Continue thinking...")。也支持旧版的合成工具调用对(tool_pair)模式。
- Python
>= 3.12 - 推荐使用
uv
运行依赖在 pyproject.toml 中声明:
httpxstarletteuvicorn
uv sync
cp config.example.toml config.toml
uv run python run.pyrun.py 会读取本地 config.toml;请先从 config.example.toml 复制一份,再按需调整。
示例默认服务监听 127.0.0.1:8787,接受以下路径的 POST 请求:
/v1/responses
也可以直接使用当前虚拟环境运行:
# Windows / 本工作区 Git Bash
.venv/Scripts/python.exe run.py把原本的上游接口地址替换为本代理地址即可。
示例:
http://127.0.0.1:8787/v1/responses
示例默认配置(config.example.toml,复制为 config.toml 后使用)为:
[upstream]
url = "https://chatgpt.com/backend-api/codex/responses"
mode = "header"当 mode = "header" 时,请求头 Responses-API-Base 会覆盖配置中的 url;如果没有该请求头,则回退到配置的 Codex URL。
例如,要指向通用 Responses 兼容端点,可以发送:
Responses-API-Base: https://api.openai.com/v1
中间件会自动追加 /responses;如果传入值已经以 /responses 结尾,则保持不变。该控制头不会被继续转发到上游。
config.toml 支持三种鉴权模式。示例默认值是 passthrough:
[auth]
mode = "passthrough" # passthrough | inject | passthrough_then_inject
access_token = "" # 作为 Authorization: Bearer <access_token> 发送
chatgpt_account_id = "" # 非空时作为 chatgpt-account-id 发送模式说明:
passthrough:只转发调用方提供的鉴权头,不注入配置中的凭据。inject:使用配置中的凭据设置/覆盖鉴权头。passthrough_then_inject:调用方已有鉴权头则保留;没有时才用配置中的凭据补上。
安全保护:如果请求使用 Responses-API-Base 指定了上游地址,中间件不会把配置中的凭据泄露到这个由请求方指定的 URL。如果当前鉴权模式会为该请求注入配置中的凭据,请求会被 400 拒绝。若要对每个请求动态指定上游并使用凭据,请让调用方自己携带 Authorization,并使用 mode = "passthrough" 或 mode = "passthrough_then_inject"。
不要提交密钥。.gitignore 已忽略 rt.json 和 free_rt.json;如果把 token 写入 config.toml,也请谨慎管理。
只有同时满足以下条件时,中间件才会执行折叠逻辑:
[continue].enabled = true- 请求体是 JSON 对象
stream为真- reasoning 没有被显式禁用(
"reasoning": false会关闭折叠) - 使用
method = "tool_pair"时,请求没有声明与[continue].continue_tool_name同名的真实工具
其他请求会作为普通流式请求透明透传。
每个上游 round 的处理流程:
- reasoning 相关事件实时转发,并重写
sequence_number和output_index。 - message 和 function-call 事件作为暂定输出缓存起来。
- 收到 terminal event 后读取
usage.output_tokens_details.reasoning_tokens。 - 如果 token 数匹配
518 * n - 2,位于配置的 tier 窗口内,存在 encrypted reasoning content,且安全上限允许,则中间件会:- 丢弃本轮暂定输出;
- 把本轮 reasoning 和续写标记追加到下一轮请求 input;
- 打开新的上游流式 round。
- 否则,冲刷最终缓存的输出,并发出重构后的 terminal event。
下游编码代理只会看到一个 response;隐藏轮次的细节会写入最终响应的 metadata。
心跳注入默认关闭,因为 response.keep_alive 是 Codex 兼容扩展,并非公开文档中定义的 Responses API 流式事件。启用后,请求进入折叠逻辑时,代理会定期发送这个无副作用的 SSE 事件。Codex 会先解析该事件(从而重置基于事件的 idle 计时器),再安全地忽略这个未知的 Responses event 类型。心跳特意不带 sequence_number,因此不会干扰真实响应事件的序号;即使上游 message 或 function-call delta 正在持续到达但仍被代理缓存,下游流也能保持活跃。需要时可在 [stream] 中启用:
[stream]
heartbeat_interval_seconds = 15.0 # 秒;保持为 0 则关闭心跳透传请求仍保持字节透明,不会注入本地心跳。
最终重构响应会包含代理相关 metadata,例如:
metadata.proxy_rounds:每轮 reasoning token 数和检测出的 tiern。metadata.proxy_billed_usage:隐藏上游轮次的真实累计 token 用量。metadata.proxy_stopped_reason:当续写因上限或错误停止时出现。
下游可见的 usage 会被重构得像单个 response:输入/缓存 token 取第一轮,reasoning token 累加,最终一轮的非 reasoning 输出计入 output。
测试套件是自包含的,不依赖 pytest:
uv run python tests/test_middleware.py
# 或
.venv/Scripts/python.exe tests/test_middleware.py当前离线测试覆盖:
- 截断数学判断
- 增量 SSE 解析
- 折叠输出被缓存期间的下游 SSE 心跳事件
- 基于抓包 fixture 的折叠/重写行为
- commentary 和 tool-pair 两种续写 payload
- header 透明转发
- 上游 URL 解析
- 鉴权安全保护
- EOF / 上游错误处理
middleware/
app.py # Starlette 应用和路由处理
codex.py # 截断数学和续写 payload 构造
config.py # config.toml 加载和 dataclass 配置
creds.py # 上游 header / auth 构造
proxy.py # fold_stream 状态机
sse.py # 增量 SSE 解析和序列化
store.py # 可选 stateful repair 使用的内存 ID 存储
tests/
test_middleware.py
fixtures/
run.py # uvicorn 入口
config.example.toml # 示例运行配置;复制为 config.toml 后本地使用
- 最终答案文本会被缓存到 terminal round 证明未截断之后才发出,因此最终答案首 token 延迟可能高于普通流式请求。SSE 心跳事件会在此期间保持连接活跃,但不会提前暴露暂定文本。
- 非流式请求当前会透传,不进行折叠。
- 截断检测器是针对已观察到的
518 * n - 2指纹设计的。 - 可选的
repair_followup = "stateful"使用进程内内存状态;多代理实例之间不会共享。
感谢 LINUX DO 社区的相关讨论,没有这些讨论,也就没有本项目。特别感谢 LINUX DO 社区的 @shinorochi 和 @dskdkj 一同明确截断机制和 GPT 的思考模型,感谢 @shinorochi 提出的基于 commentary 输入而非工具调用伪造的更好方案。