@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.
Files changed (99) hide show
  1. package/bin/ocx.mjs +59 -7
  2. package/gui/dist/assets/index-RL6b1bTV.js +102 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/base.ts +18 -0
  6. package/src/adapters/client-fingerprint.ts +0 -2
  7. package/src/adapters/cursor/http1-bidi.ts +361 -0
  8. package/src/adapters/cursor/live-models.ts +117 -30
  9. package/src/adapters/cursor/live-transport.ts +137 -56
  10. package/src/adapters/cursor/native-exec-fs.ts +1 -1
  11. package/src/adapters/cursor/native-exec-network.ts +1 -1
  12. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  13. package/src/adapters/cursor/protobuf-request.ts +89 -21
  14. package/src/adapters/cursor/tool-definitions.ts +18 -7
  15. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  16. package/src/adapters/cursor/transport.ts +7 -0
  17. package/src/adapters/cursor.ts +2 -0
  18. package/src/adapters/google-http.ts +38 -10
  19. package/src/adapters/google.ts +23 -3
  20. package/src/adapters/openai-chat.ts +38 -13
  21. package/src/adapters/openai-responses.ts +129 -9
  22. package/src/adapters/registry.ts +30 -1
  23. package/src/bridge.ts +50 -12
  24. package/src/chat/inbound.ts +1 -0
  25. package/src/claude/agents-inject.ts +3 -2
  26. package/src/cli/dispatch.ts +19 -6
  27. package/src/cli/help.ts +2 -1
  28. package/src/cli/integrations.ts +35 -0
  29. package/src/cli/minimax.ts +8 -2
  30. package/src/cli/registry.ts +13 -2
  31. package/src/clients/config-export.ts +120 -1
  32. package/src/codex/account-store.ts +17 -1
  33. package/src/codex/auth-api.ts +51 -17
  34. package/src/codex/catalog/effort.ts +8 -5
  35. package/src/codex/catalog/provider-fetch.ts +39 -27
  36. package/src/codex/catalog/sync.ts +30 -8
  37. package/src/codex/inject.ts +39 -13
  38. package/src/codex/main-account.ts +29 -1
  39. package/src/codex/plan-from-token.ts +140 -0
  40. package/src/codex/plan.ts +25 -0
  41. package/src/codex/prompt-journal.ts +6 -2
  42. package/src/codex/quota-rejection.ts +21 -7
  43. package/src/codex/refresh.ts +4 -2
  44. package/src/codex/sync.ts +106 -3
  45. package/src/codex/warmup.ts +187 -81
  46. package/src/config.ts +85 -46
  47. package/src/generated/compatibility-version.json +127 -83
  48. package/src/grok/inject.ts +1 -1
  49. package/src/images/loop.ts +1 -0
  50. package/src/integrations/registry.ts +7 -0
  51. package/src/lab/automation/config-persistence.ts +2 -2
  52. package/src/lab/automation/persistence.ts +2 -2
  53. package/src/lab/ledger/purge.ts +2 -2
  54. package/src/lab/subject/behavior-fingerprint.ts +2 -2
  55. package/src/lib/config-ownership.ts +5 -2
  56. package/src/lib/destination-policy.ts +18 -1
  57. package/src/lib/provider-outbound.ts +7 -0
  58. package/src/lib/redact.ts +11 -0
  59. package/src/lib/tool-argument-integers.ts +50 -6
  60. package/src/lib/upstream-http-version.ts +57 -0
  61. package/src/lib/upstream-retry.ts +2 -1
  62. package/src/lib/windows-atomic-replace.ts +155 -0
  63. package/src/lib/windows-service-wrappers.ts +72 -0
  64. package/src/oauth/google-antigravity.ts +2 -2
  65. package/src/oauth/index.ts +60 -8
  66. package/src/providers/antigravity-models.ts +58 -4
  67. package/src/providers/command-code-efforts.ts +36 -9
  68. package/src/providers/context-cap.ts +9 -0
  69. package/src/providers/derive.ts +4 -0
  70. package/src/providers/fastwire.ts +453 -0
  71. package/src/providers/registry.ts +21 -1
  72. package/src/providers/service-tier.ts +173 -89
  73. package/src/responses/parser.ts +25 -16
  74. package/src/responses/reasoning-replay-cache.ts +30 -0
  75. package/src/responses/thought-signature-replay.ts +74 -4
  76. package/src/router.ts +6 -0
  77. package/src/routing/compatibility/behavior.ts +27 -16
  78. package/src/server/index.ts +10 -1
  79. package/src/server/management/oauth-account-routes.ts +10 -2
  80. package/src/server/management/shared.ts +1 -1
  81. package/src/server/management/system-routes.ts +15 -0
  82. package/src/server/request-log.ts +57 -3
  83. package/src/server/responses/agent-task-recovery.ts +6 -1
  84. package/src/server/responses/core.ts +209 -40
  85. package/src/server/responses/fetch-helpers.ts +15 -36
  86. package/src/server/responses/responses-field-backfill.ts +173 -0
  87. package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
  88. package/src/service.ts +38 -36
  89. package/src/storage/cleanup.ts +2 -2
  90. package/src/tray/windows.ts +3 -2
  91. package/src/types.ts +101 -14
  92. package/src/update/job.ts +9 -18
  93. package/src/update/transactional-install.d.mts +22 -0
  94. package/src/update/transactional-install.mjs +259 -0
  95. package/src/usage/cost.ts +34 -5
  96. package/src/usage/log.ts +74 -4
  97. package/src/vision/anthropic-describe.ts +10 -6
  98. package/src/web-search/anthropic-executor.ts +8 -6
  99. package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
@@ -292,12 +292,56 @@ export class UnsupportedOAuthProviderError extends Error {
292
292
  }
293
293
 
294
294
  export class OAuthLoginRequiredError extends Error {
295
+ readonly provider: string;
296
+
295
297
  constructor(provider: string) {
296
298
  super(`Not logged in to ${provider}. Run: ocx login ${provider}`);
297
299
  this.name = "OAuthLoginRequiredError";
300
+ this.provider = provider;
301
+ }
302
+ }
303
+
304
+ export class OAuthProviderPublicationError extends Error {
305
+ constructor() {
306
+ super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login.");
307
+ this.name = "OAuthProviderPublicationError";
298
308
  }
299
309
  }
300
310
 
311
+ export class OAuthReauthIdentityMismatchError extends Error {
312
+ constructor() {
313
+ super("Signed-in account does not match the selected account. Sign in with the same account.");
314
+ this.name = "OAuthReauthIdentityMismatchError";
315
+ }
316
+ }
317
+
318
+ export class OAuthReauthIdentityUnverifiedError extends Error {
319
+ constructor() {
320
+ super("Could not verify signed-in account identity for reauth.");
321
+ this.name = "OAuthReauthIdentityUnverifiedError";
322
+ }
323
+ }
324
+
325
+ /** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
326
+ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
327
+ if (error instanceof OAuthMutationBusyError) {
328
+ return error.message === "OAuth mutation queue wait timed out"
329
+ ? "OAuth mutation queue wait timed out"
330
+ : "OAuth mutation queue is busy";
331
+ }
332
+ if (
333
+ (error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
334
+ || error instanceof OAuthProviderPublicationError
335
+ // Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
336
+ // generic message hides WHICH failure the user must fix (sign in with the selected account).
337
+ || error instanceof OAuthReauthIdentityMismatchError
338
+ || error instanceof OAuthReauthIdentityUnverifiedError
339
+ || error instanceof OAuthTokenRefreshBusyError
340
+ || error instanceof OAuthTokenRefreshStaleError
341
+ ) return error.message;
342
+ return "OAuth authentication failed. Check the OpenCodex account status and retry.";
343
+ }
344
+
301
345
  function accessSnapshot(provider: string, accountId: string, cred: OAuthCredentials): OAuthAccessSnapshot {
302
346
  const storedKiroRouting = {
303
347
  ...(cred.kiro?.profileArn ? { profileArn: cred.kiro.profileArn } : {}),
@@ -1111,7 +1155,7 @@ export async function runLogin(
1111
1155
  const existing = getAccountCredential(provider, opts.reauthAccountId);
1112
1156
  if (!existing) throw new Error(`Unknown account for reauth: ${opts.reauthAccountId}`);
1113
1157
  if (!existing.accountId && !existing.email) {
1114
- throw new Error("Could not verify signed-in account identity for reauth.");
1158
+ throw new OAuthReauthIdentityUnverifiedError();
1115
1159
  }
1116
1160
  const identityMatches = existing.accountId && cred.accountId
1117
1161
  ? existing.accountId === cred.accountId
@@ -1119,7 +1163,7 @@ export async function runLogin(
1119
1163
  ? existing.email.toLowerCase() === cred.email.toLowerCase()
1120
1164
  : false;
1121
1165
  if (!identityMatches) {
1122
- throw new Error("Signed-in account does not match the selected account. Sign in with the same account.");
1166
+ throw new OAuthReauthIdentityMismatchError();
1123
1167
  }
1124
1168
  await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred);
1125
1169
  } else {
@@ -1136,10 +1180,7 @@ export async function runLogin(
1136
1180
  provider,
1137
1181
  );
1138
1182
  if (lateCollision) {
1139
- throw new Error(
1140
- `${lateCollision}. The credential for "${provider}" was saved, but the provider entry was not written. `
1141
- + "Rename the account selector, then re-run the login.",
1142
- );
1183
+ throw new OAuthProviderPublicationError();
1143
1184
  }
1144
1185
  upsertOAuthProvider(latestConfig, provider);
1145
1186
  saveLatestConfig(latestConfig);
@@ -1381,7 +1422,16 @@ export async function startLoginFlow(
1381
1422
  onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(provider, abort.signal, expectedState),
1382
1423
  signal: abort.signal,
1383
1424
  };
1425
+ const abandonIfNotOwner = (error?: unknown): boolean => {
1426
+ if (loginAbort.get(provider) === abort) return false;
1427
+ if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
1428
+ return true;
1429
+ };
1384
1430
  const settle = async (error?: unknown): Promise<void> => {
1431
+ // Cancellation deletes this controller and records its own terminal result. A late provider
1432
+ // rejection (or an older flow settling after a replacement starts) must not overwrite that
1433
+ // state or delete the replacement flow's controller/manual-code slot.
1434
+ if (abandonIfNotOwner(error)) return;
1385
1435
  let finalError = error;
1386
1436
  try {
1387
1437
  await lifecycle?.onSettled?.();
@@ -1390,6 +1440,7 @@ export async function startLoginFlow(
1390
1440
  // runtime config. For an already-failed login, keep the original recovery error.
1391
1441
  if (finalError === undefined) finalError = settleError;
1392
1442
  }
1443
+ if (abandonIfNotOwner(finalError)) return;
1393
1444
  if (finalError === undefined) {
1394
1445
  loginAbort.delete(provider);
1395
1446
  clearManualCodeSlot(provider);
@@ -1403,7 +1454,7 @@ export async function startLoginFlow(
1403
1454
  const e = finalError;
1404
1455
  loginAbort.delete(provider);
1405
1456
  clearManualCodeSlot(provider);
1406
- const msg = e instanceof Error ? e.message : String(e);
1457
+ const msg = publicOAuthAuthenticationErrorMessage(e);
1407
1458
  loginState.set(provider, { done: true, error: msg });
1408
1459
  if (!urlResolved) reject(e);
1409
1460
  };
@@ -1414,9 +1465,10 @@ export async function startLoginFlow(
1414
1465
  (e: unknown) => settle(e),
1415
1466
  ).catch((e: unknown) => {
1416
1467
  // settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
1468
+ if (abandonIfNotOwner(e)) return;
1417
1469
  loginAbort.delete(provider);
1418
1470
  clearManualCodeSlot(provider);
1419
- const msg = e instanceof Error ? e.message : String(e);
1471
+ const msg = publicOAuthAuthenticationErrorMessage(e);
1420
1472
  loginState.set(provider, { done: true, error: msg });
1421
1473
  if (!urlResolved) reject(e);
1422
1474
  });
@@ -78,9 +78,6 @@ function pickerModelIdForDiscoveredWireId(
78
78
  info: Record<string, unknown>,
79
79
  available: ReadonlyMap<string, Record<string, unknown>>,
80
80
  ): string {
81
- const displayModelId = antigravityDisplayModelId(info.displayName, wireId);
82
- if (displayModelId) return displayModelId;
83
-
84
81
  const explicitPickerId = Object.hasOwn(ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID, wireId)
85
82
  ? ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID[wireId]
86
83
  : undefined;
@@ -107,9 +104,35 @@ function pickerModelIdForDiscoveredWireId(
107
104
  return baseId;
108
105
  }
109
106
  }
107
+
108
+ // Display labels are a LAST resort, never a first one. CCA labels a tier row
109
+ // "Gemini 3.7 Flash (High)", which slugs to `gemini-3.7-flash-high` — a per-tier
110
+ // picker row that re-splits exactly the ladder the collapse rules above just
111
+ // joined, and that carries no effort ladder of its own. Consulting the label
112
+ // first (the #1897 regression) turned every collapsed base model back into three
113
+ // suffix rows and stripped reasoning-effort control from the picker.
114
+ //
115
+ // The label still earns its keep where nothing else can speak: an id Google has
116
+ // renamed on the wire while keeping a stable public name.
117
+ const displayModelId = antigravityDisplayModelId(info.displayName, wireId);
118
+ if (displayModelId && !collapsesIntoKnownPickerModel(displayModelId)) return displayModelId;
119
+
110
120
  return wireId;
111
121
  }
112
122
 
123
+ /**
124
+ * Whether a display-derived id is really a tier of a picker-visible base model.
125
+ *
126
+ * `gemini-3.7-flash-high` looks like a model id and is not one: it is the "high"
127
+ * rung of `gemini-3.7-flash`, whose ladder lives in ANTIGRAVITY_MODEL_EFFORTS.
128
+ * Publishing it as its own row is what breaks effort selection.
129
+ */
130
+ function collapsesIntoKnownPickerModel(candidateId: string): boolean {
131
+ const effortMatch = /^(.*)-(low|medium|high)$/.exec(candidateId);
132
+ const baseId = effortMatch?.[1];
133
+ return baseId !== undefined && isKnownAntigravityPickerModelId(baseId);
134
+ }
135
+
113
136
  // ── Effort ladders per collapsed base model ──
114
137
  // Gemini models: effort → wire model suffix (official agy UI pattern).
115
138
  // Claude Opus: effort → thinkingConfig.thinkingLevel (CLIProxyAPI proven pattern).
@@ -389,6 +412,16 @@ export function parseAntigravityAvailableModels(
389
412
  }
390
413
  }
391
414
  }
415
+ // This model is exposed by Antigravity's agent chat surface even though the discovery
416
+ // response groups it under image generation, so it never appears in agentModelSorts.
417
+ if (Array.isArray(body.imageGenerationModelIds)
418
+ && body.imageGenerationModelIds.includes("gemini-3.1-flash-image")
419
+ && Object.hasOwn(models, "gemini-3.1-flash-image")
420
+ && antigravityRecord(models["gemini-3.1-flash-image"])
421
+ && !ids.includes("gemini-3.1-flash-image")) {
422
+ if (ids.length >= limit) return null;
423
+ ids.push("gemini-3.1-flash-image");
424
+ }
392
425
  // Newer CCA responses identify tiered Flash models through this index instead of
393
426
  // adding their synthetic wire ids to agentModelSorts.
394
427
  const tieredModelIds = antigravityRecord(body.tieredModelIds);
@@ -413,6 +446,16 @@ export function parseAntigravityAvailableModels(
413
446
  for (const wireId of ids) {
414
447
  const info = antigravityRecord(models[wireId]);
415
448
  if (!info || available.has(wireId)) continue;
449
+ // Compatibility aliases are deliberately routed to NEWER wire ids for saved
450
+ // selections. CCA keeps serving retired generations (3.5/3.6 Flash tiers) in its
451
+ // agent list long after they stop being the model anyone should pick, so admitting
452
+ // them as independently discovered rows republishes exactly the dead tiers the
453
+ // alias map exists to retire. Routing for a saved id still works — it resolves
454
+ // through ANTIGRAVITY_MODEL_ALIASES — it just no longer gets its own picker row.
455
+ const alias = Object.hasOwn(ANTIGRAVITY_MODEL_ALIASES, wireId)
456
+ ? ANTIGRAVITY_MODEL_ALIASES[wireId]
457
+ : undefined;
458
+ if (alias && alias !== wireId) continue;
416
459
  available.set(wireId, info);
417
460
  }
418
461
 
@@ -479,7 +522,18 @@ export function resolveAntigravityEffortWireModel(
479
522
  effort?: string,
480
523
  baseUrl?: string,
481
524
  ): { wireModelId: string; thinkingLevel?: string } {
482
- const discoveredWireModelId = discoveredAntigravityWireModelId(modelId, baseUrl);
525
+ // A collapsed picker row reports ONE representative wire id (whichever tier CCA
526
+ // listed first), so live discovery cannot describe a ladder — it can only name a
527
+ // single rung. Letting it answer for a base model we already have a ladder for
528
+ // collapses every effort onto that one rung: `gemini-3.1-pro` low and high both
529
+ // became `gemini-pro-agent`, and `gemini-3.7-flash` sent thinkingLevel=low against
530
+ // the `-high` wire id, a request that contradicts itself. Rules 1b/2/3 below own
531
+ // these models; discovery answers only for ids no rule knows.
532
+ const hasOwnEffortLadder = Object.hasOwn(ANTIGRAVITY_THINKING_LEVEL_MODELS, modelId)
533
+ || Object.hasOwn(ANTIGRAVITY_EFFORT_WIRE_MAP, modelId);
534
+ const discoveredWireModelId = hasOwnEffortLadder
535
+ ? undefined
536
+ : discoveredAntigravityWireModelId(modelId, baseUrl);
483
537
  if (discoveredWireModelId && (discoveredWireModelId !== modelId || isAntigravitySuffixModelId(modelId))) {
484
538
  const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId];
485
539
  return {
@@ -9,14 +9,31 @@ const COMMAND_CODE_MODEL_EFFORTS = {
9
9
  efforts: ["high", "max"],
10
10
  profileUrl: "https://commandcode.ai/models/deepseek-v4-flash",
11
11
  },
12
- "zai-org/glm-5.3": {
13
- efforts: ["low", "high", "max"],
14
- profileUrl: "https://commandcode.ai/models/glm-5-3",
12
+ // Keys must match the EXACT upstream /provider/v1/models ids (GLM ships as
13
+ // `zai-org/GLM-5.3`, not `zai-org/glm-5.3`). The table doubles as the router's
14
+ // known-ids decode source (via `knownModelIdsForProvider`), so a case mismatch
15
+ // makes the Codex-facing slug `commandcode/zai-org-GLM-5.3` pass through
16
+ // undecoded and upstream rejects it with `unsupported_model`.
17
+ "zai-org/GLM-5": {
18
+ efforts: ["high", "max"],
19
+ profileUrl: "https://commandcode.ai/models/glm-5",
15
20
  },
16
- "zai-org/glm-5.2": {
21
+ "zai-org/GLM-5.1": {
22
+ efforts: ["high", "max"],
23
+ profileUrl: "https://commandcode.ai/models/glm-5-1",
24
+ },
25
+ "zai-org/GLM-5.2": {
17
26
  efforts: ["high", "max"],
18
27
  profileUrl: "https://commandcode.ai/models/glm-5-2",
19
28
  },
29
+ "zai-org/GLM-5.2-Fast": {
30
+ efforts: ["high", "max"],
31
+ profileUrl: "https://commandcode.ai/models/glm-5-2-fast",
32
+ },
33
+ "zai-org/GLM-5.3": {
34
+ efforts: ["low", "high", "max"],
35
+ profileUrl: "https://commandcode.ai/models/glm-5-3",
36
+ },
20
37
  // Muse Spark: CLI currently prints "has no adjustable reasoning effort" and
21
38
  // blocks --effort locally, but the upstream /alpha/generate endpoint accepts
22
39
  // reasoning_effort low..max for meta/muse-spark-1.2-contributor (verified
@@ -53,8 +70,14 @@ function keyFor(modelId: string): string {
53
70
 
54
71
  export function commandCodeReasoningEfforts(modelId: string): readonly string[] | undefined {
55
72
  const key = keyFor(modelId);
56
- return refreshedEfforts.get(key)
57
- ?? (Object.hasOwn(COMMAND_CODE_MODEL_REASONING_EFFORTS, key) ? COMMAND_CODE_MODEL_REASONING_EFFORTS[key] : undefined);
73
+ const refreshed = refreshedEfforts.get(key);
74
+ if (refreshed !== undefined) return refreshed;
75
+ // Case-insensitive: the table keys match the EXACT upstream ids (e.g. `zai-org/GLM-5.3`),
76
+ // but callers may pass either case.
77
+ for (const [id, efforts] of Object.entries(COMMAND_CODE_MODEL_REASONING_EFFORTS)) {
78
+ if (keyFor(id) === key) return efforts;
79
+ }
80
+ return undefined;
58
81
  }
59
82
 
60
83
  function parsedProfileEfforts(page: string): string[] | undefined {
@@ -82,9 +105,13 @@ export async function refreshCommandCodeReasoningEfforts(
82
105
  fetchFn: typeof globalThis.fetch = globalThis.fetch,
83
106
  ): Promise<readonly string[] | undefined> {
84
107
  const key = keyFor(modelId);
85
- const profile = Object.hasOwn(COMMAND_CODE_MODEL_EFFORTS, key)
86
- ? COMMAND_CODE_MODEL_EFFORTS[key as keyof typeof COMMAND_CODE_MODEL_EFFORTS]
87
- : undefined;
108
+ let profile: { efforts: readonly string[]; profileUrl: string } | undefined;
109
+ for (const [id, row] of Object.entries(COMMAND_CODE_MODEL_EFFORTS)) {
110
+ if (keyFor(id) === key) {
111
+ profile = row;
112
+ break;
113
+ }
114
+ }
88
115
  if (!profile) return undefined;
89
116
  try {
90
117
  const response = await fetchFn(profile.profileUrl, {
@@ -27,6 +27,15 @@ export function applyProviderContextCap(contextWindow: number | undefined, cap:
27
27
  return contextWindow > cap ? cap : contextWindow;
28
28
  }
29
29
 
30
+ /**
31
+ * 上游没报窗口时,已开启的 Context cap 就是实际窗口。
32
+ * 128k 只是 Codex 解析器的兼容底线,不能当成“已发现窗口”再拿去和 cap 做 min。
33
+ */
34
+ export function resolveUnknownRoutedContextWindow(cap: number | undefined): number {
35
+ const window = isValidContextCap(cap) ? Math.floor(cap) : 0;
36
+ return window > 0 ? window : 128_000;
37
+ }
38
+
30
39
  /** Effective global cap value: explicit config value, else the built-in default. */
31
40
  export function globalContextCapValue(config: Pick<OcxConfig, "contextCapValue">): number {
32
41
  const value = config.contextCapValue;
@@ -1,4 +1,5 @@
1
1
  import type { CodexAccountMode, OcxProviderConfig } from "../types";
2
+ import { cloneFastWire } from "./fastwire";
2
3
  import {
3
4
  PROVIDER_REGISTRY,
4
5
  registryEntryForProviderDestination,
@@ -459,6 +460,9 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
459
460
  }
460
461
  // Registry-only metadata (never seeded into saved config): backfill straight from
461
462
  // the entry so an explicit user value stays distinguishable from the default.
463
+ if (prov.fastWire === undefined && entry.fastWire !== undefined) {
464
+ prov.fastWire = cloneFastWire(entry.fastWire);
465
+ }
462
466
  if (prov.supportsServiceTier === undefined && entry.supportsServiceTier !== undefined) prov.supportsServiceTier = entry.supportsServiceTier;
463
467
  if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent;
464
468
  applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries);