@bitkyc08/opencodex 2.60.0 → 2.61.0-preview.20260922

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 (249) hide show
  1. package/AGENTS_INSTALL.md +64 -0
  2. package/README.md +28 -1
  3. package/bin/ocx.mjs +382 -209
  4. package/gui/dist/assets/App-E64Rzjap.js +50 -0
  5. package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
  6. package/gui/dist/assets/index-DpdfZWMK.js +86 -0
  7. package/gui/dist/assets/index-_bpvxJu0.css +1 -0
  8. package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
  9. package/gui/dist/favicon.png +0 -0
  10. package/gui/dist/index.html +2 -2
  11. package/gui/dist/provider-icons/stepfun-color.svg +1 -0
  12. package/package.json +5 -1
  13. package/src/adapters/anthropic.ts +16 -0
  14. package/src/adapters/coding-agent/protocol.ts +36 -6
  15. package/src/adapters/coding-agent/turn.ts +10 -2
  16. package/src/adapters/command-code.ts +2 -1
  17. package/src/adapters/cursor/catalog.ts +51 -7
  18. package/src/adapters/cursor/protobuf-request.ts +6 -3
  19. package/src/adapters/cursor/request-builder.ts +13 -3
  20. package/src/adapters/cursor.ts +11 -2
  21. package/src/adapters/declaration-carrier.ts +45 -0
  22. package/src/adapters/devin.ts +75 -23
  23. package/src/adapters/google-antigravity-wire.ts +5 -2
  24. package/src/adapters/google-errors.ts +7 -1
  25. package/src/adapters/google.ts +29 -5
  26. package/src/adapters/image.ts +4 -1
  27. package/src/adapters/input-media-guard.ts +21 -9
  28. package/src/adapters/kiro/usage.ts +3 -2
  29. package/src/adapters/kiro-tool-fallback.ts +1 -1
  30. package/src/adapters/ollama-native.ts +6 -0
  31. package/src/adapters/openai-chat/developer-role.ts +61 -0
  32. package/src/adapters/openai-chat/messages.ts +46 -27
  33. package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
  34. package/src/adapters/openai-chat/passthrough.ts +33 -9
  35. package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
  36. package/src/adapters/openai-chat.ts +18 -57
  37. package/src/adapters/openai-responses/passthrough.ts +2 -0
  38. package/src/adapters/registry.ts +3 -2
  39. package/src/adapters/run-turn-queue.ts +178 -29
  40. package/src/adapters/xai-web-search.ts +16 -1
  41. package/src/bridge/errors.ts +8 -2
  42. package/src/bridge/response-json.ts +9 -1
  43. package/src/bridge/sse.ts +10 -0
  44. package/src/chat/inbound.ts +141 -5
  45. package/src/claude/desktop-3p.ts +7 -1
  46. package/src/claude/desktop-first-party.ts +183 -0
  47. package/src/claude/desktop-gateway-state.ts +41 -0
  48. package/src/claude/inbound-content-options.ts +6 -0
  49. package/src/claude/inbound.ts +32 -6
  50. package/src/claude/intercept/connect-proxy.ts +179 -0
  51. package/src/claude/intercept/listener.ts +122 -0
  52. package/src/claude/intercept/local-ca.ts +298 -0
  53. package/src/claude/intercept/runtime.ts +98 -0
  54. package/src/claude/intercept/settings.ts +189 -0
  55. package/src/cli/access.ts +87 -0
  56. package/src/cli/account-auth.ts +19 -0
  57. package/src/cli/capabilities.ts +31 -0
  58. package/src/cli/claude-desktop.ts +206 -16
  59. package/src/cli/codex-shim-autorestore.ts +3 -0
  60. package/src/cli/companion.ts +56 -0
  61. package/src/cli/dispatch.ts +43 -4
  62. package/src/cli/ensure-desired-integrations.ts +43 -5
  63. package/src/cli/help.ts +7 -9
  64. package/src/cli/index.ts +200 -61
  65. package/src/cli/init.ts +8 -0
  66. package/src/cli/integrations.ts +7 -1
  67. package/src/cli/registry.ts +41 -2
  68. package/src/cli/resolve.ts +230 -0
  69. package/src/cli/root.ts +24 -1
  70. package/src/cli/start-ownership-publication.ts +56 -0
  71. package/src/cli/status-probes.ts +2 -18
  72. package/src/cli/status.ts +62 -0
  73. package/src/cli/stop-report.ts +143 -0
  74. package/src/cli/uninstall-plan.ts +9 -0
  75. package/src/client/machine-listener.ts +2 -5
  76. package/src/clients/aside-profiles.ts +4 -0
  77. package/src/clients/config-export/zcode-store.ts +157 -0
  78. package/src/clients/config-export.ts +36 -0
  79. package/src/codex/app-server-processes.ts +72 -40
  80. package/src/codex/auth-api/login-flow.ts +6 -1
  81. package/src/codex/autostart-health.ts +28 -0
  82. package/src/codex/catalog/build-entries.ts +2 -2
  83. package/src/codex/catalog/effort.ts +3 -3
  84. package/src/codex/catalog/provider-models.ts +24 -15
  85. package/src/codex/catalog/retained-sync.ts +2 -2
  86. package/src/codex/convergence.ts +2 -2
  87. package/src/codex/history-provider.ts +12 -1
  88. package/src/codex/inject/config-toml.ts +41 -6
  89. package/src/codex/inject/paginated-openai-compat.ts +90 -0
  90. package/src/codex/inject.ts +18 -15
  91. package/src/codex/injected-marker.ts +18 -0
  92. package/src/codex/main-account.ts +6 -0
  93. package/src/codex/model-cache.ts +52 -6
  94. package/src/codex/model-entitlement-admission.ts +59 -0
  95. package/src/codex/model-entitlements.ts +87 -44
  96. package/src/codex/native-main-admission.ts +83 -0
  97. package/src/codex/routing/health-store.ts +39 -0
  98. package/src/codex/routing/selection.ts +37 -1
  99. package/src/codex/routing.ts +5 -41
  100. package/src/codex/shim-templates.ts +29 -3
  101. package/src/companion/settings.ts +132 -0
  102. package/src/config/atomic-write.ts +117 -5
  103. package/src/config/load-degrade.ts +34 -7
  104. package/src/config/process-state.ts +1 -1
  105. package/src/config/schema/config-schema.ts +27 -1
  106. package/src/config/schema/leaf-validators.ts +47 -0
  107. package/src/config.ts +1 -1
  108. package/src/generated/compatibility-version.json +418 -174
  109. package/src/integrations/config-io.ts +44 -10
  110. package/src/integrations/merge.ts +120 -13
  111. package/src/integrations/mutation-plan.ts +124 -18
  112. package/src/integrations/registry.ts +38 -0
  113. package/src/integrations/state.ts +78 -45
  114. package/src/integrations/target.ts +208 -0
  115. package/src/integrations/writer.ts +49 -11
  116. package/src/lab/conformance/fixture-provider.ts +5 -0
  117. package/src/lib/browser-launch-notice.ts +59 -0
  118. package/src/lib/bun-runtime.ts +6 -2
  119. package/src/lib/debug.ts +40 -0
  120. package/src/lib/open-url.ts +51 -7
  121. package/src/lib/package-tree-integrity.ts +2 -1
  122. package/src/lib/package-version.ts +8 -0
  123. package/src/lib/provider-egress.ts +310 -0
  124. package/src/lib/provider-outbound.ts +59 -14
  125. package/src/lib/proxy-env.ts +82 -7
  126. package/src/lib/request-execution-budget.ts +72 -0
  127. package/src/lib/request-failure-attribution.ts +183 -0
  128. package/src/lib/request-failure-model.ts +236 -0
  129. package/src/lib/request-resend-gate.ts +138 -0
  130. package/src/lib/standalone.ts +16 -0
  131. package/src/lib/upstream-retry.ts +167 -16
  132. package/src/lib/winsw.ts +2 -2
  133. package/src/oauth/index.ts +24 -1
  134. package/src/oauth/login-cli.ts +80 -29
  135. package/src/providers/api-key-resolve.ts +133 -0
  136. package/src/providers/api-key-selection.ts +5 -1
  137. package/src/providers/key-failover.ts +31 -1
  138. package/src/providers/key-store.ts +34 -110
  139. package/src/providers/model-rename-fields.ts +147 -0
  140. package/src/providers/model-rename-migration.ts +124 -37
  141. package/src/providers/quota/vendor-probes-key.ts +37 -22
  142. package/src/providers/reasoning-metadata.ts +43 -18
  143. package/src/providers/registry/entries-core.ts +9 -4
  144. package/src/providers/registry/entries-extended.ts +29 -4
  145. package/src/providers/registry/model-seeds.ts +47 -10
  146. package/src/providers/xai-transport.ts +12 -1
  147. package/src/reasoning-effort.ts +8 -0
  148. package/src/responses/function-call-compat.ts +38 -1
  149. package/src/responses/inline-document.ts +65 -0
  150. package/src/responses/input-media.ts +42 -8
  151. package/src/responses/muse-tool-name-alias.ts +19 -0
  152. package/src/responses/parser-content.ts +8 -2
  153. package/src/responses/parser-tools.ts +3 -0
  154. package/src/responses/parser.ts +3 -1
  155. package/src/responses/schema.ts +3 -0
  156. package/src/router.ts +17 -2
  157. package/src/server/admission-model-scope.ts +219 -0
  158. package/src/server/audio-live.ts +9 -3
  159. package/src/server/audio-upstream.ts +18 -0
  160. package/src/server/auth-cors.ts +26 -0
  161. package/src/server/chat-completions.ts +55 -2
  162. package/src/server/chat-native.ts +19 -4
  163. package/src/server/claude-messages.ts +55 -17
  164. package/src/server/grok-responses-snapshot-repair.ts +113 -11
  165. package/src/server/gui-freshness.ts +103 -0
  166. package/src/server/gui-static.ts +7 -9
  167. package/src/server/images.ts +59 -6
  168. package/src/server/index/claude-intercept-lifecycle.ts +49 -0
  169. package/src/server/index/serve-options.ts +56 -10
  170. package/src/server/index/spend-ledger-lifecycle.ts +34 -8
  171. package/src/server/index/startup-warnings.ts +24 -0
  172. package/src/server/index.ts +21 -28
  173. package/src/server/lifecycle.ts +4 -4
  174. package/src/server/live-call-bindings.ts +6 -0
  175. package/src/server/live.ts +88 -3
  176. package/src/server/management/agent-settings-routes.ts +121 -36
  177. package/src/server/management/companion-routes.ts +77 -0
  178. package/src/server/management/logs-usage-routes.ts +19 -0
  179. package/src/server/management/native-integration-routes.ts +103 -6
  180. package/src/server/management/oauth-account-routes.ts +45 -7
  181. package/src/server/management/route-registry.ts +6 -0
  182. package/src/server/management/shared.ts +18 -1
  183. package/src/server/management/usage-timeline-routes.ts +44 -0
  184. package/src/server/management-api.ts +8 -9
  185. package/src/server/proxy-liveness.ts +75 -0
  186. package/src/server/relay.ts +19 -2
  187. package/src/server/request-log-failure-attribution.ts +99 -0
  188. package/src/server/request-log.ts +114 -0
  189. package/src/server/request-metrics.ts +92 -30
  190. package/src/server/responses/codex-ws-wire.ts +34 -8
  191. package/src/server/responses/combo-stream-preflight.ts +168 -6
  192. package/src/server/responses/compact.ts +11 -0
  193. package/src/server/responses/core-opaque-recovery.ts +90 -0
  194. package/src/server/responses/fetch-helpers.ts +124 -8
  195. package/src/server/responses/input-admission.ts +10 -0
  196. package/src/server/responses/passthrough-delivery.ts +14 -1
  197. package/src/server/responses/passthrough-dispatch.ts +179 -35
  198. package/src/server/responses/passthrough-error.ts +27 -8
  199. package/src/server/responses/request-prepare.ts +42 -1
  200. package/src/server/responses/request-send-budget.ts +12 -0
  201. package/src/server/responses/request-transport.ts +24 -4
  202. package/src/server/responses/reset-replay.ts +108 -0
  203. package/src/server/responses-request-tool-scope.ts +214 -0
  204. package/src/server/responses-undeclared-tool-guard.ts +4 -1
  205. package/src/server/search.ts +25 -1
  206. package/src/server/usage-ledger-retention.ts +73 -0
  207. package/src/service/cli.ts +48 -2
  208. package/src/service/health.ts +3 -2
  209. package/src/service/install-state-contract.d.mts +27 -0
  210. package/src/service/install-state-contract.mjs +34 -0
  211. package/src/service/launchd.ts +1 -1
  212. package/src/service/orchestration.ts +2 -4
  213. package/src/service/ownership-compatibility.ts +164 -0
  214. package/src/service/ownership-mutation-lease.d.mts +32 -0
  215. package/src/service/ownership-mutation-lease.mjs +211 -0
  216. package/src/service/repair.ts +45 -1
  217. package/src/service/state-lock.ts +269 -0
  218. package/src/service/state-record.d.mts +36 -0
  219. package/src/service/state-record.mjs +138 -0
  220. package/src/service/state.ts +582 -68
  221. package/src/service/windows-taskxml.ts +11 -10
  222. package/src/service.ts +7 -3
  223. package/src/tray/windows-tray.ps1 +1 -1
  224. package/src/types/config.ts +37 -0
  225. package/src/types/provider.ts +73 -0
  226. package/src/types/request.ts +28 -2
  227. package/src/types/tools.ts +19 -0
  228. package/src/types.ts +3 -0
  229. package/src/update/index.ts +207 -63
  230. package/src/update/job.ts +9 -5
  231. package/src/update/ownership-transaction.ts +47 -0
  232. package/src/update/restart-ownership.ts +54 -0
  233. package/src/update/runtime-ownership.d.mts +40 -0
  234. package/src/update/runtime-ownership.mjs +122 -0
  235. package/src/usage/attempt-delivery.ts +198 -0
  236. package/src/usage/cache-diagnostic.ts +305 -0
  237. package/src/usage/failure-fingerprint.ts +118 -0
  238. package/src/usage/failure-projection-cache.ts +174 -0
  239. package/src/usage/failure-projection.ts +174 -0
  240. package/src/usage/ledger-retention.ts +165 -0
  241. package/src/usage/log.ts +126 -79
  242. package/src/usage/request-outcome.ts +150 -0
  243. package/src/usage/retention-contract.ts +28 -0
  244. package/src/usage/summary.ts +2 -2
  245. package/src/usage/telemetry-contract.ts +237 -0
  246. package/src/usage/timeline.ts +236 -0
  247. package/src/web-search/alpha-search.ts +21 -1
  248. package/gui/dist/assets/index-BTuCbqQd.css +0 -1
  249. package/gui/dist/assets/index-DoBVdPHP.js +0 -134
@@ -32,6 +32,7 @@ import {
32
32
  quotaResetNotifySchema,
33
33
  remoteGuiConfigSchema,
34
34
  retryOn429PolicySchema,
35
+ retryOnResetPolicySchema,
35
36
  runtimeRoleSchema,
36
37
  spendSchema,
37
38
  } from "./schema/leaf-validators";
@@ -195,18 +196,44 @@ export function sanitizeRetryOn429ForLoad(parsed: unknown): void {
195
196
  * redacted (a malformed write can place a secret in a property name).
196
197
  */
197
198
  export function retryOn429PolicyConfigError(policy: unknown): string | null {
199
+ return strictPolicyConfigError("retryOn429", retryOn429PolicySchema, policy);
200
+ }
201
+
202
+ /**
203
+ * Management write-boundary validation for `retryOnReset`, with the same fail-closed contract
204
+ * as `retryOn429PolicyConfigError`: the load-time schema degrades a malformed block to
205
+ * "absent", so this is the one place a bad value is refused instead of silently dropped.
206
+ */
207
+ export function retryOnResetPolicyConfigError(policy: unknown): string | null {
208
+ return strictPolicyConfigError("retryOnReset", retryOnResetPolicySchema, policy);
209
+ }
210
+
211
+ /**
212
+ * The shared body of both. Written once because the two differ only in the field name they
213
+ * report, and a second hand-copied formatter is a second place for the redaction to be
214
+ * forgotten.
215
+ */
216
+ function strictPolicyConfigError(
217
+ field: string,
218
+ schema: {
219
+ safeParse: (value: unknown) => { success: true } | {
220
+ success: false;
221
+ error: { issues: Array<{ code: string; message: string; path: PropertyKey[]; keys?: string[] }> };
222
+ };
223
+ },
224
+ policy: unknown,
225
+ ): string | null {
198
226
  if (policy === undefined) return null;
199
- const result = retryOn429PolicySchema.safeParse(policy);
227
+ const result = schema.safeParse(policy);
200
228
  if (result.success) return null;
201
229
  const first = result.error.issues[0];
202
- if (!first) return "retryOn429 is invalid";
203
- if (first.code === "unrecognized_keys") {
230
+ if (!first) return `${field} is invalid`;
231
+ if (first.code === "unrecognized_keys" && first.keys) {
204
232
  const names = first.keys.map(key => JSON.stringify(redactSecretString(key))).join(", ");
205
- return `retryOn429 has unrecognized field${first.keys.length > 1 ? "s" : ""}: ${names}`;
233
+ return `${field} has unrecognized field${first.keys.length > 1 ? "s" : ""}: ${names}`;
206
234
  }
207
- if (first.path.length === 0) return `retryOn429 is invalid (${first.message})`;
208
- const field = String(first.path[first.path.length - 1]);
209
- return `retryOn429.${field} is invalid (${first.message})`;
235
+ if (first.path.length === 0) return `${field} is invalid (${first.message})`;
236
+ return `${field}.${String(first.path[first.path.length - 1])} is invalid (${first.message})`;
210
237
  }
211
238
 
212
239
  export function sanitizeCapabilityDeclarationsForLoad(parsed: unknown): void {
@@ -127,7 +127,7 @@ export function isOcxCommandLine(commandLine: string): boolean {
127
127
  || normalized.includes("src/cli/index.ts")
128
128
  || normalized.includes("@bitkyc08/opencodex")
129
129
  || /@bitkyc08\/\.opencodex-/.test(normalized)
130
- || /(?:^|[\s/"'])(?:ocx|opencodex)(?:\.cmd)?(?:$|[\s"'])/.test(normalized);
130
+ || /(?:^|[\s/"'])(?:ocx|opencodex)(?:\.cmd|\.exe)?(?:$|[\s"'])/.test(normalized);
131
131
  }
132
132
 
133
133
  export function isOcxStartCommandLine(commandLine: string): boolean {
@@ -46,6 +46,7 @@ import {
46
46
  MAIN_CODEX_ACCOUNT_NAMESPACE_TARGET,
47
47
  } from "../../codex/account-namespace-match";
48
48
  import { UPSTREAM_HOST_CIRCUIT_MAX_THRESHOLD } from "../../codex/upstream-host-health";
49
+ import { MIN_USAGE_LEDGER_MAX_BYTES } from "../../usage/retention-contract";
49
50
  import { COMBO_NAMESPACE, comboConfigIssues } from "../../combos/types";
50
51
  import { routingProfileIssues } from "../../routing/profile";
51
52
  import { POLICY_NAMESPACE } from "../../routing/profile-namespace";
@@ -82,6 +83,14 @@ export const configSchema = z.object({
82
83
  managementUsageMaxReadBytes: z.number().int().positive().default(64 * 1024 * 1024).describe(
83
84
  "Deprecated compatibility limit for bounded legacy usage readers; GET /api/usage always aggregates the complete ledger",
84
85
  ),
86
+ // Opt-in ledger ceiling. A hand edit below the floor, or a non-safe integer, disables only
87
+ // this limit rather than failing the config: refusing to start because history retention was
88
+ // mistyped would be a worse outcome than not trimming history.
89
+ usageLedgerMaxBytes: z.number().int()
90
+ .min(MIN_USAGE_LEDGER_MAX_BYTES)
91
+ .max(Number.MAX_SAFE_INTEGER)
92
+ .optional()
93
+ .catch(undefined),
85
94
  // Invalid hand edits disable only this opt-in circuit. Live writes remain strict.
86
95
  upstreamHostCircuitThreshold: z.number().int()
87
96
  .min(0)
@@ -261,7 +270,24 @@ export const configSchema = z.object({
261
270
  if (claudeCode !== undefined && (!claudeCode || typeof claudeCode !== "object" || Array.isArray(claudeCode))) {
262
271
  ctx.addIssue({ code: "custom", path: ["claudeCode"], message: "claudeCode must be an object" });
263
272
  } else if (claudeCode) {
264
- const claude = claudeCode as { desktopProfile?: unknown };
273
+ const claude = claudeCode as { desktopProfile?: unknown; desktopMode?: unknown; intercept?: unknown };
274
+ if (claude.desktopMode !== undefined && claude.desktopMode !== "first-party" && claude.desktopMode !== "gateway") {
275
+ ctx.addIssue({ code: "custom", path: ["claudeCode", "desktopMode"], message: "desktopMode must be \"first-party\" or \"gateway\"" });
276
+ }
277
+ if (claude.intercept !== undefined) {
278
+ const intercept = claude.intercept;
279
+ if (!intercept || typeof intercept !== "object" || Array.isArray(intercept)) {
280
+ ctx.addIssue({ code: "custom", path: ["claudeCode", "intercept"], message: "intercept must be an object" });
281
+ } else {
282
+ const { enabled, port } = intercept as { enabled?: unknown; port?: unknown };
283
+ if (enabled !== undefined && typeof enabled !== "boolean") {
284
+ ctx.addIssue({ code: "custom", path: ["claudeCode", "intercept", "enabled"], message: "intercept.enabled must be a boolean" });
285
+ }
286
+ if (port !== undefined && (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535)) {
287
+ ctx.addIssue({ code: "custom", path: ["claudeCode", "intercept", "port"], message: "intercept.port must be an integer between 1 and 65535" });
288
+ }
289
+ }
290
+ }
265
291
  if (claude.desktopProfile !== undefined) {
266
292
  try {
267
293
  parseDesktopProfile(claude.desktopProfile);
@@ -17,6 +17,7 @@ import { isCodexAccountPriorityKey } from "../../codex/account-priority";
17
17
  import { parseAccountPriority } from "../../codex/pool-rotation";
18
18
  import { credentialGroupIssues } from "../../routing/identity-domains";
19
19
  import { providerDestinationConfigError } from "../../lib/destination-policy";
20
+ import { providerEgressConfigError } from "../../lib/provider-egress";
20
21
  import { redactSecretString } from "../../lib/redact";
21
22
  import {
22
23
  MODEL_ADAPTER_OVERRIDE_ALLOWED,
@@ -80,6 +81,17 @@ const transientRetryOn5xxPolicySchema = z.object({
80
81
  attempts: z.number().int().min(1).max(10).optional(),
81
82
  }).strict();
82
83
 
84
+ /**
85
+ * `retryOnReset` accepts only these keys. `replacements` counts DUPLICATE inferences the
86
+ * operator is willing to risk for one logical request, so the ceiling is two rather than a
87
+ * send budget: this is the one send the proxy otherwise refuses outright, and a third of them
88
+ * says the connection, not the retry policy, is the problem.
89
+ */
90
+ export const retryOnResetPolicySchema = z.object({
91
+ enabled: z.boolean().optional(),
92
+ replacements: z.number().int().min(1).max(2).optional(),
93
+ }).strict();
94
+
83
95
  const requestPacingRuleSchema = z.object({
84
96
  // Keep the RPM-derived timer within the same one-hour bound as minIntervalMs.
85
97
  requestsPerMinute: z.number().min(1 / 60).max(60_000).optional(),
@@ -221,6 +233,25 @@ const modelCapabilitiesSchema = z.unknown().superRefine((value, ctx) => {
221
233
  if (error) ctx.addIssue({ code: "custom", message: error });
222
234
  }).transform(value => mergeModelCapabilities(undefined, value));
223
235
 
236
+ /**
237
+ * Per-provider egress fields, validated by the resolver the transports themselves use.
238
+ *
239
+ * Calling `providerEgressConfigError` rather than restating the accepted forms keeps one
240
+ * definition of a usable value: a proxy the config loader admits is one the transport can
241
+ * carry, and a value rejected here is rejected at request time for the identical reason.
242
+ * Each field is checked on its own because neither depends on the other's value to be
243
+ * well-formed; how they combine is decided per request against the destination.
244
+ */
245
+ const providerProxySchema = z.unknown().superRefine((value, ctx) => {
246
+ const error = providerEgressConfigError({ proxy: value as string | null | undefined });
247
+ if (error) ctx.addIssue({ code: "custom", message: error });
248
+ }).transform(value => value as string | null | undefined);
249
+
250
+ const providerNoProxySchema = z.unknown().superRefine((value, ctx) => {
251
+ const error = providerEgressConfigError({ noProxy: value as string | string[] | undefined });
252
+ if (error) ctx.addIssue({ code: "custom", message: error });
253
+ }).transform(value => value as string | string[] | undefined);
254
+
224
255
  /**
225
256
  * Zod schema for one provider entry: known fields are validated strictly while unknown
226
257
  * fields pass through (preserved for runtime extensions).
@@ -258,6 +289,7 @@ export const providerConfigSchema = z.object({
258
289
  requiresAdjacentResponsesToolResults: z.boolean().optional(),
259
290
  requiresPairedResponsesToolResults: z.boolean().optional(),
260
291
  annotateEmptyToolOutputs: z.boolean().optional(),
292
+ foldDeveloperRoleToSystem: z.boolean().optional(),
261
293
  fastWire: fastWireSchema.nullable().optional(),
262
294
  supportsServiceTier: z.boolean().optional(),
263
295
  modelSupportsServiceTier: z.record(z.string().min(1), z.boolean()).optional(),
@@ -268,6 +300,10 @@ export const providerConfigSchema = z.object({
268
300
  decodesNativeCompactionBlobs: z.boolean().optional(),
269
301
  allowEncryptedV2AgentTasks: z.boolean().optional(),
270
302
  allowPrivateNetwork: z.boolean().optional(),
303
+ // Per-provider egress (#2894): absent inherits the global proxy decision, "direct"/null
304
+ // refuses it, and an http(s) or socks5 URL replaces it for this provider only.
305
+ proxy: providerProxySchema.optional(),
306
+ noProxy: providerNoProxySchema.optional(),
271
307
  // The management API accepts `null` as "clear this", so a config written before the POST
272
308
  // canonicalization below can hold one on disk. Rejecting it here would send the operator
273
309
  // through invalid-config recovery for a value the API told them was fine.
@@ -306,6 +342,10 @@ export const providerConfigSchema = z.object({
306
342
  .optional(),
307
343
  retryOn429: retryOn429PolicySchema.optional(),
308
344
  transientRetryOn5xx: transientRetryOn5xxPolicySchema.optional(),
345
+ // Degrades to "absent" like `webSearchBridge`: a malformed hand edit of an opt-in feature
346
+ // that is off by default must not send the operator through invalid-config recovery. The
347
+ // management write boundary still rejects it loudly (`retryOnResetPolicyConfigError`).
348
+ retryOnReset: retryOnResetPolicySchema.optional().catch(undefined),
309
349
  codexAccountMode: z.enum(["pool", "direct"]).optional(),
310
350
  // Validated rather than passed through: this schema ends in `.passthrough()`, so an
311
351
  // undeclared key survives verbatim. A misspelled `codexToolMode` therefore used to be
@@ -649,6 +689,13 @@ export const apiKeyEntrySchema = z.object({
649
689
  createdAt: z.string().catch(""),
650
690
  // A damaged overlap record must never discard the still-authoritative key.
651
691
  pendingRotation: pendingApiKeyRotationSchema.optional().catch(undefined),
692
+ // Deliberately NOT `.catch`ed, unlike every field above. Degrading a damaged
693
+ // scope to `undefined` would silently widen the key to the whole catalog,
694
+ // which is the one direction a permission field must never fail. Letting the
695
+ // record fail instead drops the key, so a corrupted scope stops that client
696
+ // rather than promoting it.
697
+ allowedProviders: z.array(z.string().trim().min(1).max(256)).optional(),
698
+ allowedModels: z.array(z.string().trim().min(1).max(256)).optional(),
652
699
  }).passthrough();
653
700
 
654
701
  /**
package/src/config.ts CHANGED
@@ -110,7 +110,7 @@ export {
110
110
  sanitizeModelCostsForDisplay,
111
111
  modelPreferHostedToolsConfigError,
112
112
  } from "./config/schema/leaf-validators";
113
- export { hardenExistingSecret, retryOn429PolicyConfigError } from "./config/load-degrade";
113
+ export { hardenExistingSecret, retryOn429PolicyConfigError, retryOnResetPolicyConfigError } from "./config/load-degrade";
114
114
  export { backupInvalidConfig } from "./config/salvage";
115
115
  export type { ConfigDiagnostics, ConfigAdmissionSnapshot } from "./config/diagnostics";
116
116
  export {