@coseung2/opencodex 2.8.0-cs.13 → 2.8.0-cs.14
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/index-MUpaVatk.js +67 -0
- package/gui/dist/index.html +1 -1
- package/package.json +3 -3
- package/packages/ocx-notch/README.md +2 -1
- package/src/adapters/cursor/discovery.ts +6 -2
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/google-antigravity-replay.ts +24 -0
- package/src/adapters/google.ts +16 -11
- package/src/chat/inbound.ts +5 -11
- package/src/cli/account-api.ts +9 -1
- package/src/cli/account-extended.ts +4 -1
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +12 -1
- package/src/codex/account-namespaces.ts +21 -0
- package/src/codex/account-priority.ts +49 -0
- package/src/codex/account-store.ts +2 -1
- package/src/codex/auth-api.ts +108 -17
- package/src/codex/auth-context.ts +61 -16
- package/src/codex/catalog/metadata.ts +34 -12
- package/src/codex/catalog/parsing.ts +8 -1
- package/src/codex/catalog/provider-fetch.ts +24 -6
- package/src/codex/catalog.ts +1 -1
- package/src/codex/pool-rotation.ts +51 -4
- package/src/codex/quota.ts +154 -35
- package/src/codex/routing.ts +133 -33
- package/src/codex/warmup.ts +193 -85
- package/src/config.ts +84 -1
- package/src/lib/bounded-body.ts +13 -6
- package/src/lib/bun-stream-caps.ts +5 -6
- package/src/lib/redact.ts +13 -0
- package/src/oauth/index.ts +79 -12
- package/src/oauth/log.ts +3 -1
- package/src/oauth/store.ts +31 -8
- package/src/providers/antigravity-models.ts +53 -24
- package/src/providers/codex-capacity.ts +303 -0
- package/src/providers/model-rename-migration.ts +147 -0
- package/src/providers/model-rename-startup.ts +29 -0
- package/src/providers/quota.ts +126 -16
- package/src/providers/registry.ts +258 -38
- package/src/responses/parser.ts +19 -12
- package/src/responses/spill-store.ts +14 -1
- package/src/responses/state.ts +108 -14
- package/src/server/index.ts +9 -1
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +8 -1
- package/src/server/relay.ts +10 -42
- package/src/server/request-log.ts +42 -1
- package/src/server/responses/compact.ts +16 -4
- package/src/server/responses/core.ts +217 -59
- package/src/server/responses/empty-completion-guard.ts +275 -0
- package/src/server/responses/encrypted-payload.ts +54 -39
- package/src/server/responses/fetch-helpers.ts +24 -3
- package/src/server/responses/ws-upstream.ts +318 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/ws-bridge.ts +17 -11
- package/src/types.ts +8 -0
- package/src/usage/log.ts +24 -0
- package/src/usage/summary.ts +152 -2
- package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
- package/gui/dist/assets/index-BucjyD4I.js +0 -67
|
@@ -3,7 +3,6 @@ import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type Resp
|
|
|
3
3
|
import { formatPassthroughUpstreamError } from "./passthrough-error";
|
|
4
4
|
import { describeUpstreamConnectFailure } from "./upstream-error";
|
|
5
5
|
import {
|
|
6
|
-
getConfigPath,
|
|
7
6
|
multiAgentGuidanceEnabled,
|
|
8
7
|
resolveEnvValue,
|
|
9
8
|
} from "../../config";
|
|
@@ -42,6 +41,7 @@ import {
|
|
|
42
41
|
getOAuthCredentialProjectId,
|
|
43
42
|
getValidAccessTokenForAccount,
|
|
44
43
|
getValidAccessTokenSnapshot,
|
|
44
|
+
publicOAuthAuthenticationErrorMessage,
|
|
45
45
|
type OAuthAccessSnapshot,
|
|
46
46
|
UnsupportedOAuthProviderError,
|
|
47
47
|
} from "../../oauth";
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import { buildWebSearchTool, planWebSearch, runWithWebSearch, shouldResolveOpenAiWebSearchSidecar } from "../../web-search";
|
|
61
61
|
import { buildImageTool, buildVideoTool, planImageBridge, planVideoBridge, runWithImageBridge, clampImageMaxRounds, IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "../../images";
|
|
62
62
|
import { describeImagesInPlace, planVisionSidecar, shouldResolveOpenAiVisionSidecar, stripImagesInPlace } from "../../vision";
|
|
63
|
-
import { createAdapterEventQueue, preflightAdapterEvents } from "../../adapters/run-turn-queue";
|
|
63
|
+
import { createAdapterEventQueue, preflightAdapterEvents, type AdapterEventQueue } from "../../adapters/run-turn-queue";
|
|
64
64
|
import {
|
|
65
65
|
applyCodexAuthContextToProvider,
|
|
66
66
|
CodexAccountCooldownError,
|
|
@@ -78,9 +78,11 @@ import {
|
|
|
78
78
|
stripCodexRuntimeProviderFields,
|
|
79
79
|
type CodexAuthContext,
|
|
80
80
|
} from "../../codex/auth-context";
|
|
81
|
+
import { codexAuthContextLogLabel } from "../../codex/account-label";
|
|
81
82
|
import {
|
|
82
83
|
computeQuotaCooldown,
|
|
83
84
|
formatCodexProviderForLog,
|
|
85
|
+
getPoolAccountPlan,
|
|
84
86
|
previewCodexAccountForRequest,
|
|
85
87
|
recordCodexUpstreamOutcome,
|
|
86
88
|
type CodexUpstreamOutcome,
|
|
@@ -161,7 +163,9 @@ import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/cat
|
|
|
161
163
|
import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
|
|
162
164
|
import { hasUnreadableEncryptedAgentTask, looksLikeBackendCiphertext, sanitizeEncryptedContentInPlace } from "./encrypted-payload";
|
|
163
165
|
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel } from "./fetch-helpers";
|
|
166
|
+
import { isCodexWsUpstreamResponse, type BunRuntimeGateInput } from "./ws-upstream";
|
|
164
167
|
import { guardTerminalEventStream } from "./terminal-guard";
|
|
168
|
+
import { emptyCompletionRetryEnabled, guardEmptyCompletionEventStream } from "./empty-completion-guard";
|
|
165
169
|
|
|
166
170
|
/**
|
|
167
171
|
* Adapters whose continuation state must survive Codex's store:false requests.
|
|
@@ -178,7 +182,9 @@ export function sidecarOutcomeRecorder(
|
|
|
178
182
|
return authCtx.kind === "pool" || authCtx.kind === "main-pool"
|
|
179
183
|
? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
180
184
|
threadId,
|
|
185
|
+
fixedAccount: authCtx.fixedAccount,
|
|
181
186
|
probeLeaseId: authCtx.probeLeaseId,
|
|
187
|
+
probeQuotaScope: authCtx.probeQuotaScope,
|
|
182
188
|
writerGeneration: authCtx.writerGeneration,
|
|
183
189
|
})
|
|
184
190
|
: undefined;
|
|
@@ -264,6 +270,7 @@ interface CodexPoolAccountRetryArgs {
|
|
|
264
270
|
// needs the inbound scope or the retry could land on a different wire than the
|
|
265
271
|
// first attempt.
|
|
266
272
|
inboundWire?: InboundWire;
|
|
273
|
+
codexWsRuntimeIdentity?: BunRuntimeGateInput;
|
|
267
274
|
translatorBudget: TranslatorBudget;
|
|
268
275
|
};
|
|
269
276
|
firstAuthCtx: Extract<CodexAuthContext, { kind: "pool" | "main-pool" }>;
|
|
@@ -326,6 +333,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
326
333
|
req, config, route, parsed, logCtx, options, firstAuthCtx, firstResponse,
|
|
327
334
|
outcomeStatus, upstream, connectMs, passthroughEstimate, stream,
|
|
328
335
|
} = args;
|
|
336
|
+
if (firstAuthCtx.fixedAccount) return { kind: "no-alternate" };
|
|
329
337
|
const inboundWire = options.inboundWire ?? "responses";
|
|
330
338
|
let retryAuthCtx: CodexAuthContext | undefined;
|
|
331
339
|
try {
|
|
@@ -353,6 +361,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
353
361
|
firstAuthCtx.accountId,
|
|
354
362
|
firstResponse.headers,
|
|
355
363
|
firstAuthCtx.writerGeneration,
|
|
364
|
+
getPoolAccountPlan(config, firstAuthCtx.accountId),
|
|
356
365
|
);
|
|
357
366
|
}
|
|
358
367
|
if (!shouldDeferCodexResetDerivedCooldown(firstResponse, options.deferCodexResetDerivedCooldown)) {
|
|
@@ -392,6 +401,13 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
392
401
|
retryAuthCtx.accountId,
|
|
393
402
|
config,
|
|
394
403
|
);
|
|
404
|
+
logCtx.accountLogLabel = codexAuthContextLogLabel(retryAuthCtx, config);
|
|
405
|
+
sealRequestAttemptIdentity(
|
|
406
|
+
logCtx.activeAttempt,
|
|
407
|
+
logCtx.provider,
|
|
408
|
+
retryAdapter.name,
|
|
409
|
+
logCtx.accountLogLabel,
|
|
410
|
+
);
|
|
395
411
|
|
|
396
412
|
noteAttemptSend(logCtx.activeAttempt, passthroughEstimate);
|
|
397
413
|
try {
|
|
@@ -405,7 +421,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
405
421
|
upstream.signal,
|
|
406
422
|
connectMs,
|
|
407
423
|
stream,
|
|
408
|
-
providerFetch(route.provider),
|
|
424
|
+
providerFetch(route.provider, options.codexWsRuntimeIdentity),
|
|
409
425
|
);
|
|
410
426
|
return {
|
|
411
427
|
kind: "retried",
|
|
@@ -440,6 +456,7 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
440
456
|
// prior soft-avoid so a healthy account isn't stuck avoided.
|
|
441
457
|
recordCodexUpstreamOutcome(config, authCtx.accountId, 200, {
|
|
442
458
|
threadId,
|
|
459
|
+
fixedAccount: authCtx.fixedAccount,
|
|
443
460
|
modelId,
|
|
444
461
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
445
462
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
@@ -461,6 +478,7 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
461
478
|
: (httpStatusOverride ?? logCtx?.terminalHttpStatus ?? 502);
|
|
462
479
|
recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
463
480
|
threadId,
|
|
481
|
+
fixedAccount: authCtx.fixedAccount,
|
|
464
482
|
modelId,
|
|
465
483
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
466
484
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
@@ -514,6 +532,8 @@ export interface ConsumedComboFailure {
|
|
|
514
532
|
|
|
515
533
|
|
|
516
534
|
export interface HandleResponsesOptions {
|
|
535
|
+
/** Injectable only for transport capability tests; production uses the live Bun identity. */
|
|
536
|
+
codexWsRuntimeIdentity?: BunRuntimeGateInput;
|
|
517
537
|
turnAdmissionLease?: AdmissionLease;
|
|
518
538
|
forceEmptyResponseId?: boolean;
|
|
519
539
|
abortSignal?: AbortSignal;
|
|
@@ -702,6 +722,15 @@ type ResponsesAuthResolution =
|
|
|
702
722
|
| { ok: true; authCtx: CodexAuthContext; headers: Headers }
|
|
703
723
|
| { ok: false; response: Response };
|
|
704
724
|
|
|
725
|
+
type AccountQualifiedRoute = RouteResult & {
|
|
726
|
+
codexAccountId?: string;
|
|
727
|
+
codexAccountNamespace?: string;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
function accountQualifiedRoute(route: RouteResult): AccountQualifiedRoute {
|
|
731
|
+
return route as AccountQualifiedRoute;
|
|
732
|
+
}
|
|
733
|
+
|
|
705
734
|
/**
|
|
706
735
|
* Resolve Codex auth for a route. On unusable contexts, releases any probe lease
|
|
707
736
|
* before returning the 401 (nothing reaches upstream).
|
|
@@ -713,10 +742,16 @@ async function resolveResponsesCodexAuth(
|
|
|
713
742
|
options: HandleResponsesOptions,
|
|
714
743
|
): Promise<ResponsesAuthResolution> {
|
|
715
744
|
try {
|
|
716
|
-
|
|
745
|
+
const qualified = accountQualifiedRoute(route);
|
|
746
|
+
if (route.codexAccountMode === "direct" && qualified.codexAccountId === undefined) {
|
|
747
|
+
validateForwardAdmissionCredential(req.headers, config);
|
|
748
|
+
}
|
|
717
749
|
let authCtx: CodexAuthContext;
|
|
718
750
|
if (route.codexAccountMode) {
|
|
719
|
-
authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
|
|
751
|
+
authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
|
|
752
|
+
accountId: qualified.codexAccountId,
|
|
753
|
+
modelId: route.modelId,
|
|
754
|
+
});
|
|
720
755
|
options.onCodexAuthContextResolved?.(authCtx);
|
|
721
756
|
} else {
|
|
722
757
|
authCtx = { kind: "main", accountId: null };
|
|
@@ -736,7 +771,10 @@ async function resolveResponsesCodexAuth(
|
|
|
736
771
|
};
|
|
737
772
|
} catch (err) {
|
|
738
773
|
if (err instanceof CodexAccountCooldownError) {
|
|
739
|
-
return {
|
|
774
|
+
return {
|
|
775
|
+
ok: false,
|
|
776
|
+
response: cooldownErrorResponse(err, Date.now(), accountQualifiedRoute(route).codexAccountNamespace),
|
|
777
|
+
};
|
|
740
778
|
}
|
|
741
779
|
if (err instanceof CodexThreadAffinityExpiredError) {
|
|
742
780
|
return {
|
|
@@ -968,6 +1006,7 @@ export async function handleComboResponses(
|
|
|
968
1006
|
attempt,
|
|
969
1007
|
childLog.provider,
|
|
970
1008
|
childLog.providerAdapter ?? attempt.adapter,
|
|
1009
|
+
childLog.accountLogLabel,
|
|
971
1010
|
);
|
|
972
1011
|
finishRequestAttempt(attempt, 499, Date.now() - started, childLog.usage);
|
|
973
1012
|
(logCtx.attempts ??= []).push(attempt);
|
|
@@ -1022,6 +1061,7 @@ export async function handleComboResponses(
|
|
|
1022
1061
|
attempt,
|
|
1023
1062
|
childLog.provider,
|
|
1024
1063
|
childLog.providerAdapter ?? attempt.adapter,
|
|
1064
|
+
childLog.accountLogLabel,
|
|
1025
1065
|
);
|
|
1026
1066
|
(logCtx.attempts ??= []).push(attempt);
|
|
1027
1067
|
attemptRetained = true;
|
|
@@ -1066,6 +1106,7 @@ export async function handleComboResponses(
|
|
|
1066
1106
|
attempt,
|
|
1067
1107
|
childLog.provider,
|
|
1068
1108
|
childLog.providerAdapter ?? attempt.adapter,
|
|
1109
|
+
childLog.accountLogLabel,
|
|
1069
1110
|
);
|
|
1070
1111
|
finishRequestAttempt(
|
|
1071
1112
|
attempt,
|
|
@@ -1258,7 +1299,7 @@ async function handleResponsesInner(
|
|
|
1258
1299
|
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
1259
1300
|
(parsed._rawBody as Record<string, unknown>).reasoning = { effort: "low" };
|
|
1260
1301
|
}
|
|
1261
|
-
|
|
1302
|
+
logCtx.shadowCallRewrittenFrom = _sciOriginal;
|
|
1262
1303
|
// Helpers must not resume/append into the parent thread's Cursor conversation.
|
|
1263
1304
|
parsed._cursorIsolateConversation = true;
|
|
1264
1305
|
}
|
|
@@ -1358,6 +1399,9 @@ async function handleResponsesInner(
|
|
|
1358
1399
|
|
|
1359
1400
|
route.provider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
|
|
1360
1401
|
logCtx.provider = formatCodexProviderForLog(route.providerName, codexLogAccountId(authCtx), config);
|
|
1402
|
+
const accountLogLabel = codexAuthContextLogLabel(authCtx, config);
|
|
1403
|
+
if (accountLogLabel) logCtx.accountLogLabel = accountLogLabel;
|
|
1404
|
+
else delete logCtx.accountLogLabel;
|
|
1361
1405
|
// Prefer Codex pool account as the Cursor thread namespace when present. Cursor routes without
|
|
1362
1406
|
// codexAccountMode still get a credential-derived scope inside the Cursor adapter.
|
|
1363
1407
|
const identityScope = codexLogAccountId(authCtx);
|
|
@@ -1422,13 +1466,14 @@ async function handleResponsesInner(
|
|
|
1422
1466
|
}
|
|
1423
1467
|
} catch (err) {
|
|
1424
1468
|
if (err instanceof UnsupportedOAuthProviderError) {
|
|
1469
|
+
const safeProviderName = redactSecretString(route.providerName);
|
|
1425
1470
|
return formatErrorResponse(
|
|
1426
1471
|
400,
|
|
1427
1472
|
"invalid_request_error",
|
|
1428
|
-
`${err.message}. Remove or reconfigure provider '${
|
|
1473
|
+
`${redactSecretString(err.message)}. Remove or reconfigure provider '${safeProviderName}' in the OpenCodex configuration.`,
|
|
1429
1474
|
);
|
|
1430
1475
|
}
|
|
1431
|
-
return formatErrorResponse(401, "authentication_error",
|
|
1476
|
+
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
1432
1477
|
}
|
|
1433
1478
|
}
|
|
1434
1479
|
route.provider = resolveProviderTransport(
|
|
@@ -1440,7 +1485,7 @@ async function handleResponsesInner(
|
|
|
1440
1485
|
const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
1441
1486
|
const adapter = resolveAdapter(adapterProvider, config.cacheRetention);
|
|
1442
1487
|
logCtx.providerAdapter = adapter.name;
|
|
1443
|
-
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name);
|
|
1488
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
|
|
1444
1489
|
const isPassthrough = "passthrough" in adapter && !!adapter.passthrough;
|
|
1445
1490
|
|
|
1446
1491
|
if (adapter.name === "kiro" && parsed.previousResponseId && !parsed._previousResponseInputExpanded) {
|
|
@@ -1585,6 +1630,7 @@ async function handleResponsesInner(
|
|
|
1585
1630
|
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
1586
1631
|
recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
1587
1632
|
threadId: req.headers.get("x-codex-parent-thread-id"),
|
|
1633
|
+
fixedAccount: authCtx.fixedAccount,
|
|
1588
1634
|
modelId: route.modelId,
|
|
1589
1635
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
1590
1636
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
@@ -1607,7 +1653,7 @@ async function handleResponsesInner(
|
|
|
1607
1653
|
method: request.method,
|
|
1608
1654
|
headers: request.headers,
|
|
1609
1655
|
body: request.body,
|
|
1610
|
-
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
1656
|
+
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
|
|
1611
1657
|
},
|
|
1612
1658
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
1613
1659
|
);
|
|
@@ -1617,7 +1663,7 @@ async function handleResponsesInner(
|
|
|
1617
1663
|
request.releaseBodyObservation?.();
|
|
1618
1664
|
}
|
|
1619
1665
|
|
|
1620
|
-
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
1666
|
+
if (usesCodexForwardPoolAuth(authCtx, route.provider) && !authCtx.fixedAccount) {
|
|
1621
1667
|
let poolRetryOutcome: number | undefined;
|
|
1622
1668
|
if (await shouldRetryCodexPoolAccountModel400(
|
|
1623
1669
|
upstreamResponse,
|
|
@@ -1682,15 +1728,18 @@ async function handleResponsesInner(
|
|
|
1682
1728
|
);
|
|
1683
1729
|
const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
|
|
1684
1730
|
// Capture quota from upstream response for multi-account tracking
|
|
1685
|
-
|
|
1686
|
-
//
|
|
1687
|
-
//
|
|
1731
|
+
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
1732
|
+
// The upstream has shipped both primary=5h/secondary=weekly and
|
|
1733
|
+
// primary=weekly/secondary=monthly shapes. Header duration metadata is
|
|
1734
|
+
// parsed by applyAccountQuotaFromUpstreamHeaders, which preserves every
|
|
1735
|
+
// window it can identify instead of assuming a fixed slot meaning.
|
|
1688
1736
|
const quotaMeta = codexQuotaOutcomeMeta(upstreamResponse);
|
|
1689
1737
|
const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
|
|
1690
1738
|
applyAccountQuotaFromUpstreamHeaders(
|
|
1691
1739
|
authCtx.accountId,
|
|
1692
1740
|
upstreamResponse.headers,
|
|
1693
1741
|
authCtx.writerGeneration,
|
|
1742
|
+
getPoolAccountPlan(config, authCtx.accountId),
|
|
1694
1743
|
);
|
|
1695
1744
|
if (terminalBodyWillRecord) {
|
|
1696
1745
|
options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
|
|
@@ -1720,6 +1769,7 @@ async function handleResponsesInner(
|
|
|
1720
1769
|
recordCodexUpstreamOutcome(config, authCtx.accountId, upstreamResponse.status, {
|
|
1721
1770
|
...quotaMeta,
|
|
1722
1771
|
threadId: req.headers.get("x-codex-parent-thread-id"),
|
|
1772
|
+
fixedAccount: authCtx.fixedAccount,
|
|
1723
1773
|
modelId: route.modelId,
|
|
1724
1774
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
1725
1775
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
@@ -1775,7 +1825,12 @@ async function handleResponsesInner(
|
|
|
1775
1825
|
needsClientRewrite,
|
|
1776
1826
|
config.streamMode ?? "auto",
|
|
1777
1827
|
);
|
|
1778
|
-
|
|
1828
|
+
// A successful Codex WS upgrade is push-driven. Keep it out of tee(),
|
|
1829
|
+
// where an inspection branch can outrun a slow client without a bound.
|
|
1830
|
+
const forceCodexWsEagerRelay = isCodexWsUpstreamResponse(upstreamResponse);
|
|
1831
|
+
const inlineEagerRewrite = needsClientRewrite
|
|
1832
|
+
&& (forceCodexWsEagerRelay || eagerPath?.useEagerRelay === true || win32EagerRewrite);
|
|
1833
|
+
if (forceCodexWsEagerRelay || eagerPath?.useEagerRelay || win32EagerRewrite) {
|
|
1779
1834
|
const turn = trackActiveTurnLease(upstream, options.turnAdmissionLease);
|
|
1780
1835
|
const reportNativeTerminal = recordTerminalOutcomes
|
|
1781
1836
|
? (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
|
|
@@ -1811,7 +1866,7 @@ async function handleResponsesInner(
|
|
|
1811
1866
|
finishInspection: () => inspector.finish(),
|
|
1812
1867
|
disposeInspection: () => inspector.dispose(),
|
|
1813
1868
|
sawTerminal: () => inspector.reported(),
|
|
1814
|
-
...(
|
|
1869
|
+
...(inlineEagerRewrite
|
|
1815
1870
|
? { rewritePayload: composeSsePayloadRewrites(...payloadRewrites) }
|
|
1816
1871
|
: {}),
|
|
1817
1872
|
onSynthetic: kind => {
|
|
@@ -1822,12 +1877,13 @@ async function handleResponsesInner(
|
|
|
1822
1877
|
} else {
|
|
1823
1878
|
logCtx.transportPhase = "mid_stream";
|
|
1824
1879
|
logCtx.terminalSource = "synthetic";
|
|
1880
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
|
|
1825
1881
|
reportNativeTerminal("failed", 502);
|
|
1826
1882
|
}
|
|
1827
1883
|
},
|
|
1828
1884
|
onClientCancel: () => options.onNativePassthroughCancel?.(),
|
|
1829
1885
|
onDone: () => turn.finish("inspection_settled"),
|
|
1830
|
-
},
|
|
1886
|
+
}, inlineEagerRewrite ? { rewriteBudget: translatorBudget } : undefined);
|
|
1831
1887
|
// selectEagerPath admits only no-rewrite traffic on both eligible platforms;
|
|
1832
1888
|
// win32 rewrite traffic reaches this relay too, but with the payload rewrite
|
|
1833
1889
|
// applied inline — never via an image/item-id JS pull wrapper (#32111, #864).
|
|
@@ -2013,7 +2069,7 @@ async function handleResponsesInner(
|
|
|
2013
2069
|
: clampImageMaxRounds(config.images?.videoMaxRounds ?? 2),
|
|
2014
2070
|
connectTimeoutMs: config.connectTimeoutMs ?? 200_000,
|
|
2015
2071
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
2016
|
-
fetchImpl: providerFetch(route.provider),
|
|
2072
|
+
fetchImpl: providerFetch(route.provider, options.codexWsRuntimeIdentity),
|
|
2017
2073
|
onRequestBuilt: request => recordAdapterReasoning(logCtx, request),
|
|
2018
2074
|
...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
|
|
2019
2075
|
onUsage: usage => {
|
|
@@ -2124,22 +2180,29 @@ async function handleResponsesInner(
|
|
|
2124
2180
|
return wsResponse;
|
|
2125
2181
|
}
|
|
2126
2182
|
|
|
2183
|
+
const emptyCompletionGuardEnabled = emptyCompletionRetryEnabled(config)
|
|
2184
|
+
&& !options.comboAttempt
|
|
2185
|
+
&& !routedCompaction;
|
|
2186
|
+
|
|
2127
2187
|
if (adapter.runTurn) {
|
|
2128
2188
|
const runTurnAbort = new AbortController();
|
|
2129
2189
|
linkAbortSignal(runTurnAbort, options.abortSignal);
|
|
2130
2190
|
const queue = createAdapterEventQueue({
|
|
2131
2191
|
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
2132
2192
|
});
|
|
2133
|
-
const
|
|
2193
|
+
const runTurnAttempt = async (
|
|
2194
|
+
targetQueue: AdapterEventQueue,
|
|
2195
|
+
recovery?: AttemptRecoveryKind,
|
|
2196
|
+
): Promise<void> => {
|
|
2134
2197
|
try {
|
|
2135
|
-
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
|
|
2198
|
+
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery);
|
|
2136
2199
|
await adapter.runTurn?.(
|
|
2137
2200
|
parsed,
|
|
2138
2201
|
{ headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal, translatorBudget },
|
|
2139
|
-
|
|
2202
|
+
targetQueue.push,
|
|
2140
2203
|
);
|
|
2141
2204
|
} catch (err) {
|
|
2142
|
-
|
|
2205
|
+
targetQueue.push({
|
|
2143
2206
|
type: "error",
|
|
2144
2207
|
message: err instanceof Error ? err.message : String(err),
|
|
2145
2208
|
});
|
|
@@ -2149,9 +2212,17 @@ async function handleResponsesInner(
|
|
|
2149
2212
|
if (!logCtx.conversationId && parsed._cursorConversationId) {
|
|
2150
2213
|
logCtx.conversationId = normalizeLogConversationId(parsed._cursorConversationId);
|
|
2151
2214
|
}
|
|
2152
|
-
|
|
2215
|
+
targetQueue.close();
|
|
2153
2216
|
}
|
|
2154
2217
|
};
|
|
2218
|
+
const runTurn = async (): Promise<void> => runTurnAttempt(queue);
|
|
2219
|
+
const runTurnRetrySource = (): AsyncIterable<AdapterEvent> => {
|
|
2220
|
+
const retryQueue = createAdapterEventQueue({
|
|
2221
|
+
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
2222
|
+
});
|
|
2223
|
+
void runTurnAttempt(retryQueue, "empty-completion");
|
|
2224
|
+
return retryQueue.stream();
|
|
2225
|
+
};
|
|
2155
2226
|
|
|
2156
2227
|
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
2157
2228
|
if (parsed.stream) {
|
|
@@ -2167,8 +2238,14 @@ async function handleResponsesInner(
|
|
|
2167
2238
|
}
|
|
2168
2239
|
eventSource = preflight.stream;
|
|
2169
2240
|
}
|
|
2241
|
+
const guardedSource = emptyCompletionGuardEnabled
|
|
2242
|
+
? guardEmptyCompletionEventStream({
|
|
2243
|
+
firstEvents: eventSource,
|
|
2244
|
+
continuation: runTurnRetrySource,
|
|
2245
|
+
})
|
|
2246
|
+
: eventSource;
|
|
2170
2247
|
const sseStream = bridgeToResponsesSSE(
|
|
2171
|
-
|
|
2248
|
+
guardedSource, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
2172
2249
|
() => {
|
|
2173
2250
|
runTurnAbort.abort();
|
|
2174
2251
|
queue.close();
|
|
@@ -2208,7 +2285,17 @@ async function handleResponsesInner(
|
|
|
2208
2285
|
}
|
|
2209
2286
|
|
|
2210
2287
|
await runTurn();
|
|
2211
|
-
const
|
|
2288
|
+
const firstAttemptEvents = await queue.collect();
|
|
2289
|
+
let events: AdapterEvent[];
|
|
2290
|
+
if (emptyCompletionGuardEnabled) {
|
|
2291
|
+
events = [];
|
|
2292
|
+
for await (const event of guardEmptyCompletionEventStream({
|
|
2293
|
+
firstEvents: (async function* () { yield* firstAttemptEvents; })(),
|
|
2294
|
+
continuation: runTurnRetrySource,
|
|
2295
|
+
})) events.push(event);
|
|
2296
|
+
} else {
|
|
2297
|
+
events = firstAttemptEvents;
|
|
2298
|
+
}
|
|
2212
2299
|
if (options.comboAttempt) {
|
|
2213
2300
|
const firstMeaningful = events.find(event => event.type !== "heartbeat");
|
|
2214
2301
|
if (!firstMeaningful || firstMeaningful.type === "error") {
|
|
@@ -2257,6 +2344,14 @@ async function handleResponsesInner(
|
|
|
2257
2344
|
? request.usageLog.inputTokens
|
|
2258
2345
|
: undefined;
|
|
2259
2346
|
if (inputTokenEstimate !== undefined) logCtx.usageLogInputTokens = inputTokenEstimate;
|
|
2347
|
+
// Empty-completion recovery must resend the exact same serialized request. Keep a
|
|
2348
|
+
// same-target cache and invalidate it whenever credentials, adapter, parsed input,
|
|
2349
|
+
// or image tier changes.
|
|
2350
|
+
let sameTargetRequest: typeof request | undefined = request;
|
|
2351
|
+
let sameTargetParsed: OcxParsedRequest | undefined = parsed;
|
|
2352
|
+
let sameTargetToken = 0;
|
|
2353
|
+
let transportToken = 0;
|
|
2354
|
+
const invalidateSameTargetRequest = (): void => { transportToken += 1; };
|
|
2260
2355
|
let upstreamResponse: Response;
|
|
2261
2356
|
try {
|
|
2262
2357
|
if (activeAdapter.fetchResponse) {
|
|
@@ -2274,7 +2369,7 @@ async function handleResponsesInner(
|
|
|
2274
2369
|
method: request.method,
|
|
2275
2370
|
headers: request.headers,
|
|
2276
2371
|
body: request.body,
|
|
2277
|
-
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
2372
|
+
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
|
|
2278
2373
|
},
|
|
2279
2374
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
2280
2375
|
);
|
|
@@ -2301,18 +2396,26 @@ async function handleResponsesInner(
|
|
|
2301
2396
|
const rebuildAndRefetch = async (
|
|
2302
2397
|
recovery: AttemptRecoveryKind,
|
|
2303
2398
|
): Promise<Response | { failed: Response }> => {
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2399
|
+
let retryRequest: typeof request;
|
|
2400
|
+
if (sameTargetRequest !== undefined && sameTargetParsed === parsed && sameTargetToken === transportToken) {
|
|
2401
|
+
retryRequest = sameTargetRequest;
|
|
2402
|
+
} else {
|
|
2403
|
+
retryRequest = await activeAdapter.buildRequest(parsed, {
|
|
2404
|
+
headers: selectedForwardHeaders,
|
|
2405
|
+
translatorBudget,
|
|
2406
|
+
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
2407
|
+
});
|
|
2408
|
+
recordAdapterReasoning(logCtx, retryRequest);
|
|
2409
|
+
sameTargetRequest = retryRequest;
|
|
2410
|
+
sameTargetParsed = parsed;
|
|
2411
|
+
sameTargetToken = transportToken;
|
|
2412
|
+
}
|
|
2310
2413
|
const retryEstimate = typeof retryRequest.usageLog?.inputTokens === "number"
|
|
2311
2414
|
? retryRequest.usageLog.inputTokens
|
|
2312
2415
|
: undefined;
|
|
2313
2416
|
if (retryEstimate !== undefined) logCtx.usageLogInputTokens = retryEstimate;
|
|
2314
2417
|
logCtx.providerAdapter = activeAdapter.name;
|
|
2315
|
-
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
2418
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
2316
2419
|
noteAttemptSend(logCtx.activeAttempt, retryEstimate, recovery);
|
|
2317
2420
|
try {
|
|
2318
2421
|
try {
|
|
@@ -2320,7 +2423,7 @@ async function handleResponsesInner(
|
|
|
2320
2423
|
? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
|
|
2321
2424
|
: await fetchWithHeaderTimeout(retryRequest.url, {
|
|
2322
2425
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
2323
|
-
}, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
2426
|
+
}, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
|
|
2324
2427
|
} finally {
|
|
2325
2428
|
retryRequest.releaseBodyObservation?.();
|
|
2326
2429
|
}
|
|
@@ -2361,6 +2464,7 @@ async function handleResponsesInner(
|
|
|
2361
2464
|
route.providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(route.providerName) : undefined,
|
|
2362
2465
|
);
|
|
2363
2466
|
route.provider = refreshedProvider;
|
|
2467
|
+
invalidateSameTargetRequest();
|
|
2364
2468
|
activeAdapter = resolveAdapter(
|
|
2365
2469
|
resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire),
|
|
2366
2470
|
config.cacheRetention,
|
|
@@ -2386,6 +2490,7 @@ async function handleResponsesInner(
|
|
|
2386
2490
|
// until runtime cleanup (one per rotated key under a rate-limit storm).
|
|
2387
2491
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
2388
2492
|
route.provider = rotated;
|
|
2493
|
+
invalidateSameTargetRequest();
|
|
2389
2494
|
activeAdapter = resolveAdapter(
|
|
2390
2495
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2391
2496
|
config.cacheRetention,
|
|
@@ -2416,13 +2521,14 @@ async function handleResponsesInner(
|
|
|
2416
2521
|
anthropicPoolAccountId = nextAccountId;
|
|
2417
2522
|
anthropicPoolFailovers += 1;
|
|
2418
2523
|
route.provider = { ...route.provider, apiKey: accessToken };
|
|
2524
|
+
invalidateSameTargetRequest();
|
|
2419
2525
|
promoteAnthropicActiveAccount(nextAccountId);
|
|
2420
2526
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
|
|
2421
2527
|
activeAdapter = resolveAdapter(
|
|
2422
2528
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2423
2529
|
config.cacheRetention,
|
|
2424
2530
|
);
|
|
2425
|
-
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
2531
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
2426
2532
|
const result = await rebuildAndRefetch("anthropic-oauth-429");
|
|
2427
2533
|
if ("failed" in result) return result.failed;
|
|
2428
2534
|
upstreamResponse = result;
|
|
@@ -2440,6 +2546,7 @@ async function handleResponsesInner(
|
|
|
2440
2546
|
})) {
|
|
2441
2547
|
imageRetryAttempted = true;
|
|
2442
2548
|
imageTierBias = 1;
|
|
2549
|
+
invalidateSameTargetRequest();
|
|
2443
2550
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
2444
2551
|
const result = await rebuildAndRefetch("image-413");
|
|
2445
2552
|
if ("failed" in result) return result.failed;
|
|
@@ -2487,25 +2594,44 @@ async function handleResponsesInner(
|
|
|
2487
2594
|
// one internal continuation pass. A continuation failure becomes an in-stream adapter error so
|
|
2488
2595
|
// the client never sees a second hidden HTTP response or an unbounded retry loop.
|
|
2489
2596
|
const terminalGuardEnabled = activeAdapter.name === "anthropic" && !options.comboAttempt && !routedCompaction;
|
|
2490
|
-
const fetchTerminalGuardContinuation = async function* (
|
|
2597
|
+
const fetchTerminalGuardContinuation = async function* (
|
|
2598
|
+
nextParsed: OcxParsedRequest,
|
|
2599
|
+
initialRecoveryKind?: AttemptRecoveryKind,
|
|
2600
|
+
): AsyncGenerator<AdapterEvent> {
|
|
2491
2601
|
let imageTierBias = 0;
|
|
2492
2602
|
let response: Response | undefined;
|
|
2603
|
+
let nextRecoveryKind = initialRecoveryKind;
|
|
2493
2604
|
while (true) {
|
|
2605
|
+
const recoveryKind = nextRecoveryKind;
|
|
2606
|
+
nextRecoveryKind = undefined;
|
|
2494
2607
|
try {
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2608
|
+
let continuationRequest: typeof request;
|
|
2609
|
+
if (
|
|
2610
|
+
sameTargetRequest !== undefined
|
|
2611
|
+
&& sameTargetParsed === nextParsed
|
|
2612
|
+
&& sameTargetToken === transportToken
|
|
2613
|
+
) {
|
|
2614
|
+
continuationRequest = sameTargetRequest;
|
|
2615
|
+
} else {
|
|
2616
|
+
continuationRequest = await activeAdapter.buildRequest(nextParsed, {
|
|
2617
|
+
headers: selectedForwardHeaders,
|
|
2618
|
+
translatorBudget,
|
|
2619
|
+
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
2620
|
+
});
|
|
2621
|
+
recordAdapterReasoning(logCtx, continuationRequest);
|
|
2622
|
+
sameTargetRequest = continuationRequest;
|
|
2623
|
+
sameTargetParsed = nextParsed;
|
|
2624
|
+
sameTargetToken = transportToken;
|
|
2625
|
+
}
|
|
2626
|
+
const builtContinuationRequest = continuationRequest;
|
|
2627
|
+
const continuationEstimate = typeof builtContinuationRequest.usageLog?.inputTokens === "number"
|
|
2628
|
+
? builtContinuationRequest.usageLog.inputTokens
|
|
2503
2629
|
: undefined;
|
|
2504
2630
|
if (continuationEstimate !== undefined) logCtx.usageLogInputTokens = continuationEstimate;
|
|
2505
2631
|
try {
|
|
2506
2632
|
if (activeAdapter.fetchResponse) {
|
|
2507
|
-
noteAttemptSend(logCtx.activeAttempt, continuationEstimate);
|
|
2508
|
-
response = await activeAdapter.fetchResponse(
|
|
2633
|
+
noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recoveryKind);
|
|
2634
|
+
response = await activeAdapter.fetchResponse(builtContinuationRequest, {
|
|
2509
2635
|
abortSignal: upstream.signal,
|
|
2510
2636
|
timeoutMs: connectMs,
|
|
2511
2637
|
stream: nextParsed.stream,
|
|
@@ -2513,25 +2639,25 @@ async function handleResponsesInner(
|
|
|
2513
2639
|
} else {
|
|
2514
2640
|
response = await fetchWithResetRetry(
|
|
2515
2641
|
recovery => {
|
|
2516
|
-
noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery);
|
|
2642
|
+
noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery ?? recoveryKind);
|
|
2517
2643
|
return fetchWithHeaderTimeout(
|
|
2518
|
-
|
|
2644
|
+
builtContinuationRequest.url,
|
|
2519
2645
|
applyUpstreamRecoveryInit({
|
|
2520
|
-
method:
|
|
2521
|
-
headers:
|
|
2522
|
-
body:
|
|
2646
|
+
method: builtContinuationRequest.method,
|
|
2647
|
+
headers: builtContinuationRequest.headers,
|
|
2648
|
+
body: builtContinuationRequest.body,
|
|
2523
2649
|
}, recovery),
|
|
2524
2650
|
upstream.signal,
|
|
2525
2651
|
connectMs,
|
|
2526
2652
|
nextParsed.stream,
|
|
2527
|
-
providerFetch(route.provider),
|
|
2653
|
+
providerFetch(route.provider, options.codexWsRuntimeIdentity),
|
|
2528
2654
|
);
|
|
2529
2655
|
},
|
|
2530
|
-
{ abortSignal: upstream.signal, label: safeHostLabel(
|
|
2656
|
+
{ abortSignal: upstream.signal, label: safeHostLabel(builtContinuationRequest.url) },
|
|
2531
2657
|
);
|
|
2532
2658
|
}
|
|
2533
2659
|
} finally {
|
|
2534
|
-
|
|
2660
|
+
builtContinuationRequest.releaseBodyObservation?.();
|
|
2535
2661
|
}
|
|
2536
2662
|
} catch (error) {
|
|
2537
2663
|
if (options.abortSignal?.aborted) {
|
|
@@ -2552,6 +2678,7 @@ async function handleResponsesInner(
|
|
|
2552
2678
|
if (rotated) {
|
|
2553
2679
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
2554
2680
|
route.provider = rotated;
|
|
2681
|
+
invalidateSameTargetRequest();
|
|
2555
2682
|
activeAdapter = resolveAdapter(
|
|
2556
2683
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2557
2684
|
config.cacheRetention,
|
|
@@ -2578,13 +2705,14 @@ async function handleResponsesInner(
|
|
|
2578
2705
|
anthropicPoolAccountId = nextAccountId;
|
|
2579
2706
|
anthropicPoolFailovers += 1;
|
|
2580
2707
|
route.provider = { ...route.provider, apiKey: accessToken };
|
|
2708
|
+
invalidateSameTargetRequest();
|
|
2581
2709
|
promoteAnthropicActiveAccount(nextAccountId);
|
|
2582
2710
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
|
|
2583
2711
|
activeAdapter = resolveAdapter(
|
|
2584
2712
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2585
2713
|
config.cacheRetention,
|
|
2586
2714
|
);
|
|
2587
|
-
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
2715
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
2588
2716
|
continue;
|
|
2589
2717
|
} catch {
|
|
2590
2718
|
// fall through to emit continuation error below
|
|
@@ -2598,6 +2726,7 @@ async function handleResponsesInner(
|
|
|
2598
2726
|
alreadyAttempted: imageTierBias > 0,
|
|
2599
2727
|
})) {
|
|
2600
2728
|
imageTierBias = 1;
|
|
2729
|
+
invalidateSameTargetRequest();
|
|
2601
2730
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
2602
2731
|
continue;
|
|
2603
2732
|
}
|
|
@@ -2639,6 +2768,19 @@ async function handleResponsesInner(
|
|
|
2639
2768
|
}
|
|
2640
2769
|
};
|
|
2641
2770
|
|
|
2771
|
+
const fetchGuardedEmptyCompletionRetry = (): AsyncIterable<AdapterEvent> => {
|
|
2772
|
+
const retryEvents = fetchTerminalGuardContinuation(parsed, "empty-completion");
|
|
2773
|
+
return terminalGuardEnabled
|
|
2774
|
+
? guardTerminalEventStream({
|
|
2775
|
+
parsed,
|
|
2776
|
+
firstEvents: retryEvents,
|
|
2777
|
+
adapterName: activeAdapter.name,
|
|
2778
|
+
maxAutoContinuations: 1,
|
|
2779
|
+
continuation: fetchTerminalGuardContinuation,
|
|
2780
|
+
})
|
|
2781
|
+
: retryEvents;
|
|
2782
|
+
};
|
|
2783
|
+
|
|
2642
2784
|
if (parsed.stream) {
|
|
2643
2785
|
const initialEventStream = activeAdapter.parseStream(upstreamResponse, translatorBudget);
|
|
2644
2786
|
const eventStream = terminalGuardEnabled
|
|
@@ -2650,9 +2792,15 @@ async function handleResponsesInner(
|
|
|
2650
2792
|
continuation: fetchTerminalGuardContinuation,
|
|
2651
2793
|
})
|
|
2652
2794
|
: initialEventStream;
|
|
2795
|
+
const guardedEventStream = emptyCompletionGuardEnabled
|
|
2796
|
+
? guardEmptyCompletionEventStream({
|
|
2797
|
+
firstEvents: eventStream,
|
|
2798
|
+
continuation: fetchGuardedEmptyCompletionRetry,
|
|
2799
|
+
})
|
|
2800
|
+
: eventStream;
|
|
2653
2801
|
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
2654
2802
|
const sseStream = bridgeToResponsesSSE(
|
|
2655
|
-
|
|
2803
|
+
guardedEventStream, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
2656
2804
|
() => upstream.abort(), 2_000,
|
|
2657
2805
|
{
|
|
2658
2806
|
translatorBudget,
|
|
@@ -2694,17 +2842,27 @@ async function handleResponsesInner(
|
|
|
2694
2842
|
let events: AdapterEvent[];
|
|
2695
2843
|
try {
|
|
2696
2844
|
const initialEvents = await activeAdapter.parseResponse(upstreamResponse, translatorBudget);
|
|
2845
|
+
let terminalGuardedEvents: AdapterEvent[];
|
|
2697
2846
|
if (terminalGuardEnabled) {
|
|
2698
|
-
|
|
2847
|
+
terminalGuardedEvents = [];
|
|
2699
2848
|
for await (const event of guardTerminalEventStream({
|
|
2700
2849
|
parsed,
|
|
2701
2850
|
firstEvents: (async function* () { yield* initialEvents; })(),
|
|
2702
2851
|
adapterName: activeAdapter.name,
|
|
2703
2852
|
maxAutoContinuations: 1,
|
|
2704
2853
|
continuation: fetchTerminalGuardContinuation,
|
|
2854
|
+
})) terminalGuardedEvents.push(event);
|
|
2855
|
+
} else {
|
|
2856
|
+
terminalGuardedEvents = initialEvents;
|
|
2857
|
+
}
|
|
2858
|
+
if (emptyCompletionGuardEnabled) {
|
|
2859
|
+
events = [];
|
|
2860
|
+
for await (const event of guardEmptyCompletionEventStream({
|
|
2861
|
+
firstEvents: (async function* () { yield* terminalGuardedEvents; })(),
|
|
2862
|
+
continuation: fetchGuardedEmptyCompletionRetry,
|
|
2705
2863
|
})) events.push(event);
|
|
2706
2864
|
} else {
|
|
2707
|
-
events =
|
|
2865
|
+
events = terminalGuardedEvents;
|
|
2708
2866
|
}
|
|
2709
2867
|
} finally {
|
|
2710
2868
|
cleanupUpstreamAbort();
|