@bitkyc08/opencodex 2.7.4 → 2.7.7
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.ko.md +6 -3
- package/README.md +6 -4
- package/README.zh-CN.md +4 -3
- package/gui/dist/assets/index-C0xVu72_.css +1 -0
- package/gui/dist/assets/index-DzEDGLZh.js +40 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +30 -2
- package/src/adapters/base.ts +10 -0
- package/src/adapters/google-http.ts +37 -13
- package/src/adapters/google-tool-schema.ts +5 -0
- package/src/adapters/google.ts +3 -0
- package/src/adapters/kiro-retry.ts +33 -13
- package/src/adapters/kiro-tools.ts +4 -0
- package/src/adapters/kiro.ts +5 -1
- package/src/adapters/openai-responses.ts +42 -0
- package/src/codex/catalog.ts +77 -7
- package/src/lib/abort.ts +40 -0
- package/src/lib/bounded-body.ts +202 -0
- package/src/providers/registry.ts +8 -1
- package/src/reasoning-effort.ts +5 -0
- package/src/server/auth-cors.ts +16 -13
- package/src/server/effort-policy.ts +172 -0
- package/src/server/images.ts +218 -0
- package/src/server/index.ts +57 -2
- package/src/server/management-api.ts +28 -0
- package/src/server/responses.ts +29 -1
- package/src/server/search.ts +150 -0
- package/src/types.ts +37 -0
- package/src/web-search/index.ts +44 -10
- package/src/web-search/loop.ts +165 -112
- package/src/web-search/progress-stream.ts +329 -0
- package/gui/dist/assets/index-66dPs6l_.js +0 -40
- package/gui/dist/assets/index-D7o1qwy-.css +0 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import type { ProviderAdapter } from "../adapters/base";
|
|
2
|
+
import type { AdapterEvent } from "../types";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_POST_TERMINAL_DRAIN_TIMEOUT_MS = 5_000;
|
|
5
|
+
|
|
6
|
+
export class RoutedModelInactivityError extends Error {
|
|
7
|
+
readonly timeoutMs: number;
|
|
8
|
+
|
|
9
|
+
constructor(timeoutMs: number) {
|
|
10
|
+
super(`Routed model generation timeout after ${timeoutMs}ms without response bytes during web-search`);
|
|
11
|
+
this.name = "RoutedModelInactivityError";
|
|
12
|
+
this.timeoutMs = timeoutMs;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class WebSearchStreamProtocolError extends Error {
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(`Web-search adapter stream protocol error: ${message}`);
|
|
19
|
+
this.name = "WebSearchStreamProtocolError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ParseStreamWithProgressOptions {
|
|
24
|
+
inactivityTimeoutMs: number;
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
/** Kept configurable for focused tests; production callers should use the 5 second default. */
|
|
27
|
+
postTerminalDrainTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ParseStream = ProviderAdapter["parseStream"];
|
|
31
|
+
type Resolver<T> = (value: T | PromiseLike<T>) => void;
|
|
32
|
+
|
|
33
|
+
interface PendingDelivery {
|
|
34
|
+
event: AdapterEvent;
|
|
35
|
+
resolve: () => void;
|
|
36
|
+
reject: (reason: unknown) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Capacity-one handoff. Semantic events are lossless and acknowledged when received; raw-byte
|
|
41
|
+
* progress is only a coalesced liveness bit and never displaces a semantic event.
|
|
42
|
+
*/
|
|
43
|
+
class ProgressHandoff {
|
|
44
|
+
private semantic: PendingDelivery | undefined;
|
|
45
|
+
private progress = false;
|
|
46
|
+
private receiver: { resolve: Resolver<IteratorResult<AdapterEvent>>; reject: (reason: unknown) => void } | undefined;
|
|
47
|
+
private ended = false;
|
|
48
|
+
private failed = false;
|
|
49
|
+
private failure: unknown | undefined;
|
|
50
|
+
|
|
51
|
+
async deliver(event: AdapterEvent): Promise<void> {
|
|
52
|
+
if (this.failed) throw this.failure;
|
|
53
|
+
if (this.ended) throw new WebSearchStreamProtocolError("event delivered after collector closed");
|
|
54
|
+
|
|
55
|
+
if (this.receiver) {
|
|
56
|
+
const receiver = this.receiver;
|
|
57
|
+
this.receiver = undefined;
|
|
58
|
+
receiver.resolve({ done: false, value: event });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await new Promise<void>((resolve, reject) => {
|
|
63
|
+
this.semantic = { event, resolve, reject };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
offerProgress(): void {
|
|
68
|
+
if (this.ended || this.failed) return;
|
|
69
|
+
if (this.receiver && !this.semantic) {
|
|
70
|
+
const receiver = this.receiver;
|
|
71
|
+
this.receiver = undefined;
|
|
72
|
+
receiver.resolve({ done: false, value: { type: "heartbeat" } });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.progress = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
receive(): Promise<IteratorResult<AdapterEvent>> {
|
|
79
|
+
if (this.failed) return Promise.reject(this.failure);
|
|
80
|
+
if (this.semantic) {
|
|
81
|
+
const delivery = this.semantic;
|
|
82
|
+
this.semantic = undefined;
|
|
83
|
+
delivery.resolve();
|
|
84
|
+
return Promise.resolve({ done: false, value: delivery.event });
|
|
85
|
+
}
|
|
86
|
+
if (this.progress) {
|
|
87
|
+
this.progress = false;
|
|
88
|
+
return Promise.resolve({ done: false, value: { type: "heartbeat" } });
|
|
89
|
+
}
|
|
90
|
+
if (this.ended) return Promise.resolve({ done: true, value: undefined });
|
|
91
|
+
|
|
92
|
+
return new Promise<IteratorResult<AdapterEvent>>((resolve, reject) => {
|
|
93
|
+
this.receiver = { resolve, reject };
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fail(reason: unknown): void {
|
|
98
|
+
if (this.ended || this.failed) return;
|
|
99
|
+
this.failed = true;
|
|
100
|
+
this.failure = reason;
|
|
101
|
+
this.progress = false;
|
|
102
|
+
this.semantic?.reject(reason);
|
|
103
|
+
this.semantic = undefined;
|
|
104
|
+
this.receiver?.reject(reason);
|
|
105
|
+
this.receiver = undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
close(): void {
|
|
109
|
+
if (this.ended || this.failed) return;
|
|
110
|
+
this.ended = true;
|
|
111
|
+
this.progress = false;
|
|
112
|
+
this.semantic?.reject(new WebSearchStreamProtocolError("collector closed with a pending event"));
|
|
113
|
+
this.semantic = undefined;
|
|
114
|
+
this.receiver?.resolve({ done: true, value: undefined });
|
|
115
|
+
this.receiver = undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeTimeout(value: number, label: string): number {
|
|
120
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
121
|
+
throw new RangeError(`${label} must be a positive finite number`);
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parse an adapter stream while exposing invisible raw-byte liveness heartbeats.
|
|
128
|
+
*
|
|
129
|
+
* The original body is never cloned or tee'd. This function owns its sole reader and supplies the
|
|
130
|
+
* adapter with a demand-driven, highWaterMark-zero response body.
|
|
131
|
+
*/
|
|
132
|
+
export async function* parseStreamWithProgress(
|
|
133
|
+
response: Response,
|
|
134
|
+
parseStream: ParseStream,
|
|
135
|
+
options: ParseStreamWithProgressOptions,
|
|
136
|
+
): AsyncGenerator<AdapterEvent> {
|
|
137
|
+
const inactivityTimeoutMs = normalizeTimeout(options.inactivityTimeoutMs, "inactivityTimeoutMs");
|
|
138
|
+
const postTerminalDrainTimeoutMs = normalizeTimeout(
|
|
139
|
+
options.postTerminalDrainTimeoutMs ?? DEFAULT_POST_TERMINAL_DRAIN_TIMEOUT_MS,
|
|
140
|
+
"postTerminalDrainTimeoutMs",
|
|
141
|
+
);
|
|
142
|
+
const handoff = new ProgressHandoff();
|
|
143
|
+
const reader = response.body?.getReader();
|
|
144
|
+
if (!reader) throw new WebSearchStreamProtocolError("upstream response has no body");
|
|
145
|
+
|
|
146
|
+
let inactivityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
147
|
+
let tappedController: ReadableStreamDefaultController<Uint8Array> | undefined;
|
|
148
|
+
let settled = false;
|
|
149
|
+
let iterator: AsyncGenerator<AdapterEvent> | undefined;
|
|
150
|
+
let iteratorReturnStarted = false;
|
|
151
|
+
let detachAbort = (): void => {};
|
|
152
|
+
let stopSignalled = false;
|
|
153
|
+
let signalStop!: (reason: unknown) => void;
|
|
154
|
+
const stopped = new Promise<unknown>(resolve => { signalStop = resolve; });
|
|
155
|
+
|
|
156
|
+
const clearInactivity = (): void => {
|
|
157
|
+
if (inactivityTimer !== undefined) clearTimeout(inactivityTimer);
|
|
158
|
+
inactivityTimer = undefined;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const cancelReader = (reason: unknown): void => {
|
|
162
|
+
try {
|
|
163
|
+
void reader.cancel(reason).catch(() => {});
|
|
164
|
+
} catch { /* reader cancellation is best-effort and must never replace the foreground outcome */ }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const closeIterator = (): void => {
|
|
168
|
+
if (!iterator || iteratorReturnStarted) return;
|
|
169
|
+
iteratorReturnStarted = true;
|
|
170
|
+
try {
|
|
171
|
+
const returned = iterator.return(undefined);
|
|
172
|
+
void returned.catch(() => {});
|
|
173
|
+
} catch { /* iterator return is best-effort and must never delay foreground failure */ }
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const stop = (reason: unknown): void => {
|
|
177
|
+
if (stopSignalled) return;
|
|
178
|
+
stopSignalled = true;
|
|
179
|
+
signalStop(reason);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const fail = (reason: unknown): void => {
|
|
183
|
+
// A second failure may arrive from the parser after reader cancellation. Iterator cleanup is
|
|
184
|
+
// still required even when the foreground failure has already won the settlement race.
|
|
185
|
+
closeIterator();
|
|
186
|
+
if (settled) return;
|
|
187
|
+
settled = true;
|
|
188
|
+
clearInactivity();
|
|
189
|
+
detachAbort();
|
|
190
|
+
stop(reason);
|
|
191
|
+
handoff.fail(reason);
|
|
192
|
+
try { tappedController?.error(reason); } catch { /* already closed/cancelled */ }
|
|
193
|
+
cancelReader(reason);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const resetInactivity = (): void => {
|
|
197
|
+
if (settled) return;
|
|
198
|
+
clearInactivity();
|
|
199
|
+
inactivityTimer = setTimeout(() => fail(new RoutedModelInactivityError(inactivityTimeoutMs)), inactivityTimeoutMs);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const onAbort = (): void => fail(options.signal?.reason);
|
|
203
|
+
detachAbort = (): void => options.signal?.removeEventListener("abort", onAbort);
|
|
204
|
+
if (options.signal?.aborted) fail(options.signal.reason);
|
|
205
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
206
|
+
|
|
207
|
+
const tappedBody = new ReadableStream<Uint8Array>({
|
|
208
|
+
start(controller) {
|
|
209
|
+
tappedController = controller;
|
|
210
|
+
},
|
|
211
|
+
async pull(controller) {
|
|
212
|
+
try {
|
|
213
|
+
while (!settled) {
|
|
214
|
+
const result = await reader.read();
|
|
215
|
+
if (result.done) {
|
|
216
|
+
controller.close();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (result.value.byteLength === 0) continue;
|
|
220
|
+
resetInactivity();
|
|
221
|
+
handoff.offerProgress();
|
|
222
|
+
controller.enqueue(result.value);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
fail(error);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
cancel(reason) {
|
|
230
|
+
cancelReader(reason);
|
|
231
|
+
closeIterator();
|
|
232
|
+
},
|
|
233
|
+
}, { highWaterMark: 0 });
|
|
234
|
+
|
|
235
|
+
const tappedResponse = new Response(tappedBody, {
|
|
236
|
+
status: response.status,
|
|
237
|
+
statusText: response.statusText,
|
|
238
|
+
headers: response.headers,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
resetInactivity();
|
|
242
|
+
|
|
243
|
+
const parserPump = (async (): Promise<void> => {
|
|
244
|
+
let heldDone: Extract<AdapterEvent, { type: "done" }> | undefined;
|
|
245
|
+
try {
|
|
246
|
+
iterator = parseStream(tappedResponse);
|
|
247
|
+
if (settled) {
|
|
248
|
+
closeIterator();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
while (true) {
|
|
252
|
+
let result: IteratorResult<AdapterEvent>;
|
|
253
|
+
if (heldDone) {
|
|
254
|
+
let drainTimer: ReturnType<typeof setTimeout> | undefined;
|
|
255
|
+
const drainTimeout = new Promise<never>((_, reject) => {
|
|
256
|
+
drainTimer = setTimeout(() => reject(new WebSearchStreamProtocolError(
|
|
257
|
+
`adapter did not return within ${postTerminalDrainTimeoutMs}ms after done`,
|
|
258
|
+
)), postTerminalDrainTimeoutMs);
|
|
259
|
+
});
|
|
260
|
+
const stoppedDuringDrain = stopped.then(reason => { throw reason; });
|
|
261
|
+
try {
|
|
262
|
+
result = await Promise.race([iterator.next(), drainTimeout, stoppedDuringDrain]);
|
|
263
|
+
} finally {
|
|
264
|
+
if (drainTimer !== undefined) clearTimeout(drainTimer);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
result = await iterator.next();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (result.done) {
|
|
271
|
+
if (!heldDone) throw new WebSearchStreamProtocolError("adapter returned without a done event");
|
|
272
|
+
await handoff.deliver(heldDone);
|
|
273
|
+
if (!settled) {
|
|
274
|
+
settled = true;
|
|
275
|
+
clearInactivity();
|
|
276
|
+
detachAbort();
|
|
277
|
+
handoff.close();
|
|
278
|
+
cancelReader(undefined);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const event = result.value;
|
|
284
|
+
if (heldDone) {
|
|
285
|
+
throw new WebSearchStreamProtocolError(
|
|
286
|
+
event.type === "done" ? "adapter yielded more than one done event" : "adapter yielded an event after done",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (event.type === "error") {
|
|
290
|
+
fail(new Error(event.message));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (event.type === "done") {
|
|
294
|
+
heldDone = event;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
await handoff.deliver(event);
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
fail(error instanceof WebSearchStreamProtocolError
|
|
301
|
+
? error
|
|
302
|
+
: new WebSearchStreamProtocolError(
|
|
303
|
+
`adapter threw${heldDone ? " after done" : ""}: ${error instanceof Error ? error.message : String(error)}`,
|
|
304
|
+
));
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
// The foreground intentionally never awaits this task during cancellation; keep every rejection observed.
|
|
308
|
+
void parserPump.catch(() => {});
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
while (true) {
|
|
312
|
+
const result = await handoff.receive();
|
|
313
|
+
if (result.done) return;
|
|
314
|
+
yield result.value;
|
|
315
|
+
}
|
|
316
|
+
} finally {
|
|
317
|
+
detachAbort();
|
|
318
|
+
if (!settled) {
|
|
319
|
+
const reason = new Error("Web-search stream consumer stopped");
|
|
320
|
+
settled = true;
|
|
321
|
+
clearInactivity();
|
|
322
|
+
stop(reason);
|
|
323
|
+
handoff.close();
|
|
324
|
+
cancelReader(reason);
|
|
325
|
+
}
|
|
326
|
+
closeIterator();
|
|
327
|
+
try { reader.releaseLock(); } catch { /* an outstanding read may still own the lock */ }
|
|
328
|
+
}
|
|
329
|
+
}
|