@wacry/codeks-bridge 0.0.5 → 0.0.6
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/README.md +1 -1
- package/dist/backends/anthropic.js +5 -1
- package/dist/backends/chat.js +14 -3
- package/dist/backends/responses.js +4 -2
- package/dist/carrier.js +1 -6
- package/dist/compaction-handoff.js +2 -1
- package/dist/config.js +7 -1
- package/dist/history.d.ts +4 -0
- package/dist/history.js +31 -3
- package/dist/inspect/view.d.ts +2 -1
- package/dist/inspect/view.js +14 -17
- package/dist/types.d.ts +3 -0
- package/docs/backends-responses.md +1 -1
- package/docs/compaction.md +4 -2
- package/docs/config.md +22 -2
- package/docs/design.md +3 -1
- package/docs/inspect.md +2 -4
- package/docs/reasoning.md +3 -3
- package/package.json +1 -1
- package/ui/labels.js +5 -7
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ codeks-bridge config-snippet --config /absolute/path/bridge.json
|
|
|
29
29
|
## 使用边界
|
|
30
30
|
|
|
31
31
|
- 上游错误保留原始详情交给客户端;bridge 不自动换端点、重试或更换压缩方式。
|
|
32
|
-
-
|
|
32
|
+
- bridge 封装的 reasoning 默认只回放给原目标,原生压缩与 reasoning 的跨目标复用分别配置。无法识别的历史状态默认透传,也可配置[过滤或报错](docs/config.md#未识别的历史状态);实际兼容性由上游决定。
|
|
33
33
|
- GPT 默认使用 `compaction.mode: "native"`:先做原生加密压缩,需要文字表示时再向原来源复述并缓存。其他模型默认使用标准文字压缩,也可通过 [`compaction.model`](docs/config.md#压缩模式与执行模型) 委托常用 GPT 执行原生压缩,直接复用已有模型配置;见 [GPT+Claude 双模型示例](examples/gpt-claude.json)。
|
|
34
34
|
- 历史由 codeks 保存;按需复述的成功结果另存本地缓存。封装只是编码,文字 reasoning、摘要及复述配方可读。
|
|
35
35
|
- Lark/regex 转为工具描述约束。图片理解由模型支持,客户端配置关闭网页搜索和图片生成。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { encodeReasoning } from "../carrier.js";
|
|
2
2
|
import { resolveClaudeOptions } from "../adapter-options.js";
|
|
3
3
|
import { BridgeError, upstreamError } from "../errors.js";
|
|
4
|
-
import { prepareHistory } from "../history.js";
|
|
4
|
+
import { isOpaqueStateItem, prepareHistory } from "../history.js";
|
|
5
5
|
import { reasoningEffort, reasoningSummaryRequested } from "../profiles.js";
|
|
6
6
|
import { parseJsonSse } from "../sse.js";
|
|
7
7
|
import { safeCallId } from "../tools.js";
|
|
@@ -114,6 +114,10 @@ export function buildAnthropicRequest(context) {
|
|
|
114
114
|
type: "tool_addition", tool: { type: "tool_definition", definition },
|
|
115
115
|
})));
|
|
116
116
|
}
|
|
117
|
+
else if (isOpaqueStateItem(item)) {
|
|
118
|
+
// Keep unknown state opaque. The endpoint decides whether it accepts it.
|
|
119
|
+
append("assistant", [item]);
|
|
120
|
+
}
|
|
117
121
|
else if (item.type === "bridge_reasoning") {
|
|
118
122
|
const block = item.state.data;
|
|
119
123
|
if (!block || !["thinking", "redacted_thinking"].includes(block.type)) {
|
package/dist/backends/chat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { encodeReasoning } from "../carrier.js";
|
|
2
2
|
import { BridgeError, transportError, upstreamError } from "../errors.js";
|
|
3
|
-
import { prepareHistory } from "../history.js";
|
|
3
|
+
import { isOpaqueStateItem, prepareHistory } from "../history.js";
|
|
4
4
|
import { parseJsonSse } from "../sse.js";
|
|
5
5
|
import { buildToolSet, normalizeToolDeclarations } from "../tools.js";
|
|
6
6
|
import { upstreamFetch } from "../upstream.js";
|
|
@@ -48,7 +48,15 @@ function convertHistory(context, tools, reasoningField) {
|
|
|
48
48
|
if (context.request.instructions)
|
|
49
49
|
messages.push({ role: "system", content: context.request.instructions });
|
|
50
50
|
for (const item of prepareHistory(context.request.input, context.route)) {
|
|
51
|
-
if (item
|
|
51
|
+
if (isOpaqueStateItem(item)) {
|
|
52
|
+
flushImages();
|
|
53
|
+
// Do not invent a text or reasoning_content interpretation for opaque data.
|
|
54
|
+
const a = getAssistant();
|
|
55
|
+
if (typeof a.content === "string")
|
|
56
|
+
a.content = a.content ? [{ type: "text", text: a.content }] : [];
|
|
57
|
+
a.content.push(item);
|
|
58
|
+
}
|
|
59
|
+
else if (item.type === "bridge_reasoning") {
|
|
52
60
|
flushImages();
|
|
53
61
|
// A reasoning item starts the next assistant response, even if the
|
|
54
62
|
// previous response ended without a tool or user message.
|
|
@@ -65,7 +73,10 @@ function convertHistory(context, tools, reasoningField) {
|
|
|
65
73
|
throw new BridgeError("Chat assistant history cannot contain images.");
|
|
66
74
|
const a = getAssistant();
|
|
67
75
|
const text = messageContent(parts);
|
|
68
|
-
|
|
76
|
+
if (Array.isArray(a.content))
|
|
77
|
+
a.content.push(...parts);
|
|
78
|
+
else
|
|
79
|
+
a.content += (a.content && text ? "\n" : "") + text;
|
|
69
80
|
}
|
|
70
81
|
else {
|
|
71
82
|
flushAssistant();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { encodeReasoning } from "../carrier.js";
|
|
2
2
|
import { BridgeError, ResponsesIncompleteError, responsesStreamError } from "../errors.js";
|
|
3
|
-
import { prepareHistory } from "../history.js";
|
|
3
|
+
import { isOpaqueStateItem, prepareHistory } from "../history.js";
|
|
4
4
|
import { reasoningEffort } from "../profiles.js";
|
|
5
5
|
import { parseJsonSse } from "../sse.js";
|
|
6
6
|
import { upstreamFetch } from "../upstream.js";
|
|
@@ -12,7 +12,9 @@ export function responsesBody({ request, route, compacting }) {
|
|
|
12
12
|
const input = prepareHistory(request.input, route).map((item) => {
|
|
13
13
|
// An assistant message id can reference a reasoning item that was filtered
|
|
14
14
|
// during a model switch or an agent fork. Full content needs neither id.
|
|
15
|
-
|
|
15
|
+
// Unrecognized state has no bridge provenance: forward its complete item.
|
|
16
|
+
if (!isOpaqueStateItem(item))
|
|
17
|
+
delete item.id;
|
|
16
18
|
if (item.type === "bridge_reasoning") {
|
|
17
19
|
if (typeof item.state.data !== "string")
|
|
18
20
|
throw new BridgeError("Invalid Responses reasoning data.");
|
package/dist/carrier.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { BridgeError } from "./errors.js";
|
|
2
1
|
// Versioned, self-contained state stored in the client's encrypted_content field.
|
|
3
2
|
// These envelopes encode source facts and payloads, never replay policy.
|
|
4
3
|
const REASONING_PREFIX = "cxbr1:";
|
|
@@ -43,12 +42,8 @@ export function encodeReasoning(state) {
|
|
|
43
42
|
return encode(REASONING_PREFIX, state);
|
|
44
43
|
}
|
|
45
44
|
export function decodeReasoning(value) {
|
|
46
|
-
if (typeof value !== "string" || !value.startsWith(REASONING_PREFIX))
|
|
47
|
-
return undefined;
|
|
48
45
|
const decoded = decode(value, REASONING_PREFIX);
|
|
49
|
-
|
|
50
|
-
throw new BridgeError("Malformed bridge reasoning state envelope");
|
|
51
|
-
return decoded;
|
|
46
|
+
return statePayload(decoded) ? decoded : undefined;
|
|
52
47
|
}
|
|
53
48
|
export function encodeCompaction(carrier) {
|
|
54
49
|
if (!compactionCarrier(carrier))
|
|
@@ -7,7 +7,7 @@ import { collectHandoff, handoffRequest } from "./compaction.js";
|
|
|
7
7
|
import { splitCompactionPrefix } from "./compaction-history.js";
|
|
8
8
|
import { resolveModel } from "./config.js";
|
|
9
9
|
import { BridgeError } from "./errors.js";
|
|
10
|
-
import { restoresNativeCompaction } from "./history.js";
|
|
10
|
+
import { rejectUnrecognizedState, restoresNativeCompaction } from "./history.js";
|
|
11
11
|
function waitForTurn(previous, signal) {
|
|
12
12
|
signal.throwIfAborted();
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
@@ -101,6 +101,7 @@ export class CompactionHandoffStore {
|
|
|
101
101
|
}
|
|
102
102
|
/** Only checkpoint predecessors enter the export; later work stays on the target. */
|
|
103
103
|
async materialize(context) {
|
|
104
|
+
rejectUnrecognizedState(context.request.input, context.route);
|
|
104
105
|
if (!Array.isArray(context.request.input))
|
|
105
106
|
return context.request;
|
|
106
107
|
let changed = false;
|
package/dist/config.js
CHANGED
|
@@ -103,6 +103,9 @@ export function validateConfig(value) {
|
|
|
103
103
|
if (model.replay !== undefined) {
|
|
104
104
|
if (!object(model.replay))
|
|
105
105
|
fail(`model ${slug}: replay must be an object`);
|
|
106
|
+
if (model.replay.unrecognized !== undefined && !["passthrough", "drop", "error"].includes(model.replay.unrecognized)) {
|
|
107
|
+
fail(`model ${slug}: replay.unrecognized must be passthrough, drop, or error`);
|
|
108
|
+
}
|
|
106
109
|
for (const field of ["reasoning_from", "native_compaction_from"]) {
|
|
107
110
|
if (model.replay[field] !== undefined && !strings(model.replay[field]))
|
|
108
111
|
fail(`model ${slug}: ${field} must list source model aliases`);
|
|
@@ -155,7 +158,10 @@ export function resolveModel(config, slug) {
|
|
|
155
158
|
const allowed = (names = []) => new Set([source.id, ...names.map(name => origin(config, config.models[name]).id)]);
|
|
156
159
|
return {
|
|
157
160
|
slug, config: model, upstream: config.upstreams[model.upstream], profile, origin: source,
|
|
158
|
-
replay: {
|
|
161
|
+
replay: {
|
|
162
|
+
reasoning: allowed(model.replay?.reasoning_from), native_compaction: allowed(model.replay?.native_compaction_from),
|
|
163
|
+
unrecognized: model.replay?.unrecognized ?? "passthrough",
|
|
164
|
+
},
|
|
159
165
|
compaction: {
|
|
160
166
|
mode: model.compaction?.mode ?? (profile.native_compaction_format ? "native" : "summary"),
|
|
161
167
|
native_replay: model.compaction?.native_replay ?? !!profile.native_compaction_format,
|
package/dist/history.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type CompactionCarrier, type StatePayload } from "./carrier.ts";
|
|
2
2
|
import type { ResolvedModel, ResponsesRequest } from "./types.ts";
|
|
3
|
+
/** Raw Responses state items; decoded bridge reasoning uses an internal type. */
|
|
4
|
+
export declare function isOpaqueStateItem(item: Record<string, any>): boolean;
|
|
5
|
+
/** Reject explicit policy violations before even an auxiliary handoff can run. */
|
|
6
|
+
export declare function rejectUnrecognizedState(input: ResponsesRequest["input"], route: ResolvedModel): void;
|
|
3
7
|
/** Replay policy for decoded state. The inspector reports the same decisions. */
|
|
4
8
|
export declare function replaysReasoning(carrier: StatePayload, route: ResolvedModel): boolean;
|
|
5
9
|
export declare function restoresNativeCompaction(carrier: CompactionCarrier, route: ResolvedModel): boolean;
|
package/dist/history.js
CHANGED
|
@@ -2,6 +2,23 @@ import { decodeCompaction, decodeReasoning } from "./carrier.js";
|
|
|
2
2
|
import { BridgeError } from "./errors.js";
|
|
3
3
|
import { SUMMARY_PREFIX } from "./prompts.js";
|
|
4
4
|
import { translateToolHistory } from "./tool-history.js";
|
|
5
|
+
/** Raw Responses state items; decoded bridge reasoning uses an internal type. */
|
|
6
|
+
export function isOpaqueStateItem(item) {
|
|
7
|
+
return ["reasoning", "compaction", "compaction_summary"].includes(item.type);
|
|
8
|
+
}
|
|
9
|
+
/** Reject explicit policy violations before even an auxiliary handoff can run. */
|
|
10
|
+
export function rejectUnrecognizedState(input, route) {
|
|
11
|
+
if (route.replay.unrecognized !== "error" || !Array.isArray(input))
|
|
12
|
+
return;
|
|
13
|
+
for (const item of input) {
|
|
14
|
+
if (!item || !isOpaqueStateItem(item))
|
|
15
|
+
continue;
|
|
16
|
+
const carrier = item.type === "reasoning"
|
|
17
|
+
? decodeReasoning(item.encrypted_content) : decodeCompaction(item.encrypted_content);
|
|
18
|
+
if (!carrier)
|
|
19
|
+
throw new BridgeError(`Unrecognized ${item.type} history item; replay.unrecognized is set to error`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
/** Replay policy for decoded state. The inspector reports the same decisions. */
|
|
6
23
|
export function replaysReasoning(carrier, route) {
|
|
7
24
|
return route.replay.reasoning.has(carrier.source.id) && carrier.format === route.profile.reasoning_format;
|
|
@@ -20,7 +37,12 @@ export function prepareHistory(input, route) {
|
|
|
20
37
|
return [];
|
|
21
38
|
if (!Array.isArray(input))
|
|
22
39
|
throw new BridgeError("input must be a string or an array of items");
|
|
40
|
+
rejectUnrecognizedState(input, route);
|
|
23
41
|
const result = [];
|
|
42
|
+
const unrecognized = (item) => {
|
|
43
|
+
if (route.replay.unrecognized === "passthrough")
|
|
44
|
+
result.push(item);
|
|
45
|
+
};
|
|
24
46
|
for (const original of input) {
|
|
25
47
|
if (!original || typeof original !== "object" || Array.isArray(original)) {
|
|
26
48
|
throw new BridgeError("Each input item must be an object");
|
|
@@ -34,7 +56,9 @@ export function prepareHistory(input, route) {
|
|
|
34
56
|
}
|
|
35
57
|
if (item.type === "reasoning") {
|
|
36
58
|
const carrier = decodeReasoning(item.encrypted_content);
|
|
37
|
-
if (carrier
|
|
59
|
+
if (!carrier)
|
|
60
|
+
unrecognized(item);
|
|
61
|
+
else if (replaysReasoning(carrier, route)) {
|
|
38
62
|
result.push({ type: "bridge_reasoning", state: carrier, summary: item.summary ?? [] });
|
|
39
63
|
}
|
|
40
64
|
continue;
|
|
@@ -63,10 +87,14 @@ export function prepareHistory(input, route) {
|
|
|
63
87
|
if (item.type === "compaction" || item.type === "compaction_summary") {
|
|
64
88
|
const carrier = decodeCompaction(item.encrypted_content);
|
|
65
89
|
if (!carrier) {
|
|
66
|
-
|
|
90
|
+
unrecognized(item);
|
|
91
|
+
continue;
|
|
67
92
|
}
|
|
68
93
|
if (restoresNativeCompaction(carrier, route)) {
|
|
69
|
-
|
|
94
|
+
const native = carrier.native.data;
|
|
95
|
+
if (route.profile.api === "responses")
|
|
96
|
+
delete native.id;
|
|
97
|
+
result.push(native);
|
|
70
98
|
}
|
|
71
99
|
else {
|
|
72
100
|
if (!carrier.summary)
|
package/dist/inspect/view.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface Annotation {
|
|
|
11
11
|
pointer: string;
|
|
12
12
|
event?: number;
|
|
13
13
|
kind: "reasoning" | "compaction";
|
|
14
|
-
decision: "replay" | "drop" | "native" | "summary" | "handoff" | "reject" | "output";
|
|
14
|
+
decision: "replay" | "passthrough" | "drop" | "native" | "summary" | "handoff" | "reject" | "output";
|
|
15
15
|
reason: string;
|
|
16
16
|
source: {
|
|
17
17
|
aliases: string[];
|
|
@@ -80,6 +80,7 @@ export declare function detail(exchange: Exchange): {
|
|
|
80
80
|
replay: {
|
|
81
81
|
reasoning_from: string[];
|
|
82
82
|
native_compaction_from: string[];
|
|
83
|
+
unrecognized: import("../types.ts").UnrecognizedStatePolicy;
|
|
83
84
|
};
|
|
84
85
|
responses_lite: boolean;
|
|
85
86
|
} | null;
|
package/dist/inspect/view.js
CHANGED
|
@@ -196,7 +196,10 @@ function routeView(inspector, route, responsesLite) {
|
|
|
196
196
|
native_compaction_format: route.profile.native_compaction_format ?? null,
|
|
197
197
|
compaction_mode: route.compaction.mode,
|
|
198
198
|
native_replay: route.compaction.native_replay,
|
|
199
|
-
replay: {
|
|
199
|
+
replay: {
|
|
200
|
+
reasoning_from: names(route.replay.reasoning), native_compaction_from: names(route.replay.native_compaction),
|
|
201
|
+
unrecognized: route.replay.unrecognized,
|
|
202
|
+
},
|
|
200
203
|
responses_lite: responsesLite ?? false,
|
|
201
204
|
};
|
|
202
205
|
}
|
|
@@ -217,15 +220,9 @@ function messageView(message) {
|
|
|
217
220
|
}
|
|
218
221
|
function reasoningInput(inspector, route, item, pointer) {
|
|
219
222
|
const base = { target: "client.request", pointer, kind: "reasoning" };
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
carrier = decodeReasoning(item.encrypted_content);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return { ...base, decision: "reject", reason: "malformed", source: null, format: null, decoded: null };
|
|
226
|
-
}
|
|
223
|
+
const carrier = decodeReasoning(item.encrypted_content);
|
|
227
224
|
if (!carrier)
|
|
228
|
-
return
|
|
225
|
+
return unrecognizedInput(base, route);
|
|
229
226
|
const replay = replaysReasoning(carrier, route);
|
|
230
227
|
const reason = replay
|
|
231
228
|
? carrier.source.id === route.origin.id ? "same_source" : "allowed_source"
|
|
@@ -236,7 +233,7 @@ function compactionInput(inspector, route, item, pointer) {
|
|
|
236
233
|
const base = { target: "client.request", pointer, kind: "compaction" };
|
|
237
234
|
const carrier = decodeCompaction(item.encrypted_content);
|
|
238
235
|
if (!carrier)
|
|
239
|
-
return
|
|
236
|
+
return unrecognizedInput(base, route);
|
|
240
237
|
const native = carrier.native;
|
|
241
238
|
const restored = restoresNativeCompaction(carrier, route);
|
|
242
239
|
const reason = restored ? "native_restored"
|
|
@@ -248,15 +245,15 @@ function compactionInput(inspector, route, item, pointer) {
|
|
|
248
245
|
source: native ? source(inspector, native.source) : null, format: native?.format ?? null, decoded: carrier,
|
|
249
246
|
};
|
|
250
247
|
}
|
|
248
|
+
function unrecognizedInput(base, route) {
|
|
249
|
+
return {
|
|
250
|
+
...base, decision: route.replay.unrecognized === "error" ? "reject" : route.replay.unrecognized,
|
|
251
|
+
reason: "unrecognized_state", source: null, format: null, decoded: null,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
251
254
|
function outputNote(inspector, item, target) {
|
|
252
255
|
if (item?.type === "reasoning") {
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
carrier = decodeReasoning(item.encrypted_content);
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return undefined;
|
|
259
|
-
}
|
|
256
|
+
const carrier = decodeReasoning(item.encrypted_content);
|
|
260
257
|
if (!carrier)
|
|
261
258
|
return undefined;
|
|
262
259
|
return { ...target, kind: "reasoning", decision: "output", reason: "output", source: source(inspector, carrier.source), format: carrier.format, decoded: carrier };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 type UnrecognizedStatePolicy = "passthrough" | "drop" | "error";
|
|
4
5
|
export interface ClaudeMessagesOptions {
|
|
5
6
|
tool_updates: "inline" | "top_level";
|
|
6
7
|
thinking_mismatch: "drop_block" | "error" | "upstream_default";
|
|
@@ -36,6 +37,7 @@ export interface ModelConfig {
|
|
|
36
37
|
replay?: {
|
|
37
38
|
reasoning_from?: string[];
|
|
38
39
|
native_compaction_from?: string[];
|
|
40
|
+
unrecognized?: UnrecognizedStatePolicy;
|
|
39
41
|
};
|
|
40
42
|
compaction?: {
|
|
41
43
|
mode?: "summary" | "native";
|
|
@@ -87,6 +89,7 @@ export interface ResolvedModel {
|
|
|
87
89
|
replay: {
|
|
88
90
|
reasoning: ReadonlySet<string>;
|
|
89
91
|
native_compaction: ReadonlySet<string>;
|
|
92
|
+
unrecognized: UnrecognizedStatePolicy;
|
|
90
93
|
};
|
|
91
94
|
compaction: {
|
|
92
95
|
mode: "summary" | "native";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
随后浅层合并上游和模型 `options`,后者覆盖前者,也可覆盖推导出的普通参数。最后由 bridge 固定 `model`、`input`、`tools`、`store: false`、`stream: true`,`include` 至少包含 `reasoning.encrypted_content`。上游状态由完整历史承载,不依赖服务器保存的会话。
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
bridge 能识别的历史 reasoning 按允许来源选择,将 `responses.reasoning.v1` 的 data 还原为原生密文;原生压缩按独立策略选择。未识别状态由 `replay.unrecognized` 决定处理方式,默认保留整项,包括其 `id`。其余输入项移除顶层 `id`,工具配对的 `call_id` 保留,避免引用已过滤的 reasoning。图片、普通消息、工具结果和明文 agent message 保留原格式。[状态规则](reasoning.md)
|
|
14
14
|
|
|
15
15
|
## 流与工具交付
|
|
16
16
|
|
package/docs/compaction.md
CHANGED
|
@@ -163,7 +163,7 @@ cxbc1:<base64url(JSON)>
|
|
|
163
163
|
|
|
164
164
|
`summary` 检查点只保存 `summary` 文字字段。原生检查点的 `handoff` 包含 `prefix`、上述请求 `parameters` 和 `responsesLite` 标记,足以在后续请求提供的 `U` 上生成文字。原生 data 保留完整上游 item;来源指纹计算见 [reasoning](reasoning.md)。
|
|
165
165
|
|
|
166
|
-
每次普通请求或再次压缩前,bridge
|
|
166
|
+
每次普通请求或再次压缩前,bridge 对能识别的检查点按实际执行目标选表示;普通请求是工作模型,压缩请求是选定的压缩执行模型:
|
|
167
167
|
|
|
168
168
|
1. 有原生状态、开启 `native_replay`、来源允许、格式匹配:还原原生 item。
|
|
169
169
|
2. 其他情况已有文字摘要:将恢复前缀和文字摘要作为 user message。
|
|
@@ -171,7 +171,9 @@ cxbc1:<base64url(JSON)>
|
|
|
171
171
|
|
|
172
172
|
这是发送前的表示选择;第一种请求被上游拒绝时直接返回错误,不自动再试文字表示。生成复述必须读取源自身的 `K`,不受源路由普通续聊的 `native_replay: false` 限制。
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
恢复不改写客户端保存的载荷。按需复述只更新本次请求的副本,并通过本地缓存复用;切换模型再切回来,仍可使用原检查点。再次压缩后,新检查点取代旧检查点,不累积每个模型的检查点版本。
|
|
175
|
+
|
|
176
|
+
无法解码为 bridge carrier 的 `compaction` / `compaction_summary` 默认原样透传,不虚构来源、摘要或复述配方,也不触发 handoff。可用模型级 `replay.unrecognized` 改为过滤或报错,详见[未识别的历史状态](config.md#未识别的历史状态)。透传是否可用由上游裁决;过滤检查点会让本次请求失去其中承载的历史上下文。
|
|
175
177
|
|
|
176
178
|
恢复前缀逐字取自 Codex `rust-v0.162.1` 的 `codex-rs/prompts/templates/compact/summary_prefix.md`;handoff prompt 的许可归属见 `NOTICE`。外层是编码而非加密:文字摘要、请求前缀和参数可读,原生密文保持原样。
|
|
177
179
|
|
package/docs/config.md
CHANGED
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
| `max_output_tokens` | 覆盖目录的默认回答输出预算;请求中的显式值优先于模型配置 |
|
|
84
84
|
| `input_modalities` | 可选,覆盖条目中的 `text`/`image` 能力声明 |
|
|
85
85
|
| `reasoning` | 默认 effort 及其到原生值的映射 |
|
|
86
|
-
| `replay` |
|
|
86
|
+
| `replay` | reasoning、原生压缩的允许来源,以及未识别历史状态的处理方式 |
|
|
87
87
|
| `compaction` | 压缩方式、执行模型,以及本目标是否接收原生压缩 |
|
|
88
88
|
| `options` | 原生请求参数,覆盖上游级 options |
|
|
89
89
|
| `adapters` | 当前模型的桥接行为;逐字段覆盖上游级同名 adapter 设置 |
|
|
@@ -224,12 +224,32 @@ Claude 的 `native_replay` 仍默认为 `false`:委托生成不会让 Claude
|
|
|
224
224
|
}
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
bridge 能识别的自身状态默认允许恢复;两份列表增加其他允许来源,关系有方向且不传递。若也要接受 A 的 reasoning,把 A 加入 B 的 `reasoning_from`。这个例子的 B 自己只生成文字摘要,但仍可读取 A 的原生检查点:生成和恢复是独立能力。设置 `native_replay: false` 则已识别的压缩都使用文字表示,包括自身生成的检查点。
|
|
228
228
|
|
|
229
229
|
引用按连接、实际模型和 adapter 解析为来源身份,不绑定显示别名。加载配置时检查来源是否存在、源与目标的格式标识是否一致;每条请求恢复时再检查实际载荷。bridge 不探测密文是否被上游接受。上游拒绝被允许的状态时直接报错,不删掉状态再试。详见 [reasoning](reasoning.md) 和[压缩](compaction.md)。
|
|
230
230
|
|
|
231
231
|
连接身份包含 upstream ID 和去掉末尾斜杠的 URL,来源身份再包含 adapter 与实际模型。密钥、header 和 options 不参与身份。如果同一 URL 通过 header 或凭据切换到不兼容区域,使用不同 upstream ID;改显示别名不影响已保存状态。
|
|
232
232
|
|
|
233
|
+
### 未识别的历史状态
|
|
234
|
+
|
|
235
|
+
历史会话可能带有其他工具或旧版本保存的状态。模型级 `replay.unrecognized` 统一处理 `encrypted_content` 无法解码为相应 bridge carrier 的 `reasoning`、`compaction` 和 `compaction_summary` 项,包括未知前缀、缺少载荷或无法解析的封装:
|
|
236
|
+
|
|
237
|
+
| 值 | 行为 |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `passthrough`(默认) | 保留整项及原始字段,交给上游解释 |
|
|
240
|
+
| `drop` | 仅从本次出站历史中过滤这些项 |
|
|
241
|
+
| `error` | 在发送上游请求前报错 |
|
|
242
|
+
|
|
243
|
+
例如,在需要明确拒绝此类历史的模型中配置:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{ "replay": { "unrecognized": "error" } }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
GPT 将透传项保留在 Responses `input` 中,包括其 `id`;Claude/DeepSeek 将原项放入 assistant 消息的 content 数组,不把未知密文猜解为 thinking 或文字。这保留了原始数据,但不保证跨协议或跨模型兼容,上游可以拒绝;bridge 不自动重试或改用其他策略。
|
|
250
|
+
|
|
251
|
+
这些项没有可确认的来源与复述配方,因此不套用来源允许列表、不创建来源身份,也不触发按需复述。能解码的 bridge carrier 仍遵循原有来源、格式和压缩表示规则。三个选项都不改写客户端已保存的历史;`drop` 会减少本次请求的 reasoning 上下文,丢弃压缩检查点还可能失去早期对话的唯一表示。普通请求取工作模型的策略,委托压缩取实际执行模型的策略。
|
|
252
|
+
|
|
233
253
|
## 客户端目录
|
|
234
254
|
|
|
235
255
|
bridge 的 `GET /v1/models` 返回 Codex 专用的 `{"models": [...]}`,每项包含完整提示词、窗口、工具、多代理和 reasoning 元数据。它与经典 OpenAI 的 `{"object":"list","data":[...]}` 格式不同。仅列出配置中的别名,不暴露上游连接、实际部署名或凭据设置。
|
package/docs/design.md
CHANGED
|
@@ -52,7 +52,9 @@ Reasoning 载荷记录来源、格式和原始数据;压缩载荷保存文字
|
|
|
52
52
|
|
|
53
53
|
来源包括连接指纹、adapter 和实际模型,独立于显示别名和复用配置。连接凭据和 HTTP header 不写进载荷;复述配方保存源请求前缀及除输入、流式开关和文字输出约束外的请求参数,包括未知参数。外层编码不提供加密:文字 reasoning、签名、压缩摘要及请求前缀按普通聊天历史保护,按需复述缓存以 `0600` 文件权限保存。
|
|
54
54
|
|
|
55
|
-
每次请求按当前配置选取历史,不改写已保存载荷。
|
|
55
|
+
每次请求按当前配置选取历史,不改写已保存载荷。bridge 能识别的 reasoning 和原生压缩有独立的允许来源列表,默认只接受自身,关系有方向且不传递。修改配置即可改变已有历史的恢复方式;格式版本负责解释数据,不编码“永远隔离”这类策略。
|
|
56
|
+
|
|
57
|
+
未识别状态默认透传,因为历史会话可能来自其他客户端或版本,bridge 无法解码不等于上游不能使用。模型级 `replay.unrecognized` 也可显式选择过滤或报错;不为这类状态猜测来源或生成 handoff。它与已识别载荷的来源策略分开,保留未来协议扩展与用户选择。[配置与协议边界](config.md#未识别的历史状态)
|
|
56
58
|
|
|
57
59
|
明文 reasoning 使用可共享的文本格式;不同 adapter 将来可把同一格式恢复到各自字段。密文与签名保留各自原生结构,不把格式可解码等同于上游允许使用。
|
|
58
60
|
|
package/docs/inspect.md
CHANGED
|
@@ -107,20 +107,18 @@ codeks-bridge serve --config /absolute/path/bridge.json --capture
|
|
|
107
107
|
| reasoning | `replay` | `allowed_source` | 来自配置允许的其他目标,本次回放 |
|
|
108
108
|
| reasoning | `drop` | `source_not_allowed` | 来源不在当前目标的允许列表,本次请求中过滤 |
|
|
109
109
|
| reasoning | `drop` | `format_mismatch` | 来源允许,但载荷格式与当前 adapter 不同 |
|
|
110
|
-
| reasoning | `drop` | `
|
|
111
|
-
| reasoning | `reject` | `malformed` | 有 bridge 前缀但无法解码,bridge 会拒绝请求 |
|
|
110
|
+
| reasoning / compaction | `passthrough` / `drop` / `reject` | `unrecognized_state` | 无法解码为 bridge carrier,按 `replay.unrecognized` 透传、过滤或拒绝 |
|
|
112
111
|
| compaction | `native` | `native_restored` | 恢复原生加密检查点 |
|
|
113
112
|
| compaction | `summary` | `no_native` | 检查点只有文字摘要 |
|
|
114
113
|
| compaction | `summary` / `handoff` | `native_replay_disabled` | 当前目标关闭了原生恢复 |
|
|
115
114
|
| compaction | `summary` / `handoff` | `source_not_allowed` | 原生检查点来源不在允许列表 |
|
|
116
115
|
| compaction | `summary` / `handoff` | `format_mismatch` | 原生检查点格式与当前 adapter 不同 |
|
|
117
|
-
| compaction | `reject` | `not_bridge_state` | 不是 bridge 生成的压缩项,bridge 会拒绝请求 |
|
|
118
116
|
|
|
119
117
|
`summary` 表示直接使用载荷中已有的文字,`handoff` 表示先取得文字交接:可以复用本地缓存,也可以向原来源生成。只有实际发起辅助请求时,上游调用列表才出现 `handoff`;这条状态标注本身不表示发生了额外 HTTP 请求。
|
|
120
118
|
|
|
121
119
|
响应侧标注 bridge 新生成的 reasoning 与压缩项(决定为 `output`)。流式响应指向 `response.output_item.done` 事件里的 `/item`,非流式响应指向 `/output/k`。只有文字摘要的压缩项没有原生部分,来源和格式为空;`native` 检查点的来源和格式取原生部分。
|
|
122
120
|
|
|
123
|
-
|
|
121
|
+
已识别载荷的标注附来源别名、部署名、adapter、来源指纹、载荷格式和解码后的 carrier;未识别状态不补造这些信息。`compaction_summary` 与 `compaction` 使用相同的压缩状态判断。`cxbr1:` 与 `cxbc1:` 只是编码,解码后可以看到文字 reasoning、压缩摘要和原生载荷;GPT 原生 `encrypted_content` 本身仍是密文,只能看到原样字符串。
|
|
124
122
|
|
|
125
123
|
## Claude 服务端输入处理
|
|
126
124
|
|
package/docs/reasoning.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reasoning 与状态复用
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
bridge 能识别的 reasoning 默认只恢复给产生它的目标,配置可以允许其他来源。是否允许复用和如何解码是两件事:来源策略由 `replay.reasoning_from` 决定,载荷的 `format` 告诉 adapter 如何还原内容。无法识别的历史状态默认原样透传,详见[未识别的历史状态](config.md#未识别的历史状态)。
|
|
4
4
|
|
|
5
5
|
## 显示与推理分开
|
|
6
6
|
|
|
@@ -61,7 +61,7 @@ B 可以接收自身及 A 的 reasoning;A 不会因此接收 B。A 接受 C
|
|
|
61
61
|
|
|
62
62
|
加载配置检查引用存在且源与目标的格式标识一致。两个 GPT 模型的 reasoning、同一 DeepSeek 模型的不同部署均可配置复用;每次请求恢复实际载荷,上游是否真正接受,交给实际请求验证。失败按上游错误返回,不删掉 reasoning 再发送。
|
|
63
63
|
|
|
64
|
-
`prepareHistory` 每次复制输入,恢复来源允许且格式匹配的 carrier
|
|
64
|
+
`prepareHistory` 每次复制输入,恢复来源允许且格式匹配的 carrier,其他已识别 carrier 只在本次请求中过滤。普通消息、工具调用、结果和图片继续保留。无法解码为 bridge carrier 的 reasoning 由 `replay.unrecognized` 决定透传、过滤或报错,不猜测来源,也不归属到当前目标。
|
|
65
65
|
|
|
66
66
|
例如默认 A → B → A 时,B 不接收 A 的 reasoning,切回 A 后仍可恢复。若后来配置 B 接受 A,无需重写历史,后续请求即可使用其中 A 的载荷。被新的压缩检查点取代的旧历史则不再可恢复。
|
|
67
67
|
|
|
@@ -69,6 +69,6 @@ B 可以接收自身及 A 的 reasoning;A 不会因此接收 B。A 接受 C
|
|
|
69
69
|
|
|
70
70
|
codeks 的明文 `agent_message` 在 GPT Responses 中保持原格式;Claude/DeepSeek 将其转成 user 消息,在内容开头标明 author、recipient,后面保留文本和图片。它不是 reasoning,不受 reasoning 来源列表控制。
|
|
71
71
|
|
|
72
|
-
GPT
|
|
72
|
+
GPT 出站请求使用 `store: false`,除原样透传的未识别状态项外,移除 item 顶层 `id`,保留工具配对的 `call_id`。这样普通消息不会继续通过服务器 item ID 引用已过滤的 reasoning;未识别状态的原始字段由上游解释。请求必须包含完整历史,不能只带 `item_reference` 或 `previous_response_id`。
|
|
73
73
|
|
|
74
74
|
压缩另用 `cxbc1:`,有独立的允许来源,保存文字摘要或按需复述配方。详见[压缩](compaction.md)。
|
package/package.json
CHANGED
package/ui/labels.js
CHANGED
|
@@ -14,10 +14,10 @@ export const KIND_LABEL = { turn: '轮次', compaction: '压缩', invalid: '无
|
|
|
14
14
|
export const OUTCOME_LABEL = { active: '进行中', completed: '完成', incomplete: '未完成', failed: '失败', error: '错误', aborted: '中止' };
|
|
15
15
|
export const END_LABEL = { active: '传输中', complete: '完整', cancelled: '已取消', error: '出错', unread: '终止事件后停止读取' };
|
|
16
16
|
|
|
17
|
-
const DECISION = { replay: '回放', drop: '丢弃', native: '恢复原生', summary: '文字摘要', handoff: '按需文字交接', reject: '拒绝', output: '输出' };
|
|
18
|
-
const DECISION_CLASS = { replay: 'ok', native: 'ok', drop: 'warn', summary: 'warn', handoff: 'warn', reject: 'bad', output: 'info' };
|
|
17
|
+
const DECISION = { replay: '回放', passthrough: '原样透传', drop: '丢弃', native: '恢复原生', summary: '文字摘要', handoff: '按需文字交接', reject: '拒绝', output: '输出' };
|
|
18
|
+
const DECISION_CLASS = { replay: 'ok', passthrough: 'info', native: 'ok', drop: 'warn', summary: 'warn', handoff: 'warn', reject: 'bad', output: 'info' };
|
|
19
19
|
// Verb shown before the reason text, per decision.
|
|
20
|
-
const VERB = { replay: '回放', drop: '丢弃', reject: '拒绝', summary: '改用文字摘要', handoff: '获取文字交接', native: '恢复原生压缩' };
|
|
20
|
+
const VERB = { replay: '回放', passthrough: '原样透传', drop: '丢弃', reject: '拒绝', summary: '改用文字摘要', handoff: '获取文字交接', native: '恢复原生压缩' };
|
|
21
21
|
|
|
22
22
|
const REASON = {
|
|
23
23
|
reasoning: {
|
|
@@ -25,8 +25,7 @@ const REASON = {
|
|
|
25
25
|
allowed_source: '来源在 replay 白名单内',
|
|
26
26
|
source_not_allowed: '来源未在 replay 白名单',
|
|
27
27
|
format_mismatch: '载荷格式与目标不匹配',
|
|
28
|
-
|
|
29
|
-
malformed: 'bridge 载荷损坏,无法解码',
|
|
28
|
+
unrecognized_state: '无法识别为 bridge reasoning 载荷,按 replay.unrecognized 处理',
|
|
30
29
|
},
|
|
31
30
|
compaction: {
|
|
32
31
|
native_restored: '',
|
|
@@ -34,8 +33,7 @@ const REASON = {
|
|
|
34
33
|
native_replay_disabled: '目标关闭了 native_replay',
|
|
35
34
|
source_not_allowed: '来源未在原生压缩白名单',
|
|
36
35
|
format_mismatch: '原生格式与目标不匹配',
|
|
37
|
-
|
|
38
|
-
malformed: 'bridge 检查点损坏,无法解码',
|
|
36
|
+
unrecognized_state: '无法识别为 bridge 压缩检查点,按 replay.unrecognized 处理',
|
|
39
37
|
},
|
|
40
38
|
};
|
|
41
39
|
|