@bitkyc08/opencodex 2.81.0 → 2.82.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/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-BqHF5Trd.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-D3FBCNiP.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DTBC8n7-.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound.ts +77 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +2 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +5 -0
- package/src/cli/doctor.ts +16 -3
- package/src/cli/index.ts +14 -9
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integrations.ts +16 -3
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/build-entries.ts +18 -0
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/effort.ts +26 -5
- package/src/codex/catalog/gather-capture.ts +5 -0
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +43 -35
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -220
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +8 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/generated/compatibility-version.json +303 -171
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +29 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +18 -2
- package/src/integrations/writer.ts +9 -3
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/request-execution-budget.ts +8 -0
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/oauth/anthropic-account-refusal.ts +39 -1
- package/src/oauth/anthropic-routing.ts +1 -1
- package/src/oauth/anthropic-send-ownership.ts +3 -3
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/index.ts +6 -2
- package/src/oauth/store.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +15 -2
- package/src/responses/state/metrics.ts +8 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +12 -5
- package/src/responses/state.ts +18 -10
- package/src/responses/task-input.ts +17 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +1 -0
- package/src/server/chat-native.ts +28 -12
- package/src/server/claude-messages.ts +43 -6
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +1 -0
- package/src/server/management/agent-settings-routes.ts +93 -32
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +59 -8
- package/src/server/management/context.ts +2 -2
- package/src/server/management/shared.ts +3 -3
- package/src/server/messages-native-oauth.ts +9 -8
- package/src/server/messages-native.ts +49 -13
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +31 -2
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +34 -13
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +20 -3
- package/src/server/responses/core-combo.ts +22 -14
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +19 -2
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +25 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +3 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/config.ts +21 -0
- package/src/types/request.ts +5 -0
- package/src/types/wire.ts +1 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
|
@@ -43,6 +43,10 @@ const pacingWebsocketDowngradeWarned = new Set<string>();
|
|
|
43
43
|
*/
|
|
44
44
|
const EGRESS_DECIDED = Symbol.for("opencodex.provider-egress.decided");
|
|
45
45
|
const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
|
|
46
|
+
// Survives queued credential rebuilds and executor replacement without importing budget owners.
|
|
47
|
+
const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
|
|
48
|
+
// One WS attempt and its HTTP fallback are one logical physical-send receipt.
|
|
49
|
+
const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* Announce once, per provider, that an explicit egress route moved this provider off the
|
|
@@ -200,6 +204,7 @@ export function sendWithConnectionPolicy(
|
|
|
200
204
|
const largeCodexBody = typeof body === "string"
|
|
201
205
|
&& /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
|
|
202
206
|
&& Buffer.byteLength(body, "utf8") >= 1024 * 1024;
|
|
207
|
+
(init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
|
|
203
208
|
return physicalFetch(input, {
|
|
204
209
|
...init,
|
|
205
210
|
...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
|
|
@@ -233,10 +238,13 @@ export interface ProviderFetchOptions {
|
|
|
233
238
|
onCodexWsQuota?: CodexWsQuotaObserver;
|
|
234
239
|
/** Synchronous admission at actual credential dispatch, after pacing/backoff. */
|
|
235
240
|
beforeDispatch?: (headers: Headers) => void;
|
|
241
|
+
/** One-shot receipt after final local HTTP admission, immediately before executor entry. */
|
|
242
|
+
onPhysicalDispatch?: () => void;
|
|
236
243
|
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
237
244
|
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
|
|
238
245
|
}
|
|
239
246
|
|
|
247
|
+
/** Compose provider pacing and transport admission; receipts run only at executor entry. */
|
|
240
248
|
export function providerFetch(
|
|
241
249
|
provider: OcxProviderConfig,
|
|
242
250
|
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
@@ -301,7 +309,14 @@ export function providerFetch(
|
|
|
301
309
|
options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
|
|
302
310
|
// No proxy option is attached here: a `dispatchOverride` may rebuild this request against
|
|
303
311
|
// a different destination, so the route is decided at the physical send instead.
|
|
304
|
-
const
|
|
312
|
+
const sharedReceipt = (init as Record<symbol, (() => void) | undefined> | undefined)?.[WS_FALLBACK_RECEIPT];
|
|
313
|
+
let notified = false;
|
|
314
|
+
const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0,
|
|
315
|
+
...(options.onPhysicalDispatch ? { [PHYSICAL_DISPATCH]: () => {
|
|
316
|
+
if (sharedReceipt) sharedReceipt();
|
|
317
|
+
else if (!notified) { options.onPhysicalDispatch!(); notified = true; }
|
|
318
|
+
} } : {}),
|
|
319
|
+
};
|
|
305
320
|
return options.dispatchOverride
|
|
306
321
|
? options.dispatchOverride(input, dispatchInit, dispatch)
|
|
307
322
|
: dispatch(input, dispatchInit);
|
|
@@ -332,8 +347,16 @@ export function providerFetch(
|
|
|
332
347
|
// used, protocol pin included: a WS turn that falls back is serving the
|
|
333
348
|
// request over HTTP, and dropping the provider's `upstreamHttpVersion`
|
|
334
349
|
// there would silently negotiate a transport the operator ruled out.
|
|
335
|
-
|
|
336
|
-
|
|
350
|
+
let notified = false;
|
|
351
|
+
const notifyOnce = (): void => {
|
|
352
|
+
if (notified) return;
|
|
353
|
+
notified = true;
|
|
354
|
+
options.onPhysicalDispatch?.();
|
|
355
|
+
};
|
|
356
|
+
const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
|
|
357
|
+
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
|
|
358
|
+
return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
|
|
359
|
+
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
|
|
337
360
|
}
|
|
338
361
|
return httpFetch(input, init);
|
|
339
362
|
};
|
|
@@ -24,13 +24,16 @@ import { teeWithBoundedInspection } from "../inspection-tee";
|
|
|
24
24
|
import {
|
|
25
25
|
codexForwardTerminalOutcomeRecorder,
|
|
26
26
|
usesCodexForwardPoolAuth,
|
|
27
|
+
liveMainQuotaDispatch,
|
|
27
28
|
codexQuotaOutcomeMeta,
|
|
28
29
|
codexDenialOutcomeMeta,
|
|
29
30
|
isFixedCodexAccount,
|
|
30
31
|
shouldDeferCodexResetDerivedCooldown,
|
|
31
32
|
} from "./core-codex-account";
|
|
33
|
+
import { isMainQuotaDispatchLive, isMainQuotaDispatchWsClaimed } from "../../codex/main-account-cache";
|
|
34
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/account-id";
|
|
32
35
|
import type { ResponsesTerminalStatus } from "../../bridge";
|
|
33
|
-
import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./ws-upstream";
|
|
36
|
+
import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./ws-upstream";
|
|
34
37
|
import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
|
|
35
38
|
import { recordCodexUpstreamOutcome } from "../../codex/routing";
|
|
36
39
|
import { codexProbeLeaseId, codexProbeQuotaScope, codexTransientProbeGrant, releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
@@ -139,6 +142,7 @@ import {
|
|
|
139
142
|
httpStatusFromTerminalError,
|
|
140
143
|
inspectResponseLogJson,
|
|
141
144
|
inspectResponseLogSsePayloadParsed,
|
|
145
|
+
noteUpstreamRequestId,
|
|
142
146
|
} from "../request-log";
|
|
143
147
|
import { restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
|
|
144
148
|
import { restoreRoutedToolSearchCallsInJson } from "../../responses/tool-search-compat";
|
|
@@ -406,6 +410,9 @@ export async function deliverPassthroughResponse(
|
|
|
406
410
|
| "localUpstream"
|
|
407
411
|
>,
|
|
408
412
|
): Promise<Response> {
|
|
413
|
+
const { route } = requestState;
|
|
414
|
+
// The proof must belong to the response whose headers are published.
|
|
415
|
+
const arrivalMainDispatch = liveMainQuotaDispatch(admissionState.authCtx, route.provider);
|
|
409
416
|
const { logCtx, config, options, req } = requestContext;
|
|
410
417
|
const {
|
|
411
418
|
codexSafetyBufferingOptions,
|
|
@@ -429,7 +436,7 @@ export async function deliverPassthroughResponse(
|
|
|
429
436
|
normalizeFunctionCompletionJson,
|
|
430
437
|
} = nativeExchange;
|
|
431
438
|
const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
|
|
432
|
-
const { parsed,
|
|
439
|
+
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
|
|
433
440
|
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
|
|
434
441
|
const { openAiSidecar } = sidecarState;
|
|
435
442
|
const { requestBindings } = transportState;
|
|
@@ -452,6 +459,7 @@ export async function deliverPassthroughResponse(
|
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
const headers = sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions);
|
|
462
|
+
if (!upstreamResponse.ok) noteUpstreamRequestId(logCtx, headers);
|
|
455
463
|
const resolvedModel = headers.get("openai-model")?.trim();
|
|
456
464
|
if (resolvedModel) {
|
|
457
465
|
logCtx.servedModel = resolvedModel;
|
|
@@ -535,6 +543,20 @@ export async function deliverPassthroughResponse(
|
|
|
535
543
|
...(admissionState.authCtx.kind === "pool" ? { credentialGeneration: admissionState.authCtx.generation } : {}),
|
|
536
544
|
});
|
|
537
545
|
}
|
|
546
|
+
} else {
|
|
547
|
+
// The WS observer is the only plain-main publisher for a WebSocket exchange;
|
|
548
|
+
// a prelude projection carries the prelude snapshot, not fresh evidence.
|
|
549
|
+
// The dispatch claim is authoritative because downstream wrappers can replace the Response.
|
|
550
|
+
if (arrivalMainDispatch && !isMainQuotaDispatchWsClaimed(arrivalMainDispatch)
|
|
551
|
+
&& !(isCodexWsUpstreamResponse(upstreamResponse) || isCodexWsPreludeProjection(upstreamResponse))) {
|
|
552
|
+
const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
|
|
553
|
+
// Import yields; same-account token replacement leaves the identity writer live.
|
|
554
|
+
// Re-check the credential fence with no await before publication.
|
|
555
|
+
if (isMainQuotaDispatchLive(arrivalMainDispatch)) {
|
|
556
|
+
applyAccountQuotaFromUpstreamHeaders(MAIN_CODEX_ACCOUNT_ID, upstreamResponse.headers,
|
|
557
|
+
arrivalMainDispatch.configGeneration, arrivalMainDispatch.writer, { modelId: route.modelId });
|
|
558
|
+
}
|
|
559
|
+
}
|
|
538
560
|
}
|
|
539
561
|
|
|
540
562
|
// Non-2xx passthrough failures must never reach Codex as an empty body —
|
|
@@ -12,6 +12,7 @@ import type { ResponsesTransport } from "./request-transport";
|
|
|
12
12
|
import type { ResponsesEffects } from "./response-effects";
|
|
13
13
|
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
14
14
|
import { transientSendCapFor } from "./request-send-budget";
|
|
15
|
+
import { renewMainQuotaDispatchForAttempt } from "../../codex/main-account-cache";
|
|
15
16
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
16
17
|
import { isLocalUpstream } from "../../lib/local-upstream";
|
|
17
18
|
import { codexSafetyBufferingFilterOptions, terminalStatusFromParsed } from "../relay";
|
|
@@ -225,6 +226,10 @@ export async function preparePassthroughExchange(
|
|
|
225
226
|
| "pendingHopPermit"
|
|
226
227
|
| "workflowRootId"
|
|
227
228
|
| "sendsUsed"
|
|
229
|
+
| "targetSendsUsed"
|
|
230
|
+
| "initialSendAllowance"
|
|
231
|
+
| "noteInitialDispatch"
|
|
232
|
+
| "adapterSendBudget"
|
|
228
233
|
>,
|
|
229
234
|
) {
|
|
230
235
|
const { config, logCtx, options, req } = requestContext;
|
|
@@ -753,7 +758,7 @@ export async function preparePassthroughExchange(
|
|
|
753
758
|
const transientSendPolicy = () => transientRetryPolicyFor(route.provider);
|
|
754
759
|
const transientSendAttempts = (): number => transientSendCapFor(
|
|
755
760
|
transientSendPolicy()?.attempts,
|
|
756
|
-
sendBudgetState.
|
|
761
|
+
sendBudgetState.targetSendsUsed,
|
|
757
762
|
);
|
|
758
763
|
const configuredTransientSendBudgetExhausted = (): boolean =>
|
|
759
764
|
transientSendPolicy() !== null && transientSendAttempts() === 0;
|
|
@@ -798,6 +803,9 @@ export async function preparePassthroughExchange(
|
|
|
798
803
|
const sendAmbiguousReplacement = (
|
|
799
804
|
signal: AbortSignal = upstream.signal,
|
|
800
805
|
): Promise<Response> => {
|
|
806
|
+
if (admissionState.authCtx.kind === "main" && admissionState.authCtx.mainQuotaDispatch) {
|
|
807
|
+
admissionState.authCtx.mainQuotaDispatch = renewMainQuotaDispatchForAttempt(admissionState.authCtx.mainQuotaDispatch);
|
|
808
|
+
}
|
|
801
809
|
const report = transientSendReporter();
|
|
802
810
|
let started = false;
|
|
803
811
|
const run = () => fetchWithHeaderTimeout(
|
|
@@ -968,6 +976,11 @@ export async function preparePassthroughExchange(
|
|
|
968
976
|
};
|
|
969
977
|
const initialBodyRefusal = refuseOversizedOutboundBody(request);
|
|
970
978
|
if (initialBodyRefusal) return initialBodyRefusal;
|
|
979
|
+
// A combo-owned first send is settled by the physical-dispatch receipt unless spend
|
|
980
|
+
// enforcement is on, where the shared reporter already owns the permit lifecycle.
|
|
981
|
+
// Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
|
|
982
|
+
// policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
|
|
983
|
+
const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
|
|
971
984
|
try {
|
|
972
985
|
// Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
|
|
973
986
|
// the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
|
|
@@ -994,6 +1007,7 @@ export async function preparePassthroughExchange(
|
|
|
994
1007
|
dispatchOverride: oauthDispatch(request),
|
|
995
1008
|
providerName: route.providerName,
|
|
996
1009
|
modelId: route.modelId,
|
|
1010
|
+
onPhysicalDispatch: receiptMode ? sendBudgetState.noteInitialDispatch : undefined,
|
|
997
1011
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
998
1012
|
beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
|
|
999
1013
|
? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
@@ -1004,7 +1018,10 @@ export async function preparePassthroughExchange(
|
|
|
1004
1018
|
.then(adoptObservedResponse);
|
|
1005
1019
|
},
|
|
1006
1020
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url),
|
|
1007
|
-
attempts:
|
|
1021
|
+
attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
|
|
1022
|
+
// Reporter path: direct requests and enforced spend. Receipt-mode sends are settled
|
|
1023
|
+
// by `onPhysicalDispatch`; a reporter as well would count them twice.
|
|
1024
|
+
...(receiptMode ? {} : { onSendsConsumed: transientSendReporter() }),
|
|
1008
1025
|
claimAmbiguousResend: claimPreHeaderResend,
|
|
1009
1026
|
},
|
|
1010
1027
|
);
|
|
@@ -381,6 +381,10 @@ export async function prepareResponsesRequest(
|
|
|
381
381
|
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
382
382
|
try {
|
|
383
383
|
parsed = parseRequest(body);
|
|
384
|
+
if (options.inboundWire === "anthropic") {
|
|
385
|
+
parsed._nativeReasoningReplay = options.nativeReasoningReplay;
|
|
386
|
+
parsed._nativeReasoningMint = options.nativeReasoningMint;
|
|
387
|
+
}
|
|
384
388
|
parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
|
|
385
389
|
// The body may have been rebuilt since the inbound observation (previous-response
|
|
386
390
|
// expansion); alias the parsed raw body to the same draft so the outbound
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { poolContinuityRefusalReason, workflowRefusalResponse } from "../workflow-refusal";
|
|
10
10
|
import type { AttemptRecoveryKind, AttemptRecoveryWithheld } from "../../usage/log";
|
|
11
11
|
import { noteAttemptRecoveryWithheld, noteAttemptSend } from "../request-log";
|
|
12
|
-
import { TRANSIENT_RETRY_MAX_ATTEMPTS } from "../../lib/upstream-retry";
|
|
12
|
+
import { TRANSIENT_RETRY_MAX_ATTEMPTS, SendBudgetExhaustedError } from "../../lib/upstream-retry";
|
|
13
13
|
import type {
|
|
14
14
|
DispatchDecision,
|
|
15
15
|
DispatchIntent,
|
|
@@ -58,10 +58,24 @@ export function createResponsesSendBudget(
|
|
|
58
58
|
// sends once per child seven hundred times, so every send charged to the request is charged
|
|
59
59
|
// to the root as well (#4546).
|
|
60
60
|
const workflowRootId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
|
|
61
|
-
|
|
61
|
+
// A child that owns its Combo booking settles that exact permit through the reporter, even
|
|
62
|
+
// when its caller handed ownership over only as `comboInitialSend`.
|
|
63
|
+
const inheritedPermit = options.compactionRecoveryPermit ?? options.comboDispatchPermit
|
|
64
|
+
?? options.comboInitialSend?.permit;
|
|
62
65
|
let pendingHopPermit: SingleUseDispatchPermit | undefined = options.compactionRecoveryPermit;
|
|
66
|
+
let initialPermit = options.comboInitialSend?.permit;
|
|
67
|
+
// Count this owner's physical sends, not later bookings made by other shared-ledger owners.
|
|
68
|
+
// Direct callers retain sends reported before this owner was constructed.
|
|
69
|
+
let targetSendsUsed = initialPermit ? 0 : Math.max(0,
|
|
70
|
+
sendBudget.used - (options.compactionRecoveryPermit ? 1 : 0));
|
|
71
|
+
/** Attribute reported physical sends to this owner; the prepaid booking is spent once reported. */
|
|
72
|
+
const noteTargetSends = (charged: number): void => {
|
|
73
|
+
targetSendsUsed += charged;
|
|
74
|
+
if (charged > 0) initialPermit = undefined;
|
|
75
|
+
};
|
|
63
76
|
const recordTransientSends = (used: number, permit?: SingleUseDispatchPermit): void => {
|
|
64
77
|
const charged = Math.max(0, used);
|
|
78
|
+
noteTargetSends(charged);
|
|
65
79
|
reportDispatchSends(sendBudget, charged, permit);
|
|
66
80
|
options.onCompactionRecoverySendsReported?.(charged);
|
|
67
81
|
chargeWorkflowSends(workflowRootId, charged);
|
|
@@ -71,6 +85,7 @@ export function createResponsesSendBudget(
|
|
|
71
85
|
const transientSendReporter = (permit = pendingHopPermit ?? inheritedPermit) =>
|
|
72
86
|
createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? logCtx.provider,
|
|
73
87
|
identityId: logCtx.accountLogLabel }), permit, charged => {
|
|
88
|
+
noteTargetSends(Math.max(0, charged));
|
|
74
89
|
options.onCompactionRecoverySendsReported?.(charged);
|
|
75
90
|
chargeWorkflowSends(workflowRootId, charged);
|
|
76
91
|
});
|
|
@@ -136,6 +151,7 @@ export function createResponsesSendBudget(
|
|
|
136
151
|
// Ordinal 1 is skipped because the caller normally records it before dispatch. An adapter
|
|
137
152
|
// that reports every send asks for it to be counted here instead, so that the first send is
|
|
138
153
|
// logged where it actually happens rather than before admission could still refuse it.
|
|
154
|
+
targetSendsUsed += 1;
|
|
139
155
|
if (send.ordinal <= 1 && options.includeFirst !== true) return;
|
|
140
156
|
noteAttemptSend(logCtx.activeAttempt, inputTokens, send.recovery);
|
|
141
157
|
};
|
|
@@ -191,21 +207,29 @@ export function createResponsesSendBudget(
|
|
|
191
207
|
* The hop hands its reservation down through `pendingHopPermit`, the same seam the
|
|
192
208
|
* passthrough ladder already uses, and this view spends it on the adapter's FIRST
|
|
193
209
|
* reservation. Every later send in that ladder is a new physical send and is charged
|
|
194
|
-
* normally.
|
|
195
|
-
*
|
|
196
|
-
* settlement for existing callers.
|
|
210
|
+
* normally. Claiming preserves the refundable single-use booking until physical dispatch;
|
|
211
|
+
* an abandoned replay releases only that exact booking.
|
|
197
212
|
*/
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
213
|
+
const permits = {
|
|
214
|
+
/** Transfer the initial booking once to the adapter dispatch owner. */
|
|
215
|
+
claimInitialPermit: () => {
|
|
216
|
+
const permit = initialPermit;
|
|
217
|
+
initialPermit = undefined;
|
|
218
|
+
return permit;
|
|
219
|
+
},
|
|
220
|
+
/** Transfer a pending recovery booking once, without creating a new allowance. */
|
|
221
|
+
claimHopPermit: () => {
|
|
222
|
+
const permit = pendingHopPermit;
|
|
223
|
+
pendingHopPermit = undefined;
|
|
224
|
+
return permit;
|
|
225
|
+
},
|
|
202
226
|
};
|
|
203
|
-
const adapterDispatchBudget
|
|
204
|
-
? undefined : adapterDispatchBudgetView(adapterSendBudget,
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
const refundableAdapterDispatchBudget
|
|
208
|
-
? undefined : adapterDispatchBudgetView(adapterSendBudget,
|
|
227
|
+
const adapterDispatchBudget = adapterSendBudget === undefined
|
|
228
|
+
? undefined : adapterDispatchBudgetView(adapterSendBudget, permits);
|
|
229
|
+
// Preserve the existing opted-in view for direct callers, including its admitted replacement
|
|
230
|
+
// of an already-settled hop. Exact-owned Combo and sidecar handoffs use the ordinary view.
|
|
231
|
+
const refundableAdapterDispatchBudget = adapterSendBudget === undefined
|
|
232
|
+
? undefined : adapterDispatchBudgetView(adapterSendBudget, permits, true);
|
|
209
233
|
/**
|
|
210
234
|
* How many sends a recovery leg may make, and the permit that authorises the last one.
|
|
211
235
|
*
|
|
@@ -292,12 +316,34 @@ export function createResponsesSendBudget(
|
|
|
292
316
|
noteTransientSends,
|
|
293
317
|
transientSendReporter,
|
|
294
318
|
remainingTransientSendBudget,
|
|
319
|
+
/** Only this child's prepaid send, not every pending external booking, is spendable here. */
|
|
320
|
+
initialSendAllowance: (cap: number): number => Math.min(cap,
|
|
321
|
+
remainingTransientSendBudget(cap) + (initialPermit ? 1 : 0)),
|
|
322
|
+
/** At final executor admission, settle only the owning booking or reserve this send. */
|
|
323
|
+
noteInitialDispatch: (prepaid = initialPermit): void => {
|
|
324
|
+
if (prepaid) {
|
|
325
|
+
if (!prepaid.assumeCharge()) throw new SendBudgetExhaustedError();
|
|
326
|
+
if (prepaid === initialPermit) initialPermit = undefined;
|
|
327
|
+
} else if (adapterSendBudget && (options.comboInitialSend || options.compactionRecoveryPermit)) {
|
|
328
|
+
const decision = adapterSendBudget.reserveDispatch({ sendClass: "transient",
|
|
329
|
+
targetKey: adapterSendBudget.lastTargetKey ?? `${logCtx.provider}/${logCtx.model}` });
|
|
330
|
+
if (!decision.allowed || !decision.permit.use()) throw new SendBudgetExhaustedError();
|
|
331
|
+
} else {
|
|
332
|
+
// Legacy direct callers can prebook without handing over a permit; keep their reporter.
|
|
333
|
+
noteTransientSends(1);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
targetSendsUsed += 1;
|
|
337
|
+
options.onCompactionRecoverySendsReported?.(1);
|
|
338
|
+
chargeWorkflowSends(workflowRootId, 1);
|
|
339
|
+
},
|
|
340
|
+
/** Physical sends reported by this owner; unrelated shared reservations cannot consume it. */
|
|
341
|
+
get targetSendsUsed(): number { return targetSendsUsed; },
|
|
295
342
|
/**
|
|
296
|
-
*
|
|
343
|
+
* Charged sends, including open prepaid reservations, across this logical request.
|
|
297
344
|
*
|
|
298
|
-
* A live getter, not a snapshot
|
|
299
|
-
*
|
|
300
|
-
* nothing.
|
|
345
|
+
* A live getter, not a completed-send snapshot. Initial ladders use the child's own
|
|
346
|
+
* prepaid allowance and target-local count; shared admission keeps every booking spent.
|
|
301
347
|
*/
|
|
302
348
|
get sendsUsed(): number { return sendBudget.used; },
|
|
303
349
|
adapterSendBudget,
|
|
@@ -335,8 +381,11 @@ export type ResponsesSendBudget = Exclude<ReturnType<typeof createResponsesSendB
|
|
|
335
381
|
*/
|
|
336
382
|
function adapterDispatchBudgetView(
|
|
337
383
|
budget: RequestExecutionBudget,
|
|
338
|
-
hop: {
|
|
339
|
-
|
|
384
|
+
hop: {
|
|
385
|
+
claimInitialPermit: () => SingleUseDispatchPermit | undefined;
|
|
386
|
+
claimHopPermit: () => SingleUseDispatchPermit | undefined;
|
|
387
|
+
},
|
|
388
|
+
legacyHopReplacement = false,
|
|
340
389
|
): RequestExecutionBudget {
|
|
341
390
|
return {
|
|
342
391
|
get physicalStarted() { return budget.physicalStarted; },
|
|
@@ -360,10 +409,22 @@ function adapterDispatchBudgetView(
|
|
|
360
409
|
claimAmbiguousResend: (limit: number): boolean => budget.claimAmbiguousResend?.(limit) === true,
|
|
361
410
|
get ambiguousResendSpent(): boolean { return budget.ambiguousResendSpent === true; },
|
|
362
411
|
reserveDispatch(intent: DispatchIntent): DispatchDecision {
|
|
363
|
-
const refundable = refundableHop || budget.spendEnforced === true;
|
|
364
412
|
// A dispatch whose upstream state is unknown is refused on its own merits. A hop that
|
|
365
413
|
// already paid does not make an unsafe replay safe, so that check stays with the budget.
|
|
366
414
|
if (intent.replaySafe !== false) {
|
|
415
|
+
// The Combo reserves a logical provider/model identity. The child's first actual
|
|
416
|
+
// endpoint is not a recovery transition; retain the original refundable permit.
|
|
417
|
+
const initialPermit = hop.claimInitialPermit();
|
|
418
|
+
if (initialPermit) {
|
|
419
|
+
budget.bindPrepaidTarget?.(intent.targetKey);
|
|
420
|
+
return { allowed: true, permit: {
|
|
421
|
+
sendClass: initialPermit.sendClass,
|
|
422
|
+
execute: run => initialPermit.execute ? initialPermit.execute(run) : run(),
|
|
423
|
+
use: () => initialPermit.assumeCharge(),
|
|
424
|
+
assumeCharge: () => initialPermit.assumeCharge(),
|
|
425
|
+
release: () => initialPermit.release(),
|
|
426
|
+
} };
|
|
427
|
+
}
|
|
367
428
|
const hopPermit = hop.claimHopPermit();
|
|
368
429
|
if (hopPermit && intent.targetKey !== budget.lastTargetKey) {
|
|
369
430
|
// The rotated credential can select a different regional endpoint. Replace the
|
|
@@ -372,35 +433,37 @@ function adapterDispatchBudgetView(
|
|
|
372
433
|
hopPermit.release();
|
|
373
434
|
return budget.reserveDispatch({ ...intent, sendClass: hopPermit.sendClass });
|
|
374
435
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
settled =
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
permit: {
|
|
436
|
+
// Reservation precedes pacing and beforeDispatch. Transfer the exact open booking;
|
|
437
|
+
// only physical consumption closes external reporting and makes it non-refundable.
|
|
438
|
+
// The underlying permit enforces single use, including settlement by another owner.
|
|
439
|
+
if (hopPermit !== undefined) {
|
|
440
|
+
if (legacyHopReplacement) {
|
|
441
|
+
let settled = false;
|
|
442
|
+
let executionPermit = hopPermit;
|
|
443
|
+
const confirm = (): boolean => {
|
|
444
|
+
if (settled) return false;
|
|
445
|
+
settled = true;
|
|
446
|
+
if (hopPermit.assumeCharge()) return true;
|
|
447
|
+
// Preserve the old fallback for a permit another leg already settled.
|
|
448
|
+
const fresh = budget.reserveDispatch(intent);
|
|
449
|
+
if (!fresh.allowed) return false;
|
|
450
|
+
executionPermit = fresh.permit;
|
|
451
|
+
return fresh.permit.assumeCharge();
|
|
452
|
+
};
|
|
453
|
+
return { allowed: true, permit: {
|
|
393
454
|
sendClass: hopPermit.sendClass,
|
|
394
455
|
execute: run => executionPermit.execute ? executionPermit.execute(run) : run(),
|
|
395
|
-
use: confirm,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
456
|
+
use: confirm, assumeCharge: confirm,
|
|
457
|
+
release: () => { if (!settled) { settled = true; hopPermit.release(); } },
|
|
458
|
+
} };
|
|
459
|
+
}
|
|
460
|
+
return { allowed: true, permit: {
|
|
461
|
+
sendClass: hopPermit.sendClass,
|
|
462
|
+
execute: run => hopPermit.execute ? hopPermit.execute(run) : run(),
|
|
463
|
+
use: () => hopPermit.assumeCharge(),
|
|
464
|
+
assumeCharge: () => hopPermit.assumeCharge(),
|
|
465
|
+
release: () => hopPermit.release(),
|
|
466
|
+
} };
|
|
404
467
|
}
|
|
405
468
|
}
|
|
406
469
|
return budget.reserveDispatch(intent);
|
|
@@ -75,6 +75,7 @@ export function createResponsesEffects(
|
|
|
75
75
|
// message, and leave Codex fataling on a missing compaction item (#422).
|
|
76
76
|
const commitReasoningReplayServingRoute = (outboundHeaders?: HeadersInit): void => {
|
|
77
77
|
commitReasoningReplayServingIdentity(parsed._reasoningReplayScope);
|
|
78
|
+
if (parsed._nativeReasoningMint) parsed._nativeReasoningMint.owner = parsed._nativeReasoningBoundOwner;
|
|
78
79
|
rememberServingConversationStateIssuer(admissionState.authCtx, poolAffinityKey);
|
|
79
80
|
// History has no model namespace. Record the account that actually accepted this
|
|
80
81
|
// final attempt, after refresh/failover, rather than guessing from mutable affinity.
|
|
@@ -235,6 +235,7 @@ export async function executeResponsesRunTurn(
|
|
|
235
235
|
preacquiredSlot?: ProviderRequestSlot,
|
|
236
236
|
): Promise<void> => {
|
|
237
237
|
const attemptSeq = ++runTurnAttemptSeq;
|
|
238
|
+
if (attemptSeq === 1 && options.comboInitialSend) options.comboInitialSend.producerOwned = true;
|
|
238
239
|
let pacingSlot = preacquiredSlot;
|
|
239
240
|
const emit = (event: AdapterEvent) => {
|
|
240
241
|
options.onCompactionRecoveryAdapterEvent?.(event);
|
|
@@ -339,6 +340,7 @@ export async function executeResponsesRunTurn(
|
|
|
339
340
|
message: err instanceof Error ? err.message : String(err),
|
|
340
341
|
});
|
|
341
342
|
} finally {
|
|
343
|
+
if (attemptSeq === 1) options.comboInitialSend?.permit.release();
|
|
342
344
|
devinProducers.delete(producerAbort);
|
|
343
345
|
cleanupProducerAbort?.();
|
|
344
346
|
releaseProviderRequestSlot(pacingSlot);
|
|
@@ -48,6 +48,7 @@ import { normalizeLogConversationId } from "../request-log-conversation";
|
|
|
48
48
|
import { rememberResponseState } from "../../responses/state";
|
|
49
49
|
import { trackStreamLifetime } from "../lifecycle";
|
|
50
50
|
import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
|
|
51
|
+
import { createSidecarSendBudget } from "./sidecar-send-budget";
|
|
51
52
|
|
|
52
53
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
53
54
|
export async function executeResponsesSidecars(
|
|
@@ -90,7 +91,7 @@ export async function executeResponsesSidecars(
|
|
|
90
91
|
| "notifyResponseComplete"
|
|
91
92
|
| "cancelResponseCompletion"
|
|
92
93
|
>,
|
|
93
|
-
sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "
|
|
94
|
+
sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
|
|
94
95
|
) {
|
|
95
96
|
const { config, options, logCtx } = requestContext;
|
|
96
97
|
const antigravityPoolActivated = requestState.route.providerName === "google-antigravity"
|
|
@@ -114,6 +115,7 @@ export async function executeResponsesSidecars(
|
|
|
114
115
|
} = requestState;
|
|
115
116
|
const { routedCompaction, openAiSidecar } = sidecarState;
|
|
116
117
|
const { reserveCredentialHop } = sendBudgetState;
|
|
118
|
+
const sidecarBudget = createSidecarSendBudget(options, sendBudgetState, () => logCtx.usageLogInputTokens);
|
|
117
119
|
const {
|
|
118
120
|
commitReasoningReplayServingRoute,
|
|
119
121
|
continuationStateForResponse,
|
|
@@ -202,7 +204,7 @@ export async function executeResponsesSidecars(
|
|
|
202
204
|
): Promise<{ adapter: ProviderAdapter; recoveryKind: AttemptRecoveryKind } | null> => {
|
|
203
205
|
const antigravityValidationResponse = canRunWebSearch && route.providerName === "google-antigravity"
|
|
204
206
|
&& route.provider.authMode === "oauth" && originalResponse?.status === 403;
|
|
205
|
-
if (route.providerName !== "kiro" && !(anthropicInstance && originalResponse?.status === 403)
|
|
207
|
+
if (route.providerName !== "kiro" && !(anthropicInstance && (originalResponse?.status === 403 || originalResponse?.status === 401))
|
|
206
208
|
&& !antigravityValidationResponse && originalResponse && originalResponse.status !== 429) return null;
|
|
207
209
|
let antigravityVerification = false;
|
|
208
210
|
if (antigravityValidationResponse) {
|
|
@@ -264,7 +266,8 @@ export async function executeResponsesSidecars(
|
|
|
264
266
|
return null;
|
|
265
267
|
}
|
|
266
268
|
recoveryKind = "oauth-account-403";
|
|
267
|
-
sendBudgetState.pendingHopPermit = hop.permit;
|
|
269
|
+
if (!options.comboInitialSend) sendBudgetState.pendingHopPermit = hop.permit;
|
|
270
|
+
else sidecarBudget.ownCredentialHop(hop.permit);
|
|
268
271
|
}
|
|
269
272
|
const rotated = !originalResponse || originalResponse.status === 429
|
|
270
273
|
? rotateProviderTransportOn429(config, route.providerName, route.provider, {
|
|
@@ -323,8 +326,9 @@ export async function executeResponsesSidecars(
|
|
|
323
326
|
return null;
|
|
324
327
|
}
|
|
325
328
|
recoveryKind = "oauth-account-429";
|
|
326
|
-
if (canRunWebSearch && route.providerName === "google-antigravity")
|
|
327
|
-
|
|
329
|
+
if (!options.comboInitialSend && canRunWebSearch && route.providerName === "google-antigravity")
|
|
330
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
331
|
+
else sidecarBudget.ownCredentialHop(hop.permit);
|
|
328
332
|
} else if (
|
|
329
333
|
// Anthropic's pool is excluded from generic failover, so without this arm a 429 inside a
|
|
330
334
|
// web-search or image-bridge turn was terminal even with the pool fully enabled -- while
|
|
@@ -364,8 +368,8 @@ export async function executeResponsesSidecars(
|
|
|
364
368
|
hop.permit?.release();
|
|
365
369
|
return null;
|
|
366
370
|
}
|
|
367
|
-
recoveryKind = "anthropic-oauth-429";
|
|
368
|
-
hop.permit
|
|
371
|
+
recoveryKind = originalResponse?.status === 401 ? "oauth-401" : "anthropic-oauth-429";
|
|
372
|
+
sidecarBudget.ownCredentialHop(hop.permit);
|
|
369
373
|
} else {
|
|
370
374
|
// No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
|
|
371
375
|
// credential. The 429 is terminal for this sidecar turn.
|
|
@@ -451,11 +455,14 @@ export async function executeResponsesSidecars(
|
|
|
451
455
|
const imageProviderFetch = providerFetch(
|
|
452
456
|
route.provider,
|
|
453
457
|
options.codexWsRuntimeIdentity,
|
|
454
|
-
{ providerName: route.providerName, modelId: route.modelId },
|
|
458
|
+
{ providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(transportState.adapter) },
|
|
455
459
|
);
|
|
456
460
|
const imgResponse = await runWithImageBridge({
|
|
457
461
|
parsed, adapter: transportState.adapter,
|
|
458
|
-
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget },
|
|
462
|
+
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
|
|
463
|
+
onProducerStart: sidecarBudget.takeProducerOwnership,
|
|
464
|
+
onProducerEnd: sidecarBudget.release,
|
|
465
|
+
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
459
466
|
...(imgPlan ? { plan: imgPlan } : {}),
|
|
460
467
|
...(vidPlan ? { videoPlan: vidPlan } : {}),
|
|
461
468
|
forwardHeaders: requestState.selectedForwardHeaders,
|
|
@@ -471,10 +478,10 @@ export async function executeResponsesSidecars(
|
|
|
471
478
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
472
479
|
waitForRequestSlot: imageProviderFetch.waitForPacing,
|
|
473
480
|
fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
|
|
474
|
-
fetchForRequest: (request, iterParsed) => {
|
|
481
|
+
fetchForRequest: (request, iterParsed, adapter) => {
|
|
475
482
|
const fetch = providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
476
483
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
477
|
-
providerName: route.providerName, modelId: route.modelId,
|
|
484
|
+
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
478
485
|
});
|
|
479
486
|
return fetch.unpacedFetch ?? fetch;
|
|
480
487
|
},
|
|
@@ -541,12 +548,14 @@ export async function executeResponsesSidecars(
|
|
|
541
548
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
542
549
|
providerName: route.providerName,
|
|
543
550
|
modelId: route.modelId,
|
|
551
|
+
...sidecarBudget.fetchOptions(transportState.adapter),
|
|
544
552
|
})(input, init)) as typeof globalThis.fetch;
|
|
545
553
|
const wsResponse = await runWithWebSearch({
|
|
546
554
|
parsed, adapter: transportState.adapter,
|
|
547
|
-
|
|
555
|
+
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
556
|
+
fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
548
557
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
549
|
-
providerName: route.providerName, modelId: route.modelId,
|
|
558
|
+
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
550
559
|
}),
|
|
551
560
|
incomingMeta: {
|
|
552
561
|
headers: requestState.selectedForwardHeaders,
|
|
@@ -554,11 +563,11 @@ export async function executeResponsesSidecars(
|
|
|
554
563
|
abortSignal: options.abortSignal,
|
|
555
564
|
translatorBudget,
|
|
556
565
|
providerFetch: routedProviderFetch,
|
|
557
|
-
...(route.providerName === "google-antigravity" ? {
|
|
566
|
+
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? {
|
|
558
567
|
sendBudget: sendBudgetState.refundableAdapterDispatchBudget,
|
|
559
568
|
onPhysicalSend: (send: { ordinal: number; recovery?: AttemptRecoveryKind }) =>
|
|
560
569
|
sendBudgetState.noteAdapterPhysicalSend(logCtx.usageLogInputTokens, send),
|
|
561
|
-
} :
|
|
570
|
+
} : sidecarBudget.incomingMeta),
|
|
562
571
|
},
|
|
563
572
|
backend: wsPlan.backend,
|
|
564
573
|
forwardProvider: wsPlan.forwardSidecar?.provider,
|
|
@@ -591,7 +600,7 @@ export async function executeResponsesSidecars(
|
|
|
591
600
|
stallTimeoutSec: wsPlan.stallTimeoutSec,
|
|
592
601
|
streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
|
|
593
602
|
on429: rotateSidecarProviderOn429,
|
|
594
|
-
...(route.providerName === "google-antigravity" ? { onIterationEnd: () => {
|
|
603
|
+
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
|
|
595
604
|
// The next Antigravity adapter dispatch claims this permit. If request construction,
|
|
596
605
|
// admission or cancellation stopped it first, return the unused send allowance.
|
|
597
606
|
const permit = sendBudgetState.pendingHopPermit;
|