@bitkyc08/opencodex 2.81.0 → 2.83.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-Ba8uG696.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-BHRZdJuv.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.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/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +18 -14
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +11 -4
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/bridge/errors.ts +7 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +22 -20
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-picker.ts +1 -1
- 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/account-auth.ts +20 -12
- 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 +26 -3
- 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/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +1 -1
- 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/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +28 -4
- 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/native-models.ts +1 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +68 -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/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -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/main-account.ts +6 -7
- 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 +375 -227
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +276 -206
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- 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/cursor-detect.ts +1 -1
- 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/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- 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 +7 -3
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/store.ts +1 -1
- package/src/oauth/types.ts +1 -1
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +6 -6
- package/src/providers/registry/entries-extended.ts +4 -0
- package/src/providers/registry/model-seeds.ts +3 -3
- package/src/providers/xai-fast-model.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 +25 -7
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +18 -4
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +2 -1
- package/src/server/lifecycle.ts +31 -4
- 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/grok-coupon-routes.ts +277 -52
- package/src/server/management/link-routes.ts +42 -20
- package/src/server/management/route-registry.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/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-terminal-status.ts +186 -0
- package/src/server/request-log.ts +15 -89
- package/src/server/responses/adapter-continuation.ts +11 -3
- package/src/server/responses/adapter-dispatch.ts +99 -19
- 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/compact.ts +3 -2
- 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-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +9 -9
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +134 -62
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +15 -5
- package/src/server/responses/sidecar-execution.ts +30 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/spend-storage-error.ts +33 -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 +10 -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/usage/expected-prices.ts +1 -1
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
|
@@ -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);
|
|
@@ -53,6 +53,9 @@ import { classifyModelFamilyForQuota } from "../../oauth/account-quota-rank";
|
|
|
53
53
|
import { expandInferenceOAuthSendBudget } from "../inference/context";
|
|
54
54
|
import { stampOAuthAccountLabel, usesApiKeyAccount } from "../../providers/label";
|
|
55
55
|
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
56
|
+
import { captureRouteStaticPolicy } from "../../router";
|
|
57
|
+
import { CopilotAutoHttpError, resolveCopilotAuto } from "../../providers/github-copilot-auto";
|
|
58
|
+
import { renameRoutedIdentityInContext } from "../../adapters/identity";
|
|
56
59
|
import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
|
|
57
60
|
import {
|
|
58
61
|
providerApiKeySelectionIsCurrent,
|
|
@@ -87,6 +90,13 @@ import { resolvePassiveRouteSubjectId } from "../passive-route-linker";
|
|
|
87
90
|
import { clientCancelledResponse } from "./core-errors";
|
|
88
91
|
import { acquireAccountLease, KIRO_ACCOUNT_WAIT_MS } from "../../oauth/kiro-account-load";
|
|
89
92
|
|
|
93
|
+
/** Return a renewed Chat wire to the native owner before sending inference. */
|
|
94
|
+
export class CopilotAutoAdapterHandoff extends Error {
|
|
95
|
+
constructor(readonly adapter: ProviderAdapter) {
|
|
96
|
+
super("Copilot Auto dispatch requires the Chat adapter");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
/** Owns live credential selection and adapter bindings for one request. */
|
|
91
101
|
export async function prepareResponsesTransport(
|
|
92
102
|
requestContext: Pick<ResponsesRequestContext, "config" | "logCtx" | "options" | "req">,
|
|
@@ -160,6 +170,8 @@ export async function prepareResponsesTransport(
|
|
|
160
170
|
|| route.providerName === "devin"
|
|
161
171
|
) && route.provider.authMode === "oauth";
|
|
162
172
|
let sentOAuthSnapshot: OAuthAccessSnapshot | undefined;
|
|
173
|
+
// One inference-401 replay belongs to this request, even when Auto changes exchange wire.
|
|
174
|
+
let oauth401ReplayAttempted = false;
|
|
163
175
|
let replayOAuthCredentialSnapshot: Pick<OAuthAccessSnapshot, "accountId" | "generation"> | undefined;
|
|
164
176
|
let anthropicPoolAccountId: string | null = null;
|
|
165
177
|
let anthropicPoolFailovers = 0;
|
|
@@ -281,6 +293,84 @@ export async function prepareResponsesTransport(
|
|
|
281
293
|
* the case the fence exists for. Stays 0 for every provider without a passive quota.
|
|
282
294
|
*/
|
|
283
295
|
let passiveQuotaWriterGeneration = 0;
|
|
296
|
+
const copilotRequestedModel = route.modelId;
|
|
297
|
+
/** Project only bounded Copilot refusal metadata; upstream bodies and session tokens never reach the client. */
|
|
298
|
+
const copilotRefusalResponse = (error: unknown): Response | undefined => {
|
|
299
|
+
if (!(error instanceof CopilotAutoHttpError) || ![401, 403, 429].includes(error.status)) return;
|
|
300
|
+
const response = formatErrorResponse(error.status, error.status === 429 ? "rate_limit_error" : "authentication_error",
|
|
301
|
+
error.message);
|
|
302
|
+
if (error.retryAfter) response.headers.set("retry-after", error.retryAfter);
|
|
303
|
+
return response;
|
|
304
|
+
};
|
|
305
|
+
let copilotSessionExpiresAt = Infinity;
|
|
306
|
+
let copilotNegotiationRefreshed = false;
|
|
307
|
+
let copilotAutoAdapter: OcxProviderConfig["adapter"] | undefined;
|
|
308
|
+
/** The operator-configured wire, captured before the first Auto negotiation can rewrite route.provider. */
|
|
309
|
+
let copilotConfiguredWire: { adapter: OcxProviderConfig["adapter"]; responsesPath?: string;
|
|
310
|
+
chatCompletionsPath?: string; apiVersion?: string } | undefined;
|
|
311
|
+
/** Start every negotiation from the configured wire, so a previous credential's Auto adapter,
|
|
312
|
+
* endpoint and session headers never carry over to a credential that keeps the named model. */
|
|
313
|
+
const copilotNegotiationBase = (provider: OcxProviderConfig): OcxProviderConfig => {
|
|
314
|
+
if (!copilotConfiguredWire) return provider;
|
|
315
|
+
const headers = new Headers(provider.headers);
|
|
316
|
+
headers.delete("Copilot-Session-Token");
|
|
317
|
+
if (copilotConfiguredWire.apiVersion === undefined) headers.delete("X-GitHub-Api-Version");
|
|
318
|
+
else headers.set("X-GitHub-Api-Version", copilotConfiguredWire.apiVersion);
|
|
319
|
+
const base: OcxProviderConfig = { ...provider, adapter: copilotConfiguredWire.adapter,
|
|
320
|
+
headers: Object.fromEntries(headers) };
|
|
321
|
+
if (copilotConfiguredWire.responsesPath === undefined) delete base.responsesPath;
|
|
322
|
+
else base.responsesPath = copilotConfiguredWire.responsesPath;
|
|
323
|
+
if (copilotConfiguredWire.chatCompletionsPath === undefined) delete base.chatCompletionsPath;
|
|
324
|
+
else base.chatCompletionsPath = copilotConfiguredWire.chatCompletionsPath;
|
|
325
|
+
return base;
|
|
326
|
+
};
|
|
327
|
+
/** Resolve the request's ephemeral wire selection and recover at most one OAuth negotiation 401. */
|
|
328
|
+
const resolveCopilotSelection = async (requestParsed: OcxParsedRequest, insideDispatch = false): Promise<void> => {
|
|
329
|
+
if (route.providerName !== "github-copilot" || route.provider.authMode === "forward") return;
|
|
330
|
+
copilotConfiguredWire ??= {
|
|
331
|
+
adapter: route.provider.adapter,
|
|
332
|
+
responsesPath: route.provider.responsesPath,
|
|
333
|
+
chatCompletionsPath: route.provider.chatCompletionsPath,
|
|
334
|
+
apiVersion: new Headers(route.provider.headers).get("X-GitHub-Api-Version") ?? undefined,
|
|
335
|
+
};
|
|
336
|
+
/** Fence control-plane sends to the captured credential; reuse an already-held dispatch concurrency lease. */
|
|
337
|
+
const negotiate = () => {
|
|
338
|
+
const binding: DispatchBinding | undefined = oauthSelection && servingOAuthSnapshot
|
|
339
|
+
? { kind: "oauth", selection: { ...oauthSelection }, snapshot: servingOAuthSnapshot }
|
|
340
|
+
: { kind: "api-key", provider: { ...route.provider } };
|
|
341
|
+
return resolveCopilotAuto(copilotNegotiationBase(route.provider), copilotRequestedModel, requestParsed,
|
|
342
|
+
options.abortSignal ?? req.signal, /** Fence each negotiation send against the captured credential binding before touching the network. */
|
|
343
|
+
() => selectionIsCurrent(binding), !insideDispatch);
|
|
344
|
+
};
|
|
345
|
+
let selected: Awaited<ReturnType<typeof resolveCopilotAuto>>;
|
|
346
|
+
try { selected = await negotiate(); }
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (!(error instanceof CopilotAutoHttpError) || error.status !== 401 || copilotNegotiationRefreshed
|
|
349
|
+
|| oauth401ReplayAttempted || route.provider.authMode !== "oauth" || !servingOAuthSnapshot) throw error;
|
|
350
|
+
copilotNegotiationRefreshed = true;
|
|
351
|
+
const refreshed = await refreshResolvedOAuthSelection(servingOAuthSnapshot);
|
|
352
|
+
route.provider = resolveProviderTransport(route.providerName,
|
|
353
|
+
{ ...route.provider, apiKey: refreshed.accessToken }, parsed.options.promptCacheKey,
|
|
354
|
+
resolveCopilotApiBaseUrl(refreshed.apiBaseUrl));
|
|
355
|
+
sentOAuthSnapshot = refreshed;
|
|
356
|
+
replayOAuthCredentialSnapshot = { accountId: refreshed.accountId, generation: refreshed.generation };
|
|
357
|
+
selected = await negotiate();
|
|
358
|
+
}
|
|
359
|
+
copilotSessionExpiresAt = selected.expiresAt ?? Infinity;
|
|
360
|
+
route.provider = selected.provider;
|
|
361
|
+
route.modelId = selected.modelId;
|
|
362
|
+
route.staticPolicy = captureRouteStaticPolicy(route.providerName, route.modelId, route.provider,
|
|
363
|
+
route.staticPolicy.effectiveAlias, inboundWire);
|
|
364
|
+
copilotAutoAdapter = selected.auto ? selected.provider.adapter : undefined;
|
|
365
|
+
for (const target of new Set([parsed, requestParsed])) {
|
|
366
|
+
target.modelId = selected.modelId;
|
|
367
|
+
if (target._rawBody && typeof target._rawBody === "object")
|
|
368
|
+
(target._rawBody as { model?: string }).model = selected.modelId;
|
|
369
|
+
target.context = renameRoutedIdentityInContext(target.context, selected.modelId);
|
|
370
|
+
}
|
|
371
|
+
logCtx.model = selected.modelId;
|
|
372
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.model = selected.modelId;
|
|
373
|
+
};
|
|
284
374
|
/**
|
|
285
375
|
* Apply a rotated account's FULL credential snapshot to the live route (#2568d).
|
|
286
376
|
*
|
|
@@ -306,6 +396,7 @@ export async function prepareResponsesTransport(
|
|
|
306
396
|
const applyFailoverSnapshot = async (
|
|
307
397
|
snapshot: OAuthAccessSnapshot,
|
|
308
398
|
retryParsed: OcxParsedRequest = parsed,
|
|
399
|
+
insideDispatch = false,
|
|
309
400
|
): Promise<OAuthAccessSnapshot | null> => {
|
|
310
401
|
requireAnthropicAdmission();
|
|
311
402
|
if (snapshot.provider !== route.providerName) return null;
|
|
@@ -342,6 +433,9 @@ export async function prepareResponsesTransport(
|
|
|
342
433
|
}
|
|
343
434
|
if (snapshot.projectId) rotatedProvider = { ...rotatedProvider, project: snapshot.projectId };
|
|
344
435
|
route.provider = rotatedProvider;
|
|
436
|
+
await resolveCopilotSelection(retryParsed, insideDispatch);
|
|
437
|
+
// Negotiation itself may have recovered a 401 with a newer committed credential.
|
|
438
|
+
if (route.providerName === "github-copilot" && servingOAuthSnapshot) snapshot = servingOAuthSnapshot;
|
|
345
439
|
if (route.providerName === "kiro") {
|
|
346
440
|
const kiroContext = { ...(snapshot.kiro ?? {}) };
|
|
347
441
|
// Terminal-guard continuations are rebuilt from a shallow clone. Updating only the
|
|
@@ -411,7 +505,9 @@ export async function prepareResponsesTransport(
|
|
|
411
505
|
&& (anthropicInstance === undefined || !anthropicRouting!.getAnthropicAccountHealthSnapshot(binding.snapshot.accountId) && !anthropicRatePolicy!.anthropicRatePauseUntil(binding.snapshot.accountId) && !anthropicQuota!.anthropicFamilyRejected(binding.snapshot.accountId, route.modelId))
|
|
412
506
|
&& credentialGeneration(row.credential) === binding.snapshot.generation;
|
|
413
507
|
};
|
|
508
|
+
/** Bind the negotiated wire adapter and its built requests to the serving credential's authority. */
|
|
414
509
|
const resolveSelectionAdapter = (provider: OcxProviderConfig, retention = config.cacheRetention): ProviderAdapter => {
|
|
510
|
+
if (copilotAutoAdapter) provider = { ...provider, adapter: copilotAutoAdapter };
|
|
415
511
|
const resolved = resolveAdapter(provider, retention, route.providerName);
|
|
416
512
|
if (route.provider.authMode === "forward") return resolved;
|
|
417
513
|
const binding: DispatchBinding | undefined = route.provider.authMode === "oauth"
|
|
@@ -468,6 +564,7 @@ export async function prepareResponsesTransport(
|
|
|
468
564
|
}
|
|
469
565
|
return resolved;
|
|
470
566
|
};
|
|
567
|
+
/** Rebuild a stale queued selection while retaining the caller's dispatch lease and renewing Auto metadata. */
|
|
471
568
|
const refreshDispatchAdapter = async (requestParsed: OcxParsedRequest): Promise<ProviderAdapter> => {
|
|
472
569
|
requireAnthropicAdmission();
|
|
473
570
|
if (route.provider.authMode === "oauth") {
|
|
@@ -479,7 +576,7 @@ export async function prepareResponsesTransport(
|
|
|
479
576
|
const accountId = await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
|
|
480
577
|
candidate = await anthropicRouting!.getAnthropicPoolAccessSnapshot(accountId);
|
|
481
578
|
}
|
|
482
|
-
if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed)) {
|
|
579
|
+
if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed, true)) {
|
|
483
580
|
if (anthropicInstance !== undefined) await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
|
|
484
581
|
throw new Error("OAuth account selection changed before dispatch");
|
|
485
582
|
}
|
|
@@ -487,6 +584,7 @@ export async function prepareResponsesTransport(
|
|
|
487
584
|
const current = resolveCurrentProviderApiKeyTransport(config, route.providerName, route.provider);
|
|
488
585
|
if (!current) throw new Error("API key selection is unavailable before dispatch");
|
|
489
586
|
route.provider = current;
|
|
587
|
+
await resolveCopilotSelection(requestParsed, true);
|
|
490
588
|
}
|
|
491
589
|
adapter = activeAdapter = runTurnAdapter = resolveSelectionAdapter(
|
|
492
590
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
@@ -552,13 +650,14 @@ export async function prepareResponsesTransport(
|
|
|
552
650
|
throw new Error("Account selection changed repeatedly before turn dispatch");
|
|
553
651
|
} finally { producer?.close(); }
|
|
554
652
|
};
|
|
555
|
-
|
|
653
|
+
/** Fence the physical send, with bounded credential/session rebuilds and safe negotiated refusals. */
|
|
654
|
+
const oauthDispatch = (wireRequest: AdapterRequest, requestParsed = parsed, owner: "adapter" | "passthrough" = "adapter"): ProviderFetchOptions["dispatchOverride"] => {
|
|
556
655
|
if (route.provider.authMode === "forward") return undefined;
|
|
557
|
-
return async (input, init, execute) => {
|
|
656
|
+
return /** Dispatch with the current binding and session, rebuilding at most three times before returning a safe refusal. */ async (input, init, execute) => {
|
|
558
657
|
let destination = input;
|
|
559
658
|
let dispatchInit = init;
|
|
560
659
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
561
|
-
if (selectionIsCurrent(requestBindings.get(wireRequest))) {
|
|
660
|
+
if (selectionIsCurrent(requestBindings.get(wireRequest)) && copilotSessionExpiresAt > Date.now() + 1000) {
|
|
562
661
|
const fetchImpl = (route.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? execute;
|
|
563
662
|
const binding = requestBindings.get(wireRequest);
|
|
564
663
|
const snapshot = anthropicInstance !== undefined && binding?.kind === "oauth"
|
|
@@ -622,7 +721,16 @@ export async function prepareResponsesTransport(
|
|
|
622
721
|
if (!response.ok) await recordKeyAttemptFailure(logCtx, response, dispatchInit.signal ?? options.abortSignal);
|
|
623
722
|
return response;
|
|
624
723
|
}
|
|
625
|
-
|
|
724
|
+
let nextAdapter: ProviderAdapter;
|
|
725
|
+
try { nextAdapter = await refreshDispatchAdapter(requestParsed); }
|
|
726
|
+
catch (error) {
|
|
727
|
+
const refusal = copilotRefusalResponse(error);
|
|
728
|
+
if (refusal) return refusal;
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
if (owner === "passthrough" && route.providerName === "github-copilot" && nextAdapter.name === "openai-chat") {
|
|
732
|
+
throw new CopilotAutoAdapterHandoff(nextAdapter);
|
|
733
|
+
}
|
|
626
734
|
// Rebind before rebuilding: the rebuild's bridged-search restore and continuation
|
|
627
735
|
// restore key on the serving identity, which must be the refreshed route's, not the
|
|
628
736
|
// credential whose selection just lapsed.
|
|
@@ -876,7 +984,15 @@ export async function prepareResponsesTransport(
|
|
|
876
984
|
? resolveCopilotApiBaseUrl(sentOAuthSnapshot?.apiBaseUrl)
|
|
877
985
|
: undefined,
|
|
878
986
|
);
|
|
987
|
+
try { await resolveCopilotSelection(parsed); }
|
|
988
|
+
catch (error) {
|
|
989
|
+
if ((options.abortSignal ?? req.signal).aborted) return clientCancelledResponse();
|
|
990
|
+
const refusal = copilotRefusalResponse(error);
|
|
991
|
+
if (refusal) return refusal;
|
|
992
|
+
return formatErrorResponse(502, "provider_error", "GitHub Copilot Auto negotiation failed; retry or check your Copilot account permissions.");
|
|
993
|
+
}
|
|
879
994
|
let adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy);
|
|
995
|
+
if (copilotAutoAdapter) adapterProvider = { ...adapterProvider, adapter: copilotAutoAdapter };
|
|
880
996
|
const stripClaudeMainAuth = options.stripClaudeMainAuthForNoncanonicalForward === true
|
|
881
997
|
&& !isCanonicalOpenAiForwardProvider(adapterProvider)
|
|
882
998
|
&& ((adapterProvider.adapter === "openai-responses" && adapterProvider.authMode === "forward")
|
|
@@ -1008,6 +1124,8 @@ export async function prepareResponsesTransport(
|
|
|
1008
1124
|
set genericFailovers(value: typeof genericFailovers) {
|
|
1009
1125
|
genericFailovers = value;
|
|
1010
1126
|
},
|
|
1127
|
+
get oauth401ReplayAttempted(): boolean { return oauth401ReplayAttempted; },
|
|
1128
|
+
set oauth401ReplayAttempted(value: boolean) { oauth401ReplayAttempted = value; },
|
|
1011
1129
|
get adapter(): ProviderAdapter {
|
|
1012
1130
|
return adapter;
|
|
1013
1131
|
},
|
|
@@ -1065,6 +1183,8 @@ export async function prepareResponsesTransport(
|
|
|
1065
1183
|
applyFailoverSnapshot,
|
|
1066
1184
|
selectionIsCurrent,
|
|
1067
1185
|
resolveSelectionAdapter,
|
|
1186
|
+
resolveCopilotSelection,
|
|
1187
|
+
copilotRefusalResponse,
|
|
1068
1188
|
refreshRunTurnAdapter,
|
|
1069
1189
|
oauthDispatch,
|
|
1070
1190
|
noteRoutedAttemptSend,
|
|
@@ -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.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapSpendLedgerStorageError } from "./spend-storage-error";
|
|
1
2
|
import type { ResponsesRequestContext, ResponsesAdmissionState } from "./core-options";
|
|
2
3
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
3
4
|
import type { ResponsesTransport } from "./request-transport";
|
|
@@ -20,7 +21,7 @@ import type { AttemptRecoveryKind } from "../../usage/log";
|
|
|
20
21
|
import { providerFetch } from "./fetch-helpers";
|
|
21
22
|
import { normalizeLogConversationId } from "../request-log-conversation";
|
|
22
23
|
import { normalizeDeclaredToolName, type AdapterEvent, type OcxProviderContinuationState } from "../../types";
|
|
23
|
-
import { adapterFailureFromMessage, SEND_BUDGET_EXHAUSTED_CODE } from "../../lib/errors";
|
|
24
|
+
import { adapterFailureFromMessage, SEND_BUDGET_EXHAUSTED_CODE, SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE, type OcxErrorPayload } from "../../lib/errors";
|
|
24
25
|
import { SendBudgetExhaustedError, markResponseNonReplayable } from "../../lib/upstream-retry";
|
|
25
26
|
import {
|
|
26
27
|
hasEligibleGenericOAuthFailoverTarget,
|
|
@@ -235,6 +236,7 @@ export async function executeResponsesRunTurn(
|
|
|
235
236
|
preacquiredSlot?: ProviderRequestSlot,
|
|
236
237
|
): Promise<void> => {
|
|
237
238
|
const attemptSeq = ++runTurnAttemptSeq;
|
|
239
|
+
if (attemptSeq === 1 && options.comboInitialSend) options.comboInitialSend.producerOwned = true;
|
|
238
240
|
let pacingSlot = preacquiredSlot;
|
|
239
241
|
const emit = (event: AdapterEvent) => {
|
|
240
242
|
options.onCompactionRecoveryAdapterEvent?.(event);
|
|
@@ -314,7 +316,8 @@ export async function executeResponsesRunTurn(
|
|
|
314
316
|
Object.assign(parsed, routeState);
|
|
315
317
|
}
|
|
316
318
|
} catch (err) {
|
|
317
|
-
|
|
319
|
+
const storageRefusal = mapSpendLedgerStorageError(err, logCtx);
|
|
320
|
+
emit(storageRefusal ? { type: "error", ...storageRefusal } : err instanceof RequestPacingQueueOverloadError
|
|
318
321
|
? {
|
|
319
322
|
type: "error",
|
|
320
323
|
status: 429,
|
|
@@ -339,6 +342,7 @@ export async function executeResponsesRunTurn(
|
|
|
339
342
|
message: err instanceof Error ? err.message : String(err),
|
|
340
343
|
});
|
|
341
344
|
} finally {
|
|
345
|
+
if (attemptSeq === 1) options.comboInitialSend?.permit.release();
|
|
342
346
|
devinProducers.delete(producerAbort);
|
|
343
347
|
cleanupProducerAbort?.();
|
|
344
348
|
releaseProviderRequestSlot(pacingSlot);
|
|
@@ -711,7 +715,8 @@ export async function executeResponsesRunTurn(
|
|
|
711
715
|
runTurnAbort.abort();
|
|
712
716
|
queue.close();
|
|
713
717
|
const message = preflight.error?.message ?? "Adapter ended before producing a response";
|
|
714
|
-
const failure = formatErrorResponse(502,
|
|
718
|
+
const failure = formatErrorResponse(preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? 503 : 502,
|
|
719
|
+
preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
|
|
715
720
|
// A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
|
|
716
721
|
// combo must not send this turn to another target: the failure stays with this child.
|
|
717
722
|
if (preflight.replayUnsafe) markResponseNonReplayable(failure);
|
|
@@ -910,7 +915,9 @@ export async function executeResponsesRunTurn(
|
|
|
910
915
|
const message = classifiedError?.message ?? (firstMeaningful?.type === "error"
|
|
911
916
|
? firstMeaningful.message
|
|
912
917
|
: "Adapter ended before producing a response");
|
|
913
|
-
const
|
|
918
|
+
const storageRefusal = firstMeaningful?.type === "error" && firstMeaningful.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
|
|
919
|
+
const failure = formatErrorResponse(storageRefusal ? 503 : 502,
|
|
920
|
+
storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
|
|
914
921
|
if (replayUnsafe) markResponseNonReplayable(failure);
|
|
915
922
|
return failure;
|
|
916
923
|
}
|
|
@@ -954,7 +961,10 @@ export async function executeResponsesRunTurn(
|
|
|
954
961
|
commitReasoningReplayServingRoute();
|
|
955
962
|
}
|
|
956
963
|
notifyResponseComplete(json);
|
|
957
|
-
return new Response(JSON.stringify(json), {
|
|
964
|
+
return new Response(JSON.stringify(json), {
|
|
965
|
+
status: (json.error as OcxErrorPayload | undefined)?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? 503 : 200,
|
|
966
|
+
headers: { "Content-Type": "application/json" },
|
|
967
|
+
});
|
|
958
968
|
} catch (error) {
|
|
959
969
|
if (!isTranslatorBudgetExceededError(error)) throw error;
|
|
960
970
|
runTurnAbort.abort();
|