@wacry/codeks-bridge 0.0.0-stage → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +13 -0
  3. package/README.md +40 -2
  4. package/catalog/README.md +63 -0
  5. package/catalog/claude-opus-5-5.json +194 -0
  6. package/catalog/claude-sonnet-5-5.json +194 -0
  7. package/catalog/deepseek-flash.json +203 -0
  8. package/catalog/gpt-5.6-sol.json +152 -0
  9. package/catalog/gpt-6-astra.json +184 -0
  10. package/catalog/gpt-6-luna.json +178 -0
  11. package/catalog/gpt-6-sol.json +182 -0
  12. package/catalog/gpt-6.1-sol.json +187 -0
  13. package/dist/adapter-options.d.ts +5 -0
  14. package/dist/adapter-options.js +41 -0
  15. package/dist/auth.d.ts +2 -0
  16. package/dist/auth.js +42 -0
  17. package/dist/backends/anthropic-tools.d.ts +13 -0
  18. package/dist/backends/anthropic-tools.js +41 -0
  19. package/dist/backends/anthropic.d.ts +9 -0
  20. package/dist/backends/anthropic.js +356 -0
  21. package/dist/backends/chat.d.ts +16 -0
  22. package/dist/backends/chat.js +284 -0
  23. package/dist/backends/deepseek.d.ts +6 -0
  24. package/dist/backends/deepseek.js +37 -0
  25. package/dist/backends/responses.d.ts +5 -0
  26. package/dist/backends/responses.js +227 -0
  27. package/dist/carrier.d.ts +22 -0
  28. package/dist/carrier.js +61 -0
  29. package/dist/catalog.d.ts +8 -0
  30. package/dist/catalog.js +40 -0
  31. package/dist/cli.d.ts +2 -0
  32. package/dist/cli.js +64 -0
  33. package/dist/compaction-handoff.d.ts +13 -0
  34. package/dist/compaction-handoff.js +126 -0
  35. package/dist/compaction-history.d.ts +7 -0
  36. package/dist/compaction-history.js +20 -0
  37. package/dist/compaction.d.ts +20 -0
  38. package/dist/compaction.js +134 -0
  39. package/dist/config.d.ts +6 -0
  40. package/dist/config.js +170 -0
  41. package/dist/errors.d.ts +43 -0
  42. package/dist/errors.js +141 -0
  43. package/dist/headers.d.ts +6 -0
  44. package/dist/headers.js +39 -0
  45. package/dist/history.d.ts +7 -0
  46. package/dist/history.js +85 -0
  47. package/dist/ids.d.ts +1 -0
  48. package/dist/ids.js +4 -0
  49. package/dist/index.d.ts +5 -0
  50. package/dist/index.js +4 -0
  51. package/dist/inspect/capture.d.ts +119 -0
  52. package/dist/inspect/capture.js +370 -0
  53. package/dist/inspect/http.d.ts +7 -0
  54. package/dist/inspect/http.js +149 -0
  55. package/dist/inspect/view.d.ts +239 -0
  56. package/dist/inspect/view.js +388 -0
  57. package/dist/model-catalog.d.ts +18 -0
  58. package/dist/model-catalog.js +16 -0
  59. package/dist/profiles.d.ts +6 -0
  60. package/dist/profiles.js +63 -0
  61. package/dist/prompts.d.ts +5 -0
  62. package/dist/prompts.js +118 -0
  63. package/dist/server.d.ts +4 -0
  64. package/dist/server.js +232 -0
  65. package/dist/sse.d.ts +12 -0
  66. package/dist/sse.js +93 -0
  67. package/dist/tool-history.d.ts +4 -0
  68. package/dist/tool-history.js +73 -0
  69. package/dist/tools.d.ts +35 -0
  70. package/dist/tools.js +193 -0
  71. package/dist/types.d.ts +125 -0
  72. package/dist/types.js +1 -0
  73. package/dist/upstream.d.ts +3 -0
  74. package/dist/upstream.js +89 -0
  75. package/dist/writer.d.ts +95 -0
  76. package/dist/writer.js +430 -0
  77. package/docs/backends-anthropic.md +88 -0
  78. package/docs/backends-deepseek-chat.md +75 -0
  79. package/docs/backends-responses.md +33 -0
  80. package/docs/compaction.md +180 -0
  81. package/docs/config.md +271 -0
  82. package/docs/design.md +97 -0
  83. package/docs/inspect.md +148 -0
  84. package/docs/reasoning.md +72 -0
  85. package/docs/releasing.md +50 -0
  86. package/docs/responses-stream.md +90 -0
  87. package/docs/testing.md +177 -0
  88. package/docs/tools.md +68 -0
  89. package/examples/config.json +35 -0
  90. package/examples/gpt-claude.json +27 -0
  91. package/package.json +46 -4
  92. package/ui/api.js +64 -0
  93. package/ui/app.css +379 -0
  94. package/ui/app.js +489 -0
  95. package/ui/detail.js +713 -0
  96. package/ui/diff.js +281 -0
  97. package/ui/dom.js +258 -0
  98. package/ui/events.js +376 -0
  99. package/ui/favicon.svg +1 -0
  100. package/ui/index.html +58 -0
  101. package/ui/labels.js +106 -0
  102. package/ui/list.js +180 -0
  103. package/ui/structure.js +285 -0
  104. package/ui/tree.js +158 -0
@@ -0,0 +1,148 @@
1
+ # 调试检查器
2
+
3
+ 检查器是 bridge 内置的网页,用来确认一条请求在 bridge 里实际经过了什么转换。打开捕获后,每条 `POST /v1/responses` 记录为一次交换(exchange),包含四段原始内容:
4
+
5
+ 1. codeks 发给 bridge 的请求;
6
+ 2. bridge 发给上游的每个请求;
7
+ 3. 上游返回的原始响应,含 SSE 事件和到达时间;
8
+ 4. bridge 写回 codeks 的响应。
9
+
10
+ 普通轮次使用上游调用 `main`。压缩请求按模式使用文字摘要调用 `summary` 或原生调用 `native`。委托压缩仍标为 `native` 或 `summary`,上游请求中的模型和连接来自实际执行别名。请求需要按需复述且缓存尚无结果时,先执行 `handoff`,成功后再执行目标调用;命中缓存时不产生这条辅助调用。路由前失败的请求(例如 JSON 无效或模型别名未知)没有上游调用,只有前后两段。
11
+
12
+ ## 打开
13
+
14
+ ```sh
15
+ codeks-bridge serve --config /absolute/path/bridge.json --capture
16
+ ```
17
+
18
+ 启动日志会打印页面地址,例如 `Inspector: http://127.0.0.1:8787/inspect/ (capture on)`。不加 `--capture` 时页面同样可用,捕获默认关闭,可以在页面上随时打开或关闭。也可以写进配置:
19
+
20
+ ```json
21
+ { "inspect": { "capture": true, "max_mib": 256 } }
22
+ ```
23
+
24
+ | 字段 | 含义 |
25
+ | --- | --- |
26
+ | `inspect.capture` | 启动时是否捕获,默认 `false`;`--capture` 覆盖为 `true` |
27
+ | `inspect.max_mib` | 内存软上限,默认 256 MiB |
28
+
29
+ ## 访问与鉴权
30
+
31
+ 页面文件(HTML、脚本、样式)不含数据,无需鉴权。数据接口 `/inspect/api/*` 的规则:
32
+
33
+ - 配置了 `token_env`:请求必须带 `Authorization: Bearer <token>`。页面第一次访问会要求输入 token,只保存在当前标签页的 sessionStorage。通过反向代理、远程域名或非 loopback 地址访问时用这种方式。
34
+ - 没有 `token_env`:只接受 Host 为 `127.0.0.1`、`localhost` 或 `[::1]` 的同源请求,其他网站的页面和改写过 Host 的请求都会收到 403。bridge 在远程机器上时,用 SSH 端口转发后在本机打开:
35
+
36
+ ```sh
37
+ ssh -L 8787:127.0.0.1:8787 <remote-host>
38
+ ```
39
+
40
+ 检查器接口在 bridge 入口鉴权之前处理,因此页面请求带 `Origin` 不受入口“带 `Origin` 必须有 token”规则影响;模型请求的鉴权不变。页面使用严格 CSP,只加载本服务的脚本和样式,不访问外部资源。
41
+
42
+ ## 使用页面
43
+
44
+ 左侧列表每行是一次交换,显示开始时间、模型别名和 adapter、类型(轮次或压缩)、每个上游调用的状态、token 用量、子代理与父线程标记和预览。顶部可以按文字筛选(按 `/` 聚焦),或只看错误、压缩、进行中的交换;“按线程分组”把同一线程的请求放在一起。`↑`/`↓` 切换选中项。列表每 1.5 秒刷新,标签页隐藏时暂停。
45
+
46
+ 右侧顶部的卡片按数据流排列:① codeks → bridge,② bridge → 上游,③ 上游 → bridge,④ bridge → codeks。每个实际上游调用各占一行,并显示 `main`、`summary`、`native` 或 `handoff` 标签。点击卡片查看这一段的正文,Shift+点击在右侧并排打开第二段。每段正文有几种视图:
47
+
48
+ | 视图 | 内容 |
49
+ | --- | --- |
50
+ | 结构 | 全部顶层字段;`input`/`output`、`system`/`messages`、`tools` 逐项一行,带类型、角色、长度和标注 |
51
+ | 事件 | SSE 事件列表,名称取 `event`,没有时取 `data.type`;连续的 delta 合并为一行,可展开 |
52
+ | JSON | 可展开的 JSON 树 |
53
+ | 原始 | 原始字节 |
54
+ | 头部 | 状态码与 header |
55
+
56
+ 大内容只在显示时折叠,记录本身完整。长字符串显示开头和结尾,中间注明省略的字符数,可以展开全部、复制或下载;`cxbr1:` 与 `cxbc1:` 载荷可以直接解码。很长的列表分批显示,可以继续加载。Responses Lite 的终止事件可能省略完整 `output`;结构视图会用之前的 `response.output_item.done` 还原输出,原始事件保持不变。
57
+
58
+ 列表和详情顶部的 token 用量取自写回 codeks 的响应,只代表该次 main、summary 或 native 调用。handoff 的独立 usage 可在它的上游响应中查看:SSE 事件、结构或 JSON 视图保留原始字段,不累加进客户端用量。缓存命中不产生新的 handoff 调用,也不重复展示为本次消耗。检查器没有跨调用的计费总额;上游没有报告的字段不能按零消耗理解。
59
+
60
+ “概要”给出实际执行模型的路由配置、每个调用的时间线和上游调用表;客户端模型字段仍保留用户请求的工作别名。“标注”列出全部回放决定,点击可定位到对应项。“对比”选择任意两次交换的任意一段做结构比较,常用的是和同一线程上一轮的客户端请求或上游请求比较;数组按类型和角色对齐,新增、删除的项单独标出。
61
+
62
+ “导出全部”和详情里的“导出此条”下载导出文件。“导入”(或把文件拖到页面上)离线查看导出文件,“返回实时”回到 bridge 的记录。浏览器不允许从本地文件加载页面脚本,离线查看也要通过 HTTP 打开页面,例如任意运行中的 bridge 的 `/inspect/`。
63
+
64
+ ## 记录规则
65
+
66
+ 正文完整保存,不做字段白名单,也不在服务端截断。codeks 或上游新增的字段原样出现,页面只在显示时折叠。
67
+
68
+ 只有凭据类 header 会脱敏。名称按 `-`/`_` 分段后含 `auth`、`authorization`、`token`、`secret`、`password`、`passwd`、`cookie`、`key`、`apikey`、`credential(s)` 或 `signature` 的 header,值替换为 `[redacted: N chars, sha256 xxxxxxxx]`。长度和哈希前缀可以用来判断凭据是否传到了上游、两次请求是否用了同一个凭据。正文不脱敏。
69
+
70
+ 几处需要注意的边界:
71
+
72
+ - 上游请求的 header 是 bridge 设置的 header;Node `fetch` 自动补充的 `host`、`content-length`、`connection`、`accept-encoding` 等不在记录里。上游请求正文是实际发送的字节。
73
+ - 客户端响应记录 bridge 实际写出的状态码、header 和字节,包括 bridge 自己生成的错误。
74
+ - 时间以交换开始为零点,单位毫秒。SSE 事件取完成它的那个数据块的到达时间。
75
+
76
+ 每段响应的 `end` 状态:
77
+
78
+ | `end` | 含义 |
79
+ | --- | --- |
80
+ | `active` | 仍在传输 |
81
+ | `complete` | 读完或写完 |
82
+ | `unread` | bridge 已不再读取这个上游响应,例如收到终止事件后结束;剩余字节没有读取 |
83
+ | `cancelled` | 客户端断开;仍在进行的上游调用随之标记为 `cancelled` |
84
+ | `error` | 传输出错,`error` 字段给出原因 |
85
+
86
+ 交换的 `outcome` 来自写回 codeks 的内容:流式响应取最后一个终止事件(`response.completed`、`response.incomplete`、`response.failed`、`error`),非流式响应取 JSON 的 `status`。客户端断开为 `aborted`;没有写出响应或流没有终止事件时为 `error`。
87
+
88
+ ## 状态标注
89
+
90
+ 检查器对 reasoning 与压缩载荷做标注,判断直接调用 bridge 构造上游历史时使用的同一组函数,结果与实际转换一致。
91
+
92
+ 请求侧标注 codeks 历史里的每个 reasoning 与压缩项,指针形如 `/input/3`。委托压缩按执行模型判断恢复方式,普通请求按工作模型判断:
93
+
94
+ | 类型 | 决定 | 原因 | 含义 |
95
+ | --- | --- | --- | --- |
96
+ | reasoning | `replay` | `same_source` | 来自当前目标,本次回放 |
97
+ | reasoning | `replay` | `allowed_source` | 来自配置允许的其他目标,本次回放 |
98
+ | reasoning | `drop` | `source_not_allowed` | 来源不在当前目标的允许列表,本次请求中过滤 |
99
+ | reasoning | `drop` | `format_mismatch` | 来源允许,但载荷格式与当前 adapter 不同 |
100
+ | reasoning | `drop` | `not_bridge_state` | 不是 bridge 封装的状态,例如直接来自其他 provider 的密文 |
101
+ | reasoning | `reject` | `malformed` | 有 bridge 前缀但无法解码,bridge 会拒绝请求 |
102
+ | compaction | `native` | `native_restored` | 恢复原生加密检查点 |
103
+ | compaction | `summary` | `no_native` | 检查点只有文字摘要 |
104
+ | compaction | `summary` / `handoff` | `native_replay_disabled` | 当前目标关闭了原生恢复 |
105
+ | compaction | `summary` / `handoff` | `source_not_allowed` | 原生检查点来源不在允许列表 |
106
+ | compaction | `summary` / `handoff` | `format_mismatch` | 原生检查点格式与当前 adapter 不同 |
107
+ | compaction | `reject` | `not_bridge_state` | 不是 bridge 生成的压缩项,bridge 会拒绝请求 |
108
+
109
+ `summary` 表示直接使用载荷中已有的文字,`handoff` 表示先取得文字交接:可以复用本地缓存,也可以向原来源生成。只有实际发起辅助请求时,上游调用列表才出现 `handoff`;这条状态标注本身不表示发生了额外 HTTP 请求。
110
+
111
+ 响应侧标注 bridge 新生成的 reasoning 与压缩项(决定为 `output`)。流式响应指向 `response.output_item.done` 事件里的 `/item`,非流式响应指向 `/output/k`。只有文字摘要的压缩项没有原生部分,来源和格式为空;`native` 检查点的来源和格式取原生部分。
112
+
113
+ 每条标注附来源别名、部署名、adapter、来源指纹、载荷格式和解码后的 carrier。`cxbr1:` 与 `cxbc1:` 只是编码,解码后可以看到文字 reasoning、压缩摘要和原生载荷;GPT 原生 `encrypted_content` 本身仍是密文,只能看到原样字符串。
114
+
115
+ ## Claude 服务端输入处理
116
+
117
+ 上游响应详情单独显示服务端原始 `input_transformations` 报告。它与 bridge 根据来源策略做的本地回放过滤不同:本地决定回放的 thinking,仍可能被 Claude 拒绝或丢弃。
118
+
119
+ - `thinking_dropped` 表示服务端报告已丢弃对应 thinking。
120
+ - `thinking_mismatch_allowed` 表示允许失配并保留,不代表丢弃。
121
+ - 每项保留原始 `path`、`reason` 与未知字段;`path` 指向该次实际发出的上游请求,不是 codeks 原始输入的位置。
122
+
123
+ 页面保留报告所在 SSE 事件及字段路径,可回到原始内容核对。流里重复出现的报告不会累加为“丢弃总数”;显式空列表显示“服务端报告为空”,没有报告则不展示这个区块,不能解读为零丢弃或全部验签通过。丢弃可能减少可用的推理上下文,检查器只展示上游报告,不推断模型质量或自行改写历史。策略与故障恢复见 [Claude Messages](backends-anthropic.md#失配策略与故障续接)。
124
+
125
+ ## 内存、清空与导出
126
+
127
+ 记录只在内存里,不写磁盘,进程退出即清空。超过 `max_mib` 时淘汰最早的已结束交换;仍在传输的交换不淘汰,所以占用可能短时超过上限。页面显示已淘汰的数量。
128
+
129
+ 关闭捕获后已有记录保留,正在进行的交换继续记录到结束。清空会释放全部记录,并停止记录这些交换的后续内容;实际模型请求继续运行。
130
+
131
+ 导出下载一个 JSON 文件,`format` 为 `codeks-bridge-capture`,`version` 为 `1`,`exchanges` 是每条交换的完整详情,与页面详情接口相同。记录和导出文件包含提示词、工具输出、文件内容以及解码后的 reasoning,按敏感数据保存和分享。
132
+
133
+ ## 接口
134
+
135
+ 页面使用以下 JSON 接口,也可以用脚本调用。所有响应都带 `cache-control: no-store`。
136
+
137
+ | 接口 | 用途 |
138
+ | --- | --- |
139
+ | `GET /inspect/api/state` | 捕获开关、记录数、进行中数、占用字节、上限、淘汰数、版本号和模型列表 |
140
+ | `POST /inspect/api/capture` | 请求体 `{"enabled": true}` 或 `false`,返回新状态 |
141
+ | `GET /inspect/api/exchanges?since=<rev>` | 版本号之后有变化的摘要,以及仍存在的全部 id |
142
+ | `GET /inspect/api/exchanges/<id>` | 一次交换的完整详情;已清空或淘汰时返回 404 |
143
+ | `DELETE /inspect/api/exchanges` | 清空 |
144
+ | `GET /inspect/api/export` | 流式导出全部详情 |
145
+
146
+ 交换 id 是不透明字符串,只用于查询。
147
+
148
+ 摘要里的 `thread` 取自 codeks 的请求 header:`session-id`、`thread-id`、`x-codex-parent-thread-id`、`x-openai-subagent` 和 `x-codex-window-id`。子代理请求带父线程 id 和子代理类型,可以据此把父子请求对应起来。`preview` 取最后一条用户消息;子代理请求取最后一条 `agent_message`,也就是父代理发来的任务。
@@ -0,0 +1,72 @@
1
+ # Reasoning 与状态复用
2
+
3
+ Reasoning 默认只恢复给产生它的目标,配置可以允许其他来源。是否允许复用和如何解码是两件事:来源策略由 `replay.reasoning_from` 决定,载荷的 `format` 告诉 adapter 如何还原内容。
4
+
5
+ ## 显示与推理分开
6
+
7
+ 目录的 `default_reasoning_summary`、客户端的 `model_reasoning_summary` 和请求的 `reasoning.summary` 控制可见摘要,不决定是否进行推理。Claude / DeepSeek 沿用 GPT-6.1 Sol 的摘要默认值 `none`;在 codeks 配置 `model_reasoning_summary = "auto"` 可打开显示,也可使用 `concise` 或 `detailed`。Codex `0.163.0` 在 `codex-rs/core/src/client.rs` 的 `build_reasoning` 中将有效配置 `none` 表达为省略请求的 summary 字段,因此这两个 adapter 对缺省和 `none` 都不输出可见摘要,仍保存完整原生载荷,并按来源策略回放。GPT 直接保留原生参数和上游摘要事件,由上游响应请求中的显示选择,不在桥接层再次改写摘要。
8
+
9
+ 启用显示时,Claude 请求 summarized thinking,DeepSeek 展示上游 reasoning 文本,不另外调用模型生成摘要。`auto`、`concise` 和 `detailed` 在这两个 adapter 中都表示请求可见内容,不承诺复现 GPT 的摘要长度差异。隐藏摘要也不意味着丢弃签名、密文或明文 reasoning;这些数据仍由 carrier 持久化。
10
+
11
+ ## 保存格式
12
+
13
+ Responses reasoning item 的 `encrypted_content` 保存:
14
+
15
+ ```text
16
+ cxbr1:<base64url(JSON)>
17
+ ```
18
+
19
+ JSON 的结构如下,示例指纹为占位值:
20
+
21
+ ```json
22
+ {
23
+ "source": {
24
+ "id": "source-fingerprint",
25
+ "connection": "connection-fingerprint",
26
+ "model": "deployment-name",
27
+ "adapter": "gpt-responses"
28
+ },
29
+ "format": "responses.reasoning.v1",
30
+ "data": "original-encrypted-content"
31
+ }
32
+ ```
33
+
34
+ | adapter | format | data |
35
+ | --- | --- | --- |
36
+ | `gpt-responses` | `responses.reasoning.v1` | 上游原始 `encrypted_content` 字符串 |
37
+ | `claude-messages` | `anthropic.thinking.v1` | 完整 `thinking` 或 `redacted_thinking` block,包含签名或隐藏数据 |
38
+ | `deepseek-chat` | `reasoning.text.v1` | 明文 reasoning 字符串 |
39
+
40
+ 一个 carrier 对应一项 reasoning;格式可以保存字符串或结构化数据,不限于可见文字。外层只是编码,不是加密。GPT 密文本身不变;Claude 可见 thinking 和 DeepSeek 文本仍可解码。
41
+
42
+ codeks 会再次发送 `encrypted_content`,因此载荷可以跨请求、会话恢复、服务重启和子代理 fork 保留。只放在 reasoning 的可见 `content` 中不足以完成这种回放。
43
+
44
+ ## 来源身份
45
+
46
+ 连接指纹是 `SHA256(JSON.stringify([upstream ID, 去除末尾斜杠的 base_url]))`。来源指纹再对 `[连接指纹, adapter, 实际模型]` 做同样的 SHA256。两个指纹用十六进制表示。
47
+
48
+ 载荷保留实际模型名和 adapter,端点和凭据不进入历史。显示别名及 replay 配置不参与身份,所以改显示名称或允许范围不需要重写历史。若同一 URL 背后通过 header/凭据区分不兼容目标,应使用不同 upstream ID。
49
+
50
+ ## 定向允许来源
51
+
52
+ 例如在 B 的配置中设置:
53
+
54
+ ```json
55
+ { "replay": { "reasoning_from": ["a"] } }
56
+ ```
57
+
58
+ B 可以接收自身及 A 的 reasoning;A 不会因此接收 B。A 接受 C 也不会让 B 自动接受 C。别名按连接、adapter 和实际部署解析为来源身份。
59
+
60
+ 加载配置检查引用存在且源与目标的格式标识一致。两个 GPT 模型的 reasoning、同一 DeepSeek 模型的不同部署均可配置复用;每次请求恢复实际载荷,上游是否真正接受,交给实际请求验证。失败按上游错误返回,不删掉 reasoning 再发送。
61
+
62
+ `prepareHistory` 每次复制输入,恢复来源允许且格式匹配的 carrier,其他 reasoning 只在本次请求中过滤。普通消息、工具调用、结果和图片继续保留。没有来源包装的 reasoning 不归属到当前目标。
63
+
64
+ 例如默认 A → B → A 时,B 不接收 A 的 reasoning,切回 A 后仍可恢复。若后来配置 B 接受 A,无需重写历史,后续请求即可使用其中 A 的载荷。被新的压缩检查点取代的旧历史则不再可恢复。
65
+
66
+ ## 消息与工具历史
67
+
68
+ codeks 的明文 `agent_message` 在 GPT Responses 中保持原格式;Claude/DeepSeek 将其转成 user 消息,在内容开头标明 author、recipient,后面保留文本和图片。它不是 reasoning,不受 reasoning 来源列表控制。
69
+
70
+ GPT 出站请求移除 item 顶层 `id`,保留工具配对的 `call_id`,使用 `store: false`。这样消息不会继续通过服务器 item ID 引用已过滤的 reasoning。请求必须包含完整历史,不能只带 `item_reference` 或 `previous_response_id`。
71
+
72
+ 压缩另用 `cxbc1:`,有独立的允许来源,保存文字摘要或按需复述配方。详见[压缩](compaction.md)。
@@ -0,0 +1,50 @@
1
+ # npm 发布
2
+
3
+ 包名为 `@wacry/codeks-bridge`。只发布编译后的 JavaScript、模型目录、调试 UI 和随包文档,运行时零依赖。版本使用普通 semver,tag 为 `v<版本号>`。
4
+
5
+ ## 日常验证
6
+
7
+ GitHub Actions 工作流 `codeks-bridge-npm.yml` 只手动触发,不在每次提交时运行。默认 `publish=false`:安装开发依赖、类型检查、测试、构建、公开信息检查,再生成并安装测试 npm tarball。工作流保存实际测试过的 tarball,发布步骤直接使用同一份文件。
8
+
9
+ ```sh
10
+ gh workflow run codeks-bridge-npm.yml --ref main
11
+ ```
12
+
13
+ ## Trusted publisher
14
+
15
+ 首次发布 `0.0.1` 使用本机临时 npm token 建立包。后续 GitHub Actions 只使用 OIDC,不保存 npm 发布 token。
16
+
17
+ 首次发布后,在 npm 包的 Settings → Trusted Publisher 配置 GitHub Actions:
18
+
19
+ - Organization or user:`WAcry`
20
+ - Repository:`codeks-bridge`
21
+ - Workflow filename:`codeks-bridge-npm.yml`
22
+ - Environment name:留空
23
+ - 若页面提供权限选项,只授予 publish。
24
+
25
+ 可在 npm 页面完成配置;允许 bypass 2FA 的发布 token 不能用于创建 trusted publisher。工作流使用 GitHub 托管 runner、Node.js 24 和 npm 11,发布时请求 OIDC 身份并生成 provenance。配置要求见 [npm trusted publishing 文档](https://docs.npmjs.com/trusted-publishers/)。
26
+
27
+ ## 发布新版本
28
+
29
+ 在开发分支修改版本,完成审查并合并到 `main`。`npm version` 同时更新 `package.json` 和 lockfile;已有版本不能覆盖。
30
+
31
+ ```sh
32
+ npm version 0.0.2 --no-git-tag-version
33
+ ```
34
+
35
+ 合并后,在对应的 `main` 提交上创建并推送 tag,再手动发布:
36
+
37
+ ```sh
38
+ git tag -a v0.0.2 -m 'Release 0.0.2'
39
+ git push origin v0.0.2
40
+ gh workflow run codeks-bridge-npm.yml --ref v0.0.2 -f publish=true
41
+ ```
42
+
43
+ 只有名称与 `package.json` 版本一致、提交已进入 `main` 的 tag 才能发布。验证或包安装测试失败时停止,不发布。
44
+
45
+ 发布后核对 npm 的版本、tarball 和 CLI:
46
+
47
+ ```sh
48
+ npm view @wacry/codeks-bridge version dist.tarball --registry=https://registry.npmjs.org
49
+ npx --yes --package @wacry/codeks-bridge@0.0.2 codeks-bridge --help
50
+ ```
@@ -0,0 +1,90 @@
1
+ # Responses 流与错误
2
+
3
+ 客户端使用 `POST /v1/responses`,也接受不带 `/v1` 的同一路径。`stream: true` 返回 SSE,其他请求返回完整 JSON response。上游统一使用流式请求,以便及时显示文字、处理取消与错误。
4
+
5
+ ## Header 与转发边界
6
+
7
+ 请求及成功、失败响应的 header 默认转发,包括 `session-id`、`thread-id`、`x-codex-turn-state`、请求 ID 和未知扩展字段。`prompt_cache_key` 属于 body,不能替代会话及路由 header。GPT body、事件及错误同样保留未知字段;必要转换不等于按已知字段重建整个协议。
8
+
9
+ 每段 HTTP 连接独立负责传输:去掉 hop-by-hop header、`Connection` 指定的字段,以及重写或解压后失效的 `Content-Length`、`Content-Encoding`。出站请求重新生成 Host 与压缩协商,不转发客户端的 `Authorization`、API key、Cookie 或所选连接的自定义鉴权 header;上游静态 headers 覆盖同名客户端值,再由该连接的凭据配置设置鉴权。返回 body 被转换时由 bridge 设置 Content-Type,`x-models-etag` 始终属于 bridge 自己的模型目录。其余字段不按厂商或名称白名单筛选。
10
+
11
+ 普通回答、原生压缩、文字摘要及按需复述统一透传客户端 metadata,包括 `session-id`、`thread-id` 和 `x-codex-turn-state`。按需复述使用本次请求的 metadata 和原来源的当前鉴权,header 不写进检查点或摘要缓存。
12
+
13
+ OpenAI 订阅端点的实测确认了 `x-codex-turn-state` 的实际使用,也观察到携带错误或合成 state 的请求被接受并收到新的 state。本项目据此选择默认透传,由上游处理;出现可复现的不兼容时再针对性调整。这项证据仅来自 OpenAI 订阅端点,不代表 Azure 或 AWS 的处理方式。
14
+
15
+ 成功的辅助复述不把响应 header 混入目标响应;最终返回目标请求的 header。辅助请求发生 HTTP 或流内失败时,默认透传该失败的响应 header,包括请求 ID、路由状态和未知诊断字段。
16
+
17
+ ## 事件与交付
18
+
19
+ 每项输出先发 `response.output_item.added`,随后发 delta,最后发包含完整内容的 `response.output_item.done`。客户端从 done 项持久化历史,因此 reasoning carrier 必须出现在完整项中。
20
+
21
+ GPT 保留原生事件、response ID、时间、usage 分项和额外元数据,只转换模型别名、reasoning carrier 和客户端需要的缺省字段。完整 JSON 响应或仅带最终输出的流会补齐 item 事件;不把完整摘要伪造成上游曾发送的 delta。Claude/DeepSeek 由 writer 生成 Responses 事件。
22
+
23
+ 从第一个工具调用开始,writer 缓存它和此后的全部输出,整个响应成功后按原顺序释放。此前的文字仍可流式显示;之后的文字和 reasoning 会延迟。这样既不提前执行工具,也不会把原本穿插在文字之间的调用挪到末尾。失败时丢弃整个未交付后缀,keepalive 不受缓冲影响。
24
+
25
+ GPT 消息优先保留原生 `phase`,包括 `partial_answer`。缺省时,后面还有输出或工具调用的文字为 `commentary`;最后一条文字在 `end_turn: false` 时也为 `commentary`,其他情况为 `final_answer`。没有工具调用不等于当前 turn 已结束。
26
+
27
+ GPT 同一 reasoning item 内的摘要保留各段 `summary_index`,对应的 part、text delta/done 与最终 `item.summary` 结构一致。Claude 和 DeepSeek 没有这种分段协议时,继续使用单段摘要;不同 reasoning item 各自从索引 0 开始。
28
+
29
+ 末尾 `response.completed` 带 ID、完整 output 和 usage。`end_turn: false` 保留需要继续生成的语义,例如 Claude `pause_turn`。缺少终止信号或无效工具结果返回错误,不伪装成成功。
30
+
31
+ GPT 的 `response.incomplete` 保留原类型、原因和 usage,不改成通用错误。已核对 Codex `0.163.0`:`interrupted` 表示继续当前 turn;`content_filter` 使用内容过滤提示;`max_output_tokens` 等其他原因结束当前生成并记录已有用量。完整 JSON 与 SSE 终止事件中的 `output` 都会补齐尚未交付的 item 事件,已有 done 不重复发送,因为客户端不会从终止事件的 `output` 重建历史。未完成的工具后缀仍不交付。Claude/DeepSeek 没有同样的 Responses 事件时,继续按各自停止原因转换。
32
+
33
+ ## 上游错误
34
+
35
+ ### GPT 原生错误
36
+
37
+ 流式入口等上游成功响应头到达后,才发送下游 HTTP 200 并开始 SSE。上游 HTTP 失败保留 status、body 内容和诊断 header,包括 `Retry-After`;不解析后重新包装错误体。fetch 已解压响应体,因此去掉原 `Content-Encoding`、`Content-Length`,以及只适用于当前连接的传输 header。
38
+
39
+ 上游流内错误保留完整 error 对象,放入 `response.failed.response.error`;上游提供完整 response envelope 时也一并保留。`code`、`type`、`misalignment`、`plan_type`、`resets_at` 等字段不改写,也不按 GPT 错误码白名单分类。只有 error 中缺少 `retry-after` 而上游 HTTP header 提供了它时,才补入 `error.headers`。非流式客户端收到相同原生错误,HTTP 状态为 502;它不能恢复已经成功建立的上游流的 HTTP 错误状态。响应 header 按上述边界保留,不制造复合请求 ID。
40
+
41
+ 这是保留客户端行为的协议边界。已核对 Codex `0.163.0` 的 `codex-rs/codex-api/src/api_bridge.rs` 与 `sse/responses_error.rs`:HTTP 和 SSE 使用不同的错误处理路径,部分专用提示依赖原错误字段,未知 SSE code 则会进入可重试分支。把 HTTP 400 改成 200 + SSE,或把原生策略错误改名为 `upstream_error`,都可能把原本会停止的请求变成重试。bridge 因此接受等待响应头期间没有心跳的代价,不自行维护另一套 GPT 重试分类。
42
+
43
+ ### Claude 与 DeepSeek 的协议转换
44
+
45
+ 非 Responses 协议仍需要转换为客户端能识别的错误。`code` 供 codeks 判断如何处理,`upstream` 记录原始错误体和可得的 HTTP 状态码。例如:
46
+
47
+ ```json
48
+ {
49
+ "code": "server_is_overloaded",
50
+ "message": "Capacity unavailable",
51
+ "headers": { "retry-after": "30" },
52
+ "upstream": {
53
+ "status": 503,
54
+ "body": { "error": { "type": "overloaded_error", "message": "Capacity unavailable" } }
55
+ }
56
+ }
57
+ ```
58
+
59
+ HTTP 失败发生在建立下游流之前,响应体使用顶层 `error`,Retry-After 使用 HTTP header;建立流后的失败放入 `response.failed.response.error`。流内错误没有独立 HTTP 失败状态时,`upstream` 只包含原始事件内容。非 JSON 的 HTTP 错误体按原文本保存。
60
+
61
+ | 分类 | codeks 错误码 |
62
+ | --- | --- |
63
+ | 上下文过长 | `context_length_exceeded` |
64
+ | 配额或余额不足 | `insufficient_quota` |
65
+ | 速率限制 | `rate_limit_exceeded` |
66
+ | 容量不足 | `server_is_overloaded` |
67
+ | 无效输入、鉴权或权限错误 | `invalid_prompt` |
68
+ | 断流、传输等其他失败 | `stream_error` 或 `upstream_error` |
69
+
70
+ 原始错误体保留厂商 code/type 和其他诊断。这个转换表只适用于非 Responses 上游,不用于改写 GPT 原生错误。网络错误保留可用的原因链。缺少凭据、凭据命令失败、输出为空或多行均返回 HTTP 400 + `invalid_prompt`,保持原本的终止语义;命令的 stdout/stderr 不外传。
71
+
72
+ bridge 不重试、不替换端点,也不因错误移除 reasoning、关闭 thinking 或改用文字压缩。客户端决定重试预算和退避。下游取消会取消当前相关上游请求,包括正在进行的原生压缩或复述;尚未开始的后续步骤不会执行。
73
+
74
+ ## Usage
75
+
76
+ GPT 保留原始 usage,包括提供的 cache write 等分项;DeepSeek 使用 prompt/completion 与 cache hit;Claude 将普通 input、cache read、cache creation 相加得到完整 input。转换后的总数只计算 input + output;reasoning 作为输出分项,不重复叠加。`native` 初始只返回原生压缩用量;后续辅助复述的 usage 独立保存在本地缓存,不加入目标回答的 usage。
77
+
78
+ 成功响应的必需 usage 缺失或无效时返回错误,不使用零值影响客户端压缩判断。释放工具前还会按当前客户端的终止解码要求检查 response ID、可选 `end_turn` 与 usage metadata 的类型,以及 `total_tokens` 和已提供的 usage 分项。保留原生 envelope 与未知字段,不补造缺失总数;否则工具可能已经执行,客户端才发现成功终止事件无法解码。这里核对的是 Codex `0.163.0` 的 `sse/responses.rs` 与 `protocol/src/response_usage.rs`,升级客户端时复核这些少量必需字段。原生失败或 incomplete 保留上游提供的用量,不以 usage 校验覆盖原始终止原因。
79
+
80
+ ## Keepalive 与超时
81
+
82
+ 等待上游响应头期间不发送下游响应头或 keepalive。上游接受请求后,每 15 秒发送 JSON `keepalive` 事件,客户端忽略内容并刷新流空闲计时。SSE 注释不能提供同样效果。
83
+
84
+ `native` 初始压缩在原生请求被接受后建立流;后续需要按需复述时,先完成并缓存文字,再等待目标请求被接受。辅助复述不提前打开下游流。
85
+
86
+ 上述等待仍可能遇到上游流内失败。即使下游尚未建立流,bridge 也会以原生 SSE 错误结束请求,而非把已经发生的流内失败伪造成 HTTP 失败。HTTP 失败则保留原状态码和响应体。辅助请求的 `response.failed` 保留原 error 及其他 response 字段,但清空 output、移除 usage,避免把源请求的部分结果算成目标回答。辅助请求的 `response.incomplete` 视为压缩不完整,返回 `invalid_prompt`,原 response 留在 `upstream.body` 供诊断,不交付其部分输出。
87
+
88
+ 按需复述的等待可能覆盖完整的辅助推理,客户端空闲超时应足以容纳该等待。
89
+
90
+ 上游空闲超时独立控制,默认 300 秒,可设置 `upstreams.<id>.stream_idle_timeout_ms`,也覆盖等待响应头和读取错误体的阶段。只有上游字节重置它,下游 keepalive 不延长它。因此已接受的慢压缩可以保持客户端连接,卡住的上游仍会超时报错。
@@ -0,0 +1,177 @@
1
+ # 验证
2
+
3
+ 桥接器的测试只使用 Node 内置测试器和本地假上游,不需要模型密钥。普通测试不启动 codeks,也不编译其 Rust 项目。
4
+
5
+ ```sh
6
+ npm ci
7
+ npm run typecheck
8
+ npm test
9
+ npm run build
10
+ npm run check
11
+ ```
12
+
13
+ ## 测试覆盖什么
14
+
15
+ - 工具:普通函数、namespace、自定义工具的原始字符串、Lark/regex 描述、名称冲突与超长名称、加密 schema 关键字的清理。
16
+ - Lite:工具声明原位传给 GPT;Claude/DeepSeek 提取增量 schema,保留原历史与默认 namespace 身份;header、all-turns 和原生压缩前缀保持。
17
+ - 流:跨字节分块的 UTF-8、CR/LF、SSE 多行数据、结束标记、非法 JSON。
18
+ - Responses 输出:先 added 后 delta/done、完整历史项、usage、reasoning carrier、原生消息 phase(含 `partial_answer`)、缺省 phase 与 `end_turn` 的关系、摘要分段索引和最终数组一致、明文多代理参数、失败时不执行尚未提交的工具。
19
+ - 配置与目录:有限 key、原始 GPT 条目保留、第三方精调、别名与部署名分离、窗口覆盖、effort 映射及默认值、catalog 与出站参数一致、options 覆盖顺序。
20
+ - 目录发现:Codex 专用 `/models` 响应、入口鉴权、ETag 与响应版本标记、连接及凭据配置不进入目录;`expose` 过滤、首个公开别名作为默认、空目录报错边界及隐藏模型继续参与委托压缩和 handoff。
21
+ - 状态:reasoning 与原生压缩的独立允许来源、定向非传递关系、配置改变后恢复已有 carrier、身份与显示别名分离、输入历史不变。
22
+ - 压缩:生成模式与 native replay 独立;summary 保留标准 prompt;工具定义、选择及未知参数保留,意外工具调用明确失败;GPT 默认 native 生成原生检查点;所有必需步骤成功才交付;失败、截断与取消保持原路径。
23
+ - 按需复述:兼容目标不导出,其他目标向原来源导出;不包含检查点后的新工作;来源别名恢复、缓存及重启复用、同键排队与独立取消;辅助 usage 不进入目标上下文统计。
24
+ - 委托压缩:执行别名与工作模型默认模式独立、单跳引用、同部署不同别名的 effort 预设;先按执行者恢复旧原生状态,再压缩包含其他模型新增消息和工具的历史;新检查点来源、复述 effort 与执行者一致。
25
+ - 保留历史:按需复述使用客户端实际保留的历史而不再裁剪;源指令与工具声明恢复,检查点后的工作留给目标请求。
26
+ - 后端与历史:各 API 的消息、工具、图片、schema、thinking、usage 与错误转换;Claude 空工具结果、上下文边界停止及不完整工具不交付。
27
+ - 检查器:四段内容逐字节保存、只脱敏凭据 header、上游调用标签、标注与实际回放决定一致、捕获开关与结束时的软上限淘汰、清空释放数据且不打断请求、客户端断开、无 token 时的 loopback 与同源限制、token 鉴权、静态文件路径、导出格式和跨块 SSE 解析;Responses Lite 结构视图补全输出且保留原始事件。
28
+ - Header 与错误:会话、路由及未知 header 默认透传;鉴权归所选上游,必要的传输字段移除;等待上游响应头时不发送 200;GPT HTTP status/body 与原生 SSE error 字段保留;辅助复述成功时返回目标 header,失败时返回该失败的 header;Claude/DeepSeek 继续转换协议;验证没有额外请求或自动降级。
29
+
30
+ 这些测试检查协议和客户端行为;假上游不会验证模型是否理解提示词,也不会验证真实服务的缓存命中率、容量和区域之间的密文兼容性。
31
+
32
+ `test/delegated-compaction-e2e.test.ts` 通过 bridge 的真实 HTTP 入口连接本地假上游,覆盖 GPT 原生续聊 → Claude 按需复述 → Claude 委托 GPT 再压缩 → Claude 复述新检查点 → GPT 恢复新检查点。另检查 native 失败不交付部分检查点、handoff 失败不调用工作模型、工作模型失败后跨服务重启复用成功缓存,以及 inspector 的独立 handoff usage 不进入客户端响应。它不启动 codeks,也不调用真实模型;委托压缩的质量与成本不由这些测试推断。
33
+
34
+ ## 使用真实 codeks 二进制
35
+
36
+ 可选集成测试启动已经安装的 codeks,并让它通过桥接器访问本地假上游。指定要验证的二进制即可,测试不会下载或编译 codeks:
37
+
38
+ ```sh
39
+ CODEKS_BIN=/absolute/path/to/codex node --test test/codeks-e2e.test.ts test/catalog-e2e.test.ts test/reasoning-e2e.test.ts test/errors-e2e.test.ts test/protocol-e2e.test.ts test/compaction-e2e.test.ts
40
+ ```
41
+
42
+ 未设置 `CODEKS_BIN` 时,这些测试自动跳过。它们为每个场景创建独立的 `CODEX_HOME` 和工作目录,监听随机的回环端口。临时目录路径会写入测试输出,保留会话和进程输出便于复查。退出时关闭本轮创建的服务器。
43
+
44
+ 以 `@wacry/codeks@0.162.1-codeks.10` 的现成 Linux 二进制验证了以下场景:
45
+
46
+ | 场景 | 实际检查 |
47
+ | --- | --- |
48
+ | 普通工具与图片 | codeks 执行 shell 和 `apply_patch`;文件内容正确;用户附图与 `view_image` 结果都进入 Chat 图片字段 |
49
+ | Code Mode 与多代理 V2 | 自定义 `exec` 执行;namespace 工具回传;明文 `spawn_agent` 创建真实子代理;子代理收到任务,父代理收到最终答复 |
50
+ | 远程压缩 V2 | 高 usage 触发 `compaction_trigger`;桥接器生成可回放摘要;codeks 替换旧工具历史;退出后 resume 仍读到摘要 |
51
+
52
+ 当前测试使用仓库内已审阅目录。协议测试检查 Lite header、`additional_tools` 中的完整 schema、`reasoning.context: "all_turns"`、`parallel_tool_calls: false` 和省略顶层 tools;普通工具测试可显式覆盖工具模式,以单独检查直接调用契约。目录集成测试通过 HTTP 发现目录,检查默认 Code Mode Only 下的 `exec`、`ALL_TOOLS` 和 MCP 调用,不依赖其他 namespace 的固定拼写。
53
+
54
+ 最新正式版 `@openai/codex@0.163.0` 的现成二进制另外通过普通工具/图片及压缩/resume 两项测试。它用于核对最新客户端 Lite 契约;明文多代理由上述 codeks fork 二进制验证,不将原生客户端验证等同于 fork 补丁验证。
55
+
56
+ ### 已审阅目录与 HTTP 发现验证
57
+
58
+ 2026-10-18(America/Los_Angeles),`@wacry/codeks@0.162.1-codeks.10` 与 `@openai/codex@0.163.0` 的现成二进制分别通过新增目录场景。客户端是真实进程,模型上游与 MCP 服务是本地测试服务器:
59
+
60
+ - 从 bridge 的 HTTP 目录发现两个窗口别名,保留各自窗口、提示词和工具元数据,并把目录与 ETag 写入客户端缓存。
61
+ - 使用配置片段中的公开鉴权标记进入 API key 发现流程;模型别名与实际部署名不同,出站路由仍正确。
62
+ - 保持 `supports_search_tool: true` 和 Code Mode Only。外层没有独立 `tool_search` 或 MCP 工具,`exec` 描述也没有内嵌延迟 MCP 的 schema。
63
+ - 模型通过 `ALL_TOOLS` 按描述找到延迟 MCP 工具,再调用 `tools[name]`。真实客户端执行 MCP 请求并把结果回传模型上游。
64
+
65
+ 同轮 codeks 二进制还重新通过了上表中的普通工具/图片、明文多代理 V2、远程压缩/resume 三个场景。TypeScript 检查、普通测试、构建与打包解包后的 CLI 检查通过;包中包含全部八个目录文件。这里验证客户端协议与包的完整性,不代表新增了真实模型端点或缓存时长的实测。
66
+
67
+ ### 摘要显示与 Ultra
68
+
69
+ 2026-10-18(America/Los_Angeles),两个上述客户端版本均通过 `test/reasoning-e2e.test.ts` 的四个新增场景:
70
+
71
+ - 默认配置及显式 `model_reasoning_summary = "none"` 都省略出站 summary 字段,Claude 请求 `display: "omitted"`。
72
+ - 显式 `auto`、`detailed` 请求可见摘要,bridge 返回客户端可识别的 reasoning 事件。
73
+ - 关闭显示仍保存载荷,包括只有签名、thinking 文本为空的块,并在后续请求中回放。
74
+ - `ultra` 的多代理提示词由真实客户端注入一次;Opus 使用 `xhigh`,Sonnet 与 DeepSeek 使用 `max`。
75
+
76
+ TypeScript 检查和普通测试通过,客户端场景各在两个现成二进制上单独运行。这些结果验证协议与客户端行为,不作为真实模型规格的验证。
77
+
78
+ ### 最终审计与构建产物
79
+
80
+ 2026-10-18(America/Los_Angeles),补充 Claude 的 `parallel_tool_calls` 转换回归测试,覆盖客户端显式选择及原生 options 覆盖优先级。普通测试、类型检查与构建通过。
81
+
82
+ 构建后实际打包并在独立目录解包,确认八份目录、文档和示例完整,CLI 与 HTTP 目录可用,包内文档相对链接及私有内容扫描通过。
83
+
84
+ ### 错误路径与客户端重试
85
+
86
+ 2026-10-18(America/Los_Angeles),`@openai/codex@0.163.0` 的现成二进制通过 `test/errors-e2e.test.ts` 的八个场景,上游均为本地测试服务器:
87
+
88
+ - SSE 策略拦截、预算超限、`usage_not_included`、`flex_unavailable` 保留客户端专用分类和提示,各请求一次即停止。
89
+ - HTTP 429 用量限制保留 `plan_type`、`resets_at`,客户端显示对应升级和恢复时间提示;HTTP 429 预算限制停止。
90
+ - HTTP 400 的未知错误码保留原响应体,不变成流内未知错误而触发重试。
91
+ - 未知 SSE 错误按客户端配置重试一次,bridge 收到和转发的请求均为两次,没有额外重试。
92
+
93
+ 当时的普通 HTTP 测试另覆盖失败取消、错误体解压后的 header 清理、凭据失败的终止语义,以及原生错误附加字段的完整保留。这里验证客户端协议,不代表真实服务产生过上述合成错误。
94
+
95
+ ### 协议细节与完整客户端回归
96
+
97
+ 2026-10-18(America/Los_Angeles),补充 `test/protocol-e2e.test.ts` 的三个真实客户端场景,上游均为本地测试服务器:
98
+
99
+ - 原生 `response.incomplete` 的 `interrupted` 原因使客户端自动发起下一请求,即使配置关闭重试;已经交付的文字进入下一请求历史。
100
+ - `max_output_tokens` 原因使客户端停止当前 turn,并将上游 input、output、cache 和 reasoning usage 记入会话的 token 统计。
101
+ - Code Mode `exec` 执行两次 `notify()` 和一次最终输出,客户端实际生成三个同 call ID 的结果。Chat 出站只保留一个配对结果,另外两条作为通知,三段内容全部保留。
102
+
103
+ 本轮 `@openai/codex@0.163.0` 通过上述五个文件中的全部 19 个场景,包括 HTTP 目录、工具/图片、Code Mode 与明文多代理、压缩/resume、四种摘要配置及八种错误路径。`@wacry/codeks@0.162.1-codeks.10` 另外通过 `test/codeks-e2e.test.ts` 的三个场景,继续覆盖 fork 的工具、多代理及压缩行为。
104
+
105
+ 这些验证使用现成二进制,没有编译 Rust,也没有新增真实模型 API 请求。原生客户端在合成明文调用上的成功不代表它能跨模型解密真实上游生成的任务。
106
+
107
+ ## 真实上游验证
108
+
109
+ 真实端点的配置、鉴权、请求与响应只保存在仓库外。公开测试数据必须是合成内容,不能复制真实密钥、端点、签名、密文、对话或账号信息。
110
+
111
+ 真实验证应区分:单次请求、工具回环、图片、切换模型后 reasoning 隔离、压缩后续接。原生压缩模式还需要检查同源续聊没有辅助请求,跨目标首次导出与缓存复用行为正确,文字准备完成后才发送目标请求。未允许的端点或模型使用可读摘要,允许来源且目标开启 native replay 时恢复原生压缩内容;分别测允许与不允许的配对。某一服务上的成功不代表另一服务也支持同样的字段。
112
+
113
+ ### 缓存实验的边界
114
+
115
+ 2026-10-19(America/Los_Angeles),透传及压缩工具选择修改通过类型检查、构建和私有内容检查;普通测试 222 项通过,20 项需显式启用的真实客户端测试跳过。HTTP 假上游覆盖三种 adapter 的未知 header、鉴权覆盖、SSE/JSON、错误诊断、handoff metadata 透传,以及意外工具调用不交付、不执行、不自动重试。上述自动化测试未调用真实模型,不据此声明缓存命中率。OpenAI 订阅端点的 state 实测依据及适用范围另见 [header 边界](responses-stream.md#header-与转发边界)。
116
+
117
+ 缓存实验应保留暖身请求的完整输入,再追加新内容,避免把暖身末尾的临时问题替换成压缩 trigger。保持工具定义、工具选择、会话及路由 header 等条件与真实客户端一致;不完整的合成前缀不能代表正常会话的缓存表现。
118
+
119
+ 分别记录原生检查点 `K` 的生成、后续 `U + K` 复用,以及 `U + K + handoff prompt` 复述的 input、cached input、output 和请求条件,不能用其中一次高命中推断另一类请求的成本。Responses 的 `cached_tokens` 已包含在 `input_tokens` 中,不能再次相加;聚合命中率用总 cached input 除以总 input。提示词质量另看事实保留、错误和待办完整性,不把输出长度或缓存命中率当成质量证明。
120
+
121
+ ### Responses Lite 验证
122
+
123
+ 2026-10-18(America/Los_Angeles)通过实际 bridge HTTP 入口完成以下验证:
124
+
125
+ - AWS GPT-6.1 Sol、Azure GPT-5.6 Sol、Claude Sonnet 5.5、DeepSeek Flash 均通过 Lite 工具往返与 reasoning 载荷回放。
126
+ - GPT 出站请求实际保留 Lite header、原位 `additional_tools`、`reasoning.context: "all_turns"` 和 `parallel_tool_calls: false`,没有增加顶层 `tools` 或改用其他协议重试。
127
+ - Claude/DeepSeek 提取完整工具声明,返回调用的身份可供客户端继续执行。
128
+ - AWS GPT 原生压缩及同目标原生检查点续聊成功。
129
+
130
+ 上述场景包含 11 次成功上游请求。该轮未实测原生 OpenAI 订阅端点;官方 Codex 二进制与假上游的协议测试不能替代该端点验证。
131
+
132
+ ### 配置重构验证
133
+
134
+ 2026-10-18(America/Los_Angeles)通过实际 bridge HTTP 入口发送了 10 次真实上游请求:
135
+
136
+ - GPT-6.1 Sol、Claude Sonnet 5.5、DeepSeek Flash 均生成了新的 reasoning carrier,并成功完成同源 reasoning 回放。
137
+ - GPT-6.1 Sol 原生压缩成功,检查点的原生内容可供后续恢复。
138
+ - 显式允许另一连接上的 GPT-5.6 恢复该原生检查点后,bridge 确实发送了原密文;目标返回 HTTP 400 解密不兼容。原始错误完整传回,没有额外重试或改用文字摘要。
139
+
140
+ 这验证配置能控制尝试的配对,不能让上游原本不兼容的密文变得兼容。Claude 对简单任务可以不生成 thinking;本次改用需要推理的任务验证载荷回放,没有把普通成功回答算作 reasoning 回放成功。
141
+
142
+ ### 既有真实端点证据
143
+
144
+ 2026-10-17(America/Los_Angeles)在当时的默认隔离策略下完成以下真实端点验证。这些是既有协议互操作证据,不代表新的共享配对已得到验证;每次配置共享来源仍需验证具体目标。当前代码的验证以本次运行的测试结果为准。
145
+
146
+ | 后端 | 已通过 |
147
+ | --- | --- |
148
+ | GPT Responses(两个云端连接) | reasoning 回放、跨连接切换、namespace 工具及结果、自定义 Lark 工具、图片识别、原生压缩、同连接 native 与跨连接 summary 回放 |
149
+ | Claude Messages(Opus / Sonnet) | namespace 工具、工具图片、custom regex 工具、Opus → Sonnet → Opus reasoning 隔离与恢复、带签名 reasoning 的压缩及续接、Responses JSON Schema 转 Messages 结构化 JSON 输出 |
150
+ | DeepSeek 官方 `deepseek-flash` | namespace 和 custom 工具、同模型 reasoning 工具循环、跨模型历史配空 reasoning 字段、用户/工具图片、完整文字 reasoning 重放 |
151
+
152
+ Claude 一次压缩输入 19,778 tokens,其中 19,541 命中缓存,约 98.8%;`1h` 前缀与 `5m` 末块参数被上游接受。这只验证即时命中,没有等待 1 小时测到期。DeepSeek 一次 reasoning 回放输入 1,488 tokens,其中 1,280 命中缓存。
153
+
154
+ HTTP 集成测试另外覆盖入口鉴权、响应校验、429/503/配额分类、`Retry-After`、失败时不释放工具、客户端断开取消上游,以及下游 keepalive 持续时上游依然会触发空闲超时。使用合成 checkpoint 验证过服务重启、跨后端与跨区域恢复。
155
+
156
+ 完整的真实 HTTP 路径也已验证:将含 GPT 原生状态与文字摘要的 bridge checkpoint 序列化后交给 Claude 和 DeepSeek 续聊,再切回原 GPT。跨模型请求实际使用文字摘要且不含原生密文;切回请求恢复原生 checkpoint 并过滤外来 reasoning。原 GPT 正确回答了只存在于 checkpoint、此前续聊明文未出现的第二个标记,原始 carrier 保持不变。
157
+
158
+ ## 检查点复述 prompt 的选择
159
+
160
+ 在 OpenAI 订阅端点的 GPT-6.1 Sol 上,以 Responses Lite、`xhigh` 对比了原历史直接压缩与 `U + K` 复述。候选包括 Codex 内置 prompt、现有标准 prompt、短复述、详细复述和最终用户给定的 handoff prompt。原始请求、会话与密文保存在仓库外;这些实测用于选择 prompt,不等同于按需复述 HTTP 流程的端到端验证。
161
+
162
+ 最终 prompt 的一次结果如下:
163
+
164
+ | 输入 | 文字输出 tokens | 检查结果 |
165
+ | --- | ---: | --- |
166
+ | 约 31k tokens 的开发历史,经原生压缩后复述 | 5,738 | 50 个检查项中 47 个完整、3 个部分保留,没有完全遗漏或错误项 |
167
+ | 约 215k tokens 的长历史,经原生压缩后复述 | 19,240 | 150 个已知事实均命中,其中 114 个不在保留的用户消息中;5 个未知项均未编造 |
168
+
169
+ 这些结果支持使用该版本作交接复述,但每类只有一个输入,长历史的其他候选也达到同一检查上限,不能据此证明统计优势或任意历史无损。其他候选曾误述精确字段名,说明复述需要检查事实,不能仅按输出长短排序。prompt 保留用户给定措辞及 Codex 的交接主题,没有额外要求逐字还原密文,也没有强制标准文字压缩的格式。
170
+
171
+ 独立的短请求验证曾观察到原生检查点前缀的高 cache hit,也观察到相同前缀、变化后缀时的低命中。`U + K` 给缓存复用提供机会;是否命中不是按需复述正确性的前提,也不作为节省用量的保证。
172
+
173
+ ## 按需复述的客户端验证
174
+
175
+ `test/compaction-e2e.test.ts` 使用真实 codeks `0.162.1` 与 Codex `0.163.0` 二进制,验证 `native` 模式。上游是合成 Responses/Chat 服务,凭据与用户配置隔离,不调用真实模型端点。
176
+
177
+ 测试让客户端执行工具、触发远程压缩、恢复会话并切换到 DeepSeek 目标,验证 checkpoint 持久化、原生续聊、原来源复述和目标文字恢复。同源续聊不额外请求,首次切换才导出;导出排除检查点后的工作,物化不改写客户端存储的载荷。客户端重新注入的环境上下文也进入按需复述的实际保留历史。