@wacry/codeks-bridge 0.0.0-stage → 0.0.1
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/LICENSE +202 -0
- package/NOTICE +13 -0
- package/README.md +40 -2
- package/catalog/README.md +63 -0
- package/catalog/claude-opus-5-5.json +194 -0
- package/catalog/claude-sonnet-5-5.json +194 -0
- package/catalog/deepseek-flash.json +203 -0
- package/catalog/gpt-5.6-sol.json +152 -0
- package/catalog/gpt-6-astra.json +184 -0
- package/catalog/gpt-6-luna.json +178 -0
- package/catalog/gpt-6-sol.json +182 -0
- package/catalog/gpt-6.1-sol.json +187 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +42 -0
- package/dist/backends/anthropic.d.ts +8 -0
- package/dist/backends/anthropic.js +309 -0
- package/dist/backends/chat.d.ts +16 -0
- package/dist/backends/chat.js +284 -0
- package/dist/backends/deepseek.d.ts +6 -0
- package/dist/backends/deepseek.js +37 -0
- package/dist/backends/responses.d.ts +5 -0
- package/dist/backends/responses.js +205 -0
- package/dist/carrier.d.ts +22 -0
- package/dist/carrier.js +61 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.js +40 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/compaction-handoff.d.ts +13 -0
- package/dist/compaction-handoff.js +126 -0
- package/dist/compaction-history.d.ts +7 -0
- package/dist/compaction-history.js +20 -0
- package/dist/compaction.d.ts +20 -0
- package/dist/compaction.js +134 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +167 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +141 -0
- package/dist/headers.d.ts +6 -0
- package/dist/headers.js +39 -0
- package/dist/history.d.ts +7 -0
- package/dist/history.js +85 -0
- package/dist/ids.d.ts +1 -0
- package/dist/ids.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/inspect/capture.d.ts +119 -0
- package/dist/inspect/capture.js +370 -0
- package/dist/inspect/http.d.ts +7 -0
- package/dist/inspect/http.js +149 -0
- package/dist/inspect/view.d.ts +239 -0
- package/dist/inspect/view.js +388 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +16 -0
- package/dist/profiles.d.ts +6 -0
- package/dist/profiles.js +63 -0
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +118 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +232 -0
- package/dist/sse.d.ts +12 -0
- package/dist/sse.js +93 -0
- package/dist/tool-history.d.ts +4 -0
- package/dist/tool-history.js +72 -0
- package/dist/tools.d.ts +33 -0
- package/dist/tools.js +182 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.js +1 -0
- package/dist/upstream.d.ts +3 -0
- package/dist/upstream.js +84 -0
- package/dist/writer.d.ts +95 -0
- package/dist/writer.js +430 -0
- package/docs/backends-anthropic.md +61 -0
- package/docs/backends-deepseek-chat.md +75 -0
- package/docs/backends-responses.md +33 -0
- package/docs/compaction.md +176 -0
- package/docs/config.md +231 -0
- package/docs/design.md +93 -0
- package/docs/inspect.md +138 -0
- package/docs/reasoning.md +72 -0
- package/docs/releasing.md +50 -0
- package/docs/responses-stream.md +90 -0
- package/docs/testing.md +177 -0
- package/docs/tools.md +55 -0
- package/examples/config.json +35 -0
- package/package.json +46 -4
- package/ui/api.js +64 -0
- package/ui/app.css +375 -0
- package/ui/app.js +489 -0
- package/ui/detail.js +682 -0
- package/ui/diff.js +281 -0
- package/ui/dom.js +258 -0
- package/ui/events.js +351 -0
- package/ui/favicon.svg +1 -0
- package/ui/index.html +58 -0
- package/ui/labels.js +101 -0
- package/ui/list.js +180 -0
- package/ui/structure.js +285 -0
- package/ui/tree.js +158 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { ServerResponse } from "node:http";
|
|
2
|
+
import type { Exchange, Inspector, Part } from "./capture.ts";
|
|
3
|
+
export interface SseRecord {
|
|
4
|
+
t: number;
|
|
5
|
+
event: string | null;
|
|
6
|
+
data: string;
|
|
7
|
+
}
|
|
8
|
+
type Outcome = "active" | "completed" | "incomplete" | "failed" | "error" | "aborted";
|
|
9
|
+
export interface Annotation {
|
|
10
|
+
target: "client.request" | "client.response";
|
|
11
|
+
pointer: string;
|
|
12
|
+
event?: number;
|
|
13
|
+
kind: "reasoning" | "compaction";
|
|
14
|
+
decision: "replay" | "drop" | "native" | "summary" | "handoff" | "reject" | "output";
|
|
15
|
+
reason: string;
|
|
16
|
+
source: {
|
|
17
|
+
aliases: string[];
|
|
18
|
+
model: string;
|
|
19
|
+
adapter: string;
|
|
20
|
+
origin_id: string;
|
|
21
|
+
} | null;
|
|
22
|
+
format: string | null;
|
|
23
|
+
decoded: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Splits timed text into SSE events. An event takes the time of the part that completed it. */
|
|
26
|
+
export declare function sseEvents(parts: readonly Part[]): SseRecord[];
|
|
27
|
+
export declare function summary(exchange: Exchange): {
|
|
28
|
+
id: string;
|
|
29
|
+
seq: number;
|
|
30
|
+
rev: number;
|
|
31
|
+
started_at: number;
|
|
32
|
+
duration_ms: number | null;
|
|
33
|
+
active: boolean;
|
|
34
|
+
model: string | null;
|
|
35
|
+
adapter: import("../types.ts").AdapterId | null;
|
|
36
|
+
api: import("../types.ts").Api | null;
|
|
37
|
+
upstream: string | null;
|
|
38
|
+
upstream_model: string | null;
|
|
39
|
+
kind: "compaction" | "turn" | "invalid";
|
|
40
|
+
stream: boolean | null;
|
|
41
|
+
status: number | null;
|
|
42
|
+
outcome: Outcome;
|
|
43
|
+
error: string | null;
|
|
44
|
+
usage: unknown;
|
|
45
|
+
thread: {
|
|
46
|
+
session_id: string | null;
|
|
47
|
+
thread_id: string | null;
|
|
48
|
+
parent_thread_id: string | null;
|
|
49
|
+
subagent: string | null;
|
|
50
|
+
window_id: string | null;
|
|
51
|
+
};
|
|
52
|
+
input_items: number | null;
|
|
53
|
+
preview: string | null;
|
|
54
|
+
upstream_calls: {
|
|
55
|
+
label: string;
|
|
56
|
+
status: number | null;
|
|
57
|
+
end: import("./capture.ts").EndKind;
|
|
58
|
+
}[];
|
|
59
|
+
bytes: {
|
|
60
|
+
request: number;
|
|
61
|
+
response: number;
|
|
62
|
+
upstream_request: number;
|
|
63
|
+
upstream_response: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export declare function detail(exchange: Exchange): {
|
|
67
|
+
route: {
|
|
68
|
+
alias: string;
|
|
69
|
+
catalog: string;
|
|
70
|
+
adapter: import("../types.ts").AdapterId;
|
|
71
|
+
api: import("../types.ts").Api;
|
|
72
|
+
upstream: string;
|
|
73
|
+
base_url: string;
|
|
74
|
+
upstream_model: string;
|
|
75
|
+
origin_id: string;
|
|
76
|
+
reasoning_format: string;
|
|
77
|
+
native_compaction_format: string | null;
|
|
78
|
+
compaction_mode: "native" | "summary";
|
|
79
|
+
native_replay: boolean;
|
|
80
|
+
replay: {
|
|
81
|
+
reasoning_from: string[];
|
|
82
|
+
native_compaction_from: string[];
|
|
83
|
+
};
|
|
84
|
+
responses_lite: boolean;
|
|
85
|
+
} | null;
|
|
86
|
+
client: {
|
|
87
|
+
request: {
|
|
88
|
+
method: string;
|
|
89
|
+
path: string;
|
|
90
|
+
headers: import("./capture.ts").HeaderMap;
|
|
91
|
+
body: {
|
|
92
|
+
text: string;
|
|
93
|
+
bytes: number;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
response: {
|
|
97
|
+
status: number | null;
|
|
98
|
+
status_text: string | null;
|
|
99
|
+
headers: import("./capture.ts").HeaderMap;
|
|
100
|
+
headers_ms: number | null;
|
|
101
|
+
ended_ms: number | null;
|
|
102
|
+
end: import("./capture.ts").EndKind;
|
|
103
|
+
error: string | null;
|
|
104
|
+
body: {
|
|
105
|
+
text: string;
|
|
106
|
+
bytes: number;
|
|
107
|
+
};
|
|
108
|
+
events: SseRecord[] | null;
|
|
109
|
+
} | null;
|
|
110
|
+
};
|
|
111
|
+
upstream: {
|
|
112
|
+
index: number;
|
|
113
|
+
label: string;
|
|
114
|
+
started_ms: number;
|
|
115
|
+
method: string;
|
|
116
|
+
url: string;
|
|
117
|
+
headers: import("./capture.ts").HeaderMap;
|
|
118
|
+
body: {
|
|
119
|
+
text: string;
|
|
120
|
+
bytes: number;
|
|
121
|
+
};
|
|
122
|
+
error: string | null;
|
|
123
|
+
response: {
|
|
124
|
+
status: number | null;
|
|
125
|
+
status_text: string | null;
|
|
126
|
+
headers: import("./capture.ts").HeaderMap;
|
|
127
|
+
headers_ms: number | null;
|
|
128
|
+
ended_ms: number | null;
|
|
129
|
+
end: import("./capture.ts").EndKind;
|
|
130
|
+
error: string | null;
|
|
131
|
+
body: {
|
|
132
|
+
text: string;
|
|
133
|
+
bytes: number;
|
|
134
|
+
};
|
|
135
|
+
events: SseRecord[] | null;
|
|
136
|
+
} | null;
|
|
137
|
+
}[];
|
|
138
|
+
annotations: Annotation[];
|
|
139
|
+
id: string;
|
|
140
|
+
seq: number;
|
|
141
|
+
rev: number;
|
|
142
|
+
started_at: number;
|
|
143
|
+
duration_ms: number | null;
|
|
144
|
+
active: boolean;
|
|
145
|
+
model: string | null;
|
|
146
|
+
adapter: import("../types.ts").AdapterId | null;
|
|
147
|
+
api: import("../types.ts").Api | null;
|
|
148
|
+
upstream_model: string | null;
|
|
149
|
+
kind: "compaction" | "turn" | "invalid";
|
|
150
|
+
stream: boolean | null;
|
|
151
|
+
status: number | null;
|
|
152
|
+
outcome: Outcome;
|
|
153
|
+
error: string | null;
|
|
154
|
+
usage: unknown;
|
|
155
|
+
thread: {
|
|
156
|
+
session_id: string | null;
|
|
157
|
+
thread_id: string | null;
|
|
158
|
+
parent_thread_id: string | null;
|
|
159
|
+
subagent: string | null;
|
|
160
|
+
window_id: string | null;
|
|
161
|
+
};
|
|
162
|
+
input_items: number | null;
|
|
163
|
+
preview: string | null;
|
|
164
|
+
upstream_calls: {
|
|
165
|
+
label: string;
|
|
166
|
+
status: number | null;
|
|
167
|
+
end: import("./capture.ts").EndKind;
|
|
168
|
+
}[];
|
|
169
|
+
bytes: {
|
|
170
|
+
request: number;
|
|
171
|
+
response: number;
|
|
172
|
+
upstream_request: number;
|
|
173
|
+
upstream_response: number;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
export declare function stateView(inspector: Inspector): {
|
|
177
|
+
capturing: boolean;
|
|
178
|
+
exchanges: number;
|
|
179
|
+
active: number;
|
|
180
|
+
bytes: number;
|
|
181
|
+
max_bytes: number;
|
|
182
|
+
evicted: number;
|
|
183
|
+
rev: number;
|
|
184
|
+
started_at: number;
|
|
185
|
+
models: {
|
|
186
|
+
alias: string;
|
|
187
|
+
catalog: string;
|
|
188
|
+
adapter: import("../types.ts").AdapterId;
|
|
189
|
+
api: import("../types.ts").Api;
|
|
190
|
+
upstream: string;
|
|
191
|
+
upstream_model: string;
|
|
192
|
+
}[];
|
|
193
|
+
};
|
|
194
|
+
export declare function listView(inspector: Inspector, since: number): {
|
|
195
|
+
rev: number;
|
|
196
|
+
ids: string[];
|
|
197
|
+
exchanges: {
|
|
198
|
+
id: string;
|
|
199
|
+
seq: number;
|
|
200
|
+
rev: number;
|
|
201
|
+
started_at: number;
|
|
202
|
+
duration_ms: number | null;
|
|
203
|
+
active: boolean;
|
|
204
|
+
model: string | null;
|
|
205
|
+
adapter: import("../types.ts").AdapterId | null;
|
|
206
|
+
api: import("../types.ts").Api | null;
|
|
207
|
+
upstream: string | null;
|
|
208
|
+
upstream_model: string | null;
|
|
209
|
+
kind: "compaction" | "turn" | "invalid";
|
|
210
|
+
stream: boolean | null;
|
|
211
|
+
status: number | null;
|
|
212
|
+
outcome: Outcome;
|
|
213
|
+
error: string | null;
|
|
214
|
+
usage: unknown;
|
|
215
|
+
thread: {
|
|
216
|
+
session_id: string | null;
|
|
217
|
+
thread_id: string | null;
|
|
218
|
+
parent_thread_id: string | null;
|
|
219
|
+
subagent: string | null;
|
|
220
|
+
window_id: string | null;
|
|
221
|
+
};
|
|
222
|
+
input_items: number | null;
|
|
223
|
+
preview: string | null;
|
|
224
|
+
upstream_calls: {
|
|
225
|
+
label: string;
|
|
226
|
+
status: number | null;
|
|
227
|
+
end: import("./capture.ts").EndKind;
|
|
228
|
+
}[];
|
|
229
|
+
bytes: {
|
|
230
|
+
request: number;
|
|
231
|
+
response: number;
|
|
232
|
+
upstream_request: number;
|
|
233
|
+
upstream_response: number;
|
|
234
|
+
};
|
|
235
|
+
}[];
|
|
236
|
+
};
|
|
237
|
+
/** Streams every stored exchange without building one large string. */
|
|
238
|
+
export declare function writeExport(inspector: Inspector, res: ServerResponse): Promise<void>;
|
|
239
|
+
export {};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { decodeCompaction, decodeReasoning } from "../carrier.js";
|
|
2
|
+
import { resolveModel } from "../config.js";
|
|
3
|
+
import { replaysReasoning, restoresNativeCompaction } from "../history.js";
|
|
4
|
+
/** Splits timed text into SSE events. An event takes the time of the part that completed it. */
|
|
5
|
+
export function sseEvents(parts) {
|
|
6
|
+
const events = [];
|
|
7
|
+
let event = null;
|
|
8
|
+
let data = [];
|
|
9
|
+
let hasData = false;
|
|
10
|
+
const line = (text, t) => {
|
|
11
|
+
if (text === "") {
|
|
12
|
+
if (hasData)
|
|
13
|
+
events.push({ t, event, data: data.join("\n") });
|
|
14
|
+
event = null;
|
|
15
|
+
data = [];
|
|
16
|
+
hasData = false;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (text.startsWith(":"))
|
|
20
|
+
return;
|
|
21
|
+
const colon = text.indexOf(":");
|
|
22
|
+
const field = colon < 0 ? text : text.slice(0, colon);
|
|
23
|
+
let value = colon < 0 ? "" : text.slice(colon + 1);
|
|
24
|
+
if (value.startsWith(" "))
|
|
25
|
+
value = value.slice(1);
|
|
26
|
+
if (field === "data") {
|
|
27
|
+
data.push(value);
|
|
28
|
+
hasData = true;
|
|
29
|
+
}
|
|
30
|
+
else if (field === "event")
|
|
31
|
+
event = value;
|
|
32
|
+
};
|
|
33
|
+
let buffer = "";
|
|
34
|
+
let t = 0;
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
t = part.t;
|
|
37
|
+
buffer += part.text;
|
|
38
|
+
let start = 0;
|
|
39
|
+
// A trailing CR waits for the next part, which may start with LF.
|
|
40
|
+
for (const match of buffer.matchAll(/\r\n|\n|\r(?=[^\n])/g)) {
|
|
41
|
+
line(buffer.slice(start, match.index), t);
|
|
42
|
+
start = match.index + match[0].length;
|
|
43
|
+
}
|
|
44
|
+
buffer = buffer.slice(start);
|
|
45
|
+
}
|
|
46
|
+
if (buffer.endsWith("\r"))
|
|
47
|
+
buffer = buffer.slice(0, -1);
|
|
48
|
+
if (buffer)
|
|
49
|
+
line(buffer, t);
|
|
50
|
+
line("", t);
|
|
51
|
+
return events;
|
|
52
|
+
}
|
|
53
|
+
function isSse(message) {
|
|
54
|
+
return (message.headers?.["content-type"] ?? "").toLowerCase().includes("text/event-stream");
|
|
55
|
+
}
|
|
56
|
+
function parse(text) {
|
|
57
|
+
if (text === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(text);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function terminal(data, status) {
|
|
67
|
+
const response = data?.response;
|
|
68
|
+
switch (data?.type) {
|
|
69
|
+
case "response.completed": return { outcome: "completed", error: null, usage: response?.usage ?? null };
|
|
70
|
+
case "response.incomplete": return { outcome: "incomplete", error: response?.incomplete_details?.reason ?? null, usage: response?.usage ?? null };
|
|
71
|
+
case "response.failed": return { outcome: "failed", error: response?.error?.message ?? null, usage: response?.usage ?? null };
|
|
72
|
+
case "error": return { outcome: "error", error: data.error?.message ?? data.message ?? null, usage: null };
|
|
73
|
+
}
|
|
74
|
+
if (status !== undefined && data?.object === "response") {
|
|
75
|
+
if (data.status === "completed")
|
|
76
|
+
return { outcome: "completed", error: null, usage: data.usage ?? null };
|
|
77
|
+
if (data.status === "incomplete")
|
|
78
|
+
return { outcome: "incomplete", error: data.incomplete_details?.reason ?? null, usage: data.usage ?? null };
|
|
79
|
+
if (data.status === "failed")
|
|
80
|
+
return { outcome: "failed", error: data.error?.message ?? null, usage: data.usage ?? null };
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const results = new WeakMap();
|
|
85
|
+
/** What the client saw, from its terminal event or JSON body. */
|
|
86
|
+
function result(exchange) {
|
|
87
|
+
if (!exchange.closed)
|
|
88
|
+
return { outcome: "active", error: null, usage: null };
|
|
89
|
+
const cached = results.get(exchange);
|
|
90
|
+
if (cached)
|
|
91
|
+
return cached;
|
|
92
|
+
const response = exchange.response;
|
|
93
|
+
let value;
|
|
94
|
+
if (response.end === "cancelled")
|
|
95
|
+
value = { outcome: "aborted", error: response.error ?? null, usage: null };
|
|
96
|
+
else if (response.status === undefined)
|
|
97
|
+
value = { outcome: "error", error: "No response was written", usage: null };
|
|
98
|
+
else if (isSse(response)) {
|
|
99
|
+
const events = sseEvents(response.parts);
|
|
100
|
+
value = { outcome: "error", error: "Stream ended without a terminal event", usage: null };
|
|
101
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
102
|
+
const found = terminal(parse(events[i].data));
|
|
103
|
+
if (found) {
|
|
104
|
+
value = found;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const body = parse(response.text());
|
|
111
|
+
const message = body?.error?.message ?? (typeof body?.error === "string" ? body.error : null);
|
|
112
|
+
value = response.status >= 400
|
|
113
|
+
? { outcome: "error", error: message ?? response.text().slice(0, 200), usage: null }
|
|
114
|
+
: terminal(body, response.status) ?? { outcome: "completed", error: null, usage: body?.usage ?? null };
|
|
115
|
+
}
|
|
116
|
+
results.set(exchange, value);
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
const originNames = new WeakMap();
|
|
120
|
+
/** Maps source fingerprints back to the aliases that produce them. */
|
|
121
|
+
function aliases(inspector) {
|
|
122
|
+
let names = originNames.get(inspector);
|
|
123
|
+
if (names)
|
|
124
|
+
return names;
|
|
125
|
+
names = new Map();
|
|
126
|
+
for (const alias of Object.keys(inspector.config.models)) {
|
|
127
|
+
const id = resolveModel(inspector.config, alias).origin.id;
|
|
128
|
+
names.set(id, [...names.get(id) ?? [], alias]);
|
|
129
|
+
}
|
|
130
|
+
originNames.set(inspector, names);
|
|
131
|
+
return names;
|
|
132
|
+
}
|
|
133
|
+
function source(inspector, origin) {
|
|
134
|
+
return { aliases: aliases(inspector).get(origin.id) ?? [], model: origin.model, adapter: origin.adapter, origin_id: origin.id };
|
|
135
|
+
}
|
|
136
|
+
function header(exchange, name) {
|
|
137
|
+
return exchange.headers[name] ?? null;
|
|
138
|
+
}
|
|
139
|
+
export function summary(exchange) {
|
|
140
|
+
const { outcome, error, usage } = result(exchange);
|
|
141
|
+
const route = exchange.route;
|
|
142
|
+
return {
|
|
143
|
+
id: exchange.id,
|
|
144
|
+
seq: exchange.seq,
|
|
145
|
+
rev: exchange.rev,
|
|
146
|
+
started_at: exchange.startedAt,
|
|
147
|
+
duration_ms: exchange.durationMs ?? null,
|
|
148
|
+
active: !exchange.closed,
|
|
149
|
+
model: exchange.meta.model,
|
|
150
|
+
adapter: route?.profile.id ?? null,
|
|
151
|
+
api: route?.profile.api ?? null,
|
|
152
|
+
upstream: route?.config.upstream ?? null,
|
|
153
|
+
upstream_model: route?.config.model ?? null,
|
|
154
|
+
kind: exchange.meta.kind,
|
|
155
|
+
stream: exchange.meta.stream,
|
|
156
|
+
status: exchange.response.status ?? null,
|
|
157
|
+
outcome,
|
|
158
|
+
error,
|
|
159
|
+
usage,
|
|
160
|
+
thread: {
|
|
161
|
+
session_id: header(exchange, "session-id") ?? header(exchange, "session_id"),
|
|
162
|
+
thread_id: header(exchange, "thread-id") ?? header(exchange, "thread_id"),
|
|
163
|
+
parent_thread_id: header(exchange, "x-codex-parent-thread-id"),
|
|
164
|
+
subagent: header(exchange, "x-openai-subagent"),
|
|
165
|
+
window_id: header(exchange, "x-codex-window-id"),
|
|
166
|
+
},
|
|
167
|
+
input_items: exchange.meta.input_items,
|
|
168
|
+
preview: exchange.meta.preview,
|
|
169
|
+
upstream_calls: exchange.calls.map(call => ({
|
|
170
|
+
label: call.label,
|
|
171
|
+
status: call.response?.status ?? null,
|
|
172
|
+
end: call.response?.end ?? (call.error !== undefined ? "error" : "active"),
|
|
173
|
+
})),
|
|
174
|
+
bytes: {
|
|
175
|
+
request: exchange.requestBytes,
|
|
176
|
+
response: exchange.response.bytes,
|
|
177
|
+
upstream_request: exchange.calls.reduce((sum, call) => sum + call.bytes, 0),
|
|
178
|
+
upstream_response: exchange.calls.reduce((sum, call) => sum + (call.response?.bytes ?? 0), 0),
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function routeView(inspector, route, responsesLite) {
|
|
183
|
+
const names = (ids) => [...new Set([...ids].flatMap(id => aliases(inspector).get(id) ?? []))];
|
|
184
|
+
return {
|
|
185
|
+
alias: route.slug,
|
|
186
|
+
catalog: route.config.catalog,
|
|
187
|
+
adapter: route.profile.id,
|
|
188
|
+
api: route.profile.api,
|
|
189
|
+
upstream: route.config.upstream,
|
|
190
|
+
base_url: route.upstream.base_url,
|
|
191
|
+
upstream_model: route.config.model,
|
|
192
|
+
origin_id: route.origin.id,
|
|
193
|
+
reasoning_format: route.profile.reasoning_format,
|
|
194
|
+
native_compaction_format: route.profile.native_compaction_format ?? null,
|
|
195
|
+
compaction_mode: route.compaction.mode,
|
|
196
|
+
native_replay: route.compaction.native_replay,
|
|
197
|
+
replay: { reasoning_from: names(route.replay.reasoning), native_compaction_from: names(route.replay.native_compaction) },
|
|
198
|
+
responses_lite: responsesLite ?? false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function messageView(message) {
|
|
202
|
+
if (!message)
|
|
203
|
+
return null;
|
|
204
|
+
return {
|
|
205
|
+
status: message.status ?? null,
|
|
206
|
+
status_text: message.statusText ?? null,
|
|
207
|
+
headers: message.headers ?? {},
|
|
208
|
+
headers_ms: message.headersMs ?? null,
|
|
209
|
+
ended_ms: message.endedMs ?? null,
|
|
210
|
+
end: message.end,
|
|
211
|
+
error: message.error ?? null,
|
|
212
|
+
body: { text: message.text(), bytes: message.bytes },
|
|
213
|
+
events: isSse(message) ? sseEvents(message.parts) : null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function reasoningInput(inspector, route, item, pointer) {
|
|
217
|
+
const base = { target: "client.request", pointer, kind: "reasoning" };
|
|
218
|
+
let carrier;
|
|
219
|
+
try {
|
|
220
|
+
carrier = decodeReasoning(item.encrypted_content);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return { ...base, decision: "reject", reason: "malformed", source: null, format: null, decoded: null };
|
|
224
|
+
}
|
|
225
|
+
if (!carrier)
|
|
226
|
+
return { ...base, decision: "drop", reason: "not_bridge_state", source: null, format: null, decoded: null };
|
|
227
|
+
const replay = replaysReasoning(carrier, route);
|
|
228
|
+
const reason = replay
|
|
229
|
+
? carrier.source.id === route.origin.id ? "same_source" : "allowed_source"
|
|
230
|
+
: route.replay.reasoning.has(carrier.source.id) ? "format_mismatch" : "source_not_allowed";
|
|
231
|
+
return { ...base, decision: replay ? "replay" : "drop", reason, source: source(inspector, carrier.source), format: carrier.format, decoded: carrier };
|
|
232
|
+
}
|
|
233
|
+
function compactionInput(inspector, route, item, pointer) {
|
|
234
|
+
const base = { target: "client.request", pointer, kind: "compaction" };
|
|
235
|
+
const carrier = decodeCompaction(item.encrypted_content);
|
|
236
|
+
if (!carrier)
|
|
237
|
+
return { ...base, decision: "reject", reason: "not_bridge_state", source: null, format: null, decoded: null };
|
|
238
|
+
const native = carrier.native;
|
|
239
|
+
const restored = restoresNativeCompaction(carrier, route);
|
|
240
|
+
const reason = restored ? "native_restored"
|
|
241
|
+
: !native ? "no_native"
|
|
242
|
+
: !route.compaction.native_replay ? "native_replay_disabled"
|
|
243
|
+
: !route.replay.native_compaction.has(native.source.id) ? "source_not_allowed" : "format_mismatch";
|
|
244
|
+
return {
|
|
245
|
+
...base, decision: restored ? "native" : carrier.summary !== undefined ? "summary" : "handoff", reason,
|
|
246
|
+
source: native ? source(inspector, native.source) : null, format: native?.format ?? null, decoded: carrier,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function outputNote(inspector, item, target) {
|
|
250
|
+
if (item?.type === "reasoning") {
|
|
251
|
+
let carrier;
|
|
252
|
+
try {
|
|
253
|
+
carrier = decodeReasoning(item.encrypted_content);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
if (!carrier)
|
|
259
|
+
return undefined;
|
|
260
|
+
return { ...target, kind: "reasoning", decision: "output", reason: "output", source: source(inspector, carrier.source), format: carrier.format, decoded: carrier };
|
|
261
|
+
}
|
|
262
|
+
if (item?.type === "compaction" || item?.type === "compaction_summary") {
|
|
263
|
+
const carrier = decodeCompaction(item.encrypted_content);
|
|
264
|
+
if (!carrier)
|
|
265
|
+
return undefined;
|
|
266
|
+
return {
|
|
267
|
+
...target, kind: "compaction", decision: "output", reason: "output",
|
|
268
|
+
source: carrier.native ? source(inspector, carrier.native.source) : null, format: carrier.native?.format ?? null, decoded: carrier,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
function annotations(exchange, client) {
|
|
274
|
+
const notes = [];
|
|
275
|
+
const { inspector, route } = exchange;
|
|
276
|
+
const request = route ? parse(exchange.requestText) : undefined;
|
|
277
|
+
if (route && Array.isArray(request?.input)) {
|
|
278
|
+
request.input.forEach((item, index) => {
|
|
279
|
+
const pointer = `/input/${index}`;
|
|
280
|
+
if (item?.type === "reasoning")
|
|
281
|
+
notes.push(reasoningInput(inspector, route, item, pointer));
|
|
282
|
+
else if (item?.type === "compaction" || item?.type === "compaction_summary")
|
|
283
|
+
notes.push(compactionInput(inspector, route, item, pointer));
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (client?.events) {
|
|
287
|
+
client.events.forEach((event, index) => {
|
|
288
|
+
if (!event.data.includes("cxbr1:") && !event.data.includes("cxbc1:"))
|
|
289
|
+
return;
|
|
290
|
+
const data = parse(event.data);
|
|
291
|
+
if (data?.type !== "response.output_item.done")
|
|
292
|
+
return;
|
|
293
|
+
const note = outputNote(inspector, data.item, { target: "client.response", event: index, pointer: "/item" });
|
|
294
|
+
if (note)
|
|
295
|
+
notes.push(note);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else if (client && (client.body.text.includes("cxbr1:") || client.body.text.includes("cxbc1:"))) {
|
|
299
|
+
const output = parse(client.body.text)?.output;
|
|
300
|
+
if (Array.isArray(output))
|
|
301
|
+
output.forEach((item, index) => {
|
|
302
|
+
const note = outputNote(inspector, item, { target: "client.response", pointer: `/output/${index}` });
|
|
303
|
+
if (note)
|
|
304
|
+
notes.push(note);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return notes;
|
|
308
|
+
}
|
|
309
|
+
export function detail(exchange) {
|
|
310
|
+
const client = messageView(exchange.response);
|
|
311
|
+
return {
|
|
312
|
+
...summary(exchange),
|
|
313
|
+
route: exchange.route ? routeView(exchange.inspector, exchange.route, exchange.responsesLite) : null,
|
|
314
|
+
client: {
|
|
315
|
+
request: {
|
|
316
|
+
method: exchange.method,
|
|
317
|
+
path: exchange.path,
|
|
318
|
+
headers: exchange.headers,
|
|
319
|
+
body: { text: exchange.requestText ?? "", bytes: exchange.requestBytes },
|
|
320
|
+
},
|
|
321
|
+
response: client,
|
|
322
|
+
},
|
|
323
|
+
upstream: exchange.calls.map(call => ({
|
|
324
|
+
index: call.index,
|
|
325
|
+
label: call.label,
|
|
326
|
+
started_ms: call.startedMs,
|
|
327
|
+
method: call.method,
|
|
328
|
+
url: call.url,
|
|
329
|
+
headers: call.headers,
|
|
330
|
+
body: { text: call.text, bytes: call.bytes },
|
|
331
|
+
error: call.error ?? null,
|
|
332
|
+
response: messageView(call.response),
|
|
333
|
+
})),
|
|
334
|
+
annotations: annotations(exchange, client),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
export function stateView(inspector) {
|
|
338
|
+
let active = 0;
|
|
339
|
+
for (const exchange of inspector.exchanges.values())
|
|
340
|
+
if (!exchange.closed)
|
|
341
|
+
active++;
|
|
342
|
+
return {
|
|
343
|
+
capturing: inspector.capturing,
|
|
344
|
+
exchanges: inspector.exchanges.size,
|
|
345
|
+
active,
|
|
346
|
+
bytes: inspector.bytes,
|
|
347
|
+
max_bytes: inspector.maxBytes,
|
|
348
|
+
evicted: inspector.evicted,
|
|
349
|
+
rev: inspector.rev,
|
|
350
|
+
started_at: inspector.startedAt,
|
|
351
|
+
models: Object.entries(inspector.config.models).map(([alias, model]) => {
|
|
352
|
+
const route = resolveModel(inspector.config, alias);
|
|
353
|
+
return { alias, catalog: model.catalog, adapter: route.profile.id, api: route.profile.api, upstream: model.upstream, upstream_model: model.model };
|
|
354
|
+
}),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
export function listView(inspector, since) {
|
|
358
|
+
const exchanges = [...inspector.exchanges.values()];
|
|
359
|
+
return { rev: inspector.rev, ids: exchanges.map(exchange => exchange.id), exchanges: exchanges.filter(exchange => exchange.rev > since).map(summary) };
|
|
360
|
+
}
|
|
361
|
+
function drained(res) {
|
|
362
|
+
return new Promise(resolve => {
|
|
363
|
+
const done = () => { res.off("drain", done); res.off("close", done); resolve(); };
|
|
364
|
+
res.once("drain", done);
|
|
365
|
+
res.once("close", done);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/** Streams every stored exchange without building one large string. */
|
|
369
|
+
export async function writeExport(inspector, res) {
|
|
370
|
+
const exportedAt = Date.now();
|
|
371
|
+
const stamp = new Date(exportedAt).toISOString().replace(/[:.]/g, "-");
|
|
372
|
+
res.writeHead(200, {
|
|
373
|
+
"content-type": "application/json; charset=utf-8",
|
|
374
|
+
"content-disposition": `attachment; filename="codeks-bridge-capture-${stamp}.json"`,
|
|
375
|
+
"cache-control": "no-store",
|
|
376
|
+
});
|
|
377
|
+
res.write(`{"format":"codeks-bridge-capture","version":1,"exported_at":${exportedAt},"exchanges":[`);
|
|
378
|
+
let first = true;
|
|
379
|
+
for (const exchange of [...inspector.exchanges.values()]) {
|
|
380
|
+
if (res.destroyed)
|
|
381
|
+
return;
|
|
382
|
+
const chunk = (first ? "" : ",") + JSON.stringify(detail(exchange));
|
|
383
|
+
first = false;
|
|
384
|
+
if (!res.write(chunk))
|
|
385
|
+
await drained(res);
|
|
386
|
+
}
|
|
387
|
+
res.end("]}\n");
|
|
388
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AdapterId } from "./types.ts";
|
|
2
|
+
export interface CatalogEntry {
|
|
3
|
+
source: {
|
|
4
|
+
repository: string;
|
|
5
|
+
tag: string;
|
|
6
|
+
commit: string;
|
|
7
|
+
path: string;
|
|
8
|
+
model: string;
|
|
9
|
+
};
|
|
10
|
+
adjustments: string[];
|
|
11
|
+
adapter: AdapterId;
|
|
12
|
+
effort_map?: Record<string, string>;
|
|
13
|
+
max_output_tokens?: number;
|
|
14
|
+
model: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
export declare const catalogKeys: readonly ["gpt-6-astra", "gpt-6.1-sol", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "claude-opus-5-5", "claude-sonnet-5-5", "deepseek-flash"];
|
|
17
|
+
/** Each alias gets its own copy of the reviewed entry. */
|
|
18
|
+
export declare function getCatalogEntry(key: string): CatalogEntry;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { BridgeError } from "./errors.js";
|
|
3
|
+
export const catalogKeys = [
|
|
4
|
+
"gpt-6-astra", "gpt-6.1-sol", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol",
|
|
5
|
+
"claude-opus-5-5", "claude-sonnet-5-5", "deepseek-flash",
|
|
6
|
+
];
|
|
7
|
+
const entries = new Map(catalogKeys.map(key => [
|
|
8
|
+
key, JSON.parse(readFileSync(new URL(`../catalog/${key}.json`, import.meta.url), "utf8")),
|
|
9
|
+
]));
|
|
10
|
+
/** Each alias gets its own copy of the reviewed entry. */
|
|
11
|
+
export function getCatalogEntry(key) {
|
|
12
|
+
const entry = entries.get(key);
|
|
13
|
+
if (!entry)
|
|
14
|
+
throw new BridgeError(`Unknown catalog model: ${key}`);
|
|
15
|
+
return structuredClone(entry);
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ModelConfig, ModelProfile, ResolvedModel, ResponsesRequest } from "./types.ts";
|
|
2
|
+
export declare function modelProfile(model: ModelConfig): ModelProfile;
|
|
3
|
+
/** Catalog choices are defaults, not a server capability allowlist. */
|
|
4
|
+
export declare function reasoningEffort(route: ResolvedModel, requested?: string): string;
|
|
5
|
+
/** Codex omits reasoning.summary when its effective setting is none. */
|
|
6
|
+
export declare function reasoningSummaryRequested(request: ResponsesRequest): boolean;
|