@bitkyc08/opencodex 2.54.0 → 2.55.0-preview.20260914

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 (86) hide show
  1. package/gui/dist/assets/{index-CkvITofZ.js → index-DH2PUHqr.js} +10 -10
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/anthropic-image-codec.ts +57 -0
  5. package/src/adapters/anthropic-image-normalize.ts +28 -1
  6. package/src/adapters/anthropic.ts +68 -6
  7. package/src/adapters/base.ts +8 -0
  8. package/src/adapters/coding-agent/protocol.ts +41 -16
  9. package/src/adapters/cursor/cursor-errors.ts +1 -1
  10. package/src/adapters/cursor/live-transport.ts +5 -1
  11. package/src/adapters/cursor/native-exec-fs.ts +10 -10
  12. package/src/adapters/cursor/native-exec-network.ts +2 -2
  13. package/src/adapters/cursor/native-exec-shell.ts +13 -12
  14. package/src/adapters/cursor/native-exec.ts +51 -10
  15. package/src/adapters/cursor/policy-error.ts +75 -0
  16. package/src/adapters/cursor/protobuf-request.ts +105 -1
  17. package/src/adapters/devin/cloud-direct/catalog.ts +34 -2
  18. package/src/adapters/devin/live-models.ts +33 -2
  19. package/src/adapters/google-wire-compiler.ts +8 -0
  20. package/src/adapters/google.ts +46 -0
  21. package/src/adapters/input-media-guard.ts +45 -0
  22. package/src/adapters/kiro/adapter.ts +8 -0
  23. package/src/adapters/kiro/payload.ts +28 -6
  24. package/src/adapters/kiro-events.ts +25 -6
  25. package/src/adapters/kiro-images.ts +30 -0
  26. package/src/adapters/kiro-retry.ts +8 -0
  27. package/src/adapters/openai-chat.ts +33 -4
  28. package/src/adapters/openai-responses.ts +26 -0
  29. package/src/adapters/registry.ts +4 -0
  30. package/src/bridge.ts +163 -116
  31. package/src/chat/image-parts.ts +151 -0
  32. package/src/chat/inbound.ts +70 -33
  33. package/src/cli/connect.ts +30 -9
  34. package/src/cli/dispatch.ts +7 -3
  35. package/src/cli/index.ts +3 -0
  36. package/src/cli/runtime-api.ts +25 -0
  37. package/src/cli/status.ts +21 -19
  38. package/src/cli/system-restart-client.ts +25 -0
  39. package/src/clients/config-export.ts +14 -4
  40. package/src/codex/app-server-processes.ts +25 -0
  41. package/src/codex/auth-context.ts +8 -0
  42. package/src/codex/autostart-health.ts +36 -2
  43. package/src/codex/catalog/provider-fetch.ts +41 -0
  44. package/src/codex/catalog-auto-refresh.ts +182 -0
  45. package/src/codex/catalog-refresh-status.ts +93 -0
  46. package/src/codex/history-provider.ts +55 -0
  47. package/src/codex/model-entitlements.ts +78 -0
  48. package/src/codex/native-profile-processes.ts +114 -15
  49. package/src/codex/prompt-text-probe.ts +274 -41
  50. package/src/codex/routing-adoption.ts +189 -0
  51. package/src/codex/routing.ts +520 -48
  52. package/src/codex/runtime.ts +249 -7
  53. package/src/combos/failover.ts +45 -0
  54. package/src/config.ts +124 -4
  55. package/src/generated/compatibility-version.json +110 -74
  56. package/src/generated/model-metadata.ts +1 -0
  57. package/src/lib/request-execution-budget.ts +202 -0
  58. package/src/lib/upstream-retry.ts +95 -8
  59. package/src/lib/workflow-budget.ts +172 -0
  60. package/src/oauth/devin.ts +57 -12
  61. package/src/providers/quota.ts +37 -6
  62. package/src/providers/registry.ts +53 -6
  63. package/src/responses/input-media.ts +65 -0
  64. package/src/responses/parser-content.ts +42 -0
  65. package/src/responses/schema.ts +12 -2
  66. package/src/server/audio-live.ts +1 -2
  67. package/src/server/audio-transcriptions.ts +1 -2
  68. package/src/server/auth-cors.ts +1 -1
  69. package/src/server/background-lifecycle.ts +18 -0
  70. package/src/server/chat-completions.ts +23 -8
  71. package/src/server/chat-native.ts +17 -17
  72. package/src/server/index.ts +24 -0
  73. package/src/server/management/request-history-routes.ts +5 -0
  74. package/src/server/request-log.ts +8 -2
  75. package/src/server/responses/compact.ts +51 -3
  76. package/src/server/responses/core.ts +238 -28
  77. package/src/server/search.ts +7 -9
  78. package/src/types/config.ts +51 -6
  79. package/src/usage/log.ts +37 -0
  80. package/src/vision/eligibility.ts +37 -4
  81. package/src/vision/index.ts +1 -0
  82. package/src/vision/plan.ts +45 -10
  83. package/src/web-search/alpha-search.ts +324 -0
  84. package/src/web-search/index.ts +13 -22
  85. package/src/web-search/passthrough-bridge.ts +195 -22
  86. package/src/web-search/sidecar-providers.ts +22 -0
package/src/config.ts CHANGED
@@ -507,9 +507,15 @@ export function requestPacingConfigError(value: unknown): string | null {
507
507
  /**
508
508
  * Bounds for the opt-in passthrough web-search bridge (`providers.<name>.webSearchBridge`,
509
509
  * #3761). Strict for the same reason `retryOn429` is: a misspelled key here would silently
510
- * leave the bridge disarmed while the operator believes they enabled it. `endpoint` is only
511
- * shape-checked here; `planPassthroughWebSearchBridge` re-validates the origin before any key
512
- * is sent to it, because config validation is not an authorization boundary.
510
+ * leave the bridge disarmed while the operator believes they enabled it.
511
+ *
512
+ * `endpoint` names the destination that receives this provider's API key, so it gets the same
513
+ * literal destination assessment `baseUrl` gets (#4519) — see `providerWebSearchBridgeConfigError`
514
+ * below. This schema itself still only shape-checks: it is `.catch(undefined)` at the provider
515
+ * row, and a hand-edited config file never reaches the error function at all. The authorization
516
+ * boundary is therefore `resolveOllamaWebSearchEndpoint`, which runs the same assessment and is
517
+ * the only reader of this field in the tree; config validation is where an operator is told why,
518
+ * not what makes the value safe.
513
519
  */
514
520
  const providerWebSearchBridgeSchema = z.object({
515
521
  enabled: z.boolean().optional(),
@@ -519,7 +525,11 @@ const providerWebSearchBridgeSchema = z.object({
519
525
  endpoint: z.string().min(1).optional(),
520
526
  }).strict();
521
527
 
522
- export function providerWebSearchBridgeConfigError(value: unknown): string | null {
528
+ export function providerWebSearchBridgeConfigError(
529
+ value: unknown,
530
+ providerName: string,
531
+ provider: Pick<OcxProviderConfig, "allowPrivateNetwork">,
532
+ ): string | null {
523
533
  if (value === undefined) return null;
524
534
  if (!value || typeof value !== "object" || Array.isArray(value)) {
525
535
  return "webSearchBridge must be a plain object";
@@ -541,6 +551,17 @@ export function providerWebSearchBridgeConfigError(value: unknown): string | nul
541
551
  if (url.protocol !== "https:" && url.protocol !== "http:") {
542
552
  return "webSearchBridge.endpoint must be an absolute http(s) URL";
543
553
  }
554
+ // Same classifier baseUrl uses, so a metadata address is refused outright and loopback or
555
+ // private space needs the provider's allowPrivateNetwork opt-in (or a registry entry that is
556
+ // local by definition, which is what keeps a self-hosted Ollama working). Literal-only and
557
+ // synchronous, exactly as at the baseUrl boundary: no DNS is resolved here.
558
+ const destinationError = providerDestinationConfigError(providerName, {
559
+ baseUrl: endpoint,
560
+ allowPrivateNetwork: provider.allowPrivateNetwork,
561
+ });
562
+ if (destinationError) {
563
+ return destinationError.replace(/^baseUrl/, "webSearchBridge.endpoint");
564
+ }
544
565
  }
545
566
  return null;
546
567
  }
@@ -1206,6 +1227,24 @@ const quotaResetNotifySchema = z.object({
1206
1227
  command: z.array(z.string()).optional(),
1207
1228
  }).strict();
1208
1229
 
1230
+ /**
1231
+ * Catalog auto-refresh section (issue #3630).
1232
+ *
1233
+ * `.strict()` like its neighbour: a typo in an optional feature section should surface as a
1234
+ * rejected write rather than a silently ignored key that leaves the operator believing they
1235
+ * enabled something.
1236
+ *
1237
+ * `intervalMinutes` admits 0 (configured but dormant, no timer) and the resolver clamps
1238
+ * anything between 1 and the 15-minute floor. Bounds live in the resolver rather than here
1239
+ * so a hand-edited value degrades to a sane one instead of discarding the whole section.
1240
+ * The 1440 ceiling keeps a hand edit from scheduling the refresh further out than a day,
1241
+ * which is operator error far more often than intent.
1242
+ */
1243
+ const catalogAutoRefreshSchema = z.object({
1244
+ enabled: z.boolean().optional(),
1245
+ intervalMinutes: z.number().int().min(0).max(1440).optional(),
1246
+ }).strict();
1247
+
1209
1248
  const configSchema = z.object({
1210
1249
  port: z.number().int().min(0).max(65535).default(10100),
1211
1250
  // A malformed hand edit must disable only remote-role behavior, not discard
@@ -1310,6 +1349,8 @@ const configSchema = z.object({
1310
1349
  agentTaskRecovery: agentTaskRecoverySchema.optional().catch(undefined),
1311
1350
  // Same rationale: a bad notify section must not cost the operator their providers.
1312
1351
  quotaResetNotify: quotaResetNotifySchema.optional().catch(undefined),
1352
+ // Same rationale: a bad auto-refresh section must not cost the operator their providers.
1353
+ catalogAutoRefresh: catalogAutoRefreshSchema.optional().catch(undefined),
1313
1354
  // These selections pre-date schema validation and used to pass through as
1314
1355
  // unknown fields. Invalid hand edits must disable only the optional
1315
1356
  // delegation/native-default feature, not reject the whole config and hide
@@ -2351,6 +2392,15 @@ function malformedQuotaResetNotifyWarning(rawParsed: unknown): string | null {
2351
2392
  return `quotaResetNotify${field ? `.${field}` : ""} ignored: invalid quota-reset notification configuration`;
2352
2393
  }
2353
2394
 
2395
+ function malformedCatalogAutoRefreshWarning(rawParsed: unknown): string | null {
2396
+ const raw = rawConfigRecord(rawParsed);
2397
+ if (!raw || !Object.hasOwn(raw, "catalogAutoRefresh")) return null;
2398
+ const result = catalogAutoRefreshSchema.safeParse(raw.catalogAutoRefresh);
2399
+ if (result.success) return null;
2400
+ const field = result.error.issues[0]?.path.join(".");
2401
+ return `catalogAutoRefresh${field ? `.${field}` : ""} ignored: invalid catalog auto-refresh configuration`;
2402
+ }
2403
+
2354
2404
  /**
2355
2405
  * Same silent-in-the-wrong-direction failure as the notification block: a dropped pool policy means
2356
2406
  * the accounts the operator meant to exclude keep taking traffic, and the only visible symptom is
@@ -2377,6 +2427,18 @@ function warnDegradedQuotaResetNotify(rawParsed: unknown): void {
2377
2427
  if (warning) console.warn(`⚠️ config.json ${warning}. Other settings were preserved.`);
2378
2428
  }
2379
2429
 
2430
+ /**
2431
+ * Warn once per load that the section was dropped.
2432
+ *
2433
+ * Same silent-in-the-wrong-direction failure as the notification block: a dropped section
2434
+ * means the scheduler never starts, so the operator sees a stale catalog — which is exactly
2435
+ * what they would see if the feature were working and no new models had shipped.
2436
+ */
2437
+ function warnDegradedCatalogAutoRefresh(rawParsed: unknown): void {
2438
+ const warning = malformedCatalogAutoRefreshWarning(rawParsed);
2439
+ if (warning) console.warn(`⚠️ config.json ${warning}. Other settings were preserved.`);
2440
+ }
2441
+
2380
2442
  /**
2381
2443
  * Warn once per load that the pool policy was dropped.
2382
2444
  *
@@ -2552,6 +2614,7 @@ export function loadConfig(): OcxConfig {
2552
2614
  warnDegradedRuntimeRole(parsed);
2553
2615
  warnDegradedOptionalRemoteBlocks(parsed);
2554
2616
  warnDegradedQuotaResetNotify(parsed);
2617
+ warnDegradedCatalogAutoRefresh(parsed);
2555
2618
  warnDegradedCodexPool(parsed);
2556
2619
  return withRefreshedCostOverlays(normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, parsed), parsed));
2557
2620
  }
@@ -2594,6 +2657,7 @@ export function loadConfig(): OcxConfig {
2594
2657
  warnDegradedRuntimeRole(parsed);
2595
2658
  warnDegradedOptionalRemoteBlocks(parsed);
2596
2659
  warnDegradedQuotaResetNotify(parsed);
2660
+ warnDegradedCatalogAutoRefresh(parsed);
2597
2661
  warnDegradedCodexPool(parsed);
2598
2662
  return withRefreshedCostOverlays(normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, parsed), parsed));
2599
2663
  }
@@ -2621,6 +2685,7 @@ export function loadConfig(): OcxConfig {
2621
2685
  warnDegradedRuntimeRole(parsed);
2622
2686
  warnDegradedOptionalRemoteBlocks(parsed);
2623
2687
  warnDegradedQuotaResetNotify(parsed);
2688
+ warnDegradedCatalogAutoRefresh(parsed);
2624
2689
  warnDegradedCodexPool(parsed);
2625
2690
  return withRefreshedCostOverlays(normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, parsed), parsed));
2626
2691
  }
@@ -2767,6 +2832,8 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
2767
2832
  if (clientWarning) warnings.push(clientWarning);
2768
2833
  const notifyWarning = malformedQuotaResetNotifyWarning(rawParsed);
2769
2834
  if (notifyWarning) warnings.push(notifyWarning);
2835
+ const catalogRefreshWarning = malformedCatalogAutoRefreshWarning(rawParsed);
2836
+ if (catalogRefreshWarning) warnings.push(catalogRefreshWarning);
2770
2837
  const codexPoolWarning = malformedCodexPoolWarning(rawParsed);
2771
2838
  if (codexPoolWarning) warnings.push(codexPoolWarning);
2772
2839
  const plaintextWarning = malformedPlaintextV2AgentMessagesWarning(rawParsed);
@@ -2932,6 +2999,16 @@ function quotaResetNotifyError(value: unknown): string | null {
2932
2999
  return `schema_invalid: quotaResetNotify${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`;
2933
3000
  }
2934
3001
 
3002
+ function catalogAutoRefreshError(value: unknown): string | null {
3003
+ const raw = rawConfigRecord(value);
3004
+ if (!raw || !Object.hasOwn(raw, "catalogAutoRefresh") || raw.catalogAutoRefresh === undefined) return null;
3005
+ const result = catalogAutoRefreshSchema.safeParse(raw.catalogAutoRefresh);
3006
+ if (result.success) return null;
3007
+ const issue = result.error.issues[0];
3008
+ const field = issue?.path.join(".");
3009
+ return `schema_invalid: catalogAutoRefresh${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`;
3010
+ }
3011
+
2935
3012
  /**
2936
3013
  * The read path degrades a malformed pool policy to undefined, which for an exclusion policy means
2937
3014
  * the excluded accounts quietly keep serving traffic. Reject it on write so `ocx config set` cannot
@@ -3167,6 +3244,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
3167
3244
  ?? plaintextV2AgentMessagesError(value)
3168
3245
  ?? agentTaskRecoveryError(value)
3169
3246
  ?? quotaResetNotifyError(value)
3247
+ ?? catalogAutoRefreshError(value)
3170
3248
  ?? codexPoolError(value)
3171
3249
  ?? googleAntigravityStaticCatalogVersionError(value)
3172
3250
  ?? codexAccountPrioritiesError(value)
@@ -3767,6 +3845,48 @@ export function ultraFastTierEnabled(config: Pick<OcxConfig, "ultraFastTier">):
3767
3845
  return config.ultraFastTier === true;
3768
3846
  }
3769
3847
 
3848
+ /**
3849
+ * Default cadence for the opt-in catalog auto-refresh (issue #3630): one converge pass
3850
+ * per hour. Each pass spends a live /models call against every enabled provider, and
3851
+ * provider catalogs are themselves cached upstream for minutes, so an hour is fresh
3852
+ * enough for newly released models to appear without an `ocx sync`.
3853
+ */
3854
+ export const CATALOG_AUTO_REFRESH_DEFAULT_INTERVAL_MS: number = 60 * 60_000;
3855
+
3856
+ /**
3857
+ * Floor under the configured cadence, for the same reason src/quota/reset-poller.ts has
3858
+ * MIN_INTERVAL_MS: below this the refresh buys no freshness — upstream caches have not
3859
+ * moved — and only multiplies the chance of a rate limit across every enabled provider.
3860
+ */
3861
+ export const CATALOG_AUTO_REFRESH_MIN_INTERVAL_MS: number = 15 * 60_000;
3862
+
3863
+ /**
3864
+ * Opt-in master switch, read with the house `=== true` idiom so an absent key and a
3865
+ * malformed one both mean off. Pure on purpose: the scheduler calls this from a
3866
+ * dynamically imported context, so it takes an explicit config slice and reads nothing
3867
+ * global.
3868
+ */
3869
+ export function isCatalogAutoRefreshEnabled(
3870
+ config: Pick<OcxConfig, "catalogAutoRefresh">,
3871
+ ): boolean {
3872
+ return config.catalogAutoRefresh?.enabled === true;
3873
+ }
3874
+
3875
+ /**
3876
+ * Resolved tick interval in milliseconds. An explicit `intervalMinutes: 0` returns 0 —
3877
+ * the section stays configured but the timer stays dormant — and any other value is
3878
+ * clamped up to CATALOG_AUTO_REFRESH_MIN_INTERVAL_MS so a hand edit cannot outrun the
3879
+ * upstream catalog caches. Absent means the hourly default.
3880
+ */
3881
+ export function resolveCatalogAutoRefreshIntervalMs(
3882
+ config: Pick<OcxConfig, "catalogAutoRefresh">,
3883
+ ): number {
3884
+ const minutes = config.catalogAutoRefresh?.intervalMinutes;
3885
+ if (minutes === undefined) return CATALOG_AUTO_REFRESH_DEFAULT_INTERVAL_MS;
3886
+ if (minutes === 0) return 0;
3887
+ return Math.max(CATALOG_AUTO_REFRESH_MIN_INTERVAL_MS, Math.floor(minutes * 60_000));
3888
+ }
3889
+
3770
3890
  // ---------------------------------------------------------------------------
3771
3891
  // Hand-edit protection for the `claudeCode` subtree (devlog 260726_claude_auth_auto/040 H1).
3772
3892
  //