@bitkyc08/opencodex 2.19.0 → 2.21.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.
Files changed (130) hide show
  1. package/gui/dist/assets/index-BOFeam5a.js +76 -0
  2. package/gui/dist/assets/index-Xq49CY8F.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic.ts +2 -28
  6. package/src/adapters/google.ts +31 -3
  7. package/src/adapters/mimo-free.ts +17 -0
  8. package/src/adapters/openai-chat.ts +392 -40
  9. package/src/adapters/registry.ts +144 -0
  10. package/src/adapters/responses-tool-schema.ts +67 -0
  11. package/src/bridge.ts +15 -2
  12. package/src/chat/inbound.ts +13 -7
  13. package/src/claude/gateway-cache.ts +41 -4
  14. package/src/cli/claude.ts +3 -2
  15. package/src/cli/combo.ts +3 -0
  16. package/src/cli/dispatch.ts +8 -0
  17. package/src/cli/export-command.ts +2 -2
  18. package/src/cli/help.ts +2 -0
  19. package/src/cli/index.ts +3 -2
  20. package/src/cli/lab.ts +135 -1
  21. package/src/cli/minimax.ts +491 -0
  22. package/src/cli/models-runtime.ts +22 -1
  23. package/src/cli/models.ts +67 -2
  24. package/src/cli/opencode.ts +2 -1
  25. package/src/cli/registry.ts +22 -2
  26. package/src/clients/config-export.ts +125 -7
  27. package/src/codex/app-server-processes.ts +57 -2
  28. package/src/codex/app-server-restart-service.ts +232 -0
  29. package/src/codex/catalog/aggregation.ts +10 -1
  30. package/src/codex/catalog/effort.ts +15 -3
  31. package/src/codex/catalog/parsing.ts +3 -1
  32. package/src/codex/catalog/provider-fetch.ts +45 -5
  33. package/src/codex/catalog/sync.ts +74 -4
  34. package/src/codex/convergence.ts +2 -0
  35. package/src/combos/index.ts +1 -0
  36. package/src/combos/request.ts +30 -0
  37. package/src/combos/types.ts +6 -0
  38. package/src/config.ts +52 -0
  39. package/src/generated/compatibility-version.json +241 -85
  40. package/src/images/loop.ts +26 -6
  41. package/src/integrations/registry.ts +7 -0
  42. package/src/lab/conformance/jcs.ts +42 -2
  43. package/src/lab/conformance/negative-controls.ts +6 -2
  44. package/src/lab/conformance/runner.ts +16 -5
  45. package/src/lab/fabric/observe.ts +49 -14
  46. package/src/lab/index.ts +16 -0
  47. package/src/lab/ledger/purge.ts +152 -83
  48. package/src/lab/ledger/store.ts +168 -54
  49. package/src/lab/observe/from-conformance.ts +8 -6
  50. package/src/lab/observe/from-live.ts +8 -2
  51. package/src/lab/paths.ts +23 -0
  52. package/src/lab/public/bundle.ts +217 -0
  53. package/src/lab/public/community-authority.ts +175 -0
  54. package/src/lab/public/community-files.ts +29 -0
  55. package/src/lab/public/community.ts +479 -0
  56. package/src/lab/public/file-safety.ts +155 -0
  57. package/src/lab/public/ids.ts +26 -0
  58. package/src/lab/public/index.ts +16 -0
  59. package/src/lab/public/mutation-lock.ts +424 -0
  60. package/src/lab/public/operator.ts +353 -0
  61. package/src/lab/public/origin-purge.ts +79 -0
  62. package/src/lab/public/origin.ts +203 -0
  63. package/src/lab/public/privacy.ts +143 -0
  64. package/src/lab/public/private-file.ts +261 -0
  65. package/src/lab/public/project.ts +124 -0
  66. package/src/lab/public/purge-test-fault.ts +21 -0
  67. package/src/lab/public/purge.ts +223 -0
  68. package/src/lab/public/registry.ts +44 -0
  69. package/src/lab/public/revocation.ts +252 -0
  70. package/src/lab/public/signature.ts +219 -0
  71. package/src/lab/public/storage.ts +105 -0
  72. package/src/lab/public/strict-json.ts +206 -0
  73. package/src/lab/public/time.ts +26 -0
  74. package/src/lab/public/types.ts +172 -0
  75. package/src/lab/public/validate.ts +391 -0
  76. package/src/lib/codex-restart-contract.ts +120 -0
  77. package/src/lib/lab-activation.ts +109 -47
  78. package/src/lib/lab-live-pinned-sender.ts +16 -5
  79. package/src/lib/pinned-http.ts +70 -16
  80. package/src/lib/self-launch-argv.ts +15 -0
  81. package/src/lib/state-store-registrations.ts +2 -0
  82. package/src/lib/upstream-reachability.ts +4 -0
  83. package/src/lib/windows-elevation.ts +10 -1
  84. package/src/providers/derive.ts +24 -4
  85. package/src/providers/registry.ts +14 -4
  86. package/src/providers/request-pacing.ts +310 -0
  87. package/src/providers/service-tier.ts +143 -0
  88. package/src/providers/static-model-discovery.ts +86 -0
  89. package/src/reasoning-effort.ts +27 -1
  90. package/src/responses/custom-tool-compat.ts +4 -1
  91. package/src/responses/parser.ts +7 -1
  92. package/src/responses/provider-opaque-metadata.ts +73 -0
  93. package/src/responses/schema.ts +6 -0
  94. package/src/router.ts +23 -6
  95. package/src/routing/capability.ts +4 -2
  96. package/src/routing/compatibility/behavior.ts +5 -1
  97. package/src/server/adapter-resolve.ts +2 -32
  98. package/src/server/auth-cors.ts +50 -6
  99. package/src/server/chat-completions.ts +74 -36
  100. package/src/server/chat-native-sse.ts +331 -0
  101. package/src/server/chat-native.ts +371 -0
  102. package/src/server/management/combo-routes.ts +16 -2
  103. package/src/server/management/config-routes.ts +6 -4
  104. package/src/server/management/context.ts +17 -0
  105. package/src/server/management/lab-routes.ts +181 -19
  106. package/src/server/management/model-routes.ts +76 -2
  107. package/src/server/management/model-rows.ts +8 -0
  108. package/src/server/management/provider-capability-config.ts +48 -0
  109. package/src/server/management/provider-routes.ts +76 -4
  110. package/src/server/management/system-restart.ts +4 -2
  111. package/src/server/management/system-routes.ts +38 -0
  112. package/src/server/relay.ts +17 -3
  113. package/src/server/responses/compact.ts +4 -1
  114. package/src/server/responses/core.ts +257 -46
  115. package/src/server/responses/empty-completion-guard.ts +276 -0
  116. package/src/server/responses/fetch-helpers.ts +35 -4
  117. package/src/server/responses/pacing-overload.ts +13 -0
  118. package/src/server/responses/policy-fallback.ts +16 -2
  119. package/src/server/responses/terminal-guard.ts +1 -1
  120. package/src/server/responses/upstream-error.ts +5 -0
  121. package/src/server/responses.ts +17 -2
  122. package/src/server/system-env.ts +1 -1
  123. package/src/types.ts +84 -5
  124. package/src/update/index.ts +6 -5
  125. package/src/update/job.ts +5 -6
  126. package/src/update/notify.ts +5 -3
  127. package/src/usage/log.ts +11 -1
  128. package/src/web-search/loop.ts +21 -5
  129. package/gui/dist/assets/index-CQ7bIKee.css +0 -1
  130. package/gui/dist/assets/index-D_JUZLEC.js +0 -76
@@ -14,6 +14,7 @@ import {
14
14
  normalizeNonBlankStringArray,
15
15
  providerBaseUrlConfigError,
16
16
  providerHeadersConfigError,
17
+ requestPacingConfigError,
17
18
  readConfigAdmissionSnapshot,
18
19
  saveConfigPreservingClaudeCode,
19
20
  withConfigMutationLockSync,
@@ -44,6 +45,7 @@ import {
44
45
  import { routedSlug, slugEquals } from "../../providers/slug-codec";
45
46
  import { clearAccountQuotaCache, clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
46
47
  import { clearKeyCooldowns } from "../../providers/key-failover";
48
+ import { providerRequestPacingStatus } from "../../providers/request-pacing";
47
49
  import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
48
50
  import { codexAccountNamespaceProviderCollisionError } from "../../codex/account-namespace-match";
49
51
  import { clearThreadAccountMap } from "../../codex/routing";
@@ -71,6 +73,7 @@ import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from ".
71
73
  import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
72
74
  import type { PersistedUsageAttempt } from "../../usage/log";
73
75
  import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
76
+ import { providerServiceTierConfigError } from "./provider-capability-config";
74
77
  import { applySystemEnvToggle } from "../system-env";
75
78
  import {
76
79
  LOCAL_PROVIDER_RELOAD_NAME_HEADER,
@@ -177,6 +180,20 @@ function applyProviderPatchFields(
177
180
  next.liveModels = rawBody.liveModels;
178
181
  touched = true;
179
182
  }
183
+ if (Object.hasOwn(rawBody, "requestPacing")) {
184
+ const value = rawBody.requestPacing;
185
+ if (value === null) {
186
+ delete next.requestPacing;
187
+ } else {
188
+ if (!isPlainRecord(value)) return { error: "requestPacing must be a plain object or null" };
189
+ const pacingError = requestPacingConfigError(value);
190
+ if (pacingError) return { error: pacingError };
191
+ // `requestPacingConfigError` is the runtime narrowing boundary above; keep the
192
+ // assertion explicit because a generic plain record cannot express `enabled`.
193
+ next.requestPacing = structuredClone(value) as unknown as OcxProviderConfig["requestPacing"];
194
+ }
195
+ touched = true;
196
+ }
180
197
  // The Models page edits the catalog hints in place; keep them on the existing
181
198
  // provider mutation path so validation, cache invalidation, and convergence stay unified (#1073).
182
199
  if (Object.hasOwn(rawBody, "contextWindow")) {
@@ -216,6 +233,29 @@ function applyProviderPatchFields(
216
233
  }
217
234
  touched = true;
218
235
  }
236
+ if (Object.hasOwn(rawBody, "modelSupportsServiceTier")) {
237
+ const value = rawBody.modelSupportsServiceTier;
238
+ if (value === null) {
239
+ delete next.modelSupportsServiceTier;
240
+ } else {
241
+ if (!isPlainRecord(value)) return { error: "modelSupportsServiceTier must be a plain object or null" };
242
+ const capabilities: Record<string, boolean> = { ...(next.modelSupportsServiceTier ?? {}) };
243
+ for (const [model, supported] of Object.entries(value)) {
244
+ if (!model.trim()) return { error: "modelSupportsServiceTier keys must be nonblank model ids" };
245
+ if (supported === null) {
246
+ delete capabilities[model];
247
+ continue;
248
+ }
249
+ if (typeof supported !== "boolean") {
250
+ return { error: "modelSupportsServiceTier values must be booleans or null" };
251
+ }
252
+ capabilities[model] = supported;
253
+ }
254
+ if (Object.keys(capabilities).length > 0) next.modelSupportsServiceTier = capabilities;
255
+ else delete next.modelSupportsServiceTier;
256
+ }
257
+ touched = true;
258
+ }
219
259
  if (Object.hasOwn(rawBody, "noStructuredOutputModels")) {
220
260
  const value = rawBody.noStructuredOutputModels;
221
261
  if (value === null) {
@@ -304,6 +344,22 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
304
344
  return jsonResponse(await fetchProviderQuotaReports(config, forceRefresh));
305
345
  }
306
346
 
347
+ if (url.pathname === "/api/provider-request-pacing" && req.method === "GET") {
348
+ const name = url.searchParams.get("name")?.trim();
349
+ if (name) {
350
+ if (!isValidProviderName(name) || !hasOwnProvider(config.providers, name)) {
351
+ return jsonResponse({ error: "unknown provider" }, 404);
352
+ }
353
+ return jsonResponse(providerRequestPacingStatus(name, config.providers[name]!));
354
+ }
355
+ return jsonResponse(Object.fromEntries(
356
+ Object.entries(config.providers).map(([providerName, provider]) => [
357
+ providerName,
358
+ providerRequestPacingStatus(providerName, provider),
359
+ ]),
360
+ ));
361
+ }
362
+
307
363
  if (url.pathname === "/api/providers" && req.method === "GET") {
308
364
  return jsonResponse(Object.entries(config.providers).map(([name, p]) => ({
309
365
  name, adapter: p.adapter, baseUrl: publicProviderBaseUrl(p.baseUrl), defaultModel: p.defaultModel,
@@ -312,9 +368,11 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
312
368
  hasHeaders: !!p.headers && Object.keys(p.headers).length > 0,
313
369
  allowPrivateNetwork: p.allowPrivateNetwork === true,
314
370
  liveModels: p.liveModels !== false,
371
+ requestPacing: p.requestPacing,
315
372
  models: p.models ?? [],
316
373
  contextWindow: p.contextWindow,
317
374
  modelContextWindows: p.modelContextWindows,
375
+ modelSupportsServiceTier: p.modelSupportsServiceTier,
318
376
  noStructuredOutputModels: p.noStructuredOutputModels,
319
377
  authMode: p.authMode,
320
378
  apiKeyTransport: p.apiKeyTransport,
@@ -405,6 +463,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
405
463
  const name = typeof body.name === "string" ? body.name.trim() : "";
406
464
  const providerError = providerManagementConfigError(name, body.provider);
407
465
  if (providerError) return jsonResponse({ error: providerError }, 400);
466
+ const serviceTierError = providerServiceTierConfigError(name, body.provider);
467
+ if (serviceTierError) return jsonResponse({ error: serviceTierError }, 400);
408
468
  const prov = body.provider ? stripCodexRuntimeProviderFields(body.provider as OcxProviderConfig) : undefined;
409
469
  if (!name || !prov?.adapter || !prov?.baseUrl) {
410
470
  return jsonResponse({ error: "name, provider.adapter and provider.baseUrl are required" }, 400);
@@ -437,6 +497,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
437
497
  // call can never fire.
438
498
  const submittedContextWindow = Object.hasOwn(prov, "contextWindow");
439
499
  const submittedModelContextWindows = Object.hasOwn(prov, "modelContextWindows");
500
+ const submittedRequestPacing = Object.hasOwn(prov, "requestPacing");
440
501
  enrichProviderFromCatalog(name, prov);
441
502
  const { saveConfigPreservingClaudeCode: save } = await import("../../config");
442
503
  // Overwriting an existing provider must not drop its multi-key pool: carry it over, then
@@ -453,6 +514,9 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
453
514
  // absence in the request means "not carried", never "the user deleted it". Deletion goes
454
515
  // through PATCH with an explicit null (#1409).
455
516
  const existing = config.providers[name];
517
+ if (!submittedRequestPacing && existing?.requestPacing) {
518
+ prov.requestPacing = structuredClone(existing.requestPacing);
519
+ }
456
520
  if (!submittedContextWindow && existing?.contextWindow !== undefined) {
457
521
  prov.contextWindow = existing.contextWindow;
458
522
  }
@@ -548,9 +612,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
548
612
  if ("error" in applied) return jsonResponse({ error: applied.error }, 400);
549
613
  const next = applied.next;
550
614
 
551
- if (applied.editorTouched) {
615
+ const pacingOnly = keys.every(key => key === "requestPacing");
616
+ if (applied.editorTouched && !pacingOnly) {
552
617
  const providerError = providerManagementConfigError(name, next);
553
618
  if (providerError) return jsonResponse({ error: providerError }, 400);
619
+ const serviceTierError = providerServiceTierConfigError(name, next);
620
+ if (serviceTierError) return jsonResponse({ error: serviceTierError }, 400);
554
621
  const resolvedError = await providerDestinationResolvedError(name, next);
555
622
  if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
556
623
  } else if (applied.enablingOpenAi) {
@@ -575,12 +642,17 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
575
642
  replayError = replay.error;
576
643
  return;
577
644
  }
578
- if (replay.editorTouched) {
645
+ if (replay.editorTouched && !pacingOnly) {
579
646
  const syncError = providerManagementConfigError(name, replay.next);
580
647
  if (syncError) {
581
648
  replayError = syncError;
582
649
  return;
583
650
  }
651
+ const serviceTierError = providerServiceTierConfigError(name, replay.next);
652
+ if (serviceTierError) {
653
+ replayError = serviceTierError;
654
+ return;
655
+ }
584
656
  } else if (replay.enablingOpenAi && !isCanonicalOpenAiForwardProvider(replay.next)) {
585
657
  replayError = "provider openai must be the canonical built-in provider";
586
658
  return;
@@ -592,11 +664,11 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
592
664
  });
593
665
  if (replayError !== undefined) return jsonResponse({ error: replayError }, 409);
594
666
  reconcileLiveStateStores();
595
- if (applied.editorTouched) {
667
+ if (applied.editorTouched && !pacingOnly) {
596
668
  const { clearModelCache } = await import("../../codex/model-cache");
597
669
  clearModelCache(name);
598
670
  }
599
- const catalogRefresh = await convergeCodexCatalog();
671
+ const catalogRefresh = pacingOnly ? null : await convergeCodexCatalog();
600
672
  return jsonResponse({
601
673
  success: true,
602
674
  name,
@@ -35,6 +35,7 @@ import {
35
35
  import { isServiceViable } from "../../service";
36
36
  import { readRuntimePort } from "../../config";
37
37
  import { withProcessRuntimeProvenance } from "../../lib/bun-runtime";
38
+ import { selfLaunchArgv } from "../../lib/self-launch-argv";
38
39
  import {
39
40
  MEMORY_DRAIN_RESTART_MS,
40
41
  REPLACEMENT_READY_TIMEOUT_MS,
@@ -213,19 +214,20 @@ function spawnDetachedStart(
213
214
  port?: number,
214
215
  waitForHealthBeforeParentExit = true,
215
216
  ): Promise<void> {
216
- const args = [process.argv[1], "start"];
217
+ const args = ["start"];
217
218
  const expectedPort = typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535
218
219
  ? Math.trunc(port)
219
220
  : undefined;
220
221
  if (expectedPort !== undefined) {
221
222
  args.push("--port", String(expectedPort));
222
223
  }
224
+ const launchArgs = selfLaunchArgv(args);
223
225
  return new Promise<void>((resolve, reject) => {
224
226
  let child: ReturnType<typeof spawn>;
225
227
  try {
226
228
  const env: NodeJS.ProcessEnv = { ...process.env };
227
229
  delete env.OCX_SERVICE;
228
- child = spawn(process.execPath, args, {
230
+ child = spawn(process.execPath, launchArgs, {
229
231
  detached: true,
230
232
  stdio: "ignore",
231
233
  windowsHide: true,
@@ -31,8 +31,16 @@ import {
31
31
  SYSTEM_RESTART_EXPECTED_PID_HEADER,
32
32
  parseExpectedSystemRestartPid,
33
33
  } from "../../lib/system-restart-contract";
34
+ import {
35
+ CODEX_APP_SERVER_STATE_PATH,
36
+ CODEX_RESTART_PATH,
37
+ } from "../../lib/codex-restart-contract";
34
38
  import { jsonResponse } from "../auth-cors";
35
39
  import { getInspectionCounters } from "../relay";
40
+ import type {
41
+ performCodexRestart,
42
+ readCodexAppServerState,
43
+ } from "../../codex/app-server-restart-service";
36
44
  import type { ManagementContext } from "./context";
37
45
  import { acceptSystemRestart } from "./system-restart";
38
46
 
@@ -137,5 +145,35 @@ export async function handleSystemRoutes(ctx: ManagementContext): Promise<Respon
137
145
  }, 202, req, config);
138
146
  }
139
147
 
148
+ if (
149
+ (url.pathname === CODEX_APP_SERVER_STATE_PATH && req.method === "GET")
150
+ || (url.pathname === CODEX_RESTART_PATH && req.method === "POST")
151
+ ) {
152
+ // Resolved inside the path check, not at the top of this function: every
153
+ // /api/system/* request runs through here, and an unconditional import would
154
+ // pull the platform process-enumeration helpers into requests that never
155
+ // touch them.
156
+ // An explicit branch rather than `??`: the seam is an optional property, and
157
+ // narrowing through a nullish default keeps its `undefined` in the union.
158
+ let service: {
159
+ readState: typeof readCodexAppServerState;
160
+ performRestart: typeof performCodexRestart;
161
+ };
162
+ const injected = ctx.deps.codexRestartService;
163
+ if (injected) {
164
+ service = injected;
165
+ } else {
166
+ const module = await import("../../codex/app-server-restart-service");
167
+ service = {
168
+ readState: module.readCodexAppServerState,
169
+ performRestart: module.performCodexRestart,
170
+ };
171
+ }
172
+ if (req.method === "GET") {
173
+ return jsonResponse(service.readState(), 200, req, config);
174
+ }
175
+ return jsonResponse(await service.performRestart(), 200, req, config);
176
+ }
177
+
140
178
  return null;
141
179
  }
@@ -366,6 +366,7 @@ export function trackSseForRequestLog(
366
366
  ): ReadableStream<Uint8Array> {
367
367
  const reader = body.getReader();
368
368
  let terminalReported = false;
369
+ let cancelled = false;
369
370
 
370
371
  const reportTerminal = (status: ResponsesTerminalStatus) => {
371
372
  if (terminalReported) return;
@@ -385,8 +386,10 @@ export function trackSseForRequestLog(
385
386
  try {
386
387
  const { done, value } = await reader.read();
387
388
  if (done) {
388
- inspector.finish();
389
- if (!terminalReported) reportTerminal("incomplete");
389
+ if (!cancelled) {
390
+ inspector.finish();
391
+ if (!terminalReported) reportTerminal("incomplete");
392
+ }
390
393
  inspector.dispose();
391
394
  controller.close();
392
395
  return;
@@ -394,12 +397,19 @@ export function trackSseForRequestLog(
394
397
  inspector.feed(value);
395
398
  controller.enqueue(value);
396
399
  } catch (err) {
397
- if (!terminalReported) reportTerminal("incomplete");
400
+ // The upstream read rejected: the 200 body died mid-flight. Client
401
+ // cancellation is the caller's separate 499 path, so a cancel-drained
402
+ // pending read (cancelled=true) must not carry the truncation marker.
403
+ if (!cancelled && !terminalReported && logCtx?.activeAttempt) {
404
+ logCtx.activeAttempt.streamAborted = true;
405
+ }
406
+ if (!cancelled && !terminalReported) reportTerminal("incomplete");
398
407
  inspector.dispose();
399
408
  try { controller.error(err); } catch { /* already torn down */ }
400
409
  }
401
410
  },
402
411
  cancel(reason) {
412
+ cancelled = true;
403
413
  inspector.dispose();
404
414
  onCancel();
405
415
  reader.cancel(reason).catch(() => {});
@@ -1122,6 +1132,10 @@ export function consumeForInspection(
1122
1132
  if (logCtx) {
1123
1133
  logCtx.transportPhase = "mid_stream";
1124
1134
  logCtx.terminalSource = "synthetic";
1135
+ // A truncated 200 body must not meter as a success the client never
1136
+ // received; the router's equivalent turn carries 502 + streamAborted
1137
+ // (codex-router #139).
1138
+ if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
1125
1139
  }
1126
1140
  onTerminal("failed", 502);
1127
1141
  }
@@ -490,7 +490,10 @@ export async function handleResponsesCompact(
490
490
  req.signal,
491
491
  connectMs,
492
492
  false,
493
- providerFetch(sendProvider),
493
+ providerFetch(sendProvider, undefined, {
494
+ providerName: route.providerName,
495
+ modelId: route.modelId,
496
+ }),
494
497
  // Every credential-bearing forward send gets manual redirects, not only
495
498
  // pool sends: direct mode carries the caller's credential too (#914).
496
499
  sendProvider.authMode === "forward",