@bitkyc08/opencodex 2.82.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/gui/dist/assets/{App-Ds_qL7o8.js → App-Ba8uG696.js} +4 -4
- package/gui/dist/assets/{Tray-BqHF5Trd.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-D3FBCNiP.js → index-BHRZdJuv.js} +12 -12
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-DiJO6sYY.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +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/passthrough.ts +2 -0
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/bridge/errors.ts +7 -1
- package/src/claude/context-windows.ts +3 -3
- package/src/claude/desktop-picker.ts +1 -1
- package/src/cli/account-auth.ts +20 -12
- package/src/cli/resolve.ts +1 -1
- 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/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/catalog/native-models.ts +1 -1
- package/src/codex/catalog/provider-models.ts +25 -0
- 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/main-account.ts +6 -7
- package/src/generated/compatibility-version.json +91 -75
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/lib/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/index.ts +1 -1
- package/src/oauth/meta-muse-device.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/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/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/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +31 -4
- 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/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- 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 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +65 -6
- package/src/server/responses/compact.ts +3 -2
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-dispatch.ts +115 -60
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +5 -0
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +1 -1
|
@@ -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,
|
|
@@ -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,
|
|
@@ -315,7 +316,8 @@ export async function executeResponsesRunTurn(
|
|
|
315
316
|
Object.assign(parsed, routeState);
|
|
316
317
|
}
|
|
317
318
|
} catch (err) {
|
|
318
|
-
|
|
319
|
+
const storageRefusal = mapSpendLedgerStorageError(err, logCtx);
|
|
320
|
+
emit(storageRefusal ? { type: "error", ...storageRefusal } : err instanceof RequestPacingQueueOverloadError
|
|
319
321
|
? {
|
|
320
322
|
type: "error",
|
|
321
323
|
status: 429,
|
|
@@ -713,7 +715,8 @@ export async function executeResponsesRunTurn(
|
|
|
713
715
|
runTurnAbort.abort();
|
|
714
716
|
queue.close();
|
|
715
717
|
const message = preflight.error?.message ?? "Adapter ended before producing a response";
|
|
716
|
-
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));
|
|
717
720
|
// A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
|
|
718
721
|
// combo must not send this turn to another target: the failure stays with this child.
|
|
719
722
|
if (preflight.replayUnsafe) markResponseNonReplayable(failure);
|
|
@@ -912,7 +915,9 @@ export async function executeResponsesRunTurn(
|
|
|
912
915
|
const message = classifiedError?.message ?? (firstMeaningful?.type === "error"
|
|
913
916
|
? firstMeaningful.message
|
|
914
917
|
: "Adapter ended before producing a response");
|
|
915
|
-
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));
|
|
916
921
|
if (replayUnsafe) markResponseNonReplayable(failure);
|
|
917
922
|
return failure;
|
|
918
923
|
}
|
|
@@ -956,7 +961,10 @@ export async function executeResponsesRunTurn(
|
|
|
956
961
|
commitReasoningReplayServingRoute();
|
|
957
962
|
}
|
|
958
963
|
notifyResponseComplete(json);
|
|
959
|
-
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
|
+
});
|
|
960
968
|
} catch (error) {
|
|
961
969
|
if (!isTranslatorBudgetExceededError(error)) throw error;
|
|
962
970
|
runTurnAbort.abort();
|
|
@@ -79,6 +79,7 @@ export async function executeResponsesSidecars(
|
|
|
79
79
|
| "anthropicSessionKey"
|
|
80
80
|
| "commitResolvedOAuthSelection"
|
|
81
81
|
| "resolveSelectionAdapter"
|
|
82
|
+
| "resolveCopilotSelection"
|
|
82
83
|
| "oauthDispatch"
|
|
83
84
|
| "noteRoutedAttemptSend"
|
|
84
85
|
| "bindKeyUsageFromBridge"
|
|
@@ -103,6 +104,7 @@ export async function executeResponsesSidecars(
|
|
|
103
104
|
anthropicSessionKey,
|
|
104
105
|
commitResolvedOAuthSelection,
|
|
105
106
|
resolveSelectionAdapter,
|
|
107
|
+
resolveCopilotSelection,
|
|
106
108
|
oauthDispatch,
|
|
107
109
|
} = transportState;
|
|
108
110
|
const {
|
|
@@ -196,6 +198,7 @@ export async function executeResponsesSidecars(
|
|
|
196
198
|
// A single request may rotate once for a verification refusal. Keep this separate from the
|
|
197
199
|
// broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
|
|
198
200
|
let antigravityValidationRotationAttempted = false;
|
|
201
|
+
/** Admit a replacement credential, or leave the loop's original refusal authoritative. */
|
|
199
202
|
const rotateSidecarProviderOn429 = async (
|
|
200
203
|
retryAfter: string | null,
|
|
201
204
|
responseHeaders?: Headers,
|
|
@@ -280,6 +283,8 @@ export async function executeResponsesSidecars(
|
|
|
280
283
|
// `applyFailoverSnapshot` has already rebound the route to the sibling identity/project.
|
|
281
284
|
} else if (rotated) {
|
|
282
285
|
route.provider = rotated;
|
|
286
|
+
try { await resolveCopilotSelection(retryParsed ?? parsed); }
|
|
287
|
+
catch { return null; } // Keep the sidecar's original refusal when replacement negotiation fails.
|
|
283
288
|
} else if (
|
|
284
289
|
// A POSITIVE gate, not an early return. An early `return null` here made every later arm
|
|
285
290
|
// unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { formatErrorResponse } from "../../bridge";
|
|
2
|
+
import { SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE } from "../../lib/errors";
|
|
3
|
+
import { markLocalRequestLogRefusal, type RequestLogContext } from "../request-log";
|
|
4
|
+
import { SpendLedgerFileRefusedError } from "../../lib/spend-reservation-ledger";
|
|
5
|
+
|
|
6
|
+
type StorageRefusalLog = Pick<RequestLogContext, "localTerminalReason" | "terminalSource" | "errorCode">;
|
|
7
|
+
|
|
8
|
+
/** Local storage refusal, including retry wrappers: never provider-health evidence. */
|
|
9
|
+
export function mapSpendLedgerStorageError(error: unknown, logCtx?: StorageRefusalLog) {
|
|
10
|
+
const seen = new Set<unknown>();
|
|
11
|
+
while (error instanceof Error && !seen.has(error)) {
|
|
12
|
+
if (error instanceof SpendLedgerFileRefusedError) {
|
|
13
|
+
if (logCtx) {
|
|
14
|
+
markLocalRequestLogRefusal(logCtx, SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE);
|
|
15
|
+
logCtx.errorCode = SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
status: 503,
|
|
19
|
+
errorType: "server_error",
|
|
20
|
+
code: SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE,
|
|
21
|
+
message: error.message,
|
|
22
|
+
} as const;
|
|
23
|
+
}
|
|
24
|
+
seen.add(error);
|
|
25
|
+
error = error.cause;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function spendLedgerStorageErrorResponse(error: unknown, logCtx?: StorageRefusalLog): Response | undefined {
|
|
31
|
+
const refusal = mapSpendLedgerStorageError(error, logCtx);
|
|
32
|
+
return refusal && formatErrorResponse(refusal.status, refusal.code, refusal.message);
|
|
33
|
+
}
|
package/src/types/request.ts
CHANGED
|
@@ -442,6 +442,11 @@ export type AdapterEvent =
|
|
|
442
442
|
errorType?: string;
|
|
443
443
|
code?: string;
|
|
444
444
|
retryable?: boolean;
|
|
445
|
+
/**
|
|
446
|
+
* Proxy-internal, never serialized: the upstream failed the turn before producing text or a
|
|
447
|
+
* tool call, so the opt-in empty-completion guard may replay the identical request once.
|
|
448
|
+
*/
|
|
449
|
+
replaySafeBeforeOutput?: boolean;
|
|
445
450
|
};
|
|
446
451
|
|
|
447
452
|
/**
|
|
@@ -835,7 +835,7 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
835
835
|
{
|
|
836
836
|
// xAI documents the same whole-request >=200k band for grok-4.7;
|
|
837
837
|
// priority stacking remains a lower bound. See
|
|
838
|
-
// devlog/
|
|
838
|
+
// devlog/_fin/260923_grok47_parity/010_probe-evidence.md.
|
|
839
839
|
provider: "xai",
|
|
840
840
|
modelId: "grok-4.7",
|
|
841
841
|
thresholdInputTokens: 200_000,
|