@wacry/codeks-bridge 0.0.1 → 0.0.4
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.
- package/NOTICE +12 -0
- package/README.md +1 -1
- package/dist/adapter-options.d.ts +5 -0
- package/dist/adapter-options.js +41 -0
- package/dist/backends/anthropic-tools.d.ts +14 -0
- package/dist/backends/anthropic-tools.js +44 -0
- package/dist/backends/anthropic.d.ts +2 -0
- package/dist/backends/anthropic.js +92 -16
- package/dist/backends/chat.js +4 -1
- package/dist/backends/responses.js +40 -18
- package/dist/config.js +5 -0
- package/dist/inspect/view.d.ts +1 -1
- package/dist/inspect/view.js +9 -7
- package/dist/server.js +7 -6
- package/dist/tool-history.js +2 -1
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +38 -24
- package/dist/types.d.ts +11 -0
- package/dist/upstream.d.ts +1 -1
- package/dist/upstream.js +7 -2
- package/docs/backends-anthropic.md +37 -1
- package/docs/compaction.md +5 -1
- package/docs/config.md +53 -10
- package/docs/design.md +6 -2
- package/docs/inspect.md +20 -0
- package/docs/reasoning.md +2 -0
- package/docs/responses-stream.md +2 -2
- package/docs/testing.md +6 -0
- package/docs/tools.md +18 -1
- package/examples/gpt-claude.json +27 -0
- package/package.json +1 -1
- package/ui/app.css +4 -0
- package/ui/detail.js +57 -7
- package/ui/events.js +25 -0
- package/ui/labels.js +5 -0
- package/ui/latency.js +88 -0
package/dist/tools.js
CHANGED
|
@@ -76,13 +76,13 @@ export function cleanSchema(schema) {
|
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
78
|
function objectSchema(parameters) {
|
|
79
|
+
if (parameters == null)
|
|
80
|
+
return { type: "object", properties: {} };
|
|
79
81
|
const cleaned = cleanSchema(parameters);
|
|
80
82
|
if (cleaned && typeof cleaned === "object" && !Array.isArray(cleaned)) {
|
|
81
|
-
|
|
82
|
-
if (schema.type === "object" || schema.properties)
|
|
83
|
-
return { type: "object", ...schema };
|
|
83
|
+
return { type: "object", ...cleaned };
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
throw new BridgeError("Function tool parameters must be a JSON Schema object.");
|
|
86
86
|
}
|
|
87
87
|
/** Describes a custom tool's input format in words, for function-only upstreams. */
|
|
88
88
|
export function customToolDescription(description, format) {
|
|
@@ -116,7 +116,9 @@ export function buildToolSet(tools) {
|
|
|
116
116
|
// Lite updates contain only changed members. Replace that definition
|
|
117
117
|
// without losing siblings or changing its established upstream name.
|
|
118
118
|
Object.assign(previous, entry);
|
|
119
|
-
|
|
119
|
+
if (entry.strict === undefined)
|
|
120
|
+
delete previous.strict;
|
|
121
|
+
return previous;
|
|
120
122
|
}
|
|
121
123
|
let flatName = baseFlatName(entry.namespace, entry.name);
|
|
122
124
|
// Add a suffix only when two different tools would get the same name.
|
|
@@ -128,39 +130,51 @@ export function buildToolSet(tools) {
|
|
|
128
130
|
entries.push(full);
|
|
129
131
|
byFlat.set(flatName, full);
|
|
130
132
|
byIdentity.set(id, full);
|
|
133
|
+
return full;
|
|
131
134
|
};
|
|
132
135
|
const visit = (tool, namespace) => {
|
|
133
136
|
if (!tool || typeof tool !== "object")
|
|
134
|
-
return;
|
|
137
|
+
return [];
|
|
135
138
|
if (tool.type === "function") {
|
|
136
|
-
add({
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
return [add({
|
|
140
|
+
kind: "function",
|
|
141
|
+
name: String(tool.name),
|
|
142
|
+
namespace,
|
|
143
|
+
description: String(tool.description ?? ""),
|
|
144
|
+
parameters: objectSchema(tool.parameters),
|
|
145
|
+
...(typeof tool.strict === "boolean" ? { strict: tool.strict } : {}),
|
|
146
|
+
})];
|
|
143
147
|
}
|
|
144
148
|
else if (tool.type === "custom") {
|
|
145
|
-
add({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
return [add({
|
|
150
|
+
kind: "custom",
|
|
151
|
+
name: String(tool.name),
|
|
152
|
+
namespace,
|
|
153
|
+
description: customToolDescription(String(tool.description ?? ""), tool.format),
|
|
154
|
+
parameters: CUSTOM_PARAMETERS,
|
|
155
|
+
})];
|
|
152
156
|
}
|
|
153
157
|
else if (tool.type === "namespace" && Array.isArray(tool.tools)) {
|
|
154
|
-
|
|
155
|
-
visit(inner, String(tool.name));
|
|
158
|
+
return tool.tools.flatMap((inner) => visit(inner, String(tool.name)));
|
|
156
159
|
}
|
|
157
160
|
// Hosted tools (web_search, image_generation, tool_search, ...) have no
|
|
158
161
|
// equivalent on these upstreams and are left out.
|
|
162
|
+
return [];
|
|
159
163
|
};
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
const addTools = (declarations) => {
|
|
165
|
+
const changed = new Map();
|
|
166
|
+
for (const declaration of declarations) {
|
|
167
|
+
for (const entry of visit(declaration, undefined))
|
|
168
|
+
changed.set(entry.flatName, entry);
|
|
169
|
+
}
|
|
170
|
+
// Later updates may mutate the registry's current definition. Earlier
|
|
171
|
+
// inline definitions must retain the exact payload used at that point.
|
|
172
|
+
return structuredClone([...changed.values()]);
|
|
173
|
+
};
|
|
174
|
+
addTools(tools ?? []);
|
|
162
175
|
return {
|
|
163
176
|
entries,
|
|
177
|
+
add: addTools,
|
|
164
178
|
byFlat,
|
|
165
179
|
flatFor(namespace, name) {
|
|
166
180
|
return byIdentity.get(identity(namespace, name))?.flatName ?? baseFlatName(namespace, name);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { ResponseWriter, Usage } from "./writer.ts";
|
|
2
2
|
export type Api = "responses" | "anthropic" | "chat";
|
|
3
3
|
export type AdapterId = "gpt-responses" | "claude-messages" | "deepseek-chat";
|
|
4
|
+
export interface ClaudeMessagesOptions {
|
|
5
|
+
tool_updates: "inline" | "top_level";
|
|
6
|
+
thinking_mismatch: "drop_block" | "error" | "upstream_default";
|
|
7
|
+
}
|
|
8
|
+
/** Bridge behavior, distinct from native request-body options. */
|
|
9
|
+
export interface AdapterConfig {
|
|
10
|
+
"claude-messages"?: Partial<ClaudeMessagesOptions>;
|
|
11
|
+
}
|
|
4
12
|
export interface UpstreamConfig {
|
|
5
13
|
base_url: string;
|
|
6
14
|
api_key_env?: string;
|
|
@@ -10,6 +18,7 @@ export interface UpstreamConfig {
|
|
|
10
18
|
stream_idle_timeout_ms?: number;
|
|
11
19
|
headers?: Record<string, string>;
|
|
12
20
|
options?: Record<string, unknown>;
|
|
21
|
+
adapters?: AdapterConfig;
|
|
13
22
|
}
|
|
14
23
|
export interface ModelConfig {
|
|
15
24
|
upstream: string;
|
|
@@ -34,11 +43,13 @@ export interface ModelConfig {
|
|
|
34
43
|
native_replay?: boolean;
|
|
35
44
|
};
|
|
36
45
|
options?: Record<string, unknown>;
|
|
46
|
+
adapters?: AdapterConfig;
|
|
37
47
|
}
|
|
38
48
|
export interface BridgeConfig {
|
|
39
49
|
host?: string;
|
|
40
50
|
port?: number;
|
|
41
51
|
token_env?: string;
|
|
52
|
+
request_max_mib?: number;
|
|
42
53
|
compaction_cache_dir?: string;
|
|
43
54
|
/** Debug capture for the /inspect/ page. */
|
|
44
55
|
inspect?: {
|
package/dist/upstream.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ResolvedModel } from "./types.ts";
|
|
2
2
|
/** base_url includes any API version prefix; path is appended, never resolved from /. */
|
|
3
|
-
export declare function upstreamFetch(route: ResolvedModel, path: string, body: unknown, signal: AbortSignal, requestHeaders?: Headers, protocolHeaders?: Record<string, string>): Promise<Response>;
|
|
3
|
+
export declare function upstreamFetch(route: ResolvedModel, path: string, body: unknown, signal: AbortSignal, requestHeaders?: Headers, protocolHeaders?: Record<string, string | string[]>): Promise<Response>;
|
package/dist/upstream.js
CHANGED
|
@@ -5,8 +5,13 @@ import { captureUpstream } from "./inspect/capture.js";
|
|
|
5
5
|
export async function upstreamFetch(route, path, body, signal, requestHeaders, protocolHeaders) {
|
|
6
6
|
const url = route.upstream.base_url.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, "");
|
|
7
7
|
const headers = await upstreamHeaders(route.upstream, route.profile.api, signal, requestHeaders);
|
|
8
|
-
for (const [name, value] of Object.entries(protocolHeaders ?? {}))
|
|
9
|
-
|
|
8
|
+
for (const [name, value] of Object.entries(protocolHeaders ?? {})) {
|
|
9
|
+
// A protocol may require additional comma-separated feature flags without
|
|
10
|
+
// replacing the endpoint's configured flags. Scalar headers still replace.
|
|
11
|
+
headers.set(name, Array.isArray(value)
|
|
12
|
+
? [...new Set([...(headers.get(name)?.split(",") ?? []), ...value].map(v => v.trim()).filter(Boolean))].join(",")
|
|
13
|
+
: value);
|
|
14
|
+
}
|
|
10
15
|
const payload = JSON.stringify(body);
|
|
11
16
|
const call = captureUpstream(url, headers, payload);
|
|
12
17
|
const idle = new AbortController();
|
|
@@ -34,12 +34,46 @@ Opus / Sonnet 5.5 目录的默认及最大上下文窗口为 `1000000`,Message
|
|
|
34
34
|
|
|
35
35
|
完整 `thinking`/`redacted_thinking` block,包括签名或隐藏数据,存入 `anthropic.thinking.v1` carrier。请求按 `replay.reasoning_from` 恢复允许来源,签名完整保留;其他来源只在当前请求过滤。允许配对不意味着上游一定接受其签名。[Reasoning 策略](reasoning.md)
|
|
36
36
|
|
|
37
|
+
## 失配策略与故障续接
|
|
38
|
+
|
|
39
|
+
默认 `adapters.claude-messages.thinking_mismatch: "drop_block"`,在 `adaptive`、`enabled` 模式的 `thinking.block_binding.prefix_mismatch_behavior` 中声明。服务端可以丢弃前缀失配的块,继续使用其余可接受的 thinking;bridge 不自行验签或删除已保存的载荷。可配置 `error` 或 `upstream_default`,原生 options 中明确的绑定策略仍优先,完整配置与优先级见 [Adapter 专用行为](config.md#adapter-专用行为)。
|
|
40
|
+
|
|
41
|
+
所有 Messages 请求都附带 `thinking-binding-controls-2026-08-01` beta,与现有 beta header 合并,以取得 `input_transformations` 诊断;声明诊断 beta 本身不等于选择某种失配策略。服务端报告与实际支持取决于端点,未知能力或请求错误直接返回,不自动更换策略。[官方 preserved thinking 规则](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking)
|
|
42
|
+
|
|
43
|
+
流式响应可能已交付完整 thinking 或文字项,随后才过载或断流。codeks 保存这些已完成项并负责重试,因此重试历史可能以 assistant 结束。若最后的 assistant 不含 `tool_use`,bridge 在出站请求追加 user `Continue.`,提供合法续接边界;含工具调用时不补这一句,以免掩盖缺失工具结果。
|
|
44
|
+
|
|
45
|
+
若这个 assistant 后紧跟持久化的增量工具声明,同样在声明前补续接边界;后续回放时,声明的位置让 bridge 能重新构造相同边界,不需要另存隐藏会话状态。
|
|
46
|
+
|
|
47
|
+
对于没有增量工具声明锚定的尾部 `Continue.`,若本次响应的第一个原生内容块是 `thinking` 或 `redacted_thinking`,bridge 在该块的 `cxbr1:` 外层记录可选字段 `continuation_before`,值为实际补入的文本。原生 thinking、签名和隐藏数据保持不变;字段随原 reasoning item 的 `output_item.done` 进入 codeks 历史,不产生独立的控制项或用户消息。
|
|
48
|
+
|
|
49
|
+
以后恢复这块 reasoning 时,先在同一位置补回记录的 user 文本。假设失败前已保存 T1,重试的首块为 T2:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
重试请求:H → assistant[T1] → user[Continue.]
|
|
53
|
+
重试输出:T2{continuation_before: "Continue."} → 正文
|
|
54
|
+
下轮请求:H → assistant[T1] → user[Continue.] → assistant[T2, 正文] → user[新问题]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
这样 T2 不会仅因续接边界消失而失配。同一响应只标记首块,后续 thinking、正文和工具继续保持原顺序;如果 T2 完整交付后又发生故障,这个边界已随 T2 保存,下一次重试可恢复它并记录新的续接边界。首块尚未完整交付时没有新载荷,下次仍按已有历史重试。字段跟随 reasoning 的来源与格式筛选:本次不恢复该载荷时,也不恢复其中的边界;以后允许恢复时,两者一起生效。
|
|
58
|
+
|
|
59
|
+
覆盖范围按实际输出顺序决定。Adaptive thinking 不保证第一个内容块就是 thinking;若先输出正文或工具,再输出 thinking,bridge 不给后来的块添加这个字段,否则会把 `Continue.` 恢复到错误的位置。这类响应及没有字段的既有历史仍沿用当前失配策略;没有 thinking 的响应则没有新签名需要通过这个字段保护。由增量工具声明锚定的边界不重复记录。[官方 thinking 规则](https://platform.claude.com/docs/en/build-with-claude/thinking)
|
|
60
|
+
|
|
61
|
+
用户选择保留正常 thinking、正文的流式显示,并用少量自包含状态恢复边界。因此只记录位置确定的首块,不缓存整轮正文,也不引入会话索引、独立控制载荷或向前查找历史位置的规则。工具调用仍按现有规则等完整成功后交付。`continuation_before` 记录已发生的请求事实,不改变来源复用配置,也不替代 `thinking_mismatch`。
|
|
62
|
+
|
|
63
|
+
工具定义等其他前缀变化仍可能导致失配,未覆盖的续接次序也可能丢失 thinking。默认 `drop_block` 让服务端报告并丢弃失配块;`error` 或 `upstream_default` 下,服务端仍可能拒绝请求。丢失推理上下文可能影响质量或增加重新推理成本,不能把边界恢复视为所有签名和输入均可接受的保证。具体发生了什么以[检查器中的服务端报告](inspect.md#claude-服务端输入处理)为准。
|
|
64
|
+
|
|
65
|
+
一次失配不会令今后生成的 thinking 永久失效。后续请求保持前缀稳定时,新生成的块仍可正常复用;原始历史里的旧失配块可能在多轮请求中被重复报告,重复报告不代表每轮都丢弃新的 thinking。实际保留与丢弃以各次上游报告为准。
|
|
66
|
+
|
|
37
67
|
## 输出格式、工具与图片
|
|
38
68
|
|
|
39
69
|
Responses `text.format: {type: "json_schema", schema: ...}` 转为 `output_config.format: {type: "json_schema", schema: ...}`;name/strict 不转发。schema 内容直接交给上游,随后 options 仍可覆盖原生格式。普通 `text` 不添加约束;无法表达为 Messages 的未知格式报错。
|
|
40
70
|
|
|
41
71
|
namespace 和 custom tool 使用共享映射。自由文本工具转换成 `{input: string}`,Lark/regex 放入描述,返回时恢复原始字符串;不添加工具专属提示词。指定工具必须能在当前工具表中解析,`tool_choice: "required"` 转为原生 `any`,不会改成 `auto`。显式 `temperature`、`top_p` 也继续发送;这些参数与具体模型、thinking 的组合是否受支持由上游判断。静默删除参数或关闭 thinking 会改变用户请求,bridge 不做这类修正。
|
|
42
72
|
|
|
73
|
+
工具更新默认使用完整 inline 定义,保持已有顶部工具与历史前缀稳定;仅实际包含中途工具更新的请求添加 `inline-tools-2026-09-15` beta。初始声明、更新位置与兼容模式见 [Lite 工具声明](tools.md#lite-工具声明)。端点不支持时可在连接配置中显式选择 `tool_updates: "top_level"`;不会在错误后自动探测或降级。
|
|
74
|
+
|
|
75
|
+
从 `0.0.1` 的顶部合并方式切到默认 inline 时,已有增量声明历史会改变出站工具前缀,可能触发旧 thinking 丢弃或缓存重建;可显式设为 `top_level` 保持原工具策略。切换模式不改变状态来源身份,也不改写客户端保存的历史。
|
|
76
|
+
|
|
43
77
|
有工具且未选择 `none` 时,显式 `parallel_tool_calls: false` 转为 `tool_choice.disable_parallel_tool_use: true`,显式 `true` 则转为 `false`;未指定 choice 时采用 `auto`,原生 options 仍可覆盖。这保留 Codex Lite 的外层工具调用限制,不限制一次 `exec` 内 JavaScript 的并发执行。[Claude 并行工具调用](https://platform.claude.com/docs/en/agents-and-tools/tool-use/parallel-tool-use)
|
|
44
78
|
|
|
45
79
|
用户与工具结果都可带图片。HTTP(S) URL 和 JPEG、PNG、GIF、WebP 的 base64 data URL 转为原生 image block,bridge 不下载图片。图片能力、尺寸和 schema 限制交给上游,不能转换的内容类型明确报错。空工具结果仍发送 `tool_result` 及原调用 ID,只省略空的 `content` 数组,不添加占位文字。
|
|
@@ -48,7 +82,7 @@ namespace 和 custom tool 使用共享映射。自由文本工具转换成 `{inp
|
|
|
48
82
|
|
|
49
83
|
## 缓存与压缩
|
|
50
84
|
|
|
51
|
-
|
|
85
|
+
最多四个显式缓存断点:最后一个顶层工具、最后一段顶层 system、最近两条 user 消息的末块。普通请求 TTL 为 `1h`。压缩保留公共前缀的 `1h`,只将最后的摘要请求末块设为 `5m`,使长 TTL 位于短 TTL 之前。thinking 块不直接设置缓存断点。inline 模式不为新增定义重写顶部 tools;兼容模式修改顶部定义可能导致缓存未命中及 thinking 前缀失配,两者最终由上游判断。
|
|
52
86
|
|
|
53
87
|
压缩只生成文字摘要,保留工具定义及普通请求的工具选择,移除 `output_config.format`,其他参数保留。摘要封装为客户端 V2 compaction item,可供其他模型恢复。保留工具选择的缓存依据及意外工具调用的处理见 [压缩](compaction.md#工具选择与缓存)。
|
|
54
88
|
|
|
@@ -58,4 +92,6 @@ input 为 `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`
|
|
|
58
92
|
|
|
59
93
|
HTTP 与流内错误经共享层分类,同时保留上游原始错误体;未完整结束的流返回错误。bridge 不改参数重试,容量与限流退避由 codeks 决定。[错误结构](responses-stream.md)
|
|
60
94
|
|
|
95
|
+
`stop_reason: "refusal"` 保持不可重试的 `invalid_prompt`。若上游提供 `stop_details.category` 或 `explanation`,错误消息附上这些原文,便于直接在 codeks 看到拒绝原因;未提供时保留通用提示,不猜测分类。完整的终止 `message_delta` 放入 `error.upstream.body`,包含未知字段及上游 usage,便于诊断,不计入普通成功响应的 usage。工具后缀仍整体丢弃,不因保留详情而交付工具或自动重试。
|
|
96
|
+
|
|
61
97
|
实现与测试:[`src/backends/anthropic.ts`](https://github.com/WAcry/codeks-bridge/blob/main/src/backends/anthropic.ts)、[`test/anthropic.test.ts`](https://github.com/WAcry/codeks-bridge/blob/main/test/anthropic.test.ts)。协议参考:[thinking](https://platform.claude.com/docs/en/build-with-claude/thinking)、[结构化输出](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)、[缓存](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)。
|
package/docs/compaction.md
CHANGED
|
@@ -92,7 +92,11 @@ Claude 接收 U + S + 后续内容
|
|
|
92
92
|
|
|
93
93
|
GPT 返回 `K` 时,客户端尚未选定最终的 `U`。让 codeks 先安装检查点,再从下一条真实请求取得 `U`,可以复用现有按需复述流程,无需复制客户端的保留算法。自动压缩后正常继续的请求即可触发,不需要用户再发一轮消息。
|
|
94
94
|
|
|
95
|
-
同样适用于多次切换:GPT 生成 `K1`,Claude 首次接续时使用 `S1`;Claude 的新工作使上下文再次达到阈值后,兼容的 GPT 执行模型接收 `U1 + K1 + 新工作` 并生成 `K2`。Claude 随后取得 `S2`;切回允许该来源的 GPT 时仍可恢复 `K2
|
|
95
|
+
同样适用于多次切换:GPT 生成 `K1`,Claude 首次接续时使用 `S1`;Claude 的新工作使上下文再次达到阈值后,兼容的 GPT 执行模型接收 `U1 + K1 + 新工作` 并生成 `K2`。Claude 随后取得 `S2`;切回允许该来源的 GPT 时仍可恢复 `K2`。
|
|
96
|
+
|
|
97
|
+
经常在同一个 GPT 与 Claude 之间切换时,直接让 `claude.compaction.model` 指向常用的 `gpt`,并设置 `mode: "native"` 即可。两者产生的 `K` 都属于该 GPT,默认就能恢复。Claude 使用过 `S1` 后,下一次 GPT 压缩仍读取 `K1`,无需先把 `S1` 再压缩;切回 GPT 也无需为了转换摘要而发起压缩。达到客户端窗口阈值时仍会正常生成新的检查点。
|
|
98
|
+
|
|
99
|
+
Reasoning 与检查点分别处理:仍在客户端历史中的 GPT reasoning,在 Claude 普通请求中被过滤,在 GPT 普通请求和受托压缩中默认恢复;Claude reasoning 仍由 GPT 的来源策略过滤。压缩后客户端已从活动历史移除的 reasoning 不会作为独立项找回,任务状态由新检查点承接。这是复用已有 GPT 别名的常用场景,无需专用压缩别名或额外来源授权,见[双模型配置](config.md#压缩模式与执行模型)。
|
|
96
100
|
|
|
97
101
|
用户选择这一可选路径,是希望即使主要使用文字模型,也能利用原生压缩整理任务状态的能力。已有 GPT 复述实验支持该设计,但未证明每种 Claude 历史都优于标准摘要。委托会增加实际调用与等待,跨模型输入也可能无法复用 prompt cache;不承诺固定成本增幅。标准 `summary` 保持可选。压缩阈值仍由工作模型决定,bridge 不换算不同模型的 token 比例、不裁剪超限输入,实际容量错误直接返回。
|
|
98
102
|
|
package/docs/config.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 目录、别名与连接
|
|
6
6
|
|
|
7
|
-
`upstreams`
|
|
7
|
+
`upstreams` 描述连接、鉴权及该连接共用的 adapter 设置。`models` 的键是 codeks 显示和请求的别名,每个别名由三项配置组成:
|
|
8
8
|
|
|
9
9
|
| 字段 | 示例 | 用途 |
|
|
10
10
|
| --- | --- | --- |
|
|
@@ -46,12 +46,15 @@
|
|
|
46
46
|
| `host` | 默认 `127.0.0.1` |
|
|
47
47
|
| `port` | 默认 `8787`;程序集成测试可用 `0` 分配端口 |
|
|
48
48
|
| `token_env` | 保存 bridge 入口 Bearer token 的环境变量名称 |
|
|
49
|
+
| `request_max_mib` | `POST /v1/responses` 请求体上限,单位 MiB,默认 `512`;必须是正整数且换算后的字节数可安全表示 |
|
|
49
50
|
| `compaction_cache_dir` | 按需复述缓存目录;默认 `$XDG_CACHE_HOME/codeks-bridge/compaction`,未设置 XDG 时为 `~/.cache/codeks-bridge/compaction` |
|
|
50
51
|
| `upstreams` | 上游连接配置表 |
|
|
51
52
|
| `models` | 模型别名表,是否进入客户端目录由各自的 `expose` 控制 |
|
|
52
53
|
| `inspect` | 可选;`capture` 决定启动时是否捕获,`max_mib` 是内存软上限,见[调试检查器](inspect.md) |
|
|
53
54
|
|
|
54
|
-
监听非 loopback 地址时必须配置 `token_env`,客户端也要设置同一个变量。它是 bridge 入口鉴权,不是上游 API key
|
|
55
|
+
监听非 loopback 地址时必须配置 `token_env`,客户端也要设置同一个变量。它是 bridge 入口鉴权,不是上游 API key。入口没有 CORS。携带 `Origin` 的请求必须使用配置好的 bridge token,即使监听 loopback 也一样,避免网页的简单 POST 在无法读取响应时仍触发上游调用。没有 `Origin` 的 codeks 请求保持原行为。`GET /health`、`GET /v1/models` 和 `POST /v1/responses` 使用同一鉴权。`/inspect/` 页面与接口使用[自己的规则](inspect.md#访问与鉴权)。
|
|
56
|
+
|
|
57
|
+
请求体上限按实际收到的字节计算,包含 JSON 和图片编码,不按字符数或声明的 `Content-Length` 判断。恰好达到上限的请求可接受,超限立即返回 HTTP 413 并关闭连接,不调用上游。默认 `512` MiB 为长截图历史留出空间;可按会话需要及可用内存调高或调低。保留上限是为了约束完整缓冲、JSON 解析和转换的内存开销,启用检查器时还会保留捕获副本;它不是进程总内存上限,也不等于模型 token 窗口。
|
|
55
58
|
|
|
56
59
|
## 上游连接
|
|
57
60
|
|
|
@@ -64,6 +67,7 @@
|
|
|
64
67
|
| `stream_idle_timeout_ms` | 等待上游 headers 或后续字节的最长空闲时间,默认 300000 ms |
|
|
65
68
|
| `headers` | 静态 header,覆盖同名客户端 header,例如 API 版本 |
|
|
66
69
|
| `options` | 原生请求参数,覆盖 adapter 推导出的参数 |
|
|
70
|
+
| `adapters` | 按 adapter ID 分组的桥接行为;供使用该连接的模型继承 |
|
|
67
71
|
|
|
68
72
|
`api_key_command` 不经过 shell,每条上游请求执行一次,最多 30 秒,适合短期凭据。命令错误不会把 stdout/stderr 回传客户端。连接无需 key 的服务时可省略凭据设置。Claude 默认版本 header 为 `anthropic-version: 2023-06-01`。客户端 metadata 默认透传,鉴权使用上游配置;覆盖顺序及必要过滤见 [header 边界](responses-stream.md#header-与转发边界)。
|
|
69
73
|
|
|
@@ -82,6 +86,7 @@
|
|
|
82
86
|
| `replay` | 分别允许哪些来源的 reasoning、原生压缩 |
|
|
83
87
|
| `compaction` | 压缩方式、执行模型,以及本目标是否接收原生压缩 |
|
|
84
88
|
| `options` | 原生请求参数,覆盖上游级 options |
|
|
89
|
+
| `adapters` | 当前模型的桥接行为;逐字段覆盖上游级同名 adapter 设置 |
|
|
85
90
|
|
|
86
91
|
`expose` 控制目录展示,不改变别名的路由、来源身份或访问权限。隐藏模型仍可用于压缩、handoff 和状态复用,显式按别名发送请求也仍可调用;inspector 保留其诊断记录。`config-snippet` 选择首个公开别名作为默认模型;全部隐藏时目录为空,该命令明确报错。
|
|
87
92
|
|
|
@@ -91,6 +96,38 @@
|
|
|
91
96
|
|
|
92
97
|
最终由 bridge 固定模型路由、转换后的历史、工具定义、流模式和必要协议字段。摘要请求保留普通工具选择并移除结构化输出约束;原生压缩移除普通回答的输出上限。必须重建的字段承载桥接契约,不由 options 改写。详见上表链接的后端文档,以及[摘要的工具选择](compaction.md#工具选择与缓存)。
|
|
93
98
|
|
|
99
|
+
## Adapter 专用行为
|
|
100
|
+
|
|
101
|
+
`adapters` 与 `options` 分工不同:前者控制 bridge 怎样转换协议,后者是发送给上游的原生请求参数。命名空间使用目录已有的 adapter ID;以后新增协议专用行为时,在对应命名空间增加字段,无需向模型目录或连接顶层添加协议特例。目前有专用设置的是 `claude-messages`:
|
|
102
|
+
|
|
103
|
+
| 字段 | 值与默认行为 |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `tool_updates` | 默认 `inline`,把中途工具声明转换为原生完整 `tool_addition`;兼容模式 `top_level` 将所有定义合并到顶部 |
|
|
106
|
+
| `thinking_mismatch` | 默认 `drop_block`,请服务端丢弃前缀失配的 thinking;`error` 请求报错;`upstream_default` 不补充该策略 |
|
|
107
|
+
|
|
108
|
+
连接级设置适合端点差异,模型级设置适合同一连接下的模型差异。每个叶字段依次取模型设置、连接设置、adapter 默认值;模型只覆盖一个字段时,仍继承连接里的其他字段。下面的连接显式使用兼容工具模式,模型再单独调整 thinking 策略:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"upstreams": {
|
|
113
|
+
"compatible": {
|
|
114
|
+
"base_url": "https://example.com/v1",
|
|
115
|
+
"adapters": { "claude-messages": { "tool_updates": "top_level" } }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"models": {
|
|
119
|
+
"sonnet": {
|
|
120
|
+
"upstream": "compatible", "catalog": "claude-sonnet-5-5", "model": "deployment-a",
|
|
121
|
+
"adapters": { "claude-messages": { "thinking_mismatch": "error" } }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
省略 `thinking_mismatch` 表示 bridge 默认的 `drop_block`,与显式 `upstream_default` 不同。对最终 `thinking.type` 为 `adaptive` 或 `enabled` 的请求,bridge 补充 `block_binding.prefix_mismatch_behavior`;原生 `options.thinking.block_binding` 中明确设置的同名值优先。`between_tools` 等不支持该字段的模式不补充策略,不擅自换 thinking 模式。`options` 本身仍按前述浅层规则合并,不进行通用递归合并。
|
|
128
|
+
|
|
129
|
+
专用设置不作为请求参数发给上游,不改变状态来源身份或客户端目录。共享连接可以配置供不同模型使用的 adapter;模型级只接受自身 adapter 的设置。未知命名空间、字段及枚举会报配置错误;尚无专用字段的 adapter 无需配置空对象。`inline` 需要上游支持相关协议,不兼容时直接返回错误,应在该连接显式选择 `top_level`。恢复代价及端点行为见 [Claude Messages](backends-anthropic.md#失配策略与故障续接)。
|
|
130
|
+
|
|
94
131
|
## Reasoning 与模型目录
|
|
95
132
|
|
|
96
133
|
```json
|
|
@@ -131,28 +168,34 @@ GPT 的默认配置等价于:
|
|
|
131
168
|
|
|
132
169
|
`native` 初始只生成原生检查点;目标无法按来源策略回放它时,先向生成检查点的模型和连接复述为文字。原来源须继续在配置中可用。成功摘要及辅助 usage 存入顶层 `compaction_cache_dir`,重启后可复用;它不是上游 prompt cache。`summary` 继续使用标准 prompt 直接压缩原历史。
|
|
133
170
|
|
|
134
|
-
|
|
171
|
+
经常在 GPT 与 Claude 之间切换时,建议直接复用日常使用的 GPT 别名执行压缩。只需两个模型,Claude 的历史由同一个 GPT 原生压缩,再按需复述给 Claude。以下为模型部分,含连接的完整配置见 [examples/gpt-claude.json](../examples/gpt-claude.json):
|
|
135
172
|
|
|
136
173
|
```json
|
|
137
174
|
{
|
|
138
175
|
"models": {
|
|
176
|
+
"gpt": {
|
|
177
|
+
"catalog": "gpt-6.1-sol", "upstream": "openai", "model": "gpt-6.1-sol"
|
|
178
|
+
},
|
|
139
179
|
"claude": {
|
|
140
180
|
"catalog": "claude-opus-5-5", "upstream": "anthropic", "model": "claude-opus-5-5",
|
|
141
|
-
"compaction": { "mode": "native", "model": "gpt
|
|
142
|
-
},
|
|
143
|
-
"gpt-compactor": {
|
|
144
|
-
"catalog": "gpt-6.1-sol", "upstream": "openai", "model": "gpt-6.1-sol",
|
|
145
|
-
"expose": false,
|
|
146
|
-
"reasoning": { "default_effort": "xhigh" }
|
|
181
|
+
"compaction": { "mode": "native", "model": "gpt" }
|
|
147
182
|
}
|
|
148
183
|
}
|
|
149
184
|
}
|
|
150
185
|
```
|
|
151
186
|
|
|
152
|
-
这里的 `anthropic` 和 `openai` 使用已有 `upstreams`
|
|
187
|
+
这里的 `anthropic` 和 `openai` 使用已有 `upstreams` 连接,客户端目录只展示 `gpt` 和 `claude`。Claude 必须同时设置 `mode: "native"`;只设置 `model: "gpt"` 会让 GPT 执行 Claude 默认的标准文字压缩。
|
|
188
|
+
|
|
189
|
+
两条压缩路径都由 `gpt` 执行,因此生成的原生检查点与日常 GPT 的来源身份相同。GPT 默认就能恢复它们及历史中仍保存的自身 reasoning,无需添加 `replay.reasoning_from` 或 `replay.native_compaction_from`。Claude 使用文字 handoff;切换到 Claude 不会改写客户端保存的 GPT 原生状态。具体切换时序见[委托压缩与切换模型](compaction.md#委托压缩与切换模型)。
|
|
190
|
+
|
|
191
|
+
只有需要独立的压缩参数或不同部署时,才有必要增加专用模型别名;可用 `expose: false` 隐藏于客户端目录。同一 upstream ID、URL、adapter 和实际部署的别名本就共享来源身份,即使别名不同也不需要额外的 replay 配置。
|
|
192
|
+
|
|
193
|
+
压缩只跟随一次 `compaction.model`,不继续读取执行模型自己的委托配置或生成模式。工作模型的窗口与压缩阈值仍决定何时压缩,执行模型须能接收实际历史;超限由上游报错。
|
|
153
194
|
|
|
154
195
|
未设置 `compaction.model` 或显式引用自身别名时,沿用请求的 reasoning effort;引用其他别名时使用执行别名的 `reasoning.default_effort`,未设置则使用它的目录默认值,再按该模型的映射转换。即使两个别名指向同一部署,也可用专用压缩别名选择不同默认 effort。复述配方保留选定的请求 effort,后续切换工作模型不改变它。显式原生 options 仍按普通请求的规则覆盖推导参数;后续修改原来源的 options 也会作用于复述,它们不作为快照固化。
|
|
155
196
|
|
|
197
|
+
因此,Claude 委托 `gpt` 压缩不会记住之前 GPT 轮次在客户端临时选择的 effort。若希望受托压缩固定使用某档位,直接在现有 `gpt` 条目设置 `reasoning.default_effort`;它也会成为该别名的普通请求默认值,普通请求仍可显式覆盖。
|
|
198
|
+
|
|
156
199
|
Claude 的 `native_replay` 仍默认为 `false`:委托生成不会让 Claude 获得读取 GPT 密文的能力。`native` 要求执行模型的 adapter 支持原生压缩格式;`native_replay` 则要求工作模型能恢复该格式。它们与[状态复用](#状态复用)的来源策略独立。完整时序、失败及用量规则见[压缩](compaction.md)。
|
|
157
200
|
|
|
158
201
|
## 状态复用
|
package/docs/design.md
CHANGED
|
@@ -22,7 +22,7 @@ flowchart LR
|
|
|
22
22
|
|
|
23
23
|
优先匹配最新 Codex 的 GPT 默认行为,以 OpenAI 订阅、AWS 和 Azure 的使用场景为基准。当前已审阅模型目录使用 Responses Lite,保留 `use_responses_lite: true`、Code Mode Only 和多代理字段。目录固定到明确的 Codex 源码版本,不随用户本机二进制或订阅账号变化。
|
|
24
24
|
|
|
25
|
-
Lite 仍调用 `/responses` 并返回 Responses SSE。完整工具 schema 位于历史中的 `additional_tools` 项,普通函数和 custom 工具归入默认 `functions` namespace。GPT 路径保留这些声明的位置、顺序、schema 和 grammar,并发送 `x-openai-internal-codex-responses-lite: true`。Claude
|
|
25
|
+
Lite 仍调用 `/responses` 并返回 Responses SSE。完整工具 schema 位于历史中的 `additional_tools` 项,普通函数和 custom 工具归入默认 `functions` namespace。GPT 路径保留这些声明的位置、顺序、schema 和 grammar,并发送 `x-openai-internal-codex-responses-lite: true`。Claude 默认将初始声明放到顶部,增量声明转换为原生中途工具更新;DeepSeek 及 Claude 的显式兼容模式将各批定义合并到顶部。声明不会变成 user reminder。中途提醒保留所在对话位置;若插在一次完整的工具交换内,则移至该批结果之后,以满足目标协议的配对要求。
|
|
26
26
|
|
|
27
27
|
已核对 Codex `0.163.0`:Lite 设置 `reasoning.context: "all_turns"` 和 `parallel_tool_calls: false`。bridge 保留客户端传入值,原生 options 仍可按配置覆盖普通参数。reasoning 的来源筛选与服务端 context 语义是两件事:前者决定发送哪些载荷,后者决定 GPT 如何使用它们。原生压缩与随后的复述保留源 Lite 工具声明及 transport 标记。
|
|
28
28
|
|
|
@@ -36,7 +36,9 @@ Lite 仍调用 `/responses` 并返回 Responses SSE。完整工具 schema 位于
|
|
|
36
36
|
|
|
37
37
|
## 配置表达策略,adapter 负责协议
|
|
38
38
|
|
|
39
|
-
`models.catalog` 选择条目;`upstreams`
|
|
39
|
+
`models.catalog` 选择条目;`upstreams` 包含连接、鉴权及共用的 adapter 设置;别名和真实部署名独立。目录条目决定 adapter,实际模型名不参与方言推断。模型窗口、输入模态、effort 映射、状态复用范围、压缩模式及执行模型可由配置覆盖。
|
|
40
|
+
|
|
41
|
+
协议专用桥接行为放在 `adapters.<adapter ID>`,模型逐字段覆盖连接,再取对应 adapter 默认值;原生请求参数仍通过开放的 `options` 浅层合并。这样的边界让未来 GPT、Messages 或 Chat 方言可以添加各自确有需求的设置,而不用在连接顶层堆积特例,也不提前维护空的配置对象或通用插件框架。配置行为不改变来源身份和模型目录。当前 Claude 的工具更新方式与 thinking 失配策略分别配置,因为前者保持工具前缀,后者还影响故障续接等其他历史变化;见 [Adapter 专用行为](config.md#adapter-专用行为)。
|
|
40
42
|
|
|
41
43
|
`src/profiles.ts` 将目录与配置组合为运行时 profile,同时用于客户端目录和出站请求,避免界面显示一套 effort、实际发送另一套。未覆盖字段保留原模型差异,不能用一套通用默认值改写所有 GPT。来源身份继续取连接、adapter 与实际部署,不因提示词更新或窗口预设改变。
|
|
42
44
|
|
|
@@ -62,6 +64,8 @@ bridge 检查自身必须维持的不变量:配置引用存在、历史能转
|
|
|
62
64
|
|
|
63
65
|
上游负责判定模型能力:effort、thinking、强制工具选择、schema、图片以及允许复用的密文是否可接受。bridge 不预先维护这些能力的限制列表,也不在失败后自动移除 reasoning、关闭 thinking、换端点或改压缩模式。原生参数可通过 options 显式覆盖。
|
|
64
66
|
|
|
67
|
+
Claude 默认主动请求服务端对前缀失配 thinking 使用 `drop_block`,保留其余可接受的块。这是已配置的请求策略,不是在收到错误后重试或改写已存历史。它允许流式故障后的部分回答继续工作,但丢失 reasoning 仍可能影响质量或增加重新推理的开销,不能视作无损。检查器展示上游实际报告的丢弃位置和原因,区分 bridge 的来源过滤与服务端判断。协议与恢复时序见 [Claude Messages](backends-anthropic.md#失配策略与故障续接)。
|
|
68
|
+
|
|
65
69
|
GPT 原生 HTTP 失败保留 status、body 和诊断 header,流内失败保留 error 对象。下游 HTTP 200 推迟到上游接受请求后,避免把 HTTP 失败变成 SSE 错误而改变 codeks 的重试判断;等待响应头期间没有 keepalive,是这一选择的明确代价。Claude/DeepSeek 只在协议转换边界映射错误分类。重试和退避由客户端控制,bridge 不叠加第二层重试。详见 [Responses 流](responses-stream.md)。
|
|
66
70
|
|
|
67
71
|
## 压缩的生成和恢复分开
|
package/docs/inspect.md
CHANGED
|
@@ -59,6 +59,16 @@ codeks-bridge serve --config /absolute/path/bridge.json --capture
|
|
|
59
59
|
|
|
60
60
|
“概要”给出实际执行模型的路由配置、每个调用的时间线和上游调用表;客户端模型字段仍保留用户请求的工作别名。“标注”列出全部回放决定,点击可定位到对应项。“对比”选择任意两次交换的任意一段做结构比较,常用的是和同一线程上一轮的客户端请求或上游请求比较;数组按类型和角色对齐,新增、删除的项单独标出。
|
|
61
61
|
|
|
62
|
+
概要中的“流式内容延迟”直接从已有 SSE 捕获派生,不增加请求或修改原始记录:
|
|
63
|
+
|
|
64
|
+
- 每条 `main`、`native`、`summary`、`handoff` 从该次上游调用开始计时。`bridge → codeks` 从客户端请求开始计时,包含必要的前置 handoff 和 bridge 缓冲时间。
|
|
65
|
+
- “首内容”是首次实际文字、reasoning 或工具内容;“首文字”“首 reasoning”“首工具”分别记录各类首次到达。工具以首次出现名称或非空参数为准,不表示工具已经执行。空 delta、响应头、created、keepalive、签名更新和压缩密文不算内容。
|
|
66
|
+
- 上游 reasoning 可以是文字或不透明载荷;写回 codeks 一侧只计可展示的 reasoning 摘要,不把隐藏载荷当成可见输出。这里测量 bridge 收到或写出的时刻,不是模型内部生成时间或 codeks TUI 实际渲染时间。
|
|
67
|
+
- SSE 事件按完成该事件的数据块计时;若上游只在终止事件里交付完整 output,就记该事件到达时刻。仍未结束的事件不会提前计入。
|
|
68
|
+
- `—` 表示尚未观测到该类内容或无法测量,不等于零延迟。JSON 响应、未完成的 JSON 事件及缺少时间戳的导入记录不估算首 token 时间;原生压缩只有密文而没有上述内容时,这些指标也为空。已观测的延迟不代表整条响应成功,需同时看调用状态。
|
|
69
|
+
|
|
70
|
+
时间线中的“响应头”保留其独立含义,不作为首内容时间。选择逐调用记录,是为了区分上游等待与客户端实际等待,避免把辅助压缩的时间混成普通模型的首 token 延迟。
|
|
71
|
+
|
|
62
72
|
“导出全部”和详情里的“导出此条”下载导出文件。“导入”(或把文件拖到页面上)离线查看导出文件,“返回实时”回到 bridge 的记录。浏览器不允许从本地文件加载页面脚本,离线查看也要通过 HTTP 打开页面,例如任意运行中的 bridge 的 `/inspect/`。
|
|
63
73
|
|
|
64
74
|
## 记录规则
|
|
@@ -112,6 +122,16 @@ codeks-bridge serve --config /absolute/path/bridge.json --capture
|
|
|
112
122
|
|
|
113
123
|
每条标注附来源别名、部署名、adapter、来源指纹、载荷格式和解码后的 carrier。`cxbr1:` 与 `cxbc1:` 只是编码,解码后可以看到文字 reasoning、压缩摘要和原生载荷;GPT 原生 `encrypted_content` 本身仍是密文,只能看到原样字符串。
|
|
114
124
|
|
|
125
|
+
## Claude 服务端输入处理
|
|
126
|
+
|
|
127
|
+
上游响应详情单独显示服务端原始 `input_transformations` 报告。它与 bridge 根据来源策略做的本地回放过滤不同:本地决定回放的 thinking,仍可能被 Claude 拒绝或丢弃。
|
|
128
|
+
|
|
129
|
+
- `thinking_dropped` 表示服务端报告已丢弃对应 thinking。
|
|
130
|
+
- `thinking_mismatch_allowed` 表示允许失配并保留,不代表丢弃。
|
|
131
|
+
- 每项保留原始 `path`、`reason` 与未知字段;`path` 指向该次实际发出的上游请求,不是 codeks 原始输入的位置。
|
|
132
|
+
|
|
133
|
+
页面保留报告所在 SSE 事件及字段路径,可回到原始内容核对。流里重复出现的报告不会累加为“丢弃总数”;显式空列表显示“服务端报告为空”,没有报告则不展示这个区块,不能解读为零丢弃或全部验签通过。丢弃可能减少可用的推理上下文,检查器只展示上游报告,不推断模型质量或自行改写历史。策略与故障恢复见 [Claude Messages](backends-anthropic.md#失配策略与故障续接)。
|
|
134
|
+
|
|
115
135
|
## 内存、清空与导出
|
|
116
136
|
|
|
117
137
|
记录只在内存里,不写磁盘,进程退出即清空。超过 `max_mib` 时淘汰最早的已结束交换;仍在传输的交换不淘汰,所以占用可能短时超过上限。页面显示已淘汰的数量。
|
package/docs/reasoning.md
CHANGED
|
@@ -39,6 +39,8 @@ JSON 的结构如下,示例指纹为占位值:
|
|
|
39
39
|
|
|
40
40
|
一个 carrier 对应一项 reasoning;格式可以保存字符串或结构化数据,不限于可见文字。外层只是编码,不是加密。GPT 密文本身不变;Claude 可见 thinking 和 DeepSeek 文本仍可解码。
|
|
41
41
|
|
|
42
|
+
Claude 的外层还可包含 `continuation_before: "Continue."`,记录生成该块前实际补入的 user 续接边界。它只用于响应首块为 thinking/redacted 的情况,原生 `data` 和签名不变,前缀仍为 `cxbr1:`。字段随载荷保存、筛选和恢复,不改变复用策略;具体时序与覆盖范围见 [Claude 故障续接](backends-anthropic.md#失配策略与故障续接)。
|
|
43
|
+
|
|
42
44
|
codeks 会再次发送 `encrypted_content`,因此载荷可以跨请求、会话恢复、服务重启和子代理 fork 保留。只放在 reasoning 的可见 `content` 中不足以完成这种回放。
|
|
43
45
|
|
|
44
46
|
## 来源身份
|
package/docs/responses-stream.md
CHANGED
|
@@ -28,7 +28,7 @@ GPT 同一 reasoning item 内的摘要保留各段 `summary_index`,对应的 p
|
|
|
28
28
|
|
|
29
29
|
末尾 `response.completed` 带 ID、完整 output 和 usage。`end_turn: false` 保留需要继续生成的语义,例如 Claude `pause_turn`。缺少终止信号或无效工具结果返回错误,不伪装成成功。
|
|
30
30
|
|
|
31
|
-
GPT 的 `response.incomplete` 保留原类型、原因和 usage,不改成通用错误。已核对 Codex `0.163.0`:`interrupted` 表示继续当前 turn;`content_filter` 使用内容过滤提示;`max_output_tokens`
|
|
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
32
|
|
|
33
33
|
## 上游错误
|
|
34
34
|
|
|
@@ -75,7 +75,7 @@ bridge 不重试、不替换端点,也不因错误移除 reasoning、关闭 th
|
|
|
75
75
|
|
|
76
76
|
GPT 保留原始 usage,包括提供的 cache write 等分项;DeepSeek 使用 prompt/completion 与 cache hit;Claude 将普通 input、cache read、cache creation 相加得到完整 input。转换后的总数只计算 input + output;reasoning 作为输出分项,不重复叠加。`native` 初始只返回原生压缩用量;后续辅助复述的 usage 独立保存在本地缓存,不加入目标回答的 usage。
|
|
77
77
|
|
|
78
|
-
成功响应的必需 usage
|
|
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
79
|
|
|
80
80
|
## Keepalive 与超时
|
|
81
81
|
|
package/docs/testing.md
CHANGED
|
@@ -29,6 +29,12 @@ npm run check
|
|
|
29
29
|
|
|
30
30
|
这些测试检查协议和客户端行为;假上游不会验证模型是否理解提示词,也不会验证真实服务的缓存命中率、容量和区域之间的密文兼容性。
|
|
31
31
|
|
|
32
|
+
压缩后的信息保留与 handoff 质量另有[手动 benchmark](../benchmarks/compaction/README.md):使用公开合成材料,对比两种文字 prompt、原生压缩和原生压缩后 handoff。它独立于 CI,提供复跑方法、输入、产物和逐题结果;真实模型请求需要显式执行。
|
|
33
|
+
|
|
34
|
+
Claude 续接边界的回归同时检查生成和下一轮回放:首块 thinking/redacted 的 `continuation_before` 只记录一次,原生载荷不变;连续失败、多个 thinking、关闭摘要显示时仍保持顺序;来源过滤与切回按同一载荷生效;增量工具声明已有边界时不重复插入。正文或工具先于 thinking、没有 thinking,以及没有该字段的既有载荷,继续使用原有失配策略。`test/claude-continuation.test.ts` 检查转换,`test/claude-recovery-e2e.test.ts` 使用真实 codeks 和假上游检查持久化与重试;签名是否被真实上游接受须另行验证,不能由合成签名测试推断。
|
|
35
|
+
|
|
36
|
+
2026-10-20(America/Los_Angeles),以 `@wacry/codeks@0.163.0-codeks.5` 启用全部客户端测试,286 项通过、0 跳过,类型检查、构建和私有内容检查通过。续接场景覆盖 overload、EOF、连续两次失败及进程重启后的 resume。另复用真实 T1/T2 签名直接请求官方 Sonnet 5.5:恢复原位 `user: Continue.` 后返回 HTTP 200,`input_transformations` 明确为空数组。这验证边界恢复的协议依据;carrier 的生成、保存与重放由真实客户端加本地假上游验证,两类证据分别成立。
|
|
37
|
+
|
|
32
38
|
`test/delegated-compaction-e2e.test.ts` 通过 bridge 的真实 HTTP 入口连接本地假上游,覆盖 GPT 原生续聊 → Claude 按需复述 → Claude 委托 GPT 再压缩 → Claude 复述新检查点 → GPT 恢复新检查点。另检查 native 失败不交付部分检查点、handoff 失败不调用工作模型、工作模型失败后跨服务重启复用成功缓存,以及 inspector 的独立 handoff usage 不进入客户端响应。它不启动 codeks,也不调用真实模型;委托压缩的质量与成本不由这些测试推断。
|
|
33
39
|
|
|
34
40
|
## 使用真实 codeks 二进制
|
package/docs/tools.md
CHANGED
|
@@ -4,10 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## Lite 工具声明
|
|
6
6
|
|
|
7
|
-
客户端默认把完整工具 schema 放在 `input[].additional_tools`。GPT
|
|
7
|
+
客户端默认把完整工具 schema 放在 `input[].additional_tools`。GPT 保留原始声明项。DeepSeek 及 Claude 的 `top_level` 模式在转换消息前提取声明,与顶层 `tools` 按历史顺序合并;Claude 默认 `inline` 模式保留增量定义生效的位置。声明不会变成普通 reminder。
|
|
8
8
|
|
|
9
9
|
增量声明只包含新增或变更的成员,按 namespace 与工具名覆盖旧定义,保留未变的其他成员和已分配的上游名字。工具移除的 developer 通知保留原位置,不从提示词文字推断删工具。codeks 将默认 `functions` namespace、空 namespace 和省略 namespace 视为同一身份,因此历史调用和显式工具选择均按此规则查找;返回调用保留声明中的原 namespace。
|
|
10
10
|
|
|
11
|
+
Claude 的 `adapters.claude-messages.tool_updates` 有两种明确模式:
|
|
12
|
+
|
|
13
|
+
| 模式 | 初始定义 | 中途新增与同名更新 |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `inline`(默认) | 顶层 `tools` 及会话开始前的声明组成原生顶部工具表 | 在原生中途 system 消息中发送 `tool_addition`,携带完整 `tool_definition`;此前定义不变 |
|
|
16
|
+
| `top_level` | 各批定义累积成同一顶部工具表 | 用最新定义覆盖同身份工具,适用于尚未支持完整 inline 的端点 |
|
|
17
|
+
|
|
18
|
+
完整 inline 定义能同时表达新工具和同名工具更新,避免为了更新定义重写早先的工具前缀;这是选择它作为默认方式的理由。bridge 在 user 或整批工具结果之后、下一条 assistant 之前发送中途 system 工具声明;工具交换内的声明先等待结果完整,无法形成合法边界则报错。普通 system/developer 提醒仍转为原位 user reminder,不因工具使用原生中途 system 而改变角色。
|
|
19
|
+
|
|
20
|
+
端点需要支持 `inline-tools-2026-09-15` beta;bridge 在请求包含中途更新时自动追加,保留已有 beta。不兼容时直接交付错误,不自动切换模式。`top_level` 可能改变缓存前缀和旧 thinking 的绑定;失配处理由独立的 [thinking 策略](backends-anthropic.md#失配策略与故障续接)决定。Code Mode Only 下多数工具位于 `exec` 内的目录,因此外层更新较少,但仍按协议完整处理。
|
|
21
|
+
|
|
11
22
|
## 工具发现与网页搜索
|
|
12
23
|
|
|
13
24
|
`supports_search_tool` 表示客户端的工具发现能力,与网页搜索不同。当前源目录的 GPT 条目显式设为 `true`,bridge 保留它。是否出现独立 `tool_search` 还取决于客户端工具模式和功能开关,不能只从目录字段推断。
|
|
@@ -36,6 +47,12 @@ Messages 对不符合端点格式的 call ID 采用确定性映射,并添加
|
|
|
36
47
|
|
|
37
48
|
普通 JSON Schema 清理 Responses 专用的布尔 `encrypted` 关键字,不删除名为 `encrypted` 的普通属性或数据。客户端使用明文多代理工具;返回的函数项显式携带 `encrypted_function_args: []`。
|
|
38
49
|
|
|
50
|
+
根 schema 的 `$ref`、`$defs`、`allOf`、`anyOf`、`oneOf` 及其他约束均保留;缺少根 `type` 时补为 `object`,已有类型不覆盖,由上游判断是否支持。codeks 可以发送没有根 `type` 的引用或组合 schema,不能因此把参数清空。未提供参数 schema 时使用空对象定义;显式提供非对象 schema 值则报错,不在 bridge 内解析引用或删减约束。
|
|
51
|
+
|
|
52
|
+
普通函数显式给出的 `strict: true` 或 `strict: false` 原样保留:Messages 放在工具定义顶层,Chat 放在 `function` 内;省略时不添加默认值。Lite 同名更新替换完整定义,因此新定义省略 `strict` 时也不继承旧值;Claude inline 的早先版本保持不变。custom 工具的 JSON 外壳不因此自动启用 strict。
|
|
53
|
+
|
|
54
|
+
[Claude strict tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use) 与 [DeepSeek strict mode](https://api-docs.deepseek.com/guides/tool_calls/#strict-mode-beta) 各有支持的 schema 范围;DeepSeek 官方文档还要求 beta 端点及所有函数都设为 `strict: true`。bridge 只转换调用方明确提供的字段,不切换端点、不改其他工具或删减 schema 来满足 strict。需要该能力时由用户配置支持的端点,上游不接受则保留错误。
|
|
55
|
+
|
|
39
56
|
codeks V2 多代理工具的 task/message 是明文,历史中的 `agent_message` 对 Messages/Chat 转成 user 消息,保留 author、recipient 和内容顺序。它不会成为模型 reasoning。不能解密的工具或 agent message 会明确报错。
|
|
40
57
|
|
|
41
58
|
## 结果与范围
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"host": "127.0.0.1",
|
|
3
|
+
"port": 8787,
|
|
4
|
+
"upstreams": {
|
|
5
|
+
"openai": {
|
|
6
|
+
"base_url": "https://api.openai.com/v1",
|
|
7
|
+
"api_key_env": "OPENAI_API_KEY"
|
|
8
|
+
},
|
|
9
|
+
"anthropic": {
|
|
10
|
+
"base_url": "https://api.anthropic.com/v1",
|
|
11
|
+
"api_key_env": "ANTHROPIC_API_KEY"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"models": {
|
|
15
|
+
"gpt": {
|
|
16
|
+
"catalog": "gpt-6.1-sol",
|
|
17
|
+
"upstream": "openai",
|
|
18
|
+
"model": "gpt-6.1-sol"
|
|
19
|
+
},
|
|
20
|
+
"claude": {
|
|
21
|
+
"catalog": "claude-opus-5-5",
|
|
22
|
+
"upstream": "anthropic",
|
|
23
|
+
"model": "claude-opus-5-5",
|
|
24
|
+
"compaction": { "mode": "native", "model": "gpt" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
package/ui/app.css
CHANGED
|
@@ -44,6 +44,10 @@ h3, h4 { margin: 0; }
|
|
|
44
44
|
.err { color: var(--bad); }
|
|
45
45
|
.grow { flex: 1 1 auto; }
|
|
46
46
|
.note { padding: 4px 8px; margin: 0 0 8px; background: var(--info-bg); border-left: 3px solid var(--info); border-radius: 3px; font-size: 12px; }
|
|
47
|
+
.server-transformations { margin-bottom: 12px; }
|
|
48
|
+
.transformation-report { margin: 4px 0; }
|
|
49
|
+
.transformation-entry { padding: 3px 0; border-bottom: 1px solid var(--line2); }
|
|
50
|
+
.transformation-entry > summary { cursor: pointer; overflow-wrap: anywhere; }
|
|
47
51
|
.empty { padding: 48px 24px; text-align: center; color: var(--fg2); }
|
|
48
52
|
.empty.small { padding: 14px 4px; text-align: left; }
|
|
49
53
|
.link { color: var(--accent); text-decoration: none; }
|