@bitkyc08/opencodex 2.8.0 → 2.9.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/README.md +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
import { parseRequest } from "../../responses/parser";
|
|
11
11
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
12
12
|
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
expandPreviousResponseInput,
|
|
15
|
+
previousResponseProviderState,
|
|
16
|
+
previousResponseReplayFailure,
|
|
17
|
+
rememberResponseState,
|
|
18
|
+
} from "../../responses/state";
|
|
14
19
|
import { routeModel, type RouteResult } from "../../router";
|
|
15
20
|
import {
|
|
16
21
|
advanceComboAfterFailure,
|
|
@@ -82,6 +87,7 @@ import {
|
|
|
82
87
|
} from "../../codex/routing";
|
|
83
88
|
import { fetchWithResetRetry, fetchWithTransientRetry, applyUpstreamRecoveryInit } from "../../lib/upstream-retry";
|
|
84
89
|
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../auth-cors";
|
|
90
|
+
import { createTranslatorBudget, isTranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
|
|
85
91
|
import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
86
92
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
87
93
|
import { slugsEquivalent } from "../../providers/slug-codec";
|
|
@@ -89,6 +95,7 @@ import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../provid
|
|
|
89
95
|
import { isUsageDebugEnabled } from "../../usage/debug";
|
|
90
96
|
import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress";
|
|
91
97
|
import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
|
|
98
|
+
import type { InboundWire } from "../../providers/registry";
|
|
92
99
|
import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../../providers/key-failover";
|
|
93
100
|
import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
94
101
|
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
@@ -96,6 +103,7 @@ import type { WsData } from "../ws-bridge";
|
|
|
96
103
|
import { registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
|
|
97
104
|
import { redactSecretString } from "../../lib/redact";
|
|
98
105
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
106
|
+
import type { AdmissionLease } from "../../lib/admission";
|
|
99
107
|
import { supportedLadderFor } from "../effort-policy";
|
|
100
108
|
import { isThreadSpawnRequest } from "../effort-policy";
|
|
101
109
|
import {
|
|
@@ -127,13 +135,16 @@ import {
|
|
|
127
135
|
consumeForInspection,
|
|
128
136
|
consumeForResponseLogMetadata,
|
|
129
137
|
createSseInspector,
|
|
138
|
+
isEagerRelaySseResponse,
|
|
139
|
+
isNativePassthroughSseResponse,
|
|
140
|
+
markEagerRelaySseResponse,
|
|
130
141
|
markNativePassthroughSseResponse,
|
|
131
142
|
relaySseWithFailedTail,
|
|
132
143
|
relayWithAbort,
|
|
133
144
|
sanitizePassthroughHeaders,
|
|
134
145
|
} from "../relay";
|
|
135
146
|
import { relaySseEagerBounded } from "../relay-eager";
|
|
136
|
-
import {
|
|
147
|
+
import { selectEagerPath } from "../../lib/bun-stream-caps";
|
|
137
148
|
import { cancelBodyOnAbort } from "../../lib/abort";
|
|
138
149
|
import {
|
|
139
150
|
createResponsesItemIdPayloadRewrite,
|
|
@@ -168,6 +179,7 @@ export function sidecarOutcomeRecorder(
|
|
|
168
179
|
? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
169
180
|
threadId,
|
|
170
181
|
probeLeaseId: authCtx.probeLeaseId,
|
|
182
|
+
writerGeneration: authCtx.writerGeneration,
|
|
171
183
|
})
|
|
172
184
|
: undefined;
|
|
173
185
|
}
|
|
@@ -248,6 +260,11 @@ interface CodexPoolAccountRetryArgs {
|
|
|
248
260
|
abortSignal?: AbortSignal;
|
|
249
261
|
onCodexAuthContextResolved?: (ctx: CodexAuthContext) => void;
|
|
250
262
|
deferCodexResetDerivedCooldown?: boolean;
|
|
263
|
+
// Narrowed subset of HandleResponsesOptions: the retry rebuilds the adapter, so it
|
|
264
|
+
// needs the inbound scope or the retry could land on a different wire than the
|
|
265
|
+
// first attempt.
|
|
266
|
+
inboundWire?: InboundWire;
|
|
267
|
+
translatorBudget: TranslatorBudget;
|
|
251
268
|
};
|
|
252
269
|
firstAuthCtx: Extract<CodexAuthContext, { kind: "pool" | "main-pool" }>;
|
|
253
270
|
firstResponse: Response;
|
|
@@ -309,6 +326,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
309
326
|
req, config, route, parsed, logCtx, options, firstAuthCtx, firstResponse,
|
|
310
327
|
outcomeStatus, upstream, connectMs, passthroughEstimate, stream,
|
|
311
328
|
} = args;
|
|
329
|
+
const inboundWire = options.inboundWire ?? "responses";
|
|
312
330
|
let retryAuthCtx: CodexAuthContext | undefined;
|
|
313
331
|
try {
|
|
314
332
|
retryAuthCtx = await resolveCodexAuthContext(
|
|
@@ -331,7 +349,11 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
331
349
|
const quotaMeta = codexQuotaOutcomeMeta(firstResponse);
|
|
332
350
|
if (outcomeStatus === 429 || outcomeStatus === 402) {
|
|
333
351
|
const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
|
|
334
|
-
applyAccountQuotaFromUpstreamHeaders(
|
|
352
|
+
applyAccountQuotaFromUpstreamHeaders(
|
|
353
|
+
firstAuthCtx.accountId,
|
|
354
|
+
firstResponse.headers,
|
|
355
|
+
firstAuthCtx.writerGeneration,
|
|
356
|
+
);
|
|
335
357
|
}
|
|
336
358
|
if (!shouldDeferCodexResetDerivedCooldown(firstResponse, options.deferCodexResetDerivedCooldown)) {
|
|
337
359
|
recordCodexUpstreamOutcome(config, firstAuthCtx.accountId, outcomeStatus, {
|
|
@@ -340,6 +362,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
340
362
|
modelId: route.modelId,
|
|
341
363
|
probeLeaseId: codexProbeLeaseId(firstAuthCtx),
|
|
342
364
|
probeQuotaScope: codexProbeQuotaScope(firstAuthCtx),
|
|
365
|
+
writerGeneration: firstAuthCtx.writerGeneration,
|
|
343
366
|
// Retry already advanced the RR ring via excludeAccountId — reuse for promotion.
|
|
344
367
|
...(retryAuthCtx.accountId ? { promoteAccountId: retryAuthCtx.accountId } : {}),
|
|
345
368
|
});
|
|
@@ -352,10 +375,13 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
352
375
|
"pool",
|
|
353
376
|
);
|
|
354
377
|
const retryAdapter = resolveAdapter(
|
|
355
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, retryProvider),
|
|
378
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, retryProvider, inboundWire),
|
|
356
379
|
config.cacheRetention,
|
|
357
380
|
);
|
|
358
|
-
const request = await retryAdapter.buildRequest(parsed, {
|
|
381
|
+
const request = await retryAdapter.buildRequest(parsed, {
|
|
382
|
+
headers: retryHeaders,
|
|
383
|
+
translatorBudget: options.translatorBudget,
|
|
384
|
+
});
|
|
359
385
|
recordAdapterReasoning(logCtx, request);
|
|
360
386
|
|
|
361
387
|
await firstResponse.body?.cancel().catch(() => undefined);
|
|
@@ -391,6 +417,8 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
391
417
|
} catch (error) {
|
|
392
418
|
// Attribute the transport failure to the alternate account (already selected).
|
|
393
419
|
return { kind: "transport", error, authCtx: retryAuthCtx };
|
|
420
|
+
} finally {
|
|
421
|
+
request.releaseBodyObservation?.();
|
|
394
422
|
}
|
|
395
423
|
}
|
|
396
424
|
|
|
@@ -415,6 +443,7 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
415
443
|
modelId,
|
|
416
444
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
417
445
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
446
|
+
writerGeneration: authCtx.writerGeneration,
|
|
418
447
|
});
|
|
419
448
|
return;
|
|
420
449
|
}
|
|
@@ -435,6 +464,7 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
435
464
|
modelId,
|
|
436
465
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
437
466
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
467
|
+
writerGeneration: authCtx.writerGeneration,
|
|
438
468
|
});
|
|
439
469
|
};
|
|
440
470
|
}
|
|
@@ -442,6 +472,11 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
442
472
|
|
|
443
473
|
|
|
444
474
|
export function decodeRequestErrorResponse(err: unknown, label: string): Response {
|
|
475
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
476
|
+
return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
477
|
+
code: "translation_buffer_limit",
|
|
478
|
+
});
|
|
479
|
+
}
|
|
445
480
|
if (err instanceof UnsupportedContentEncodingError) {
|
|
446
481
|
return formatErrorResponse(415, "invalid_request_error", err.message);
|
|
447
482
|
}
|
|
@@ -479,6 +514,7 @@ export interface ConsumedComboFailure {
|
|
|
479
514
|
|
|
480
515
|
|
|
481
516
|
export interface HandleResponsesOptions {
|
|
517
|
+
turnAdmissionLease?: AdmissionLease;
|
|
482
518
|
forceEmptyResponseId?: boolean;
|
|
483
519
|
abortSignal?: AbortSignal;
|
|
484
520
|
/** One-shot TTFT callback: first non-empty model output observed (WP4). */
|
|
@@ -493,12 +529,22 @@ export interface HandleResponsesOptions {
|
|
|
493
529
|
* (system/tools hash), not a per-session id — do not use it for Anthropic pool affinity.
|
|
494
530
|
*/
|
|
495
531
|
promptCacheKeyIsSharedCohort?: boolean;
|
|
532
|
+
/**
|
|
533
|
+
* Wire protocol the ORIGINAL client spoke. The Chat and Anthropic surfaces translate
|
|
534
|
+
* their body into a Responses shape and replay through this function, so without an
|
|
535
|
+
* explicit value the replay would look like a native Responses request and an
|
|
536
|
+
* inbound-scoped registry wire default would fire for a client that never asked for
|
|
537
|
+
* it. Omitted means a genuine Responses inbound.
|
|
538
|
+
*/
|
|
539
|
+
inboundWire?: InboundWire;
|
|
496
540
|
/** Internal recursion guard; callers outside this module must not set it. */
|
|
497
541
|
comboAttempt?: boolean;
|
|
498
542
|
/** Internal combo handoff: allow a later same-provider model after a reset-derived 429/402. */
|
|
499
543
|
deferCodexResetDerivedCooldown?: boolean;
|
|
500
544
|
/** 030-owned handoff when a child consumed the original failure under bounds. */
|
|
501
545
|
onConsumedComboFailure?: (failure: ConsumedComboFailure) => void;
|
|
546
|
+
/** Caller-owned for Chat/Claude replay; omitted only at genuine Responses ingress. */
|
|
547
|
+
translatorBudget?: TranslatorBudget;
|
|
502
548
|
}
|
|
503
549
|
|
|
504
550
|
|
|
@@ -729,8 +775,9 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
729
775
|
config: OcxConfig;
|
|
730
776
|
req: Request;
|
|
731
777
|
logCtx: RequestLogContext;
|
|
778
|
+
inboundWire: InboundWire;
|
|
732
779
|
}): Promise<void> {
|
|
733
|
-
const { parsed, route, config, req, logCtx } = args;
|
|
780
|
+
const { parsed, route, config, req, logCtx, inboundWire } = args;
|
|
734
781
|
|
|
735
782
|
// Apply the routed model id upstream: routing may strip a "<provider>/" namespace.
|
|
736
783
|
if (route.modelId !== parsed.modelId) {
|
|
@@ -741,7 +788,7 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
741
788
|
}
|
|
742
789
|
// Settle the wire once so logging, fast-mode, auth, and sidecars read the adapter
|
|
743
790
|
// this request will actually use (#404).
|
|
744
|
-
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider);
|
|
791
|
+
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
745
792
|
logCtx.model = route.modelId;
|
|
746
793
|
logCtx.provider = route.providerName;
|
|
747
794
|
logCtx.providerAdapter = route.provider.adapter;
|
|
@@ -978,7 +1025,7 @@ export async function handleComboResponses(
|
|
|
978
1025
|
);
|
|
979
1026
|
(logCtx.attempts ??= []).push(attempt);
|
|
980
1027
|
attemptRetained = true;
|
|
981
|
-
noteComboSuccess(comboId, combo, pick.target);
|
|
1028
|
+
noteComboSuccess(comboId, combo, pick.target, pick.writerGeneration);
|
|
982
1029
|
Object.assign(logCtx, childLog, {
|
|
983
1030
|
requestedModel,
|
|
984
1031
|
model: requestedModel,
|
|
@@ -1051,15 +1098,81 @@ export async function handleComboResponses(
|
|
|
1051
1098
|
|
|
1052
1099
|
|
|
1053
1100
|
|
|
1101
|
+
function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget): Response {
|
|
1102
|
+
if (!response.body) {
|
|
1103
|
+
budget.dispose();
|
|
1104
|
+
return response;
|
|
1105
|
+
}
|
|
1106
|
+
const reader = response.body.getReader();
|
|
1107
|
+
let finalized = false;
|
|
1108
|
+
const finalize = () => {
|
|
1109
|
+
if (finalized) return;
|
|
1110
|
+
finalized = true;
|
|
1111
|
+
budget.dispose();
|
|
1112
|
+
};
|
|
1113
|
+
const body = new ReadableStream<Uint8Array>({
|
|
1114
|
+
async pull(controller) {
|
|
1115
|
+
try {
|
|
1116
|
+
const result = await reader.read();
|
|
1117
|
+
if (result.done) {
|
|
1118
|
+
finalize();
|
|
1119
|
+
controller.close();
|
|
1120
|
+
} else {
|
|
1121
|
+
controller.enqueue(result.value);
|
|
1122
|
+
}
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
finalize();
|
|
1125
|
+
controller.error(error);
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
async cancel(reason) {
|
|
1129
|
+
try { await reader.cancel(reason); } finally { finalize(); }
|
|
1130
|
+
},
|
|
1131
|
+
});
|
|
1132
|
+
const finalizedResponse = new Response(body, {
|
|
1133
|
+
status: response.status,
|
|
1134
|
+
statusText: response.statusText,
|
|
1135
|
+
headers: response.headers,
|
|
1136
|
+
});
|
|
1137
|
+
if (isNativePassthroughSseResponse(response)) {
|
|
1138
|
+
markNativePassthroughSseResponse(finalizedResponse);
|
|
1139
|
+
}
|
|
1140
|
+
if (isEagerRelaySseResponse(response)) {
|
|
1141
|
+
markEagerRelaySseResponse(finalizedResponse);
|
|
1142
|
+
}
|
|
1143
|
+
return finalizedResponse;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1054
1146
|
export async function handleResponses(
|
|
1055
1147
|
req: Request,
|
|
1056
1148
|
config: OcxConfig,
|
|
1057
1149
|
logCtx: RequestLogContext,
|
|
1058
1150
|
options: HandleResponsesOptions = {},
|
|
1059
1151
|
): Promise<Response> {
|
|
1152
|
+
const ownsBudget = options.translatorBudget === undefined;
|
|
1153
|
+
const translatorBudget = options.translatorBudget ?? createTranslatorBudget();
|
|
1154
|
+
try {
|
|
1155
|
+
const response = await handleResponsesInner(req, config, logCtx, { ...options, translatorBudget });
|
|
1156
|
+
return ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (ownsBudget) translatorBudget.dispose();
|
|
1159
|
+
throw error;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function handleResponsesInner(
|
|
1164
|
+
req: Request,
|
|
1165
|
+
config: OcxConfig,
|
|
1166
|
+
logCtx: RequestLogContext,
|
|
1167
|
+
options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
|
|
1168
|
+
): Promise<Response> {
|
|
1169
|
+
// The Chat and Anthropic surfaces replay through here with a Responses-shaped body,
|
|
1170
|
+
// so an omitted value means a genuine Responses inbound.
|
|
1171
|
+
const inboundWire = options.inboundWire ?? "responses";
|
|
1172
|
+
const translatorBudget = options.translatorBudget;
|
|
1060
1173
|
let body: unknown;
|
|
1061
1174
|
try {
|
|
1062
|
-
body = await readJsonRequestBody(req);
|
|
1175
|
+
body = await readJsonRequestBody(req, translatorBudget);
|
|
1063
1176
|
} catch (err) {
|
|
1064
1177
|
return decodeRequestErrorResponse(err, "responses");
|
|
1065
1178
|
}
|
|
@@ -1072,6 +1185,13 @@ export async function handleResponses(
|
|
|
1072
1185
|
);
|
|
1073
1186
|
const originalBody = body;
|
|
1074
1187
|
body = expandPreviousResponseInput(body);
|
|
1188
|
+
if (previousResponseReplayFailure(body)) {
|
|
1189
|
+
return formatErrorResponse(
|
|
1190
|
+
400,
|
|
1191
|
+
"previous_response_not_found",
|
|
1192
|
+
"Continuation state is unavailable or corrupt; resend the full conversation without previous_response_id.",
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1075
1195
|
const previousResponseInputExpanded = body !== originalBody;
|
|
1076
1196
|
|
|
1077
1197
|
// Spawn-message compatibility (both directions): agent_message task payloads ride in
|
|
@@ -1090,14 +1210,21 @@ export async function handleResponses(
|
|
|
1090
1210
|
}
|
|
1091
1211
|
|
|
1092
1212
|
let parsed;
|
|
1213
|
+
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
1093
1214
|
try {
|
|
1094
1215
|
parsed = parseRequest(body);
|
|
1216
|
+
toolBridgeMaps = buildToolBridgeMaps(parsed, translatorBudget);
|
|
1095
1217
|
if (previousResponseInputExpanded) parsed._previousResponseInputExpanded = true;
|
|
1096
1218
|
parsed._providerContinuation = previousResponseProviderState(parsed.previousResponseId);
|
|
1097
1219
|
parsed._cursorConversationId = parsed._providerContinuation?.cursor?.conversationId;
|
|
1098
1220
|
const clientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim();
|
|
1099
1221
|
if (clientThreadId) parsed._clientThreadId = clientThreadId;
|
|
1100
1222
|
} catch (err) {
|
|
1223
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
1224
|
+
return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
1225
|
+
code: "translation_buffer_limit",
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1101
1228
|
return formatErrorResponse(400, "invalid_request_error", err instanceof Error ? err.message : String(err));
|
|
1102
1229
|
}
|
|
1103
1230
|
// Prefer a pre-populated id (routed Claude) over Responses headers that may be
|
|
@@ -1220,7 +1347,7 @@ export async function handleResponses(
|
|
|
1220
1347
|
);
|
|
1221
1348
|
}
|
|
1222
1349
|
|
|
1223
|
-
await applyFinalRouteRequestNormalization({ parsed, route, config, req, logCtx });
|
|
1350
|
+
await applyFinalRouteRequestNormalization({ parsed, route, config, req, logCtx, inboundWire });
|
|
1224
1351
|
|
|
1225
1352
|
{
|
|
1226
1353
|
const finalAuth = await resolveResponsesCodexAuth(req, config, route, options);
|
|
@@ -1310,7 +1437,7 @@ export async function handleResponses(
|
|
|
1310
1437
|
parsed.options.promptCacheKey,
|
|
1311
1438
|
route.providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(route.providerName) : undefined,
|
|
1312
1439
|
);
|
|
1313
|
-
const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider);
|
|
1440
|
+
const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
1314
1441
|
const adapter = resolveAdapter(adapterProvider, config.cacheRetention);
|
|
1315
1442
|
logCtx.providerAdapter = adapter.name;
|
|
1316
1443
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name);
|
|
@@ -1353,11 +1480,18 @@ export async function handleResponses(
|
|
|
1353
1480
|
const visionPlan = planVisionSidecar(config, route.provider, route.modelId, parsed, openAiSidecar);
|
|
1354
1481
|
const recordSidecarOutcome = openAiSidecar?.recordOutcome;
|
|
1355
1482
|
if (visionPlan) {
|
|
1356
|
-
await describeImagesInPlace(
|
|
1483
|
+
await describeImagesInPlace(
|
|
1484
|
+
parsed,
|
|
1485
|
+
visionPlan,
|
|
1486
|
+
openAiSidecar?.headers ?? selectedForwardHeaders,
|
|
1487
|
+
options.abortSignal,
|
|
1488
|
+
recordSidecarOutcome,
|
|
1489
|
+
translatorBudget,
|
|
1490
|
+
);
|
|
1357
1491
|
} else if (modelInList(route.provider.noVisionModels, route.modelId)) {
|
|
1358
1492
|
// Sidecar-covered model but NO plan (no forward provider / missing forwarded auth / sidecar
|
|
1359
1493
|
// disabled): fail closed — never forward raw images to a text-only upstream.
|
|
1360
|
-
stripImagesInPlace(parsed);
|
|
1494
|
+
stripImagesInPlace(parsed, translatorBudget);
|
|
1361
1495
|
}
|
|
1362
1496
|
|
|
1363
1497
|
const recordTerminalOutcomes = options.recordTerminalOutcomes !== false;
|
|
@@ -1407,7 +1541,7 @@ export async function handleResponses(
|
|
|
1407
1541
|
if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
|
|
1408
1542
|
const imageGenCallAliases = route.provider.authMode === "forward"
|
|
1409
1543
|
? new Map<string, { namespace: string; name: string }>()
|
|
1410
|
-
: imageGenToolCallAliases(
|
|
1544
|
+
: imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
|
|
1411
1545
|
// Local continuation cache for the ChatGPT passthrough. Codex WS turns chain with
|
|
1412
1546
|
// previous_response_id, ocx converts them to internal HTTP requests, and the ChatGPT Codex
|
|
1413
1547
|
// REST backend rejects the parameter — the adapter strips it in forward mode, so the ONLY
|
|
@@ -1429,7 +1563,7 @@ export async function handleResponses(
|
|
|
1429
1563
|
+ `(model ${parsed.modelId}); forwarding without it — earlier turns may be missing from this request`,
|
|
1430
1564
|
);
|
|
1431
1565
|
}
|
|
1432
|
-
let request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
|
|
1566
|
+
let request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
1433
1567
|
recordAdapterReasoning(logCtx, request);
|
|
1434
1568
|
const passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
|
|
1435
1569
|
? request.usageLog.inputTokens
|
|
@@ -1454,6 +1588,7 @@ export async function handleResponses(
|
|
|
1454
1588
|
modelId: route.modelId,
|
|
1455
1589
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
1456
1590
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
1591
|
+
writerGeneration: authCtx.writerGeneration,
|
|
1457
1592
|
});
|
|
1458
1593
|
}
|
|
1459
1594
|
const msg = outcome === "timeout"
|
|
@@ -1478,6 +1613,8 @@ export async function handleResponses(
|
|
|
1478
1613
|
);
|
|
1479
1614
|
} catch (err) {
|
|
1480
1615
|
return transportFailureResponse(err);
|
|
1616
|
+
} finally {
|
|
1617
|
+
request.releaseBodyObservation?.();
|
|
1481
1618
|
}
|
|
1482
1619
|
|
|
1483
1620
|
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
@@ -1550,7 +1687,11 @@ export async function handleResponses(
|
|
|
1550
1687
|
// Prefer primary when present, fall back to secondary for compatibility.
|
|
1551
1688
|
const quotaMeta = codexQuotaOutcomeMeta(upstreamResponse);
|
|
1552
1689
|
const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
|
|
1553
|
-
applyAccountQuotaFromUpstreamHeaders(
|
|
1690
|
+
applyAccountQuotaFromUpstreamHeaders(
|
|
1691
|
+
authCtx.accountId,
|
|
1692
|
+
upstreamResponse.headers,
|
|
1693
|
+
authCtx.writerGeneration,
|
|
1694
|
+
);
|
|
1554
1695
|
if (terminalBodyWillRecord) {
|
|
1555
1696
|
options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
|
|
1556
1697
|
terminalRecorder(status, httpStatusOverride);
|
|
@@ -1582,6 +1723,7 @@ export async function handleResponses(
|
|
|
1582
1723
|
modelId: route.modelId,
|
|
1583
1724
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
1584
1725
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
1726
|
+
writerGeneration: authCtx.writerGeneration,
|
|
1585
1727
|
});
|
|
1586
1728
|
}
|
|
1587
1729
|
}
|
|
@@ -1607,21 +1749,25 @@ export async function handleResponses(
|
|
|
1607
1749
|
// async-pull segfault on Windows. Branch[0] goes directly to the Response (Bun
|
|
1608
1750
|
// native relay, never enters JS Sink.write); branch[1] is consumed in the
|
|
1609
1751
|
// background for terminal-outcome/quota inspection only.
|
|
1610
|
-
// #314 alternative shape:
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
// bounded reader with inline inspection
|
|
1614
|
-
//
|
|
1615
|
-
//
|
|
1752
|
+
// #314 alternative shape: win32 no-rewrite traffic follows the runtime/config
|
|
1753
|
+
// gate; darwin no-rewrite traffic joins it only for explicit
|
|
1754
|
+
// `streamMode: "eager-relay"` opt-in. Darwin `auto` always stays tee. The
|
|
1755
|
+
// eager shape skips tee and uses one bounded reader with inline inspection
|
|
1756
|
+
// (src/server/relay-eager.ts; policy:
|
|
1757
|
+
// devlog/_plan/260731_macos_rss_retention/100_darwin_eager_optin.md).
|
|
1758
|
+
// The bundled known-bad runtime remains on tee by default on both platforms.
|
|
1616
1759
|
if (isEventStream && upstreamResponse.body) {
|
|
1617
1760
|
const repairConfig = route.provider.responsesItemIdRepair;
|
|
1618
1761
|
const needsClientRewrite = imageGenCallAliases.size > 0 || hasResponsesItemIdRepair(repairConfig);
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1621
|
-
|
|
1762
|
+
const eagerPath = selectEagerPath(
|
|
1763
|
+
process.platform,
|
|
1764
|
+
needsClientRewrite,
|
|
1765
|
+
config.streamMode ?? "auto",
|
|
1766
|
+
);
|
|
1767
|
+
if (eagerPath?.useEagerRelay) {
|
|
1622
1768
|
const turnAc = new AbortController();
|
|
1623
1769
|
linkAbortSignal(upstream, turnAc.signal);
|
|
1624
|
-
registerTurn(turnAc);
|
|
1770
|
+
registerTurn(turnAc, options.turnAdmissionLease);
|
|
1625
1771
|
const reportNativeTerminal = recordTerminalOutcomes
|
|
1626
1772
|
? (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
|
|
1627
1773
|
terminalRecorder?.(status, httpStatusOverride);
|
|
@@ -1653,6 +1799,7 @@ export async function handleResponses(
|
|
|
1653
1799
|
const eagerBody = relaySseEagerBounded(upstreamResponse.body, turnAc, {
|
|
1654
1800
|
inspectChunk: chunk => inspector.feed(chunk),
|
|
1655
1801
|
finishInspection: () => inspector.finish(),
|
|
1802
|
+
disposeInspection: () => inspector.dispose(),
|
|
1656
1803
|
sawTerminal: () => inspector.reported(),
|
|
1657
1804
|
onSynthetic: kind => {
|
|
1658
1805
|
if (!reportNativeTerminal) return;
|
|
@@ -1668,18 +1815,26 @@ export async function handleResponses(
|
|
|
1668
1815
|
onClientCancel: () => options.onNativePassthroughCancel?.(),
|
|
1669
1816
|
onDone: () => unregisterTurn(turnAc),
|
|
1670
1817
|
});
|
|
1671
|
-
//
|
|
1672
|
-
//
|
|
1818
|
+
// selectEagerPath admits only no-rewrite traffic, so this stays a pure native relay on
|
|
1819
|
+
// both eligible platforms; win32 must never enter an image/item-id JS pull wrapper (#32111).
|
|
1673
1820
|
if (!headers.has("content-type")) headers.set("content-type", "text/event-stream");
|
|
1674
|
-
return
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1821
|
+
return markEagerRelaySseResponse(
|
|
1822
|
+
markNativePassthroughSseResponse(new Response(eagerBody, {
|
|
1823
|
+
status: upstreamResponse.status,
|
|
1824
|
+
headers,
|
|
1825
|
+
})),
|
|
1826
|
+
);
|
|
1678
1827
|
}
|
|
1679
1828
|
const [nativeBody, inspectBody] = upstreamResponse.body.tee();
|
|
1680
1829
|
const turnAc = new AbortController();
|
|
1830
|
+
const clientGone = new AbortController();
|
|
1681
1831
|
linkAbortSignal(upstream, turnAc.signal);
|
|
1682
|
-
registerTurn(turnAc);
|
|
1832
|
+
registerTurn(turnAc, options.turnAdmissionLease);
|
|
1833
|
+
const inspectionConsumerOptions = {
|
|
1834
|
+
clientGoneSignal: clientGone.signal,
|
|
1835
|
+
drainBounds: { ms: 15_000, bytes: 32 * 1024 * 1024 },
|
|
1836
|
+
upstream,
|
|
1837
|
+
};
|
|
1683
1838
|
if (recordTerminalOutcomes) {
|
|
1684
1839
|
// A real terminal was parsed from the (teed) inspection stream — record it as the outcome
|
|
1685
1840
|
// even if the client has already disconnected: the turn genuinely reached that terminal, so
|
|
@@ -1714,6 +1869,7 @@ export async function handleResponses(
|
|
|
1714
1869
|
() => options.onNativePassthroughCancel?.(),
|
|
1715
1870
|
rememberPassthroughResponse,
|
|
1716
1871
|
options.onFirstOutput,
|
|
1872
|
+
inspectionConsumerOptions,
|
|
1717
1873
|
);
|
|
1718
1874
|
} else {
|
|
1719
1875
|
consumeForResponseLogMetadata(
|
|
@@ -1723,6 +1879,7 @@ export async function handleResponses(
|
|
|
1723
1879
|
() => unregisterTurn(turnAc),
|
|
1724
1880
|
rememberPassthroughResponse,
|
|
1725
1881
|
options.onFirstOutput,
|
|
1882
|
+
inspectionConsumerOptions,
|
|
1726
1883
|
);
|
|
1727
1884
|
}
|
|
1728
1885
|
if (!headers.has("content-type")) headers.set("content-type", "text/event-stream");
|
|
@@ -1733,15 +1890,15 @@ export async function handleResponses(
|
|
|
1733
1890
|
const payloadRewrites = [
|
|
1734
1891
|
createImageGenCallRestoreRewrite(imageGenCallAliases),
|
|
1735
1892
|
hasResponsesItemIdRepair(repairConfig)
|
|
1736
|
-
? createResponsesItemIdPayloadRewrite(repairConfig
|
|
1893
|
+
? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
|
|
1737
1894
|
: undefined,
|
|
1738
1895
|
].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
|
|
1739
1896
|
const rewrittenBody = payloadRewrites.length > 0
|
|
1740
|
-
? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites))
|
|
1897
|
+
? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
|
|
1741
1898
|
: nativeBody;
|
|
1742
1899
|
const clientBody = process.platform === "win32" && !needsClientRewrite
|
|
1743
1900
|
? nativeBody
|
|
1744
|
-
: relaySseWithFailedTail(rewrittenBody, upstream);
|
|
1901
|
+
: relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
|
|
1745
1902
|
return markNativePassthroughSseResponse(new Response(clientBody, {
|
|
1746
1903
|
status: upstreamResponse.status,
|
|
1747
1904
|
headers,
|
|
@@ -1763,7 +1920,7 @@ export async function handleResponses(
|
|
|
1763
1920
|
}
|
|
1764
1921
|
const body = relayWithAbort(upstreamResponse.body, upstream);
|
|
1765
1922
|
const turnAc = new AbortController();
|
|
1766
|
-
const tracked = body ? trackStreamLifetime(body, turnAc) : null;
|
|
1923
|
+
const tracked = body ? trackStreamLifetime(body, turnAc, undefined, options.turnAdmissionLease) : null;
|
|
1767
1924
|
return new Response(tracked, {
|
|
1768
1925
|
status: upstreamResponse.status,
|
|
1769
1926
|
headers,
|
|
@@ -1837,6 +1994,7 @@ export async function handleResponses(
|
|
|
1837
1994
|
}
|
|
1838
1995
|
const imgResponse = await runWithImageBridge({
|
|
1839
1996
|
parsed, adapter,
|
|
1997
|
+
incomingMeta: { headers: selectedForwardHeaders, abortSignal: options.abortSignal, translatorBudget },
|
|
1840
1998
|
...(imgPlan ? { plan: imgPlan } : {}),
|
|
1841
1999
|
...(vidPlan ? { videoPlan: vidPlan } : {}),
|
|
1842
2000
|
forwardHeaders: selectedForwardHeaders,
|
|
@@ -1875,7 +2033,7 @@ export async function handleResponses(
|
|
|
1875
2033
|
if (!rotated) return null;
|
|
1876
2034
|
route.provider = rotated;
|
|
1877
2035
|
return resolveAdapter(
|
|
1878
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2036
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
1879
2037
|
config.cacheRetention,
|
|
1880
2038
|
);
|
|
1881
2039
|
},
|
|
@@ -1891,7 +2049,7 @@ export async function handleResponses(
|
|
|
1891
2049
|
});
|
|
1892
2050
|
if (imgResponse.body) {
|
|
1893
2051
|
const imgTurnAc = new AbortController();
|
|
1894
|
-
return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc), {
|
|
2052
|
+
return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc, undefined, options.turnAdmissionLease), {
|
|
1895
2053
|
status: imgResponse.status,
|
|
1896
2054
|
headers: imgResponse.headers,
|
|
1897
2055
|
});
|
|
@@ -1910,6 +2068,7 @@ export async function handleResponses(
|
|
|
1910
2068
|
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
|
|
1911
2069
|
const wsResponse = await runWithWebSearch({
|
|
1912
2070
|
parsed, adapter,
|
|
2071
|
+
incomingMeta: { headers: selectedForwardHeaders, abortSignal: options.abortSignal, translatorBudget },
|
|
1913
2072
|
backend: wsPlan.backend,
|
|
1914
2073
|
forwardProvider: wsPlan.forwardSidecar?.provider,
|
|
1915
2074
|
anthropicSidecar: wsPlan.anthropicSidecar,
|
|
@@ -1942,7 +2101,7 @@ export async function handleResponses(
|
|
|
1942
2101
|
if (!rotated) return null;
|
|
1943
2102
|
route.provider = rotated;
|
|
1944
2103
|
return resolveAdapter(
|
|
1945
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2104
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
1946
2105
|
config.cacheRetention,
|
|
1947
2106
|
);
|
|
1948
2107
|
},
|
|
@@ -1951,7 +2110,7 @@ export async function handleResponses(
|
|
|
1951
2110
|
// in-flight web-search turns instead of skipping them during graceful shutdown.
|
|
1952
2111
|
if (wsResponse.body) {
|
|
1953
2112
|
const wsTurnAc = new AbortController();
|
|
1954
|
-
return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc), {
|
|
2113
|
+
return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc, undefined, options.turnAdmissionLease), {
|
|
1955
2114
|
status: wsResponse.status,
|
|
1956
2115
|
headers: wsResponse.headers,
|
|
1957
2116
|
});
|
|
@@ -1970,7 +2129,7 @@ export async function handleResponses(
|
|
|
1970
2129
|
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
|
|
1971
2130
|
await adapter.runTurn?.(
|
|
1972
2131
|
parsed,
|
|
1973
|
-
{ headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal },
|
|
2132
|
+
{ headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal, translatorBudget },
|
|
1974
2133
|
queue.push,
|
|
1975
2134
|
);
|
|
1976
2135
|
} catch (err) {
|
|
@@ -1988,7 +2147,7 @@ export async function handleResponses(
|
|
|
1988
2147
|
}
|
|
1989
2148
|
};
|
|
1990
2149
|
|
|
1991
|
-
const { toolNsMap, freeformToolNames, toolSearchToolNames } =
|
|
2150
|
+
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
1992
2151
|
if (parsed.stream) {
|
|
1993
2152
|
void runTurn();
|
|
1994
2153
|
let eventSource: AsyncIterable<AdapterEvent> = queue.stream();
|
|
@@ -2009,6 +2168,7 @@ export async function handleResponses(
|
|
|
2009
2168
|
queue.close();
|
|
2010
2169
|
}, 2_000,
|
|
2011
2170
|
{
|
|
2171
|
+
translatorBudget,
|
|
2012
2172
|
...(options.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
2013
2173
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
2014
2174
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
@@ -2035,7 +2195,7 @@ export async function handleResponses(
|
|
|
2035
2195
|
},
|
|
2036
2196
|
);
|
|
2037
2197
|
const bridgeTurnAc = new AbortController();
|
|
2038
|
-
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc);
|
|
2198
|
+
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, undefined, options.turnAdmissionLease);
|
|
2039
2199
|
return new Response(trackedSse, {
|
|
2040
2200
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
|
|
2041
2201
|
});
|
|
@@ -2054,6 +2214,7 @@ export async function handleResponses(
|
|
|
2054
2214
|
}
|
|
2055
2215
|
let providerState: OcxProviderContinuationState | undefined;
|
|
2056
2216
|
const json = buildResponseJSON(events, parsed.modelId, {
|
|
2217
|
+
translatorBudget,
|
|
2057
2218
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
2058
2219
|
toolNsMap,
|
|
2059
2220
|
freeformToolNames,
|
|
@@ -2084,7 +2245,7 @@ export async function handleResponses(
|
|
|
2084
2245
|
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
2085
2246
|
let activeAdapter = adapter;
|
|
2086
2247
|
|
|
2087
|
-
const request = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders });
|
|
2248
|
+
const request = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
2088
2249
|
recordAdapterReasoning(logCtx, request);
|
|
2089
2250
|
const inputTokenEstimate = typeof request.usageLog?.inputTokens === "number"
|
|
2090
2251
|
? request.usageLog.inputTokens
|
|
@@ -2118,6 +2279,8 @@ export async function handleResponses(
|
|
|
2118
2279
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
2119
2280
|
const msg = describeUpstreamConnectFailure(err, connectMs);
|
|
2120
2281
|
return formatErrorResponse(502, "upstream_error", msg);
|
|
2282
|
+
} finally {
|
|
2283
|
+
request.releaseBodyObservation?.();
|
|
2121
2284
|
}
|
|
2122
2285
|
|
|
2123
2286
|
if (!upstreamResponse.ok) {
|
|
@@ -2134,6 +2297,7 @@ export async function handleResponses(
|
|
|
2134
2297
|
): Promise<Response | { failed: Response }> => {
|
|
2135
2298
|
const retryRequest = await activeAdapter.buildRequest(parsed, {
|
|
2136
2299
|
headers: selectedForwardHeaders,
|
|
2300
|
+
translatorBudget,
|
|
2137
2301
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
2138
2302
|
});
|
|
2139
2303
|
recordAdapterReasoning(logCtx, retryRequest);
|
|
@@ -2145,11 +2309,15 @@ export async function handleResponses(
|
|
|
2145
2309
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
2146
2310
|
noteAttemptSend(logCtx.activeAttempt, retryEstimate, recovery);
|
|
2147
2311
|
try {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2312
|
+
try {
|
|
2313
|
+
return activeAdapter.fetchResponse
|
|
2314
|
+
? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
|
|
2315
|
+
: await fetchWithHeaderTimeout(retryRequest.url, {
|
|
2151
2316
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
2152
2317
|
}, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
2318
|
+
} finally {
|
|
2319
|
+
retryRequest.releaseBodyObservation?.();
|
|
2320
|
+
}
|
|
2153
2321
|
} catch (err) {
|
|
2154
2322
|
cleanupUpstreamAbort();
|
|
2155
2323
|
upstream.abort();
|
|
@@ -2188,7 +2356,7 @@ export async function handleResponses(
|
|
|
2188
2356
|
);
|
|
2189
2357
|
route.provider = refreshedProvider;
|
|
2190
2358
|
activeAdapter = resolveAdapter(
|
|
2191
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider),
|
|
2359
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire),
|
|
2192
2360
|
config.cacheRetention,
|
|
2193
2361
|
);
|
|
2194
2362
|
const result = await rebuildAndRefetch("oauth-401");
|
|
@@ -2213,7 +2381,7 @@ export async function handleResponses(
|
|
|
2213
2381
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
2214
2382
|
route.provider = rotated;
|
|
2215
2383
|
activeAdapter = resolveAdapter(
|
|
2216
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2384
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2217
2385
|
config.cacheRetention,
|
|
2218
2386
|
);
|
|
2219
2387
|
const result = await rebuildAndRefetch("key-429");
|
|
@@ -2245,7 +2413,7 @@ export async function handleResponses(
|
|
|
2245
2413
|
promoteAnthropicActiveAccount(nextAccountId);
|
|
2246
2414
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
|
|
2247
2415
|
activeAdapter = resolveAdapter(
|
|
2248
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2416
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2249
2417
|
config.cacheRetention,
|
|
2250
2418
|
);
|
|
2251
2419
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
@@ -2320,6 +2488,7 @@ export async function handleResponses(
|
|
|
2320
2488
|
try {
|
|
2321
2489
|
const continuationRequest = await activeAdapter.buildRequest(nextParsed, {
|
|
2322
2490
|
headers: selectedForwardHeaders,
|
|
2491
|
+
translatorBudget,
|
|
2323
2492
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
2324
2493
|
});
|
|
2325
2494
|
recordAdapterReasoning(logCtx, continuationRequest);
|
|
@@ -2327,15 +2496,16 @@ export async function handleResponses(
|
|
|
2327
2496
|
? continuationRequest.usageLog.inputTokens
|
|
2328
2497
|
: undefined;
|
|
2329
2498
|
if (continuationEstimate !== undefined) logCtx.usageLogInputTokens = continuationEstimate;
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2499
|
+
try {
|
|
2500
|
+
if (activeAdapter.fetchResponse) {
|
|
2501
|
+
noteAttemptSend(logCtx.activeAttempt, continuationEstimate);
|
|
2502
|
+
response = await activeAdapter.fetchResponse(continuationRequest, {
|
|
2333
2503
|
abortSignal: upstream.signal,
|
|
2334
2504
|
timeoutMs: connectMs,
|
|
2335
2505
|
stream: nextParsed.stream,
|
|
2336
2506
|
});
|
|
2337
|
-
|
|
2338
|
-
|
|
2507
|
+
} else {
|
|
2508
|
+
response = await fetchWithResetRetry(
|
|
2339
2509
|
recovery => {
|
|
2340
2510
|
noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery);
|
|
2341
2511
|
return fetchWithHeaderTimeout(
|
|
@@ -2352,7 +2522,10 @@ export async function handleResponses(
|
|
|
2352
2522
|
);
|
|
2353
2523
|
},
|
|
2354
2524
|
{ abortSignal: upstream.signal, label: safeHostLabel(continuationRequest.url) },
|
|
2355
|
-
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
} finally {
|
|
2528
|
+
continuationRequest.releaseBodyObservation?.();
|
|
2356
2529
|
}
|
|
2357
2530
|
} catch (error) {
|
|
2358
2531
|
if (options.abortSignal?.aborted) {
|
|
@@ -2374,7 +2547,7 @@ export async function handleResponses(
|
|
|
2374
2547
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
2375
2548
|
route.provider = rotated;
|
|
2376
2549
|
activeAdapter = resolveAdapter(
|
|
2377
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2550
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2378
2551
|
config.cacheRetention,
|
|
2379
2552
|
);
|
|
2380
2553
|
continue;
|
|
@@ -2402,7 +2575,7 @@ export async function handleResponses(
|
|
|
2402
2575
|
promoteAnthropicActiveAccount(nextAccountId);
|
|
2403
2576
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
|
|
2404
2577
|
activeAdapter = resolveAdapter(
|
|
2405
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
2578
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
2406
2579
|
config.cacheRetention,
|
|
2407
2580
|
);
|
|
2408
2581
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
|
|
@@ -2442,9 +2615,9 @@ export async function handleResponses(
|
|
|
2442
2615
|
const detachContinuationBodyGuard = cancelBodyOnAbort(response.body, upstream.signal);
|
|
2443
2616
|
try {
|
|
2444
2617
|
if (nextParsed.stream) {
|
|
2445
|
-
yield* activeAdapter.parseStream(response);
|
|
2618
|
+
yield* activeAdapter.parseStream(response, translatorBudget);
|
|
2446
2619
|
} else if (activeAdapter.parseResponse) {
|
|
2447
|
-
yield* await activeAdapter.parseResponse(response);
|
|
2620
|
+
yield* await activeAdapter.parseResponse(response, translatorBudget);
|
|
2448
2621
|
} else {
|
|
2449
2622
|
yield { type: "error", message: "Provider continuation does not support response parsing" };
|
|
2450
2623
|
}
|
|
@@ -2461,7 +2634,7 @@ export async function handleResponses(
|
|
|
2461
2634
|
};
|
|
2462
2635
|
|
|
2463
2636
|
if (parsed.stream) {
|
|
2464
|
-
const initialEventStream = activeAdapter.parseStream(upstreamResponse);
|
|
2637
|
+
const initialEventStream = activeAdapter.parseStream(upstreamResponse, translatorBudget);
|
|
2465
2638
|
const eventStream = terminalGuardEnabled
|
|
2466
2639
|
? guardTerminalEventStream({
|
|
2467
2640
|
parsed,
|
|
@@ -2471,11 +2644,12 @@ export async function handleResponses(
|
|
|
2471
2644
|
continuation: fetchTerminalGuardContinuation,
|
|
2472
2645
|
})
|
|
2473
2646
|
: initialEventStream;
|
|
2474
|
-
const { toolNsMap, freeformToolNames, toolSearchToolNames } =
|
|
2647
|
+
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
2475
2648
|
const sseStream = bridgeToResponsesSSE(
|
|
2476
2649
|
eventStream, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
2477
2650
|
() => upstream.abort(), 2_000,
|
|
2478
2651
|
{
|
|
2652
|
+
translatorBudget,
|
|
2479
2653
|
...(options.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
2480
2654
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
2481
2655
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
@@ -2504,7 +2678,7 @@ export async function handleResponses(
|
|
|
2504
2678
|
},
|
|
2505
2679
|
);
|
|
2506
2680
|
const bridgeTurnAc = new AbortController();
|
|
2507
|
-
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, cleanupUpstreamAbort);
|
|
2681
|
+
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, cleanupUpstreamAbort, options.turnAdmissionLease);
|
|
2508
2682
|
return new Response(trackedSse, {
|
|
2509
2683
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
|
|
2510
2684
|
});
|
|
@@ -2513,7 +2687,7 @@ export async function handleResponses(
|
|
|
2513
2687
|
if (activeAdapter.parseResponse) {
|
|
2514
2688
|
let events: AdapterEvent[];
|
|
2515
2689
|
try {
|
|
2516
|
-
const initialEvents = await activeAdapter.parseResponse(upstreamResponse);
|
|
2690
|
+
const initialEvents = await activeAdapter.parseResponse(upstreamResponse, translatorBudget);
|
|
2517
2691
|
if (terminalGuardEnabled) {
|
|
2518
2692
|
events = [];
|
|
2519
2693
|
for await (const event of guardTerminalEventStream({
|
|
@@ -2529,9 +2703,10 @@ export async function handleResponses(
|
|
|
2529
2703
|
} finally {
|
|
2530
2704
|
cleanupUpstreamAbort();
|
|
2531
2705
|
}
|
|
2532
|
-
const { toolNsMap, freeformToolNames, toolSearchToolNames } =
|
|
2706
|
+
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
2533
2707
|
let providerState: OcxProviderContinuationState | undefined;
|
|
2534
2708
|
const json = buildResponseJSON(events, parsed.modelId, {
|
|
2709
|
+
translatorBudget,
|
|
2535
2710
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
2536
2711
|
toolNsMap,
|
|
2537
2712
|
freeformToolNames,
|