@bitkyc08/opencodex 2.25.0 → 2.26.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 +59 -7
- package/gui/dist/assets/index-RL6b1bTV.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +18 -0
- package/src/adapters/client-fingerprint.ts +0 -2
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/live-models.ts +117 -30
- package/src/adapters/cursor/live-transport.ts +137 -56
- package/src/adapters/cursor/native-exec-fs.ts +1 -1
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/protobuf-request.ts +89 -21
- package/src/adapters/cursor/tool-definitions.ts +18 -7
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +7 -0
- package/src/adapters/cursor.ts +2 -0
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google.ts +23 -3
- package/src/adapters/openai-chat.ts +38 -13
- package/src/adapters/openai-responses.ts +129 -9
- package/src/adapters/registry.ts +30 -1
- package/src/bridge.ts +50 -12
- package/src/chat/inbound.ts +1 -0
- package/src/claude/agents-inject.ts +3 -2
- package/src/cli/dispatch.ts +19 -6
- package/src/cli/help.ts +2 -1
- package/src/cli/integrations.ts +35 -0
- package/src/cli/minimax.ts +8 -2
- package/src/cli/registry.ts +13 -2
- package/src/clients/config-export.ts +120 -1
- package/src/codex/account-store.ts +17 -1
- package/src/codex/auth-api.ts +51 -17
- package/src/codex/catalog/effort.ts +8 -5
- package/src/codex/catalog/provider-fetch.ts +39 -27
- package/src/codex/catalog/sync.ts +30 -8
- package/src/codex/inject.ts +39 -13
- package/src/codex/main-account.ts +29 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +25 -0
- package/src/codex/prompt-journal.ts +6 -2
- package/src/codex/quota-rejection.ts +21 -7
- package/src/codex/refresh.ts +4 -2
- package/src/codex/sync.ts +106 -3
- package/src/codex/warmup.ts +187 -81
- package/src/config.ts +85 -46
- package/src/generated/compatibility-version.json +127 -83
- package/src/grok/inject.ts +1 -1
- package/src/images/loop.ts +1 -0
- package/src/integrations/registry.ts +7 -0
- package/src/lab/automation/config-persistence.ts +2 -2
- package/src/lab/automation/persistence.ts +2 -2
- package/src/lab/ledger/purge.ts +2 -2
- package/src/lab/subject/behavior-fingerprint.ts +2 -2
- package/src/lib/config-ownership.ts +5 -2
- package/src/lib/destination-policy.ts +18 -1
- package/src/lib/provider-outbound.ts +7 -0
- package/src/lib/redact.ts +11 -0
- package/src/lib/tool-argument-integers.ts +50 -6
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-retry.ts +2 -1
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/oauth/google-antigravity.ts +2 -2
- package/src/oauth/index.ts +60 -8
- package/src/providers/antigravity-models.ts +58 -4
- package/src/providers/command-code-efforts.ts +36 -9
- package/src/providers/context-cap.ts +9 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fastwire.ts +453 -0
- package/src/providers/registry.ts +21 -1
- package/src/providers/service-tier.ts +173 -89
- package/src/responses/parser.ts +25 -16
- package/src/responses/reasoning-replay-cache.ts +30 -0
- package/src/responses/thought-signature-replay.ts +74 -4
- package/src/router.ts +6 -0
- package/src/routing/compatibility/behavior.ts +27 -16
- package/src/server/index.ts +10 -1
- package/src/server/management/oauth-account-routes.ts +10 -2
- package/src/server/management/shared.ts +1 -1
- package/src/server/management/system-routes.ts +15 -0
- package/src/server/request-log.ts +57 -3
- package/src/server/responses/agent-task-recovery.ts +6 -1
- package/src/server/responses/core.ts +209 -40
- package/src/server/responses/fetch-helpers.ts +15 -36
- package/src/server/responses/responses-field-backfill.ts +173 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
- package/src/service.ts +38 -36
- package/src/storage/cleanup.ts +2 -2
- package/src/tray/windows.ts +3 -2
- package/src/types.ts +101 -14
- package/src/update/job.ts +9 -18
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +34 -5
- package/src/usage/log.ts +74 -4
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/web-search/anthropic-executor.ts +8 -6
- package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Server } from "bun";
|
|
2
2
|
import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
|
|
3
3
|
import { formatPassthroughUpstreamError } from "./passthrough-error";
|
|
4
|
+
import {
|
|
5
|
+
createResponsesFieldBackfillBlockRewrite,
|
|
6
|
+
backfillResponsesFieldsJson,
|
|
7
|
+
} from "./responses-field-backfill";
|
|
4
8
|
import { checkInputAdmission } from "./input-admission";
|
|
5
9
|
import { nativeContextLimits } from "../../codex/catalog";
|
|
6
10
|
import { describeUpstreamConnectFailure } from "./upstream-error";
|
|
7
11
|
import {
|
|
8
|
-
getConfigPath,
|
|
9
12
|
multiAgentGuidanceEnabled,
|
|
10
13
|
resolveEnvValue,
|
|
11
14
|
} from "../../config";
|
|
@@ -15,9 +18,11 @@ import {
|
|
|
15
18
|
reasoningReplayCodexCredentialIdentity,
|
|
16
19
|
reasoningReplayDestinationIdentity,
|
|
17
20
|
durableReplayDestinationIdentity,
|
|
21
|
+
durableReplayCredentialIdentity,
|
|
18
22
|
reasoningReplayKeyCredentialIdentity,
|
|
19
23
|
reasoningReplayOAuthCredentialIdentity,
|
|
20
24
|
} from "../../responses/reasoning-replay-cache";
|
|
25
|
+
import { awaitThoughtSignatureDurability, thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
|
|
21
26
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
22
27
|
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
|
|
23
28
|
import {
|
|
@@ -57,12 +62,13 @@ import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
|
57
62
|
import { resolveClientRetryAfter } from "../../lib/retry-after";
|
|
58
63
|
import { enrichOpenCodeZenRateLimitMessage } from "../../providers/opencode-zen-rate-limit";
|
|
59
64
|
import { modelInList, namespacedToolName } from "../../types";
|
|
60
|
-
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
|
|
65
|
+
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage, TierDecision } from "../../types";
|
|
61
66
|
import {
|
|
62
67
|
forceRefreshOAuthAccessSnapshot,
|
|
63
68
|
getOAuthCredentialApiBaseUrl,
|
|
64
69
|
getValidAccessTokenForAccount,
|
|
65
70
|
getValidAccessTokenSnapshot,
|
|
71
|
+
publicOAuthAuthenticationErrorMessage,
|
|
66
72
|
type OAuthAccessSnapshot,
|
|
67
73
|
UnsupportedOAuthProviderError,
|
|
68
74
|
} from "../../oauth";
|
|
@@ -123,7 +129,18 @@ import { createTranslatorBudget, isTranslatorBudgetExceededError, type Translato
|
|
|
123
129
|
import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
124
130
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
125
131
|
import { providerContextCap } from "../../providers/context-cap";
|
|
126
|
-
import {
|
|
132
|
+
import {
|
|
133
|
+
fastPolicyForModel,
|
|
134
|
+
serviceTierSupportFromPolicy,
|
|
135
|
+
SERVICE_TIER_ADAPTERS,
|
|
136
|
+
} from "../../providers/service-tier";
|
|
137
|
+
import {
|
|
138
|
+
canonicalFastTierMarker,
|
|
139
|
+
decideTier,
|
|
140
|
+
tierObservationContext,
|
|
141
|
+
tierValueAfterDecision,
|
|
142
|
+
type ResolvedFastPolicy,
|
|
143
|
+
} from "../../providers/fastwire";
|
|
127
144
|
import {
|
|
128
145
|
RequestPacingQueueOverloadError,
|
|
129
146
|
waitForProviderRequestSlot,
|
|
@@ -149,7 +166,7 @@ import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
|
149
166
|
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
150
167
|
import type { WsData } from "../ws-bridge";
|
|
151
168
|
import { codexAccountSelectionForTurn, registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
|
|
152
|
-
import { redactSecretString } from "../../lib/redact";
|
|
169
|
+
import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact";
|
|
153
170
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
154
171
|
import type { AdmissionLease } from "../../lib/admission";
|
|
155
172
|
import { supportedLadderFor } from "../effort-policy";
|
|
@@ -167,6 +184,8 @@ import {
|
|
|
167
184
|
noteAttemptSend,
|
|
168
185
|
readConfiguredCodexServiceTier,
|
|
169
186
|
recordAdapterReasoning,
|
|
187
|
+
recordAdapterTier,
|
|
188
|
+
recordAdapterTierMetadata,
|
|
170
189
|
recordAttemptRequestedEffort,
|
|
171
190
|
requestLogSpeedLabel,
|
|
172
191
|
sealRequestAttemptIdentity,
|
|
@@ -208,6 +227,11 @@ import {
|
|
|
208
227
|
hasResponsesItemIdRepair,
|
|
209
228
|
repairResponsesJsonItemIds,
|
|
210
229
|
} from "../responses-item-id-repair";
|
|
230
|
+
import {
|
|
231
|
+
createReasoningSummaryChannelPayloadRewrite,
|
|
232
|
+
rewriteReasoningSummaryInJsonString,
|
|
233
|
+
routeUsesContentChannelReasoning,
|
|
234
|
+
} from "../responses-reasoning-summary-rewrite";
|
|
211
235
|
import {
|
|
212
236
|
createImageGenCallRestoreRewrite,
|
|
213
237
|
imageGenToolCallAliases,
|
|
@@ -304,11 +328,21 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
304
328
|
}): void {
|
|
305
329
|
const { parsed, providerName, provider, adapterName } = args;
|
|
306
330
|
let credentialIdentity: string | undefined;
|
|
331
|
+
let credentialDurableIdentity: string | undefined;
|
|
332
|
+
const durableSalt = thoughtSignatureReplaySalt();
|
|
307
333
|
if (provider.authMode === "oauth") {
|
|
308
334
|
credentialIdentity = reasoningReplayOAuthCredentialIdentity(
|
|
309
335
|
args.oauthCredentialSnapshot,
|
|
310
336
|
provider.headers,
|
|
311
337
|
);
|
|
338
|
+
// The persisted account-slot id survives token refresh and restarts; the rotating
|
|
339
|
+
// generation deliberately does NOT participate (#1926 design: rotation-safe).
|
|
340
|
+
credentialDurableIdentity = durableReplayCredentialIdentity(
|
|
341
|
+
"oauth",
|
|
342
|
+
args.oauthCredentialSnapshot?.accountId,
|
|
343
|
+
provider.headers,
|
|
344
|
+
durableSalt,
|
|
345
|
+
);
|
|
312
346
|
} else if (provider.authMode === "forward") {
|
|
313
347
|
const poolContext = args.codexAuthContext?.kind === "pool"
|
|
314
348
|
|| args.codexAuthContext?.kind === "main-pool"
|
|
@@ -327,8 +361,28 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
327
361
|
writerGeneration: poolContext?.writerGeneration,
|
|
328
362
|
headers: provider.headers,
|
|
329
363
|
});
|
|
364
|
+
// Durable identity requires a STABLE, TRUSTED account handle. Pool context comes from
|
|
365
|
+
// our own account store; a client-supplied chatgpt-account-id header is attacker
|
|
366
|
+
// -influenceable bucket selection and a bearer alone is rotating material — both are
|
|
367
|
+
// refused, so direct-forward turns get no durable scope (fail closed; the in-process
|
|
368
|
+
// cache still covers same-process replay).
|
|
369
|
+
const codexDurableHandle = poolContext?.accountId
|
|
370
|
+
?? poolContext?.chatgptAccountId
|
|
371
|
+
?? undefined;
|
|
372
|
+
credentialDurableIdentity = durableReplayCredentialIdentity(
|
|
373
|
+
"codex",
|
|
374
|
+
codexDurableHandle ?? undefined,
|
|
375
|
+
provider.headers,
|
|
376
|
+
durableSalt,
|
|
377
|
+
);
|
|
330
378
|
} else if (provider.authMode !== "local") {
|
|
331
379
|
credentialIdentity = reasoningReplayKeyCredentialIdentity(provider);
|
|
380
|
+
credentialDurableIdentity = durableReplayCredentialIdentity(
|
|
381
|
+
"key",
|
|
382
|
+
nonEmptyProviderApiKey(provider),
|
|
383
|
+
provider.headers,
|
|
384
|
+
durableSalt,
|
|
385
|
+
);
|
|
332
386
|
}
|
|
333
387
|
const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
|
|
334
388
|
bindReasoningReplayScope(
|
|
@@ -341,18 +395,23 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
341
395
|
adapterName,
|
|
342
396
|
modelId: parsed.modelId,
|
|
343
397
|
credentialIdentity,
|
|
398
|
+
...(credentialDurableIdentity ? { credentialDurableIdentity } : {}),
|
|
344
399
|
}
|
|
345
400
|
: undefined,
|
|
346
401
|
);
|
|
347
402
|
}
|
|
348
403
|
|
|
404
|
+
function nonEmptyProviderApiKey(provider: OcxProviderConfig): string | undefined {
|
|
405
|
+
return typeof provider.apiKey === "string" && provider.apiKey.trim().length > 0
|
|
406
|
+
? provider.apiKey
|
|
407
|
+
: undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
349
410
|
function isFixedCodexAccount(authCtx: CodexAuthContext): boolean {
|
|
350
411
|
return (authCtx.kind === "pool" || authCtx.kind === "main-pool")
|
|
351
412
|
&& authCtx.fixedAccount === true;
|
|
352
413
|
}
|
|
353
414
|
|
|
354
|
-
|
|
355
|
-
|
|
356
415
|
export function usesCodexForwardPoolAuth(
|
|
357
416
|
authCtx: CodexAuthContext,
|
|
358
417
|
provider: OcxProviderConfig,
|
|
@@ -600,6 +659,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
600
659
|
translatorBudget: options.translatorBudget,
|
|
601
660
|
});
|
|
602
661
|
recordAdapterReasoning(logCtx, request);
|
|
662
|
+
recordAdapterTier(logCtx, request);
|
|
603
663
|
|
|
604
664
|
await firstResponse.body?.cancel().catch(() => undefined);
|
|
605
665
|
options.onCodexAuthContextResolved?.(retryAuthCtx);
|
|
@@ -969,6 +1029,23 @@ const UNREADABLE_ENCRYPTED_AGENT_TASK_MESSAGE =
|
|
|
969
1029
|
const MAX_UPSTREAM_JSON_BODY_BYTES = 32 * 1024 * 1024;
|
|
970
1030
|
const UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS = 180_000;
|
|
971
1031
|
const UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS = 30_000;
|
|
1032
|
+
const MAX_FAST_WIRE_CAPABILITY_WARNINGS = 256;
|
|
1033
|
+
const warnedFastWireCapabilityGaps = new Set<string>();
|
|
1034
|
+
|
|
1035
|
+
function warnFastWireCapabilityGap(providerName: string, modelId: string): void {
|
|
1036
|
+
const safeProvider = sanitizeLogMetadataString(providerName) ?? "unknown";
|
|
1037
|
+
const safeModel = sanitizeLogMetadataString(modelId) ?? "unknown";
|
|
1038
|
+
const key = `${safeProvider}\0${safeModel}`;
|
|
1039
|
+
if (warnedFastWireCapabilityGaps.has(key)) return;
|
|
1040
|
+
if (warnedFastWireCapabilityGaps.size >= MAX_FAST_WIRE_CAPABILITY_WARNINGS) {
|
|
1041
|
+
const oldest = warnedFastWireCapabilityGaps.values().next().value;
|
|
1042
|
+
if (oldest !== undefined) warnedFastWireCapabilityGaps.delete(oldest);
|
|
1043
|
+
}
|
|
1044
|
+
warnedFastWireCapabilityGaps.add(key);
|
|
1045
|
+
console.warn(
|
|
1046
|
+
`[opencodex] Fast policy for ${safeProvider}/${safeModel} has service-tier capability but no Fast wire; preserving only caller-permitted tier behavior`,
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
972
1049
|
export const UPSTREAM_JSON_BODY_READ_OPTIONS = {
|
|
973
1050
|
maxBytes: MAX_UPSTREAM_JSON_BODY_BYTES,
|
|
974
1051
|
totalTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS,
|
|
@@ -1154,23 +1231,21 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
1154
1231
|
logCtx.preserveResolvedModelFromRoute = true;
|
|
1155
1232
|
}
|
|
1156
1233
|
|
|
1157
|
-
// Fast
|
|
1158
|
-
//
|
|
1159
|
-
const
|
|
1234
|
+
// Resolve Fast policy after the final route/wire settles. A1 records the decision on parsed
|
|
1235
|
+
// options; the Responses adapter owns the final outbound body write.
|
|
1236
|
+
const fastPolicy = fastPolicyForModel(
|
|
1160
1237
|
route.provider,
|
|
1161
1238
|
route.modelId,
|
|
1162
1239
|
route.providerName,
|
|
1163
1240
|
inboundWire,
|
|
1164
1241
|
);
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1173
|
-
parsed.options.serviceTier = tier;
|
|
1242
|
+
const modelServiceTierSupport = serviceTierSupportFromPolicy(fastPolicy);
|
|
1243
|
+
const callerTier = parsed.options.serviceTier;
|
|
1244
|
+
parsed.options.tierObservation = tierObservationContext(fastPolicy, config.fastMode, callerTier);
|
|
1245
|
+
parsed.options.tierDecision = decideTier(fastPolicy, config.fastMode, callerTier);
|
|
1246
|
+
parsed.options.serviceTier = tierValueAfterDecision(parsed.options.tierDecision, callerTier);
|
|
1247
|
+
if (fastPolicy.capability === true && fastPolicy.fastWire === null) {
|
|
1248
|
+
warnFastWireCapabilityGap(route.providerName, route.modelId);
|
|
1174
1249
|
}
|
|
1175
1250
|
applyServiceTierGate(
|
|
1176
1251
|
route.provider,
|
|
@@ -1179,6 +1254,7 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
1179
1254
|
route.modelId,
|
|
1180
1255
|
route.providerName,
|
|
1181
1256
|
inboundWire,
|
|
1257
|
+
fastPolicy,
|
|
1182
1258
|
);
|
|
1183
1259
|
if (modelServiceTierSupport === false) {
|
|
1184
1260
|
logCtx.requestedServiceTier = undefined;
|
|
@@ -1566,29 +1642,43 @@ function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBud
|
|
|
1566
1642
|
* Service-tier capability gate, applied after the final route/wire is settled. A
|
|
1567
1643
|
* provider explicitly documented as NOT supporting `service_tier` must never
|
|
1568
1644
|
* receive it: strip the field and clear the logging value even when the caller
|
|
1569
|
-
* supplied one (fail closed).
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1573
|
-
* request against a gateway we know nothing about.
|
|
1645
|
+
* supplied one (fail closed). A policy-produced canonical Fast decision has
|
|
1646
|
+
* already passed capability validation and cannot be vetoed by Chat's caller
|
|
1647
|
+
* forwarding permission. On unclassified routes every caller tier remains subject
|
|
1648
|
+
* to `forwardCallerTier`.
|
|
1574
1649
|
*/
|
|
1575
1650
|
export function applyServiceTierGate(
|
|
1576
1651
|
provider: OcxProviderConfig,
|
|
1577
1652
|
rawBody: unknown,
|
|
1578
|
-
options: { serviceTier?: string },
|
|
1653
|
+
options: { serviceTier?: string; tierDecision?: TierDecision },
|
|
1579
1654
|
modelId?: string,
|
|
1580
1655
|
providerName?: string,
|
|
1581
1656
|
inbound: InboundWire = "responses",
|
|
1657
|
+
resolvedPolicy?: ResolvedFastPolicy,
|
|
1582
1658
|
): void {
|
|
1583
1659
|
// A direct unit caller without a model id retains the historical tri-state behavior for
|
|
1584
1660
|
// adapters outside the OpenAI service-tier family. Once a model is known, resolve the final
|
|
1585
1661
|
// model adapter as well: an explicit override to Anthropic (or another non-OpenAI wire) must
|
|
1586
1662
|
// not carry a caller-supplied `service_tier` through a route that cannot forward it.
|
|
1587
1663
|
if (modelId === undefined && !SERVICE_TIER_ADAPTERS.has(provider.adapter)) return;
|
|
1588
|
-
const
|
|
1589
|
-
?
|
|
1590
|
-
:
|
|
1591
|
-
|
|
1664
|
+
const policy = modelId === undefined
|
|
1665
|
+
? undefined
|
|
1666
|
+
: resolvedPolicy ?? fastPolicyForModel(provider, modelId, providerName, inbound);
|
|
1667
|
+
const forwardCallerTier = modelId === undefined
|
|
1668
|
+
? provider.supportsServiceTier !== false
|
|
1669
|
+
: policy!.forwardCallerTier;
|
|
1670
|
+
const rawTier = rawBody && typeof rawBody === "object"
|
|
1671
|
+
? (rawBody as Record<string, unknown>).service_tier
|
|
1672
|
+
: undefined;
|
|
1673
|
+
const canonicalDecision = options.tierDecision?.kind === "set";
|
|
1674
|
+
const callerTierIsForeign = rawTier !== undefined
|
|
1675
|
+
&& (typeof rawTier !== "string" || canonicalFastTierMarker(rawTier) === undefined);
|
|
1676
|
+
const dropForeignCallerTier = policy?.capability === true
|
|
1677
|
+
&& policy.fastWire?.kind === "service-tier"
|
|
1678
|
+
&& policy.fastWire?.foreignCallerTiers === "drop"
|
|
1679
|
+
&& callerTierIsForeign;
|
|
1680
|
+
if (policy && policy.capability !== false && canonicalDecision) return;
|
|
1681
|
+
if (forwardCallerTier && !dropForeignCallerTier) return;
|
|
1592
1682
|
if (rawBody && typeof rawBody === "object") {
|
|
1593
1683
|
delete (rawBody as Record<string, unknown>).service_tier;
|
|
1594
1684
|
}
|
|
@@ -1724,6 +1814,7 @@ async function handleResponsesInner(
|
|
|
1724
1814
|
}
|
|
1725
1815
|
logCtx.requestedModel = parsed.modelId;
|
|
1726
1816
|
logCtx.requestedEffort = parsed.options.reasoning;
|
|
1817
|
+
logCtx.callerServiceTier = sanitizeLogMetadataString(parsed.options.serviceTier);
|
|
1727
1818
|
logCtx.requestedServiceTier = parsed.options.serviceTier;
|
|
1728
1819
|
logCtx.requestedSpeedLabel = requestLogSpeedLabel(parsed.options.serviceTier);
|
|
1729
1820
|
logCtx.configuredServiceTier = readConfiguredCodexServiceTier();
|
|
@@ -2107,13 +2198,14 @@ async function handleResponsesInner(
|
|
|
2107
2198
|
}
|
|
2108
2199
|
} catch (err) {
|
|
2109
2200
|
if (err instanceof UnsupportedOAuthProviderError) {
|
|
2201
|
+
const safeProviderName = redactSecretString(route.providerName);
|
|
2110
2202
|
return formatErrorResponse(
|
|
2111
2203
|
400,
|
|
2112
2204
|
"invalid_request_error",
|
|
2113
|
-
`${err.message}. Remove or reconfigure provider '${
|
|
2205
|
+
`${redactSecretString(err.message)}. Remove or reconfigure provider '${safeProviderName}' in the OpenCodex configuration.`,
|
|
2114
2206
|
);
|
|
2115
2207
|
}
|
|
2116
|
-
return formatErrorResponse(401, "authentication_error",
|
|
2208
|
+
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
2117
2209
|
}
|
|
2118
2210
|
}
|
|
2119
2211
|
route.provider = resolveProviderTransport(
|
|
@@ -2166,6 +2258,9 @@ async function handleResponsesInner(
|
|
|
2166
2258
|
(logCtx.attempts ??= []).push(attempt);
|
|
2167
2259
|
}
|
|
2168
2260
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
|
|
2261
|
+
if (adapter.runTurn) {
|
|
2262
|
+
recordAdapterTierMetadata(logCtx, adapter.tierLogForRunTurn?.(parsed));
|
|
2263
|
+
}
|
|
2169
2264
|
// Optional route-identity linkage for attempt correlation (CL-09 consumes it). The slot
|
|
2170
2265
|
// resolves to null unless an opt-in subsystem registered a linker, so an install without
|
|
2171
2266
|
// routing profiles does no work here and loads no additional module. The non-throwing
|
|
@@ -2400,6 +2495,7 @@ async function handleResponsesInner(
|
|
|
2400
2495
|
}
|
|
2401
2496
|
: undefined;
|
|
2402
2497
|
recordAdapterReasoning(logCtx, request);
|
|
2498
|
+
recordAdapterTier(logCtx, request);
|
|
2403
2499
|
const actualHostKey = upstreamHostHealthKey(
|
|
2404
2500
|
route.providerName,
|
|
2405
2501
|
safeOriginLabel(request.url),
|
|
@@ -2781,6 +2877,10 @@ async function handleResponsesInner(
|
|
|
2781
2877
|
? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
|
|
2782
2878
|
: undefined,
|
|
2783
2879
|
responseModelRewrite,
|
|
2880
|
+
parsed.options.hideThinkingSummary !== true
|
|
2881
|
+
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
2882
|
+
? createReasoningSummaryChannelPayloadRewrite()
|
|
2883
|
+
: undefined,
|
|
2784
2884
|
].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
|
|
2785
2885
|
// #893: sparse-snapshot gateways get field backfills AND lifecycle event
|
|
2786
2886
|
// injection at the block level, after payload rewrites. Defaults come
|
|
@@ -2799,6 +2899,7 @@ async function handleResponsesInner(
|
|
|
2799
2899
|
snapshotRepairEnabled
|
|
2800
2900
|
? createResponsesSnapshotBlockRewrite(outboundRequestBody, translatorBudget)
|
|
2801
2901
|
: undefined,
|
|
2902
|
+
createResponsesFieldBackfillBlockRewrite(),
|
|
2802
2903
|
// Last: every rewrite above can still rename or reshape a call item, so the guard must
|
|
2803
2904
|
// compare the names the client will actually receive against the declared catalog.
|
|
2804
2905
|
undeclaredToolGuardActive
|
|
@@ -2992,9 +3093,16 @@ async function handleResponsesInner(
|
|
|
2992
3093
|
const repaired = hasResponsesSnapshotRepair(route.provider.responsesSnapshotRepair)
|
|
2993
3094
|
? repairResponsesSnapshotJson(restored, outboundRequestBody)
|
|
2994
3095
|
: restored;
|
|
2995
|
-
|
|
2996
|
-
? rewriteResponsesModelJson(repaired, parsed._responseModelId)
|
|
2997
|
-
: repaired;
|
|
3096
|
+
const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
|
|
3097
|
+
? rewriteResponsesModelJson(backfillResponsesFieldsJson(repaired), parsed._responseModelId)
|
|
3098
|
+
: backfillResponsesFieldsJson(repaired);
|
|
3099
|
+
// The bounded-JSON answer bypasses the SSE payload rewrite, so content-
|
|
3100
|
+
// channel reasoning needs the same normalization here for the plain
|
|
3101
|
+
// JSON answer and every reframed-SSE variant built from clientJson.
|
|
3102
|
+
return parsed.options.hideThinkingSummary !== true
|
|
3103
|
+
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
3104
|
+
? rewriteReasoningSummaryInJsonString(modelRewritten)
|
|
3105
|
+
: modelRewritten;
|
|
2998
3106
|
})();
|
|
2999
3107
|
// #1700: same fail-closed policy as the SSE relay above. Both the plain JSON answer and
|
|
3000
3108
|
// the reframed-SSE branch below are built from this body, so one check covers them. This
|
|
@@ -3196,7 +3304,10 @@ async function handleResponsesInner(
|
|
|
3196
3304
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
3197
3305
|
waitForRequestSlot: imageProviderFetch.waitForPacing,
|
|
3198
3306
|
fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
|
|
3199
|
-
onRequestBuilt: request =>
|
|
3307
|
+
onRequestBuilt: request => {
|
|
3308
|
+
recordAdapterReasoning(logCtx, request);
|
|
3309
|
+
recordAdapterTier(logCtx, request);
|
|
3310
|
+
},
|
|
3200
3311
|
...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
|
|
3201
3312
|
onUsage: usage => {
|
|
3202
3313
|
// Cursor may assign _cursorConversationId inside the image loop's first runTurn;
|
|
@@ -3274,7 +3385,10 @@ async function handleResponsesInner(
|
|
|
3274
3385
|
forceEmptyResponseId: true,
|
|
3275
3386
|
abortSignal: options.abortSignal,
|
|
3276
3387
|
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
3277
|
-
onRequestBuilt: request =>
|
|
3388
|
+
onRequestBuilt: request => {
|
|
3389
|
+
recordAdapterReasoning(logCtx, request);
|
|
3390
|
+
recordAdapterTier(logCtx, request);
|
|
3391
|
+
},
|
|
3278
3392
|
onAttemptSend: (recovery?: AttemptRecoveryKind) =>
|
|
3279
3393
|
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery),
|
|
3280
3394
|
onUsage: usage => {
|
|
@@ -3368,9 +3482,26 @@ async function handleResponsesInner(
|
|
|
3368
3482
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
3369
3483
|
}
|
|
3370
3484
|
noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery);
|
|
3485
|
+
const runTurnProviderFetch = providerFetch(
|
|
3486
|
+
route.provider,
|
|
3487
|
+
options.codexWsRuntimeIdentity,
|
|
3488
|
+
{
|
|
3489
|
+
providerName: route.providerName,
|
|
3490
|
+
modelId: route.modelId,
|
|
3491
|
+
// runTurnAttempt acquired this logical turn's first physical-request slot above.
|
|
3492
|
+
// Cursor HTTP/1.1 consumes it for RunSSE; every BidiAppend and redial then waits on
|
|
3493
|
+
// the same provider queue through this stateful wrapper.
|
|
3494
|
+
pacingSlotAcquired: true,
|
|
3495
|
+
},
|
|
3496
|
+
);
|
|
3371
3497
|
await adapter.runTurn?.(
|
|
3372
3498
|
parsed,
|
|
3373
|
-
{
|
|
3499
|
+
{
|
|
3500
|
+
headers: selectedForwardHeaders,
|
|
3501
|
+
abortSignal: runTurnAbort.signal,
|
|
3502
|
+
translatorBudget,
|
|
3503
|
+
providerFetch: runTurnProviderFetch,
|
|
3504
|
+
},
|
|
3374
3505
|
targetQueue.push,
|
|
3375
3506
|
);
|
|
3376
3507
|
} catch (err) {
|
|
@@ -3445,6 +3576,9 @@ async function handleResponsesInner(
|
|
|
3445
3576
|
toolParameterSchemas,
|
|
3446
3577
|
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
3447
3578
|
...(routedCompaction ? { compaction: true } : {}),
|
|
3579
|
+
// grok-build's strict decoder dies on the typed response.heartbeat frame; its
|
|
3580
|
+
// eventsource layer tolerates comment keep-alives. Codex needs the opposite.
|
|
3581
|
+
...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
|
|
3448
3582
|
onUsage: usage => {
|
|
3449
3583
|
// Raw adapter usage, pre wire-normalization: the bridged SSE now always carries
|
|
3450
3584
|
// zero-default detail objects, so provenance must come from here (cache_detail_missing).
|
|
@@ -3521,6 +3655,10 @@ async function handleResponsesInner(
|
|
|
3521
3655
|
responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
|
|
3522
3656
|
);
|
|
3523
3657
|
}
|
|
3658
|
+
// #1926 gap 2: the buffered path queued its signature persists inside
|
|
3659
|
+
// buildResponseJSON; bound the durability window before the JSON becomes
|
|
3660
|
+
// externally visible.
|
|
3661
|
+
await awaitThoughtSignatureDurability();
|
|
3524
3662
|
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
3525
3663
|
}
|
|
3526
3664
|
|
|
@@ -3544,6 +3682,7 @@ async function handleResponsesInner(
|
|
|
3544
3682
|
try {
|
|
3545
3683
|
initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
3546
3684
|
recordAdapterReasoning(logCtx, initialRequest);
|
|
3685
|
+
recordAdapterTier(logCtx, initialRequest);
|
|
3547
3686
|
inputTokenEstimate = typeof initialRequest.usageLog?.inputTokens === "number"
|
|
3548
3687
|
? initialRequest.usageLog.inputTokens
|
|
3549
3688
|
: undefined;
|
|
@@ -3583,9 +3722,18 @@ async function handleResponsesInner(
|
|
|
3583
3722
|
abortSignal: upstream.signal,
|
|
3584
3723
|
timeoutMs: connectMs,
|
|
3585
3724
|
stream: parsed.stream,
|
|
3725
|
+
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
3726
|
+
providerName: route.providerName,
|
|
3727
|
+
modelId: route.modelId,
|
|
3728
|
+
}),
|
|
3586
3729
|
});
|
|
3587
3730
|
} else {
|
|
3588
|
-
|
|
3731
|
+
// #1851 scope guard: transient-5xx retry on this generic adapter path is opt-in for
|
|
3732
|
+
// direct Google AI Studio only (Vertex/Antigravity use fetchResponse above). Other
|
|
3733
|
+
// adapters keep reset-only retry so combo failover still hops on the first 5xx
|
|
3734
|
+
// instead of burning ~1.2s of same-target retries per hop.
|
|
3735
|
+
const fetchWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
|
|
3736
|
+
upstreamResponse = await fetchWithRetryPolicy(
|
|
3589
3737
|
recovery => {
|
|
3590
3738
|
noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate, recovery);
|
|
3591
3739
|
return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
|
|
@@ -3648,6 +3796,7 @@ async function handleResponsesInner(
|
|
|
3648
3796
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
3649
3797
|
});
|
|
3650
3798
|
recordAdapterReasoning(logCtx, retryRequest);
|
|
3799
|
+
recordAdapterTier(logCtx, retryRequest);
|
|
3651
3800
|
} catch (err) {
|
|
3652
3801
|
// A rotated/rebuilt adapter build failure is a request-shaping error, not an
|
|
3653
3802
|
// upstream connect failure: tear the abort link down and map it as 400 (no 413
|
|
@@ -3673,7 +3822,15 @@ async function handleResponsesInner(
|
|
|
3673
3822
|
try {
|
|
3674
3823
|
if (activeAdapter.fetchResponse) {
|
|
3675
3824
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal);
|
|
3676
|
-
return await activeAdapter.fetchResponse(retryRequest, {
|
|
3825
|
+
return await activeAdapter.fetchResponse(retryRequest, {
|
|
3826
|
+
abortSignal: upstream.signal,
|
|
3827
|
+
timeoutMs: connectMs,
|
|
3828
|
+
stream: parsed.stream,
|
|
3829
|
+
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
3830
|
+
providerName: route.providerName,
|
|
3831
|
+
modelId: route.modelId,
|
|
3832
|
+
}),
|
|
3833
|
+
});
|
|
3677
3834
|
}
|
|
3678
3835
|
return await fetchWithHeaderTimeout(retryRequest.url, {
|
|
3679
3836
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
@@ -3709,7 +3866,7 @@ async function handleResponsesInner(
|
|
|
3709
3866
|
refreshed = await forceRefreshOAuthAccessSnapshot(sentOAuthSnapshot);
|
|
3710
3867
|
} catch (err) {
|
|
3711
3868
|
cleanupUpstreamAbort();
|
|
3712
|
-
return formatErrorResponse(401, "authentication_error",
|
|
3869
|
+
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
3713
3870
|
}
|
|
3714
3871
|
sentOAuthSnapshot = refreshed;
|
|
3715
3872
|
replayOAuthCredentialSnapshot = {
|
|
@@ -3970,6 +4127,7 @@ async function handleResponsesInner(
|
|
|
3970
4127
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
3971
4128
|
});
|
|
3972
4129
|
recordAdapterReasoning(logCtx, continuationRequest);
|
|
4130
|
+
recordAdapterTier(logCtx, continuationRequest);
|
|
3973
4131
|
} catch (err) {
|
|
3974
4132
|
// The main body is already streaming, so there is no HTTP error surface: release
|
|
3975
4133
|
// any partial body observation and surface the failure as an in-stream error via
|
|
@@ -3999,9 +4157,16 @@ async function handleResponsesInner(
|
|
|
3999
4157
|
abortSignal: upstream.signal,
|
|
4000
4158
|
timeoutMs: connectMs,
|
|
4001
4159
|
stream: nextParsed.stream,
|
|
4160
|
+
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
4161
|
+
providerName: route.providerName,
|
|
4162
|
+
modelId: nextParsed.modelId,
|
|
4163
|
+
}),
|
|
4002
4164
|
});
|
|
4003
4165
|
}
|
|
4004
|
-
|
|
4166
|
+
// Same #1851 scope guard as the initial send: transient-5xx retry only for direct
|
|
4167
|
+
// Google AI Studio; every other adapter keeps reset-only semantics here.
|
|
4168
|
+
const fetchContinuationWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
|
|
4169
|
+
return await fetchContinuationWithRetryPolicy(
|
|
4005
4170
|
recovery => {
|
|
4006
4171
|
noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery ?? replayKind);
|
|
4007
4172
|
return fetchWithHeaderTimeout(
|
|
@@ -4249,6 +4414,8 @@ async function handleResponsesInner(
|
|
|
4249
4414
|
toolParameterSchemas,
|
|
4250
4415
|
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
4251
4416
|
...(routedCompaction ? { compaction: true } : {}),
|
|
4417
|
+
// Same grok-surface split as the runTurn branch above.
|
|
4418
|
+
...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
|
|
4252
4419
|
onUsage: usage => {
|
|
4253
4420
|
// Raw adapter usage, pre wire-normalization (see the runTurn branch above).
|
|
4254
4421
|
logCtx.usageFromBridge = true;
|
|
@@ -4337,6 +4504,8 @@ async function handleResponsesInner(
|
|
|
4337
4504
|
responseStateOptions(activeAdapter.name === "kiro"),
|
|
4338
4505
|
);
|
|
4339
4506
|
}
|
|
4507
|
+
// #1926 gap 2: same buffered-path durability bound as the primary branch.
|
|
4508
|
+
await awaitThoughtSignatureDurability();
|
|
4340
4509
|
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
4341
4510
|
}
|
|
4342
4511
|
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import { isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
34
34
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
35
35
|
import { modelInList, namespacedToolName } from "../../types";
|
|
36
|
-
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage
|
|
36
|
+
import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
|
|
37
37
|
import {
|
|
38
38
|
forceRefreshOAuthAccessSnapshot,
|
|
39
39
|
getOAuthCredentialApiBaseUrl,
|
|
@@ -103,7 +103,9 @@ import {
|
|
|
103
103
|
import { hasResponsesItemIdRepair, relaySseWithResponsesItemIdRepair } from "../responses-item-id-repair";
|
|
104
104
|
import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
|
|
105
105
|
import { waitForProviderRequestSlot } from "../../providers/request-pacing";
|
|
106
|
+
import { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
|
|
106
107
|
|
|
108
|
+
export { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
|
|
107
109
|
|
|
108
110
|
export function disableResponsesRequestTimeout(req: Request, server: Pick<Server<WsData>, "timeout"> | undefined): boolean {
|
|
109
111
|
if (!server) return false;
|
|
@@ -147,38 +149,8 @@ export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
|
|
|
147
149
|
export interface ProviderFetchOptions {
|
|
148
150
|
providerName?: string;
|
|
149
151
|
modelId?: string;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Bun's fetch accepts a non-standard `protocol` init to pin the HTTP version
|
|
154
|
-
* (BunFetchRequestInit.protocol). The DOM lib types do not include it, so the
|
|
155
|
-
* value is carried on an intersection and stripped before non-Bun callers.
|
|
156
|
-
* The accepted values are the shared UPSTREAM_HTTP_VERSION_VALUES enum from types.
|
|
157
|
-
*/
|
|
158
|
-
const UPSTREAM_HTTP_VERSION_PROTOCOL: Record<Exclude<UpstreamHttpVersion, "auto">, string> = {
|
|
159
|
-
"http1.1": "http1.1",
|
|
160
|
-
h1: "h1",
|
|
161
|
-
http2: "http2",
|
|
162
|
-
h2: "h2",
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/** Attach Bun's `protocol` pin when the provider opted into a fixed HTTP version. */
|
|
166
|
-
export function withUpstreamHttpVersion(
|
|
167
|
-
input: Parameters<typeof globalThis.fetch>[0],
|
|
168
|
-
init: RequestInit | undefined,
|
|
169
|
-
provider: OcxProviderConfig,
|
|
170
|
-
): RequestInit | undefined {
|
|
171
|
-
const version = provider.upstreamHttpVersion;
|
|
172
|
-
if (!version || version === "auto") return init;
|
|
173
|
-
// Bun's protocol pin requires an https: target; local/plaintext upstreams keep
|
|
174
|
-
// their existing transport untouched.
|
|
175
|
-
const target = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
176
|
-
try {
|
|
177
|
-
if (new URL(target).protocol !== "https:") return init;
|
|
178
|
-
} catch {
|
|
179
|
-
return init;
|
|
180
|
-
}
|
|
181
|
-
return { ...(init ?? {}), protocol: UPSTREAM_HTTP_VERSION_PROTOCOL[version] } as RequestInit;
|
|
152
|
+
/** One pacing slot was acquired immediately before this fetch wrapper was created. */
|
|
153
|
+
pacingSlotAcquired?: boolean;
|
|
182
154
|
}
|
|
183
155
|
|
|
184
156
|
export function providerFetch(
|
|
@@ -196,9 +168,16 @@ export function providerFetch(
|
|
|
196
168
|
}
|
|
197
169
|
return base(input, withUpstreamHttpVersion(input, init, provider));
|
|
198
170
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
171
|
+
let pacingSlotAcquired = options.pacingSlotAcquired === true;
|
|
172
|
+
const waitForPacing = (signal?: AbortSignal) => {
|
|
173
|
+
if (pacingSlotAcquired) {
|
|
174
|
+
pacingSlotAcquired = false;
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
}
|
|
177
|
+
return options.providerName
|
|
178
|
+
? waitForProviderRequestSlot(options.providerName, provider, options.modelId, signal)
|
|
179
|
+
: Promise.resolve();
|
|
180
|
+
};
|
|
202
181
|
const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
203
182
|
await waitForPacing(init?.signal ?? undefined);
|
|
204
183
|
return unpaced(input, init);
|