@bitkyc08/opencodex 2.72.0 → 2.73.0
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/gui/dist/assets/{App-Cf-WYstE.js → App-mHGa7dE1.js} +15 -15
- package/gui/dist/assets/{Tray-DkM0mULy.js → Tray-BxY_RYZg.js} +1 -1
- package/gui/dist/assets/{index-PWrwOfio.js → index-ur_BPgK1.js} +12 -12
- package/gui/dist/assets/{quota-summary-D-FKtrgr.js → quota-summary-D1Vq7N9H.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/cursor/native-exec.ts +28 -10
- package/src/adapters/devin/live-models.ts +5 -0
- package/src/adapters/kiro/reasoning.ts +2 -0
- package/src/adapters/openai-chat/messages.ts +4 -1
- package/src/adapters/openai-responses/passthrough.ts +12 -1
- package/src/adapters/openai-responses/reasoning.ts +15 -0
- package/src/bridge/internal.ts +12 -0
- package/src/cli/account-anthropic-threshold.ts +41 -0
- package/src/cli/account-api.ts +2 -0
- package/src/cli/account-extended.ts +32 -21
- package/src/cli/account.ts +1 -0
- package/src/cli/capabilities.ts +8 -4
- package/src/cli/index.ts +1 -1
- package/src/codex/catalog/build-entries.ts +6 -5
- package/src/codex/catalog/derive-entry.ts +3 -4
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/native-models.ts +17 -3
- package/src/codex/catalog/parsing.ts +4 -1
- package/src/codex/catalog/pinned-models.ts +3 -1
- package/src/codex/catalog/provider-models.ts +22 -2
- package/src/codex/data/roster-pinned-models.json +178 -1
- package/src/codex/model-entitlements.ts +1 -1
- package/src/codex/native-profile-store.ts +2 -1
- package/src/combos/resolve.ts +16 -1
- package/src/config/subagent-models.ts +30 -5
- package/src/generated/compatibility-version.json +95 -67
- package/src/generated/model-metadata.ts +4 -4
- package/src/lib/account-selection-events.ts +28 -0
- package/src/lib/errors.ts +7 -5
- package/src/lib/keyring-native.ts +132 -0
- package/src/lib/provider-outbound.ts +13 -0
- package/src/lib/retry-delay.ts +10 -0
- package/src/oauth/anthropic-account-threshold.ts +13 -0
- package/src/oauth/anthropic-routing.ts +223 -52
- package/src/oauth/index.ts +4 -2
- package/src/oauth/store.ts +45 -3
- package/src/oauth/types.ts +2 -0
- package/src/providers/api-key-resolve.ts +2 -4
- package/src/providers/codebuddy-models.ts +6 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/label.ts +20 -1
- package/src/providers/quota/account-cache.ts +2 -1
- package/src/providers/quota/anthropic-cooldown-recovery.ts +118 -0
- package/src/providers/quota/vendor-probes-oauth.ts +84 -57
- package/src/providers/quota.ts +45 -46
- package/src/providers/registry/entries-core.ts +3 -1
- package/src/providers/registry/entries-extended.ts +14 -1
- package/src/providers/registry/model-seeds.ts +7 -1
- package/src/server/claude-messages.ts +84 -27
- package/src/server/management/anthropic-account-threshold.ts +27 -0
- package/src/server/management/oauth-account-routes.ts +17 -4
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/messages-native-oauth.ts +20 -21
- package/src/server/messages-native.ts +39 -14
- package/src/server/relay-eager.ts +5 -2
- package/src/server/relay.ts +268 -82
- package/src/server/responses/adapter-continuation.ts +6 -0
- package/src/server/responses/adapter-delivery.ts +25 -7
- package/src/server/responses/adapter-dispatch.ts +31 -0
- package/src/server/responses/buffered-sse-json.ts +345 -0
- package/src/server/responses/core-codex-account.ts +4 -1
- package/src/server/responses/core-combo.ts +28 -0
- package/src/server/responses/core-lifetime.ts +4 -0
- package/src/server/responses/passthrough-delivery.ts +305 -10
- package/src/server/responses/passthrough-dispatch.ts +60 -25
- package/src/server/responses/passthrough-error.ts +3 -1
- package/src/server/responses/request-transport.ts +22 -5
- package/src/server/responses/terminal-error-redaction.ts +53 -0
- package/src/service/windows-taskxml.ts +20 -6
- package/src/service-manager-probe.ts +115 -6
- package/src/types/wire.ts +7 -0
- package/src/usage/expected-prices.ts +11 -0
package/src/server/relay.ts
CHANGED
|
@@ -29,14 +29,18 @@ import {
|
|
|
29
29
|
EMPTY_BYTES,
|
|
30
30
|
joinSseFrameBytes,
|
|
31
31
|
MAX_CLIENT_SSE_FRAME_BYTES,
|
|
32
|
+
SseFrameCountLimitError,
|
|
32
33
|
} from "./sse-frame-buffer";
|
|
33
34
|
import { replaceSseDataPayload, sseDataPayload } from "./sse-payload-rewrite";
|
|
34
35
|
import { createBoundedResponseLogBody } from "./response-log-body";
|
|
35
36
|
import { clientWireLogOf } from "./inference/client-wire";
|
|
36
37
|
import { recordClientWireRequestLog } from "./inference/client-wire-log";
|
|
38
|
+
import { nativeResponseFingerprint } from "./responses/native-response-json";
|
|
39
|
+
import { nativeResponseOutput } from "./responses/native-response-output";
|
|
37
40
|
|
|
38
41
|
const nativePassthroughSseResponses = new WeakSet<Response>();
|
|
39
42
|
const eagerRelaySseResponses = new WeakSet<Response>();
|
|
43
|
+
const preinspectedJsonResponses = new WeakSet<Response>();
|
|
40
44
|
|
|
41
45
|
export const MAX_INSPECTION_SSE_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
|
|
42
46
|
export const MAX_COMPLETED_OUTPUT_ITEMS = 256;
|
|
@@ -124,12 +128,12 @@ export function relayWithAbort(
|
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
export function buildFailedTailPayload(err: unknown): string {
|
|
131
|
+
export function buildFailedTailPayload(err: unknown, maskCredential?: (text: string) => string): string {
|
|
128
132
|
const translatorOverflow = isTranslatorBudgetExceededError(err);
|
|
129
|
-
const
|
|
133
|
+
const diagnostic = redactSecretString((translatorOverflow
|
|
130
134
|
? "upstream translation buffer exceeded the safe limit"
|
|
131
|
-
: `Upstream stream terminated unexpectedly: ${err instanceof Error ? err.message : String(err)}`)
|
|
132
|
-
|
|
135
|
+
: `Upstream stream terminated unexpectedly: ${err instanceof Error ? err.message : String(err)}`));
|
|
136
|
+
const message = (maskCredential?.(diagnostic) ?? diagnostic).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS);
|
|
133
137
|
const failure = {
|
|
134
138
|
type: "upstream_error",
|
|
135
139
|
code: translatorOverflow ? "translation_buffer_limit" : "upstream_reset",
|
|
@@ -141,9 +145,9 @@ export function buildFailedTailPayload(err: unknown): string {
|
|
|
141
145
|
});
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
function buildFailedTailPayloadOrFallback(err: unknown): string {
|
|
148
|
+
function buildFailedTailPayloadOrFallback(err: unknown, maskCredential?: (text: string) => string): string {
|
|
145
149
|
try {
|
|
146
|
-
return buildFailedTailPayload(err);
|
|
150
|
+
return buildFailedTailPayload(err, maskCredential);
|
|
147
151
|
} catch {
|
|
148
152
|
// Error.message and String(error) may execute hostile accessors. Preserve a
|
|
149
153
|
// bounded protocol terminal even when diagnostic serialization is unsafe.
|
|
@@ -151,8 +155,8 @@ function buildFailedTailPayloadOrFallback(err: unknown): string {
|
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
export function failedTailFrame(encoder: TextEncoder, err: unknown): Uint8Array {
|
|
155
|
-
const payload = buildFailedTailPayloadOrFallback(err);
|
|
158
|
+
export function failedTailFrame(encoder: TextEncoder, err: unknown, maskCredential?: (text: string) => string): Uint8Array {
|
|
159
|
+
const payload = buildFailedTailPayloadOrFallback(err, maskCredential);
|
|
156
160
|
return encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`);
|
|
157
161
|
}
|
|
158
162
|
|
|
@@ -170,17 +174,19 @@ export function upstreamErrorTailFrame(
|
|
|
170
174
|
encoder: TextEncoder,
|
|
171
175
|
message: string,
|
|
172
176
|
refusalCode?: string,
|
|
177
|
+
maskCredential?: (text: string) => string,
|
|
173
178
|
): Uint8Array {
|
|
174
179
|
return encoder.encode(
|
|
175
|
-
`event: response.failed\ndata: ${upstreamErrorFailedPayload(message, refusalCode)}\n\n`,
|
|
180
|
+
`event: response.failed\ndata: ${upstreamErrorFailedPayload(message, refusalCode, maskCredential)}\n\n`,
|
|
176
181
|
);
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
function upstreamErrorFailedPayload(message: string, refusalCode?: string): string {
|
|
184
|
+
function upstreamErrorFailedPayload(message: string, refusalCode?: string, maskCredential?: (text: string) => string): string {
|
|
185
|
+
const diagnostic = redactSecretString(message);
|
|
180
186
|
const error = {
|
|
181
187
|
type: refusalCode === undefined ? "upstream_error" : "invalid_request_error",
|
|
182
|
-
code: refusalCode
|
|
183
|
-
message:
|
|
188
|
+
code: refusalCode === undefined ? "upstream_server_error" : (maskCredential?.(refusalCode) ?? refusalCode),
|
|
189
|
+
message: (maskCredential?.(diagnostic) ?? diagnostic).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS),
|
|
184
190
|
};
|
|
185
191
|
return JSON.stringify({
|
|
186
192
|
type: "response.failed",
|
|
@@ -206,8 +212,9 @@ export function refusalFailedTailFrame(
|
|
|
206
212
|
encoder: TextEncoder,
|
|
207
213
|
message: string,
|
|
208
214
|
refusalCode: string,
|
|
215
|
+
maskCredential?: (text: string) => string,
|
|
209
216
|
): Uint8Array {
|
|
210
|
-
const payload = upstreamErrorFailedPayload(message, refusalCode);
|
|
217
|
+
const payload = upstreamErrorFailedPayload(message, refusalCode, maskCredential);
|
|
211
218
|
return encoder.encode(
|
|
212
219
|
`\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`,
|
|
213
220
|
);
|
|
@@ -238,6 +245,8 @@ function boundedBareUpstreamErrorMessage(payload: unknown): string | undefined {
|
|
|
238
245
|
function boundedBareUpstreamError(payload: unknown): {
|
|
239
246
|
message: string;
|
|
240
247
|
refusalCode: string | undefined;
|
|
248
|
+
errorType: string | undefined;
|
|
249
|
+
errorCode: string | undefined;
|
|
241
250
|
} | undefined {
|
|
242
251
|
const root = asJsonRecord(payload);
|
|
243
252
|
if (!root || root.type !== "error") return undefined;
|
|
@@ -247,21 +256,30 @@ function boundedBareUpstreamError(payload: unknown): {
|
|
|
247
256
|
// that supplied the message also supplies the verdict. Taking the FIRST code
|
|
248
257
|
// rather than searching for a refusal is what stops a refusal nested below a
|
|
249
258
|
// transient one from overruling it.
|
|
250
|
-
const
|
|
259
|
+
const candidates = [
|
|
251
260
|
asJsonRecord(root.error),
|
|
252
261
|
asJsonRecord(root.last_error),
|
|
253
262
|
asJsonRecord(response?.error),
|
|
254
263
|
asJsonRecord(response?.incomplete_details),
|
|
255
264
|
root,
|
|
256
|
-
]
|
|
257
|
-
|
|
258
|
-
.find(candidate => candidate !== undefined);
|
|
265
|
+
];
|
|
266
|
+
const code = candidates.map(candidate => stringField(candidate, "code"))
|
|
267
|
+
.find(candidate => candidate !== undefined)?.slice(0, 128);
|
|
268
|
+
// Root `type` is the SSE event discriminator (`"error"`), not an error class. Only nested
|
|
269
|
+
// error records can authoritatively name classes such as rate_limit_error or server_error.
|
|
270
|
+
const errorType = candidates.slice(0, -1).map(candidate => stringField(candidate, "type"))
|
|
271
|
+
.find(candidate => candidate !== undefined)?.slice(0, 128);
|
|
259
272
|
const refusalCode = code !== undefined
|
|
260
273
|
? (isTerminalRefusalCode(code) ? code : undefined)
|
|
261
274
|
: message === undefined ? undefined : safetyRefusalCodeFromMessage(message);
|
|
262
|
-
if (message !== undefined) return { message, refusalCode };
|
|
275
|
+
if (message !== undefined) return { message, refusalCode, errorType, errorCode: code };
|
|
263
276
|
if (refusalCode === undefined) return undefined;
|
|
264
|
-
return {
|
|
277
|
+
return {
|
|
278
|
+
message: terminalRefusalFallbackMessage(refusalCode),
|
|
279
|
+
refusalCode,
|
|
280
|
+
errorType,
|
|
281
|
+
errorCode: code,
|
|
282
|
+
};
|
|
265
283
|
}
|
|
266
284
|
|
|
267
285
|
export type SseTerminalOutputBoundary = {
|
|
@@ -271,9 +289,30 @@ export type SseTerminalOutputBoundary = {
|
|
|
271
289
|
doneSeen(): boolean;
|
|
272
290
|
upstreamError(): string | undefined;
|
|
273
291
|
upstreamRefusalCode(): string | undefined;
|
|
292
|
+
upstreamErrorType(): string | undefined;
|
|
293
|
+
upstreamErrorCode(): string | undefined;
|
|
274
294
|
dispose(): void;
|
|
275
295
|
};
|
|
276
296
|
|
|
297
|
+
export class SseAggregateLimitError extends Error {
|
|
298
|
+
readonly maxBytes: number;
|
|
299
|
+
|
|
300
|
+
constructor(maxBytes: number) {
|
|
301
|
+
super(`upstream SSE transcript exceeded ${maxBytes} bytes`);
|
|
302
|
+
this.name = "SseAggregateLimitError";
|
|
303
|
+
this.maxBytes = maxBytes;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export type SseTerminalOutputBoundaryOptions = CodexSafetyBufferingFilterOptions & {
|
|
308
|
+
/** Optional raw bytes admitted before the first terminal; omitted keeps streaming behavior. */
|
|
309
|
+
maxInputBytes?: number;
|
|
310
|
+
/** Optional client-facing bytes emitted through the first terminal. */
|
|
311
|
+
maxOutputBytes?: number;
|
|
312
|
+
/** Optional aggregate frame count through the first terminal. */
|
|
313
|
+
maxFrames?: number;
|
|
314
|
+
};
|
|
315
|
+
|
|
277
316
|
/**
|
|
278
317
|
* Frame-aware client output boundary shared by both native Responses relays.
|
|
279
318
|
* It buffers only the current incomplete SSE block under the same hard byte
|
|
@@ -282,9 +321,21 @@ export type SseTerminalOutputBoundary = {
|
|
|
282
321
|
* a terminal arrives so clean EOF can synthesize one terminal and one sentinel.
|
|
283
322
|
*/
|
|
284
323
|
export function createSseTerminalOutputBoundary(
|
|
285
|
-
options?:
|
|
324
|
+
options?: SseTerminalOutputBoundaryOptions,
|
|
286
325
|
): SseTerminalOutputBoundary {
|
|
287
326
|
const dropSafetyBuffering = options?.dropCodexSafetyBuffering === true;
|
|
327
|
+
const maxInputBytes = options?.maxInputBytes;
|
|
328
|
+
const maxOutputBytes = options?.maxOutputBytes;
|
|
329
|
+
const maxFrames = options?.maxFrames;
|
|
330
|
+
if (maxInputBytes !== undefined && (!Number.isSafeInteger(maxInputBytes) || maxInputBytes <= 0)) {
|
|
331
|
+
throw new RangeError("maxInputBytes must be a positive safe integer");
|
|
332
|
+
}
|
|
333
|
+
if (maxOutputBytes !== undefined && (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0)) {
|
|
334
|
+
throw new RangeError("maxOutputBytes must be a positive safe integer");
|
|
335
|
+
}
|
|
336
|
+
if (maxFrames !== undefined && (!Number.isSafeInteger(maxFrames) || maxFrames <= 0)) {
|
|
337
|
+
throw new RangeError("maxFrames must be a positive safe integer");
|
|
338
|
+
}
|
|
288
339
|
const decoder = new TextDecoder();
|
|
289
340
|
const encoder = new TextEncoder();
|
|
290
341
|
const framer = new BoundedSseFrameBuffer(MAX_INSPECTION_SSE_FRAME_BYTES);
|
|
@@ -294,12 +345,30 @@ export function createSseTerminalOutputBoundary(
|
|
|
294
345
|
let disposed = false;
|
|
295
346
|
let upstreamError: string | undefined;
|
|
296
347
|
let upstreamRefusalCode: string | undefined;
|
|
348
|
+
let upstreamErrorType: string | undefined;
|
|
349
|
+
let upstreamErrorCode: string | undefined;
|
|
350
|
+
let inputBytes = 0;
|
|
351
|
+
let outputBytes = 0;
|
|
352
|
+
let framesSeen = 0;
|
|
297
353
|
|
|
298
354
|
const processFrames = (
|
|
299
355
|
frames: ReturnType<BoundedSseFrameBuffer["feed"]>,
|
|
300
356
|
): Uint8Array => {
|
|
301
357
|
if (disposed || terminal || frames.length === 0) return EMPTY_BYTES;
|
|
358
|
+
if (maxFrames !== undefined && frames.length > maxFrames - framesSeen) {
|
|
359
|
+
throw new SseFrameCountLimitError(maxFrames);
|
|
360
|
+
}
|
|
361
|
+
framesSeen += frames.length;
|
|
302
362
|
const output: Uint8Array[] = [];
|
|
363
|
+
const appendOutput = (...parts: Uint8Array[]): void => {
|
|
364
|
+
for (const part of parts) {
|
|
365
|
+
if (maxOutputBytes !== undefined && part.byteLength > maxOutputBytes - outputBytes) {
|
|
366
|
+
throw new SseAggregateLimitError(maxOutputBytes);
|
|
367
|
+
}
|
|
368
|
+
outputBytes += part.byteLength;
|
|
369
|
+
output.push(part);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
303
372
|
let responsesTerminal = false;
|
|
304
373
|
for (const frame of frames) {
|
|
305
374
|
const payload = sseDataPayload(decoder.decode(frame.block));
|
|
@@ -311,6 +380,8 @@ export function createSseTerminalOutputBoundary(
|
|
|
311
380
|
if (bare !== undefined) {
|
|
312
381
|
upstreamError = bare.message;
|
|
313
382
|
upstreamRefusalCode = bare.refusalCode;
|
|
383
|
+
upstreamErrorType = bare.errorType;
|
|
384
|
+
upstreamErrorCode = bare.errorCode;
|
|
314
385
|
}
|
|
315
386
|
const safetyBuffering = dropSafetyBuffering && parsed !== undefined
|
|
316
387
|
? codexSafetyBufferingBlockAction(parsed) : "keep";
|
|
@@ -331,7 +402,7 @@ export function createSseTerminalOutputBoundary(
|
|
|
331
402
|
if (isDone) {
|
|
332
403
|
done = true;
|
|
333
404
|
if (responsesTerminal) {
|
|
334
|
-
|
|
405
|
+
appendOutput(outboundBlock, frame.delimiter);
|
|
335
406
|
} else if (!pendingDone) {
|
|
336
407
|
// Do not expose a sentinel before a Responses terminal. If EOF
|
|
337
408
|
// follows, the synthetic incomplete path owns the one sentinel;
|
|
@@ -342,11 +413,11 @@ export function createSseTerminalOutputBoundary(
|
|
|
342
413
|
}
|
|
343
414
|
// Preserve every frame through the first Responses terminal. Every
|
|
344
415
|
// later non-DONE frame is dropped.
|
|
345
|
-
if (!responsesTerminal)
|
|
416
|
+
if (!responsesTerminal) appendOutput(outboundBlock, frame.delimiter);
|
|
346
417
|
if (!responsesTerminal && payload && terminalStatusFromParsed(parsed)) {
|
|
347
418
|
responsesTerminal = true;
|
|
348
419
|
if (pendingDone) {
|
|
349
|
-
|
|
420
|
+
appendOutput(pendingDone.block, pendingDone.delimiter);
|
|
350
421
|
pendingDone = null;
|
|
351
422
|
}
|
|
352
423
|
}
|
|
@@ -361,7 +432,26 @@ export function createSseTerminalOutputBoundary(
|
|
|
361
432
|
return {
|
|
362
433
|
feed(chunk) {
|
|
363
434
|
if (disposed || terminal) return EMPTY_BYTES;
|
|
364
|
-
return processFrames(framer.feed(chunk));
|
|
435
|
+
if (maxInputBytes === undefined) return processFrames(framer.feed(chunk));
|
|
436
|
+
// A fetch implementation may hand us one multi-megabyte chunk containing thousands of
|
|
437
|
+
// complete frames. Slice BEFORE framing so neither the frame array nor the final join can be
|
|
438
|
+
// allocated past the explicit transcript budget. Once a valid terminal appears, trailing
|
|
439
|
+
// bytes in the same network chunk retain the ordinary terminal-authoritative behavior.
|
|
440
|
+
const slices: Uint8Array[] = [];
|
|
441
|
+
for (let offset = 0; offset < chunk.byteLength && !terminal;) {
|
|
442
|
+
const remaining = maxInputBytes - inputBytes;
|
|
443
|
+
if (remaining <= 0) throw new SseAggregateLimitError(maxInputBytes);
|
|
444
|
+
const length = Math.min(64 * 1024, remaining, chunk.byteLength - offset);
|
|
445
|
+
const slice = chunk.subarray(offset, offset + length);
|
|
446
|
+
inputBytes += slice.byteLength;
|
|
447
|
+
offset += length;
|
|
448
|
+
const processed = processFrames(framer.feed(slice));
|
|
449
|
+
if (processed.byteLength > 0) slices.push(processed);
|
|
450
|
+
if (!terminal && offset < chunk.byteLength && inputBytes >= maxInputBytes) {
|
|
451
|
+
throw new SseAggregateLimitError(maxInputBytes);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return joinSseFrameBytes(slices);
|
|
365
455
|
},
|
|
366
456
|
finish() {
|
|
367
457
|
if (disposed || terminal) return EMPTY_BYTES;
|
|
@@ -379,6 +469,8 @@ export function createSseTerminalOutputBoundary(
|
|
|
379
469
|
doneSeen: () => done,
|
|
380
470
|
upstreamError: () => upstreamError,
|
|
381
471
|
upstreamRefusalCode: () => upstreamRefusalCode,
|
|
472
|
+
upstreamErrorType: () => upstreamErrorType,
|
|
473
|
+
upstreamErrorCode: () => upstreamErrorCode,
|
|
382
474
|
dispose() {
|
|
383
475
|
if (disposed) return;
|
|
384
476
|
disposed = true;
|
|
@@ -400,7 +492,7 @@ export function relaySseWithFailedTail(
|
|
|
400
492
|
body: ReadableStream<Uint8Array>,
|
|
401
493
|
upstream: AbortController,
|
|
402
494
|
onClientGone?: (reason?: unknown) => void,
|
|
403
|
-
opts?: { upstreamError?: string; terminalBoundary?: CodexSafetyBufferingFilterOptions },
|
|
495
|
+
opts?: { upstreamError?: string; terminalBoundary?: CodexSafetyBufferingFilterOptions; maskCredential?: (text: string) => string },
|
|
404
496
|
): ReadableStream<Uint8Array> {
|
|
405
497
|
const reader = body.getReader();
|
|
406
498
|
const encoder = new TextEncoder();
|
|
@@ -454,6 +546,7 @@ export function relaySseWithFailedTail(
|
|
|
454
546
|
encoder,
|
|
455
547
|
upstreamError,
|
|
456
548
|
terminalBoundary.upstreamRefusalCode(),
|
|
549
|
+
opts?.maskCredential,
|
|
457
550
|
));
|
|
458
551
|
controller.enqueue(doneFrame(encoder));
|
|
459
552
|
}
|
|
@@ -486,8 +579,8 @@ export function relaySseWithFailedTail(
|
|
|
486
579
|
const refusalCode = terminalBoundary.upstreamRefusalCode();
|
|
487
580
|
const refusalMessage = terminalBoundary.upstreamError();
|
|
488
581
|
controller.enqueue(refusalCode !== undefined && refusalMessage !== undefined
|
|
489
|
-
? refusalFailedTailFrame(encoder, refusalMessage, refusalCode)
|
|
490
|
-
: failedTailFrame(encoder, err));
|
|
582
|
+
? refusalFailedTailFrame(encoder, refusalMessage, refusalCode, opts?.maskCredential)
|
|
583
|
+
: failedTailFrame(encoder, err, opts?.maskCredential));
|
|
491
584
|
}
|
|
492
585
|
controller.close();
|
|
493
586
|
} catch { /* client already torn down */ }
|
|
@@ -828,9 +921,15 @@ export function responseWithDeferredRequestLog(
|
|
|
828
921
|
}
|
|
829
922
|
if (!response.body || !contentType.includes("text/event-stream")) {
|
|
830
923
|
if (response.body && (contentType.includes("application/json") || response.status >= 400)) {
|
|
924
|
+
// Some delivery paths already parsed the bounded body while constructing the client JSON.
|
|
925
|
+
// Retain EOF/cancel logging there, but do not materialize and parse the same near-limit
|
|
926
|
+
// response a second time merely for metadata the first pass already published.
|
|
927
|
+
const preinspectedJson = contentType.includes("application/json")
|
|
928
|
+
&& preinspectedJsonResponses.has(response);
|
|
831
929
|
const body = createBoundedResponseLogBody(response.body, {
|
|
832
930
|
json: contentType.includes("application/json"),
|
|
833
|
-
|
|
931
|
+
...(preinspectedJson ? { maxInspectionBytes: 0 } : {}),
|
|
932
|
+
inspect: preinspectedJson ? () => undefined : text => inspectResponseLogJson(logCtx, text),
|
|
834
933
|
finalize: reason => {
|
|
835
934
|
// Preserve wire status; request history follows the adjacent SSE
|
|
836
935
|
// convention for a client cancellation or upstream read failure.
|
|
@@ -890,6 +989,16 @@ export function markNativePassthroughSseResponse(response: Response): Response {
|
|
|
890
989
|
return response;
|
|
891
990
|
}
|
|
892
991
|
|
|
992
|
+
/** Mark bounded JSON whose request-log metadata was already derived from its source events. */
|
|
993
|
+
export function markPreinspectedJsonResponse(response: Response): Response {
|
|
994
|
+
preinspectedJsonResponses.add(response);
|
|
995
|
+
return response;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function isPreinspectedJsonResponse(response: Response): boolean {
|
|
999
|
+
return preinspectedJsonResponses.has(response);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
893
1002
|
export function isNativePassthroughSseResponse(response: Response): boolean {
|
|
894
1003
|
return nativePassthroughSseResponses.has(response);
|
|
895
1004
|
}
|
|
@@ -995,6 +1104,21 @@ export type SseInspectorHandlers = {
|
|
|
995
1104
|
onTerminal?: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void;
|
|
996
1105
|
logCtx?: RequestLogContext;
|
|
997
1106
|
onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void;
|
|
1107
|
+
/** A reconstructed snapshot for any valid terminal kind; unlike persistence, includes failures. */
|
|
1108
|
+
onTerminalResponse?: (
|
|
1109
|
+
status: ResponsesTerminalStatus,
|
|
1110
|
+
response: { id?: unknown; output?: unknown; status?: unknown },
|
|
1111
|
+
) => void;
|
|
1112
|
+
/**
|
|
1113
|
+
* Opt-in reconstruction policy for a caller that must materialize the whole terminal response.
|
|
1114
|
+
* Persistence inspectors omit this and retain their smaller historical caps/merge semantics.
|
|
1115
|
+
*/
|
|
1116
|
+
terminalReconstruction?: {
|
|
1117
|
+
maxItems: number;
|
|
1118
|
+
maxSourceBytes: number;
|
|
1119
|
+
requireCompleteOutputIndices?: boolean;
|
|
1120
|
+
mergeSparseTerminalOutput?: boolean;
|
|
1121
|
+
};
|
|
998
1122
|
/**
|
|
999
1123
|
* Every parsed SSE payload, delivered BEFORE any onCompletedResponse derived from that same
|
|
1000
1124
|
* payload. A caller that must decide on the whole turn -- not just its terminal snapshot --
|
|
@@ -1008,8 +1132,10 @@ export type SseInspectorHandlers = {
|
|
|
1008
1132
|
* An inspector that only hears about parsed events cannot tell "nothing has been emitted"
|
|
1009
1133
|
* from "something was emitted that I could not read", and a replay decision needs that
|
|
1010
1134
|
* difference: an unreadable payload is a payload the caller may already have seen.
|
|
1135
|
+
* The payload lets bounded collectors distinguish the valid `[DONE]` sentinel from malformed
|
|
1136
|
+
* JSON without adding a second SSE parser; existing observers may ignore the argument.
|
|
1011
1137
|
*/
|
|
1012
|
-
onOpaquePayload?: () => void;
|
|
1138
|
+
onOpaquePayload?: (payload: string | null) => void;
|
|
1013
1139
|
onFirstOutput?: () => void;
|
|
1014
1140
|
/**
|
|
1015
1141
|
* Provider-scoped compatibility: persist the completed snapshot under the
|
|
@@ -1021,6 +1147,13 @@ export type SseInspectorHandlers = {
|
|
|
1021
1147
|
|
|
1022
1148
|
type CompletedOutputItem = { item: unknown; sourceBytes: number };
|
|
1023
1149
|
|
|
1150
|
+
function outputBearingSseEvent(event: { type?: unknown; output_index?: unknown } | null): boolean {
|
|
1151
|
+
if (!event || typeof event.type !== "string") return false;
|
|
1152
|
+
if (Object.hasOwn(event, "output_index")) return true;
|
|
1153
|
+
return /^response\.(?:output_item|content_part|output_text|refusal|reasoning_[^.]+|function_call_arguments|custom_tool_call_input|image_generation_call|code_interpreter_call|file_search_call|web_search_call|computer_call|tool_search_call)\./
|
|
1154
|
+
.test(event.type);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1024
1157
|
function delimiterLengthAt(
|
|
1025
1158
|
index: number,
|
|
1026
1159
|
length: number,
|
|
@@ -1071,12 +1204,24 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1071
1204
|
let candidateBytes = 0;
|
|
1072
1205
|
let discardingOversizedFrame = false;
|
|
1073
1206
|
const reportFirstOutput = createFirstOutputReporter(handlers.onFirstOutput);
|
|
1074
|
-
|
|
1075
|
-
|
|
1207
|
+
const reconstructionPolicy = handlers.terminalReconstruction;
|
|
1208
|
+
if (reconstructionPolicy
|
|
1209
|
+
&& (!Number.isSafeInteger(reconstructionPolicy.maxItems) || reconstructionPolicy.maxItems <= 0
|
|
1210
|
+
|| !Number.isSafeInteger(reconstructionPolicy.maxSourceBytes) || reconstructionPolicy.maxSourceBytes <= 0)) {
|
|
1211
|
+
throw new RangeError("terminal reconstruction limits must be positive safe integers");
|
|
1212
|
+
}
|
|
1213
|
+
const maxCompletedItems = reconstructionPolicy?.maxItems ?? MAX_COMPLETED_OUTPUT_ITEMS;
|
|
1214
|
+
const maxCompletedItemSourceBytes = reconstructionPolicy?.maxSourceBytes
|
|
1215
|
+
?? MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES;
|
|
1216
|
+
// Allocate reconstruction state only for snapshot-capable inspectors.
|
|
1217
|
+
const completedItemsByOutputIndex = handlers.onCompletedResponse || handlers.onTerminalResponse
|
|
1076
1218
|
? new Map<number, CompletedOutputItem>()
|
|
1077
1219
|
: null;
|
|
1078
1220
|
let aggregateItemBytes = 0;
|
|
1079
1221
|
let reconstructionTainted = false;
|
|
1222
|
+
const observedOutputIndices = reconstructionPolicy?.requireCompleteOutputIndices
|
|
1223
|
+
? new Set<number>()
|
|
1224
|
+
: null;
|
|
1080
1225
|
let firstResponseId: string | undefined;
|
|
1081
1226
|
|
|
1082
1227
|
const clearFrameState = (): void => {
|
|
@@ -1088,6 +1233,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1088
1233
|
|
|
1089
1234
|
const clearCompletedItems = (): void => {
|
|
1090
1235
|
completedItemsByOutputIndex?.clear();
|
|
1236
|
+
observedOutputIndices?.clear();
|
|
1091
1237
|
aggregateItemBytes = 0;
|
|
1092
1238
|
reconstructionTainted = false;
|
|
1093
1239
|
};
|
|
@@ -1151,18 +1297,26 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1151
1297
|
const retainCompletedItem = (index: number, item: unknown, sourceBytes: number): void => {
|
|
1152
1298
|
const previous = completedItemsByOutputIndex!.get(index);
|
|
1153
1299
|
if (previous) {
|
|
1300
|
+
if (reconstructionPolicy?.mergeSparseTerminalOutput === true) {
|
|
1301
|
+
// Repeated identical done frames are harmless, but a later frame must never rewrite an
|
|
1302
|
+
// already-completed index. The strict buffered path treats that contradiction as taint.
|
|
1303
|
+
if (nativeResponseFingerprint(previous.item) !== nativeResponseFingerprint(item)) {
|
|
1304
|
+
reconstructionTainted = true;
|
|
1305
|
+
}
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1154
1308
|
aggregateItemBytes -= previous.sourceBytes;
|
|
1155
1309
|
completedItemsByOutputIndex!.delete(index);
|
|
1156
1310
|
}
|
|
1157
|
-
if (sourceBytes >
|
|
1311
|
+
if (sourceBytes > maxCompletedItemSourceBytes) {
|
|
1158
1312
|
reconstructionTainted = true;
|
|
1159
1313
|
inspectionCounters.itemCapEvictions += 1;
|
|
1160
1314
|
return;
|
|
1161
1315
|
}
|
|
1162
1316
|
completedItemsByOutputIndex!.set(index, { item, sourceBytes });
|
|
1163
1317
|
aggregateItemBytes += sourceBytes;
|
|
1164
|
-
while (completedItemsByOutputIndex!.size >
|
|
1165
|
-
|| aggregateItemBytes >
|
|
1318
|
+
while (completedItemsByOutputIndex!.size > maxCompletedItems
|
|
1319
|
+
|| aggregateItemBytes > maxCompletedItemSourceBytes) {
|
|
1166
1320
|
let highestIndex = -1;
|
|
1167
1321
|
for (const retainedIndex of completedItemsByOutputIndex!.keys()) {
|
|
1168
1322
|
if (retainedIndex > highestIndex) highestIndex = retainedIndex;
|
|
@@ -1201,38 +1355,30 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1201
1355
|
// The other half of the same observation. A payload that did not parse still reached the
|
|
1202
1356
|
// caller, so a consumer deciding whether anything has been emitted has to hear about it.
|
|
1203
1357
|
if (handlers.onOpaquePayload && parsed === undefined) {
|
|
1204
|
-
try { handlers.onOpaquePayload(); } catch { /* inspection must never throw into the pump */ }
|
|
1358
|
+
try { handlers.onOpaquePayload(payload); } catch { /* inspection must never throw into the pump */ }
|
|
1205
1359
|
}
|
|
1206
1360
|
reportFirstOutput.parsed(parsed);
|
|
1207
1361
|
const status = terminalStatusFromParsed(parsed);
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
handlers.onTerminal(status, policyTerminal ? 400 : undefined);
|
|
1220
|
-
} finally {
|
|
1221
|
-
if (status === "failed" || status === "incomplete") clearCompletedItems();
|
|
1362
|
+
type ParsedSseEvent = { type?: unknown; output_index?: unknown; item?: unknown; response?: unknown };
|
|
1363
|
+
const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1364
|
+
? parsed as ParsedSseEvent
|
|
1365
|
+
: null;
|
|
1366
|
+
if (observedOutputIndices && outputBearingSseEvent(parsedEvent)) {
|
|
1367
|
+
const outputIndex = parsedEvent?.output_index;
|
|
1368
|
+
if (Number.isInteger(outputIndex) && (outputIndex as number) >= 0
|
|
1369
|
+
&& (outputIndex as number) < maxCompletedItems) {
|
|
1370
|
+
observedOutputIndices.add(outputIndex as number);
|
|
1371
|
+
} else {
|
|
1372
|
+
reconstructionTainted = true;
|
|
1222
1373
|
}
|
|
1223
|
-
} else if (status === "failed" || status === "incomplete") {
|
|
1224
|
-
clearCompletedItems();
|
|
1225
1374
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1229
|
-
? parsed as ParsedSseEvent
|
|
1230
|
-
: null;
|
|
1375
|
+
let reconstructedTerminalResponse: { id?: unknown; output?: unknown; status?: unknown } | null = null;
|
|
1376
|
+
if (completedItemsByOutputIndex) {
|
|
1231
1377
|
const responseRecord = parsedEvent
|
|
1232
1378
|
&& typeof parsedEvent.response === "object"
|
|
1233
1379
|
&& parsedEvent.response !== null
|
|
1234
1380
|
&& !Array.isArray(parsedEvent.response)
|
|
1235
|
-
? parsedEvent.response as { id?: unknown }
|
|
1381
|
+
? parsedEvent.response as { id?: unknown; output?: unknown; status?: unknown }
|
|
1236
1382
|
: null;
|
|
1237
1383
|
if (handlers.pinCompletedResponseIdToFirstSeen
|
|
1238
1384
|
&& responseRecord
|
|
@@ -1244,46 +1390,86 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1244
1390
|
&& doneItem !== undefined
|
|
1245
1391
|
&& Number.isInteger(parsedEvent.output_index)
|
|
1246
1392
|
&& (parsedEvent.output_index as number) >= 0
|
|
1393
|
+
&& (!observedOutputIndices || (parsedEvent.output_index as number) < maxCompletedItems)
|
|
1247
1394
|
&& typeof doneItem === "object"
|
|
1248
1395
|
&& doneItem !== null
|
|
1249
1396
|
&& !Array.isArray(doneItem)
|
|
1250
1397
|
&& typeof (doneItem as { type?: unknown }).type === "string") {
|
|
1251
1398
|
retainCompletedItem(parsedEvent.output_index as number, doneItem, sourceBytes);
|
|
1252
1399
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
if (response) {
|
|
1400
|
+
if (status && responseRecord) {
|
|
1401
|
+
reconstructedTerminalResponse = responseRecord;
|
|
1256
1402
|
if (handlers.pinCompletedResponseIdToFirstSeen
|
|
1257
1403
|
&& firstResponseId !== undefined
|
|
1258
|
-
&&
|
|
1259
|
-
|
|
1404
|
+
&& reconstructedTerminalResponse.id !== firstResponseId) {
|
|
1405
|
+
reconstructedTerminalResponse = { ...reconstructedTerminalResponse, id: firstResponseId };
|
|
1260
1406
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
if (!hasAuthoritativeOutput && reconstructionTainted) {
|
|
1268
|
-
clearCompletedItems();
|
|
1269
|
-
return;
|
|
1407
|
+
const explicitTerminalOutput = reconstructedTerminalResponse.output;
|
|
1408
|
+
const terminalOutput = Array.isArray(explicitTerminalOutput)
|
|
1409
|
+
? explicitTerminalOutput
|
|
1410
|
+
: null;
|
|
1411
|
+
if (explicitTerminalOutput !== undefined && explicitTerminalOutput !== null && !terminalOutput) {
|
|
1412
|
+
reconstructionTainted = true;
|
|
1270
1413
|
}
|
|
1271
|
-
if (
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1414
|
+
if (terminalOutput && terminalOutput.length > maxCompletedItems) reconstructionTainted = true;
|
|
1415
|
+
const hasAuthoritativeOutput = terminalOutput !== null && terminalOutput.length > 0;
|
|
1416
|
+
if (reconstructionPolicy?.mergeSparseTerminalOutput === true) {
|
|
1417
|
+
if (terminalOutput?.some(item => !item || typeof item !== "object" || Array.isArray(item)
|
|
1418
|
+
|| typeof (item as { type?: unknown }).type !== "string")) reconstructionTainted = true;
|
|
1419
|
+
let output: Record<string, unknown>[] = [];
|
|
1420
|
+
if (!reconstructionTainted) {
|
|
1421
|
+
try {
|
|
1422
|
+
output = nativeResponseOutput(
|
|
1423
|
+
new Map([...completedItemsByOutputIndex.entries()].map(([index, retained]) => [
|
|
1424
|
+
index,
|
|
1425
|
+
retained.item as Record<string, unknown>,
|
|
1426
|
+
])),
|
|
1427
|
+
terminalOutput,
|
|
1428
|
+
);
|
|
1429
|
+
} catch {
|
|
1430
|
+
reconstructionTainted = true;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
if (output.length > maxCompletedItems) reconstructionTainted = true;
|
|
1434
|
+
for (const index of observedOutputIndices ?? []) {
|
|
1435
|
+
if (index >= output.length) reconstructionTainted = true;
|
|
1436
|
+
}
|
|
1437
|
+
reconstructedTerminalResponse = reconstructionTainted
|
|
1438
|
+
? null
|
|
1439
|
+
: { ...reconstructedTerminalResponse, output };
|
|
1440
|
+
} else if (!hasAuthoritativeOutput && reconstructionTainted) {
|
|
1441
|
+
reconstructedTerminalResponse = null;
|
|
1442
|
+
} else if (!hasAuthoritativeOutput && completedItemsByOutputIndex.size > 0) {
|
|
1443
|
+
reconstructedTerminalResponse = {
|
|
1444
|
+
...reconstructedTerminalResponse,
|
|
1445
|
+
output: [...completedItemsByOutputIndex.entries()]
|
|
1275
1446
|
.sort(([left], [right]) => left - right)
|
|
1276
1447
|
.map(([, retained]) => retained.item),
|
|
1277
1448
|
};
|
|
1278
1449
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const policyTerminal = status === "failed"
|
|
1453
|
+
&& isPolicyRewriteType(parsed)
|
|
1454
|
+
&& cyberPolicyTerminalError(parsed) !== undefined;
|
|
1455
|
+
if (status) sawTerminal = true;
|
|
1456
|
+
try {
|
|
1457
|
+
if (!reported && handlers.onTerminal && status) {
|
|
1458
|
+
reported = true;
|
|
1459
|
+
if (handlers.logCtx) {
|
|
1460
|
+
handlers.logCtx.transportPhase = "terminal_sse";
|
|
1461
|
+
handlers.logCtx.terminalSource = "upstream";
|
|
1283
1462
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1463
|
+
handlers.onTerminal(status, policyTerminal ? 400 : undefined);
|
|
1464
|
+
}
|
|
1465
|
+
if (status === "completed" && reconstructedTerminalResponse && handlers.onCompletedResponse) {
|
|
1466
|
+
handlers.onCompletedResponse(reconstructedTerminalResponse);
|
|
1286
1467
|
}
|
|
1468
|
+
if (status && reconstructedTerminalResponse && handlers.onTerminalResponse) {
|
|
1469
|
+
handlers.onTerminalResponse(status, reconstructedTerminalResponse);
|
|
1470
|
+
}
|
|
1471
|
+
} finally {
|
|
1472
|
+
if (status) clearCompletedItems();
|
|
1287
1473
|
}
|
|
1288
1474
|
};
|
|
1289
1475
|
|
|
@@ -1294,7 +1480,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
1294
1480
|
}
|
|
1295
1481
|
const sourceBytes = candidateBytes;
|
|
1296
1482
|
const frame = takeCandidate();
|
|
1297
|
-
if (reported && !handlers.onCompletedResponse) return;
|
|
1483
|
+
if (reported && !handlers.onCompletedResponse && !handlers.onTerminalResponse) return;
|
|
1298
1484
|
const decoded = decoder!.decode(frame);
|
|
1299
1485
|
scanPayload(sseDataPayload(decoded), sourceBytes);
|
|
1300
1486
|
};
|
|
@@ -35,6 +35,7 @@ import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
|
35
35
|
import {
|
|
36
36
|
ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
37
37
|
rotateAnthropicAccountOn429,
|
|
38
|
+
recordAnthropicAccount429,
|
|
38
39
|
getAnthropicPoolAccessSnapshot,
|
|
39
40
|
formatAnthropicProviderForLog,
|
|
40
41
|
} from "../../oauth/anthropic-routing";
|
|
@@ -425,6 +426,11 @@ export function createAdapterContinuations(
|
|
|
425
426
|
}
|
|
426
427
|
}
|
|
427
428
|
}
|
|
429
|
+
if (response.status === 429 && transportState.anthropicPoolAccountId
|
|
430
|
+
&& transportState.anthropicPoolFailovers >= ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST) {
|
|
431
|
+
recordAnthropicAccount429(config, transportState.anthropicPoolAccountId,
|
|
432
|
+
response.headers.get("retry-after"), Date.now(), response.headers);
|
|
433
|
+
}
|
|
428
434
|
// Generic OAuth rotation for the continuation loop. The streaming loop grew this arm with
|
|
429
435
|
// #2568 and this one did not, so an xAI/Cursor/Kimi/Copilot/Antigravity/Nous continuation
|
|
430
436
|
// 429 stayed terminal even with failover fully active -- the same class of divergence the
|