@bitkyc08/opencodex 2.69.0 → 2.70.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 (147) hide show
  1. package/gui/dist/assets/App-_MrbTrBe.js +51 -0
  2. package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
  3. package/gui/dist/assets/index-DKrO2eF3.js +86 -0
  4. package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
  5. package/gui/dist/index.html +1 -1
  6. package/package.json +1 -1
  7. package/src/adapters/anthropic-model-contract.ts +119 -0
  8. package/src/adapters/anthropic.ts +14 -83
  9. package/src/adapters/base.ts +2 -0
  10. package/src/adapters/command-code-project-context.ts +542 -0
  11. package/src/adapters/command-code-tool-text.ts +99 -67
  12. package/src/adapters/command-code.ts +5 -1
  13. package/src/adapters/cursor/catalog.ts +11 -0
  14. package/src/adapters/cursor/effort-map.ts +3 -0
  15. package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
  16. package/src/adapters/devin/cloud-direct/chat.ts +78 -48
  17. package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
  18. package/src/adapters/devin/context-overflow.ts +75 -0
  19. package/src/adapters/devin/live-models.ts +174 -13
  20. package/src/adapters/devin/reasoning-signature.ts +100 -0
  21. package/src/adapters/devin.ts +310 -117
  22. package/src/adapters/kiro/payload.ts +10 -10
  23. package/src/adapters/kiro-images.ts +58 -7
  24. package/src/adapters/openai-chat/passthrough.ts +3 -1
  25. package/src/adapters/openai-chat.ts +2 -1
  26. package/src/adapters/openai-responses/passthrough.ts +2 -1
  27. package/src/bridge/response-json.ts +15 -1
  28. package/src/bridge/sse.ts +3 -1
  29. package/src/claude/intercept/model-bindings.ts +1 -0
  30. package/src/cli/account-extended.ts +31 -1
  31. package/src/cli/account.ts +3 -0
  32. package/src/cli/capabilities.ts +13 -0
  33. package/src/cli/client-start-fence.ts +36 -0
  34. package/src/cli/dispatch.ts +3 -2
  35. package/src/cli/index.ts +98 -117
  36. package/src/cli/provider-runtime.ts +23 -2
  37. package/src/cli/provider.ts +3 -0
  38. package/src/cli/resolve.ts +18 -0
  39. package/src/cli/start-owner-fence.ts +30 -0
  40. package/src/cli/stop-approval.ts +13 -1
  41. package/src/cli/stop-restore.ts +73 -0
  42. package/src/cli/tray-proxy.ts +221 -18
  43. package/src/cli/version-skew.ts +25 -23
  44. package/src/client/runtime.ts +8 -4
  45. package/src/codex/auth-api/account-list.ts +11 -5
  46. package/src/codex/auth-api/login-flow.ts +5 -3
  47. package/src/codex/auth-api/main-account-probe.ts +186 -110
  48. package/src/codex/auth-api/pool-mode-gate.ts +61 -7
  49. package/src/codex/auth-api/pool-quota-probe.ts +142 -90
  50. package/src/codex/auth-api/reset-credit-service.ts +1 -1
  51. package/src/codex/catalog/gather-capture.ts +1 -0
  52. package/src/codex/catalog/model-hints.ts +3 -3
  53. package/src/codex/catalog/provider-models.ts +2 -0
  54. package/src/codex/low-quota-events.ts +28 -0
  55. package/src/codex/low-quota-observer.ts +23 -0
  56. package/src/codex/low-quota-protection.ts +272 -0
  57. package/src/codex/quota-query-backoff.ts +148 -0
  58. package/src/codex/quota.ts +7 -2
  59. package/src/config/diagnostics.ts +10 -1
  60. package/src/config/load-degrade.ts +17 -0
  61. package/src/config/mutation-lock.ts +3 -4
  62. package/src/config/process-state.ts +1 -1
  63. package/src/config/provider-validation.ts +22 -0
  64. package/src/config/schema/blocked-model-redirects.ts +17 -0
  65. package/src/config/schema/config-schema.ts +4 -1
  66. package/src/config/schema/leaf-validators.ts +33 -2
  67. package/src/config/serving-runtimes.ts +412 -0
  68. package/src/generated/compatibility-version.json +211 -115
  69. package/src/generated/model-metadata.ts +3 -3
  70. package/src/images/loop.ts +3 -0
  71. package/src/lib/errors.ts +2 -1
  72. package/src/lib/handled-signal-exit.ts +23 -0
  73. package/src/lib/request-failure-model.ts +1 -0
  74. package/src/lib/self-launch-argv.ts +9 -0
  75. package/src/lib/shadow-call.ts +5 -2
  76. package/src/lib/strict-semver.ts +26 -0
  77. package/src/lib/transient-service-path.ts +16 -0
  78. package/src/lib/windows-service-wrappers.ts +10 -6
  79. package/src/lib/winsw.ts +3 -1
  80. package/src/oauth/anthropic-model-routes.ts +105 -0
  81. package/src/oauth/anthropic-routing.ts +85 -48
  82. package/src/oauth/devin.ts +135 -5
  83. package/src/oauth/index.ts +20 -3
  84. package/src/oauth/kiro-terminal-failover.ts +23 -9
  85. package/src/oauth/pool-settings-capability.ts +10 -2
  86. package/src/oauth/store.ts +1 -1
  87. package/src/protocols/encoders/adapter-events.ts +3 -1
  88. package/src/providers/github-copilot-context.ts +27 -0
  89. package/src/providers/kiro-models.ts +4 -0
  90. package/src/providers/model-rename-fields.ts +3 -0
  91. package/src/providers/quota/account-cache.ts +8 -3
  92. package/src/providers/quota/devin.ts +286 -0
  93. package/src/providers/quota/vendor-probes-oauth.ts +6 -2
  94. package/src/providers/quota.ts +1 -1
  95. package/src/providers/registry/entries-core.ts +1 -1
  96. package/src/providers/registry/model-seeds.ts +5 -2
  97. package/src/responses/compaction-images.ts +27 -0
  98. package/src/router.ts +104 -16
  99. package/src/routing/analytics.ts +1 -0
  100. package/src/server/auth-cors.ts +9 -1
  101. package/src/server/background-lifecycle.ts +21 -10
  102. package/src/server/chat-completions.ts +2 -1
  103. package/src/server/chat-native.ts +1 -0
  104. package/src/server/index.ts +4 -8
  105. package/src/server/inference/client-encoder-delivery.ts +3 -0
  106. package/src/server/management/context.ts +3 -0
  107. package/src/server/management/low-quota-routes.ts +14 -0
  108. package/src/server/management/oauth-account-routes.ts +21 -1
  109. package/src/server/management/provider-overwrite-carry.ts +79 -6
  110. package/src/server/management/provider-routes.ts +41 -28
  111. package/src/server/management/route-registry.ts +1 -0
  112. package/src/server/management-api.ts +7 -0
  113. package/src/server/responses/adapter-continuation.ts +3 -0
  114. package/src/server/responses/adapter-delivery.ts +3 -0
  115. package/src/server/responses/adapter-dispatch.ts +20 -4
  116. package/src/server/responses/antigravity-validation-refusal.ts +20 -0
  117. package/src/server/responses/core-auth.ts +1 -0
  118. package/src/server/responses/core-codex-account.ts +1 -0
  119. package/src/server/responses/core-normalize.ts +4 -0
  120. package/src/server/responses/passthrough-dispatch.ts +8 -1
  121. package/src/server/responses/request-sidecar-auth.ts +2 -0
  122. package/src/server/responses/request-transport.ts +27 -10
  123. package/src/server/responses/response-effects.ts +4 -1
  124. package/src/server/responses/run-turn-execution.ts +126 -42
  125. package/src/server/responses/sidecar-execution.ts +4 -1
  126. package/src/service/diagnostics.ts +3 -1
  127. package/src/service/guarded-manager-target.ts +39 -12
  128. package/src/service/launchd.ts +14 -6
  129. package/src/service/repair.ts +6 -1
  130. package/src/service/service-child-ownership.ts +168 -0
  131. package/src/service/state.ts +22 -4
  132. package/src/service/systemd.ts +2 -2
  133. package/src/service/windows-guarded-manager.ts +530 -0
  134. package/src/service/windows-taskxml.ts +2 -2
  135. package/src/service.ts +2 -0
  136. package/src/types/config.ts +26 -0
  137. package/src/types/provider.ts +12 -0
  138. package/src/types/request.ts +2 -0
  139. package/src/types.ts +1 -0
  140. package/src/usage/expected-prices.ts +13 -0
  141. package/src/usage/telemetry-contract.ts +1 -0
  142. package/src/vision/anthropic-describe.ts +2 -1
  143. package/src/web-search/anthropic-executor.ts +2 -1
  144. package/src/web-search/loop.ts +1 -0
  145. package/gui/dist/assets/App-DDbgOTh6.js +0 -51
  146. package/gui/dist/assets/index-CuaBJkG4.js +0 -86
  147. package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
@@ -1,6 +1,26 @@
1
1
  import { nonBlankStringArrayConfigError, normalizeNonBlankStringArray } from "../../config/provider-validation";
2
+ import {
3
+ retryOn429PolicyConfigError,
4
+ retryOnResetPolicyConfigError,
5
+ transientRetryOn5xxPolicyConfigError,
6
+ } from "../../config/load-degrade";
2
7
  import type { OcxProviderConfig } from "../../types";
3
8
 
9
+ /**
10
+ * The retry policies. They are the one carried group the PATCH field mask did not know, and they
11
+ * are `"editor"` fields, so before this a save that dropped one left no API path to put it back.
12
+ * Each validator is the same strict check the loader's schema applies, so the mask cannot store a
13
+ * block that a later load would reject.
14
+ */
15
+ const RETRY_POLICY_FIELDS = ["retryOn429", "transientRetryOn5xx", "retryOnReset"] as const satisfies
16
+ readonly (keyof OcxProviderConfig)[];
17
+
18
+ const RETRY_POLICY_VALIDATORS = {
19
+ retryOn429: retryOn429PolicyConfigError,
20
+ transientRetryOn5xx: transientRetryOn5xxPolicyConfigError,
21
+ retryOnReset: retryOnResetPolicyConfigError,
22
+ } as const satisfies Record<typeof RETRY_POLICY_FIELDS[number], (policy: unknown) => string | null>;
23
+
4
24
  /**
5
25
  * What a provider save keeps (#5563).
6
26
  *
@@ -11,13 +31,22 @@ import type { OcxProviderConfig } from "../../types";
11
31
  * them afterwards: the disk row equals its baseline, so the three-way merge keeps the value the
12
32
  * save wrote.
13
33
  *
14
- * These five settings record how one upstream behaves. An overwrite that keeps the destination
34
+ * `hideRawReasoning` is an operator display policy, so every overwrite of the same provider name
35
+ * carries it when omitted, even when the destination changes. The compatibility settings below
36
+ * record how one upstream behaves. An overwrite that keeps the destination
15
37
  * carries each one the request omits, including an explicit `[]` or `false`. An overwrite that
16
38
  * moves the provider to another destination carries none of them: they describe the previous
17
39
  * upstream, and registry enrichment already fills what is known about the new one. The same
18
40
  * destination rule gates the stored `apiKeyPool`, whose keys were issued for the previous
19
41
  * destination. A value the request sends always wins, and the rest of the old row is never merged
20
42
  * into the candidate.
43
+ *
44
+ * The three retry policies join them on the same terms, and for a sharper reason: they are
45
+ * `"editor"` fields that had no branch in the PATCH field mask, so a save that dropped one left no
46
+ * API path to put it back. The mask branch below restores that path, and validates each policy
47
+ * against the loader's own schema — PATCH never runs the schema, and a malformed block would not
48
+ * degrade to "absent": the load would fail and the default config would take the whole provider
49
+ * table.
21
50
  */
22
51
  export const PROVIDER_COMPAT_CARRY_FIELDS = [
23
52
  "preserveReasoningContentModels",
@@ -25,9 +54,13 @@ export const PROVIDER_COMPAT_CARRY_FIELDS = [
25
54
  "foldDeveloperRoleToSystem",
26
55
  "reasoningWireFormat",
27
56
  "omitReasoningEffortWithToolsModels",
57
+ ...RETRY_POLICY_FIELDS,
28
58
  ] as const satisfies readonly (keyof OcxProviderConfig)[];
29
59
 
60
+ export const PROVIDER_DISPLAY_CARRY_FIELDS = ["hideRawReasoning"] as const satisfies readonly (keyof OcxProviderConfig)[];
61
+
30
62
  export type ProviderCompatCarryField = typeof PROVIDER_COMPAT_CARRY_FIELDS[number];
63
+ type ProviderDisplayCarryField = typeof PROVIDER_DISPLAY_CARRY_FIELDS[number];
31
64
 
32
65
  /** The only `reasoningWireFormat` value the adapters understand. */
33
66
  export const PROVIDER_REASONING_WIRE_FORMATS: readonly NonNullable<OcxProviderConfig["reasoningWireFormat"]>[] = [
@@ -39,13 +72,13 @@ export const PROVIDER_REASONING_WIRE_FORMATS: readonly NonNullable<OcxProviderCo
39
72
  * omitted this" and "the registry supplied it" look the same.
40
73
  */
41
74
  export interface ProviderOverwriteSample {
42
- readonly submitted: ReadonlySet<ProviderCompatCarryField>;
75
+ readonly submitted: ReadonlySet<ProviderCompatCarryField | ProviderDisplayCarryField>;
43
76
  readonly namesAuthMode: boolean;
44
77
  }
45
78
 
46
79
  export function sampleProviderOverwrite(provider: object): ProviderOverwriteSample {
47
80
  return {
48
- submitted: new Set(PROVIDER_COMPAT_CARRY_FIELDS.filter(field => Object.hasOwn(provider, field))),
81
+ submitted: new Set([...PROVIDER_COMPAT_CARRY_FIELDS, ...PROVIDER_DISPLAY_CARRY_FIELDS].filter(field => Object.hasOwn(provider, field))),
49
82
  namesAuthMode: Object.hasOwn(provider, "authMode"),
50
83
  };
51
84
  }
@@ -81,7 +114,8 @@ export function providerOverwriteKeepsDestination(
81
114
  }
82
115
 
83
116
  /**
84
- * Carry the stored compatibility settings the request omitted. `live` must be the row read after
117
+ * Carry the stored display policy on every overwrite, then the omitted compatibility settings
118
+ * only when the destination is unchanged. `live` must be the row read after
85
119
  * the route's DNS await, so a PATCH that saved one of them during the wait is kept.
86
120
  */
87
121
  export function carryProviderCompatFields(
@@ -89,13 +123,25 @@ export function carryProviderCompatFields(
89
123
  live: OcxProviderConfig | undefined,
90
124
  sample: ProviderOverwriteSample,
91
125
  ): void {
92
- if (!live || !providerOverwriteKeepsDestination(candidate, live, sample)) return;
126
+ if (!live) return;
93
127
  const target = candidate as unknown as Record<string, unknown>;
128
+ for (const field of PROVIDER_DISPLAY_CARRY_FIELDS) {
129
+ if (sample.submitted.has(field)) continue;
130
+ const stored = live[field];
131
+ if (stored !== undefined) target[field] = stored;
132
+ }
133
+ if (!providerOverwriteKeepsDestination(candidate, live, sample)) return;
94
134
  for (const field of PROVIDER_COMPAT_CARRY_FIELDS) {
95
135
  if (sample.submitted.has(field)) continue;
96
136
  const stored = live[field];
97
137
  if (stored === undefined) continue;
98
- target[field] = Array.isArray(stored) ? [...stored] : stored;
138
+ // Arrays and the retry-policy objects are copied rather than aliased, like `requestPacing`
139
+ // in the route: a later edit of one row must not reach the other through a shared value.
140
+ target[field] = Array.isArray(stored)
141
+ ? [...stored]
142
+ : stored !== null && typeof stored === "object"
143
+ ? structuredClone(stored)
144
+ : stored;
99
145
  }
100
146
  }
101
147
 
@@ -112,6 +158,8 @@ export function providerCompatFieldConfigError(provider: Record<string, unknown>
112
158
  }
113
159
  const fold = provider.foldDeveloperRoleToSystem;
114
160
  if (fold !== undefined && typeof fold !== "boolean") return "foldDeveloperRoleToSystem must be a boolean";
161
+ const hideRawReasoning = provider.hideRawReasoning;
162
+ if (hideRawReasoning !== undefined && typeof hideRawReasoning !== "boolean") return "hideRawReasoning must be a boolean";
115
163
  const wire = provider.reasoningWireFormat;
116
164
  if (wire !== undefined && !PROVIDER_REASONING_WIRE_FORMATS.includes(wire as never)) {
117
165
  return `reasoningWireFormat must be one of: ${PROVIDER_REASONING_WIRE_FORMATS.join(", ")}`;
@@ -149,6 +197,13 @@ export function applyProviderCompatPatchFields(
149
197
  else return { error: "foldDeveloperRoleToSystem must be a boolean" };
150
198
  touched = true;
151
199
  }
200
+ if (Object.hasOwn(rawBody, "hideRawReasoning")) {
201
+ const value = rawBody.hideRawReasoning;
202
+ if (value === null) delete next.hideRawReasoning;
203
+ else if (typeof value === "boolean") next.hideRawReasoning = value;
204
+ else return { error: "hideRawReasoning must be a boolean" };
205
+ touched = true;
206
+ }
152
207
  if (Object.hasOwn(rawBody, "reasoningWireFormat")) {
153
208
  const value = rawBody.reasoningWireFormat;
154
209
  if (value === null) {
@@ -160,5 +215,23 @@ export function applyProviderCompatPatchFields(
160
215
  }
161
216
  touched = true;
162
217
  }
218
+ // The retry policies. They are validated here, like `requestPacing` and `upstreamHttpVersion`,
219
+ // because PATCH does not run the config schema: only POST does, through
220
+ // `validateConfigCandidate`. A malformed block that reached disk would fail the load and take
221
+ // the whole provider table to the default config with it, so the mask refuses it rather than
222
+ // storing something the loader will reject. `null` leaves the row entirely: the schema reads an
223
+ // absent block as "off", so deleting the key is what disables one, not storing `{enabled:false}`.
224
+ for (const field of RETRY_POLICY_FIELDS) {
225
+ if (!Object.hasOwn(rawBody, field)) continue;
226
+ const value = rawBody[field];
227
+ if (value === null) {
228
+ delete next[field];
229
+ } else {
230
+ const error = RETRY_POLICY_VALIDATORS[field](value);
231
+ if (error) return { error };
232
+ next[field] = structuredClone(value) as OcxProviderConfig[typeof field];
233
+ }
234
+ touched = true;
235
+ }
163
236
  return { touched };
164
237
  }
@@ -1,4 +1,4 @@
1
- import { modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
1
+ import { contextTierRecordConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
2
2
  import { DECLARABLE_HOSTED_TOOL_TYPES } from "../../responses/hosted-tool-policy";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { readFileSync } from "node:fs";
@@ -530,6 +530,13 @@ function applyProviderPatchFields(
530
530
  else next.modelCapabilities = capabilities;
531
531
  touched = true;
532
532
  }
533
+ if (Object.hasOwn(rawBody, "modelContextTiers")) {
534
+ const error = contextTierRecordConfigError(rawBody.modelContextTiers, true);
535
+ if (error) return { error };
536
+ if (rawBody.modelContextTiers === null) delete next.modelContextTiers;
537
+ else next.modelContextTiers = Object.assign(Object.create(null), next.modelContextTiers ?? {}, rawBody.modelContextTiers);
538
+ touched = true;
539
+ }
533
540
  if (Object.hasOwn(rawBody, "modelContextWindows")) {
534
541
  const value = rawBody.modelContextWindows;
535
542
  if (value === null) {
@@ -933,6 +940,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
933
940
  models: p.models ?? [],
934
941
  contextWindow: p.contextWindow,
935
942
  modelContextWindows: p.modelContextWindows,
943
+ modelContextTiers: p.modelContextTiers,
936
944
  modelCapabilities: p.modelCapabilities,
937
945
  pinnedReasoningEffort: p.pinnedReasoningEffort,
938
946
  modelPinnedReasoningEfforts: p.modelPinnedReasoningEfforts,
@@ -1213,6 +1221,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
1213
1221
  // call can never fire.
1214
1222
  const submittedContextWindow = Object.hasOwn(prov, "contextWindow");
1215
1223
  const submittedModelContextWindows = Object.hasOwn(prov, "modelContextWindows");
1224
+ const submittedModelContextTiers = Object.hasOwn(prov, "modelContextTiers");
1216
1225
  const submittedModelAutoCompactTokenLimits = Object.hasOwn(prov, "modelAutoCompactTokenLimits");
1217
1226
  const submittedModelDisplayNames = Object.hasOwn(prov, "modelDisplayNames");
1218
1227
  const submittedRequestPacing = Object.hasOwn(prov, "requestPacing");
@@ -1298,7 +1307,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
1298
1307
  if (!submittedFastEnabled && liveFastEnabled !== undefined) prov.fastEnabled = liveFastEnabled;
1299
1308
  // The form sends none of the compatibility settings either (#5563). Read the live row rather
1300
1309
  // than `existing`, like the alias overlays below: a PATCH that saved one of them while DNS
1301
- // validation awaited must not be undone. Nothing is carried to a new destination.
1310
+ // validation awaited must not be undone. Only the hideRawReasoning display policy follows the
1311
+ // provider to a new destination; the upstream compatibility settings do not.
1302
1312
  carryProviderCompatFields(prov, config.providers[name], overwriteSample);
1303
1313
  if (existing?.modelContextWindows) {
1304
1314
  // When the client did send a map, its keys win and the user's other keys survive. When
@@ -1342,48 +1352,51 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
1342
1352
  prov.zaiResponsesDefaultVersion = latest.zaiResponsesDefaultVersion;
1343
1353
  }
1344
1354
  }
1345
- // Reapply pins to the latest live row after DNS/import awaits, then validate the
1346
- // complete draft before adopting any provider/default state.
1355
+ // Reapply pins after DNS/import awaits; validate the complete draft before adoption.
1347
1356
  const latest = config.providers[name];
1348
1357
  const latestPinError = applyProviderPinFields(prov, body.provider, latest);
1349
1358
  if (latestPinError) return jsonResponse({ error: latestPinError }, 400);
1350
1359
  const pinsOwned = Object.hasOwn(body.provider, "pinnedReasoningEffort")
1351
1360
  || Object.hasOwn(body.provider, "modelPinnedReasoningEfforts")
1352
1361
  || latest?.pinnedReasoningEffort !== undefined || latest?.modelPinnedReasoningEfforts !== undefined;
1353
- // New registration also edits discovery/disabled-model state; stage those
1354
- // side effects with the registration draft instead of mutating live state
1355
- // before validation.
1362
+ // Stage new-registration discovery state in a draft until validation passes.
1356
1363
  const registrationDraft = !latest ? {
1357
1364
  ...config,
1358
1365
  ...(config.modelDiscovery === undefined ? {} : { modelDiscovery: structuredClone(config.modelDiscovery) }),
1359
1366
  } : undefined;
1360
1367
  initializeProviderModelSelection(name, prov, latest, registrationDraft ?? config);
1361
1368
  const candidate = stripRegistryOnlyStaticHeaders(name, prov);
1362
- const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate },
1363
- ...(body.setDefault === true ? { defaultProvider: name } : {}) };
1364
- const validation = validateConfigCandidate(draft);
1365
- if (!validation.ok) return jsonResponse({ error: validation.error }, 400);
1366
1369
  const previous = Object.getOwnPropertyDescriptor(config.providers, name);
1367
1370
  const rollback = pinsOwned ? captureConfigTopLevelRollback(config, ["defaultProvider", "modelDiscovery", "disabledModels"]) : undefined;
1368
- try {
1369
- if (registrationDraft) {
1370
- for (const key of ["modelDiscovery", "disabledModels"] as const) {
1371
- if (Object.hasOwn(registrationDraft, key)) Object.defineProperty(config, key, {
1372
- value: registrationDraft[key], writable: true, enumerable: true, configurable: true,
1373
- });
1371
+ let validationError: string | undefined;
1372
+ withConfigMutationLockSync(() => {
1373
+ const liveTiers = config.providers[name]?.modelContextTiers;
1374
+ if (submittedModelContextTiers && liveTiers) candidate.modelContextTiers = Object.assign(Object.create(null), liveTiers, candidate.modelContextTiers ?? {});
1375
+ else if (!submittedModelContextTiers && liveTiers) candidate.modelContextTiers = { ...liveTiers };
1376
+ else if (!submittedModelContextTiers) delete candidate.modelContextTiers;
1377
+ const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate }, ...(body.setDefault === true ? { defaultProvider: name } : {}) };
1378
+ const validation = validateConfigCandidate(draft); if (!validation.ok) { validationError = validation.error; return; }
1379
+ try {
1380
+ if (registrationDraft) {
1381
+ for (const key of ["modelDiscovery", "disabledModels"] as const) {
1382
+ if (Object.hasOwn(registrationDraft, key)) Object.defineProperty(config, key, {
1383
+ value: registrationDraft[key], writable: true, enumerable: true, configurable: true,
1384
+ });
1385
+ }
1374
1386
  }
1387
+ config.providers[name] = candidate;
1388
+ if (body.setDefault === true) config.defaultProvider = name;
1389
+ (deps.saveConfigPreservingClaudeCode ?? save)(config);
1390
+ } catch (error) {
1391
+ if (rollback) {
1392
+ if (previous) Object.defineProperty(config.providers, name, previous);
1393
+ else delete config.providers[name];
1394
+ rollback();
1395
+ }
1396
+ throw error;
1375
1397
  }
1376
- config.providers[name] = candidate;
1377
- if (body.setDefault === true) config.defaultProvider = name;
1378
- (deps.saveConfigPreservingClaudeCode ?? save)(config);
1379
- } catch (error) {
1380
- if (rollback) {
1381
- if (previous) Object.defineProperty(config.providers, name, previous);
1382
- else delete config.providers[name];
1383
- rollback();
1384
- }
1385
- throw error;
1386
- }
1398
+ });
1399
+ if (validationError !== undefined) return jsonResponse({ error: validationError }, 400);
1387
1400
  reconcileLiveStateStores();
1388
1401
  if (prov.apiKey && prov.apiKeyPool) {
1389
1402
  const { addProviderApiKey } = await import("../../providers/api-keys");
@@ -103,6 +103,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
103
103
  { method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api/routes", mutates: false },
104
104
  { method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api/routes", mutates: false },
105
105
  { method: "GET", path: "/api/codex-auth/quota/history", module: "codex/auth-api/routes", mutates: false },
106
+ { method: "GET", path: "/api/codex-auth/low-quota-events", module: "server/management/low-quota-routes", mutates: false, exempt: { reason: "deferred-verb", why: "The authenticated event history is an operator diagnostic with no CLI verb yet.", owner: "rt5 low-quota", ownerDoc: "structure/gui-and-management-api.md" } },
106
107
  { method: "GET", path: "/api/codex-auth/reset-credits", module: "codex/auth-api/routes", mutates: false },
107
108
  { method: "PATCH", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api/routes", mutates: true },
108
109
  { method: "POST", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: true },
@@ -149,6 +149,12 @@ async function handleQuotaResetRoutesOnDemand(ctx: ManagementContext): Promise<R
149
149
  return handleQuotaResetRoutes(ctx);
150
150
  }
151
151
 
152
+ async function handleLowQuotaRoutesOnDemand(ctx: ManagementContext): Promise<Response | null> {
153
+ if (!pathInManagementNamespace(ctx.url.pathname, "/api/codex-auth/low-quota-events", false)) return null;
154
+ const { handleLowQuotaRoutes } = await import("./management/low-quota-routes");
155
+ return handleLowQuotaRoutes(ctx);
156
+ }
157
+
152
158
  /**
153
159
  * Lazy like the Lab and routing-profile handlers, and for the same recorded reason: this file is
154
160
  * mounted for every dashboard request, so a static import would put the workflow-budget ledger
@@ -328,6 +334,7 @@ export async function handleManagementAPI(
328
334
  ?? (await handleLogsUsageRoutes(ctx))
329
335
  ?? (await handleRequestHistoryRoutes(ctx))
330
336
  ?? (await handleQuotaResetRoutesOnDemand(ctx))
337
+ ?? (await handleLowQuotaRoutesOnDemand(ctx))
331
338
  ?? (await handleWorkflowBudgetRoutesOnDemand(ctx))
332
339
  ?? (await handleProtocolRoutesOnDemand(ctx))
333
340
  ?? (await handleGrokCouponRoutesOnDemand(ctx))
@@ -83,6 +83,7 @@ export function createAdapterContinuations(
83
83
  | "oauthDispatch"
84
84
  | "invalidateSameTargetRequest"
85
85
  | "resolveSelectionAdapter"
86
+ | "anthropicRouteDecision"
86
87
  | "anthropicPoolAccountId"
87
88
  | "anthropicPoolFailovers"
88
89
  | "anthropicSessionKey"
@@ -178,6 +179,7 @@ export function createAdapterContinuations(
178
179
  try {
179
180
  continuationRequest = await transportState.activeAdapter.buildRequest(nextParsed, {
180
181
  headers: requestState.selectedForwardHeaders,
182
+ providerName: route.providerName,
181
183
  translatorBudget,
182
184
  ...(transportState.imageTierBias > 0 ? { imageTierBias: transportState.imageTierBias } : {}),
183
185
  });
@@ -398,6 +400,7 @@ export function createAdapterContinuations(
398
400
  anthropicSessionKey,
399
401
  Date.now(),
400
402
  response.headers,
403
+ transportState.anthropicRouteDecision,
401
404
  );
402
405
  if (nextAccountId) {
403
406
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
@@ -153,6 +153,7 @@ export async function deliverAdapterResponse(
153
153
  fold: {
154
154
  replayCacheScope: parsed._reasoningReplayScope,
155
155
  hideThinkingSummary: parsed.options.hideThinkingSummary,
156
+ hideRawReasoning: parsed.options.hideRawReasoning,
156
157
  toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames,
157
158
  },
158
159
  stallTimeoutSec: config.stallTimeoutSec,
@@ -175,6 +176,7 @@ export async function deliverAdapterResponse(
175
176
  stallTimeoutSec: config.stallTimeoutSec,
176
177
  localUpstream,
177
178
  hideThinkingSummary: parsed.options.hideThinkingSummary,
179
+ hideRawReasoning: parsed.options.hideRawReasoning,
178
180
  declaredToolNames,
179
181
  bareCustomToolNames,
180
182
  enforceDeclaredToolNames: options.inboundWire !== "chat" && options.inboundWire !== "anthropic",
@@ -243,6 +245,7 @@ export async function deliverAdapterResponse(
243
245
  translatorBudget,
244
246
  replayCacheScope: parsed._reasoningReplayScope,
245
247
  hideThinkingSummary: parsed.options.hideThinkingSummary,
248
+ hideRawReasoning: parsed.options.hideRawReasoning,
246
249
  toolNsMap,
247
250
  declaredToolNames,
248
251
  bareCustomToolNames,
@@ -93,6 +93,7 @@ import {
93
93
  import { resolveClientRetryAfter } from "../../lib/retry-after";
94
94
  import { cancelBodyOnAbort } from "../../lib/abort";
95
95
  import { chargeWorkflowSends } from "../../lib/workflow-budget";
96
+ import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
96
97
 
97
98
  /** One responsibility of the Responses request pipeline; state owners are explicit. */
98
99
  export async function prepareAdapterExchange(
@@ -126,6 +127,7 @@ export async function prepareAdapterExchange(
126
127
  | "replayOAuthCredentialSnapshot"
127
128
  | "invalidateSameTargetRequest"
128
129
  | "resolveSelectionAdapter"
130
+ | "anthropicRouteDecision"
129
131
  | "anthropicPoolAccountId"
130
132
  | "anthropicPoolFailovers"
131
133
  | "anthropicSessionKey"
@@ -268,6 +270,7 @@ export async function prepareAdapterExchange(
268
270
  try {
269
271
  initialRequest = await transportState.activeAdapter.buildRequest(parsed, {
270
272
  headers: requestState.selectedForwardHeaders,
273
+ providerName: route.providerName,
271
274
  translatorBudget,
272
275
  abortSignal: upstream.signal,
273
276
  });
@@ -413,7 +416,7 @@ export async function prepareAdapterExchange(
413
416
  // moments later; at most one byte-identical replay is allowed per request.
414
417
  const consoleGoUploadRetryGuard: { attempted: boolean } = { attempted: false };
415
418
  let oauth401ReplayAttempted = false;
416
- let antigravity401RotationAttempted = false;
419
+ let antigravityAuthRotationAttempted = false;
417
420
  // At most one reasoning-effort downgrade per request. This sits outside the recovery loop
418
421
  // below for the same reason the two guards above do: a guard declared inside it is reset by
419
422
  // every `continue recovery`, which would let one turn walk the whole ladder down.
@@ -454,6 +457,7 @@ export async function prepareAdapterExchange(
454
457
  try {
455
458
  retryRequest = await transportState.activeAdapter.buildRequest(parsed, {
456
459
  headers: requestState.selectedForwardHeaders,
460
+ providerName: route.providerName,
457
461
  translatorBudget,
458
462
  abortSignal: upstream.signal,
459
463
  ...(transportState.imageTierBias > 0 ? { imageTierBias: transportState.imageTierBias } : {}),
@@ -665,7 +669,7 @@ export async function prepareAdapterExchange(
665
669
  if (route.providerName === "google-antigravity" && err instanceof OAuthLoginRequiredError && failed
666
670
  && getAccountSet(route.providerName)?.accounts.some(row =>
667
671
  row.id === failed.accountId && row.needsReauth === true)) {
668
- antigravity401RotationAttempted = true;
672
+ antigravityAuthRotationAttempted = true;
669
673
  const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-401");
670
674
  if (rotated) { upstreamResponse = rotated; continue recovery; }
671
675
  }
@@ -758,13 +762,24 @@ export async function prepareAdapterExchange(
758
762
  }
759
763
 
760
764
  if (route.providerName === "google-antigravity" && upstreamResponse.status === 401
761
- && oauth401ReplayAttempted && !antigravity401RotationAttempted
765
+ && oauth401ReplayAttempted && !antigravityAuthRotationAttempted
762
766
  && !isNonReplayableResponse(upstreamResponse)) {
763
- antigravity401RotationAttempted = true;
767
+ antigravityAuthRotationAttempted = true;
764
768
  const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-401");
765
769
  if (rotated) { upstreamResponse = rotated; continue recovery; }
766
770
  }
767
771
 
772
+ if (route.providerName === "google-antigravity" && upstreamResponse.status === 403
773
+ && transportState.activeAdapter.name === "google"
774
+ && antigravityPoolActivated && !antigravityAuthRotationAttempted
775
+ && !isNonReplayableResponse(upstreamResponse)
776
+ && transportState.genericFailovers < transportState.genericFailoverLimit
777
+ && await isAntigravityValidationRefusal(upstreamResponse, options.abortSignal)) {
778
+ antigravityAuthRotationAttempted = true;
779
+ const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-account-403");
780
+ if (rotated) { upstreamResponse = rotated; continue recovery; }
781
+ }
782
+
768
783
  // Static API-key pools can recover a credential-scoped 401 without abandoning the
769
784
  // provider: one revoked or mistyped key says nothing about its siblings. OAuth providers
770
785
  // refresh above and never enter here — `hasKeyPoolFailover` rejects oauth/forward modes.
@@ -959,6 +974,7 @@ export async function prepareAdapterExchange(
959
974
  anthropicSessionKey,
960
975
  Date.now(),
961
976
  upstreamResponse.headers,
977
+ transportState.anthropicRouteDecision,
962
978
  );
963
979
  if (!nextAccountId) break;
964
980
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
@@ -0,0 +1,20 @@
1
+ import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX } from "../../adapters/google-errors";
2
+ import { readBoundedResponseBody } from "../../lib/bounded-body";
3
+
4
+ /** Inspect only the Google adapter's normalized, already bounded 403 response. */
5
+ export async function isAntigravityValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
6
+ if (response.status !== 403 || signal?.aborted) return false;
7
+ try {
8
+ const body = await readBoundedResponseBody(response.clone(), {
9
+ maxBytes: 1024,
10
+ totalTimeoutMs: 1000,
11
+ firstByteTimeoutMs: 1000,
12
+ inactivityTimeoutMs: 1000,
13
+ signal,
14
+ });
15
+ return !signal?.aborted && body.displaySafe && !body.truncated && !body.timedOut && !body.oversized
16
+ && body.text.startsWith(`${ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX}: `);
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
@@ -90,6 +90,7 @@ export function codexRouteCredentialDomainHeaders(
90
90
  // caller credential. Bearer admission is substituted or stripped below.
91
91
  const routeMayChangeCredentialDomain = options.comboAttempt === true
92
92
  || route.routeKind === "policy"
93
+ || route.credentialDomainRewrite === true
93
94
  || credentialDomainWasRewritten;
94
95
  if (routeMayChangeCredentialDomain && options.admission?.source !== "bearer") {
95
96
  const scoped = new Headers(req.headers);
@@ -777,6 +777,7 @@ export async function retryCodexPoolOnAlternateAccount(
777
777
  }
778
778
  const request = await retryAdapter.buildRequest(parsed, {
779
779
  headers: retryHeaders,
780
+ providerName: route.providerName,
780
781
  translatorBudget: options.translatorBudget,
781
782
  });
782
783
  recordAdapterReasoning(logCtx, request);
@@ -176,6 +176,10 @@ export async function applyFinalRouteRequestNormalization(args: {
176
176
  || (!summary && !hasValidatedActiveReasoningEffort(parsed.options)
177
177
  && route.provider.showThinkingSummary !== true);
178
178
  }
179
+ // Provider policy, recomputed per final route like the summary default above so a fallback
180
+ // cannot inherit the previous target's choice. Raw content-channel reasoning is suppressed;
181
+ // provider-authored summaries stay on the summary channel and remain visible.
182
+ parsed.options.hideRawReasoning = route.provider.hideRawReasoning === true;
179
183
  if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
180
184
  logCtx.model = virtualModel?.selectedModelId ?? route.modelId;
181
185
  logCtx.provider = route.providerName;
@@ -327,7 +327,11 @@ export async function preparePassthroughExchange(
327
327
  const providerExecutedCallTypes = new Set<ProviderExecutedCallType>();
328
328
  let request: Awaited<ReturnType<typeof transportState.adapter.buildRequest>>;
329
329
  try {
330
- request = await transportState.adapter.buildRequest(parsed, { headers: requestState.selectedForwardHeaders, translatorBudget });
330
+ request = await transportState.adapter.buildRequest(parsed, {
331
+ headers: requestState.selectedForwardHeaders,
332
+ providerName: route.providerName,
333
+ translatorBudget,
334
+ });
331
335
  } catch (error) {
332
336
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
333
337
  // A tool catalog this proxy cannot lower onto one wire namespace is a client input error, and
@@ -993,6 +997,7 @@ export async function preparePassthroughExchange(
993
997
  if (recovery !== "console-go-upload-retry") {
994
998
  request = await retryAdapter.buildRequest(parsed, {
995
999
  headers: requestState.selectedForwardHeaders,
1000
+ providerName: route.providerName,
996
1001
  translatorBudget,
997
1002
  });
998
1003
  }
@@ -1130,6 +1135,7 @@ export async function preparePassthroughExchange(
1130
1135
  try {
1131
1136
  request = await replayAdapter.buildRequest(parsed, {
1132
1137
  headers: requestState.selectedForwardHeaders,
1138
+ providerName: route.providerName,
1133
1139
  translatorBudget,
1134
1140
  });
1135
1141
  refreshRequestToolAliases(request);
@@ -1274,6 +1280,7 @@ export async function preparePassthroughExchange(
1274
1280
  try {
1275
1281
  request = await refreshedAdapter.buildRequest(parsed, {
1276
1282
  headers: requestState.selectedForwardHeaders,
1283
+ providerName: route.providerName,
1277
1284
  translatorBudget,
1278
1285
  });
1279
1286
  refreshRequestToolAliases(request);
@@ -3,6 +3,7 @@ import type { PreparedResponsesRequest } from "./request-prepare";
3
3
  import type { ResponsesTransport } from "./request-transport";
4
4
  import type { ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
5
5
  import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
6
+ import { omitEarlierCompactionImages } from "../../responses/compaction-images";
6
7
  import {
7
8
  shouldResolveOpenAiVisionSidecar,
8
9
  resolveOpenAiVisionModel,
@@ -51,6 +52,7 @@ export async function prepareResponsesSidecarAuth(
51
52
  const visionDescribeTerminal = options.visionDescribeTerminal === true;
52
53
  const routedCompaction = parsed._compactionRequest === true
53
54
  && (!isCanonicalOpenAiForwardProvider(route.provider) || parsed._portableCompaction === true);
55
+ if (routedCompaction) parsed.context.messages = omitEarlierCompactionImages(parsed.context.messages);
54
56
  const needsOpenAiVision = !visionDescribeTerminal
55
57
  && shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed, route.providerName);
56
58
  // LOCAL PATCH (runturn-websearch): runTurn adapters can run the web-search