@bitkyc08/opencodex 2.87.0 → 2.89.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 (246) hide show
  1. package/README.md +11 -1
  2. package/bin/ocx.mjs +15 -4
  3. package/gui/dist/assets/App-BnRSujOq.js +58 -0
  4. package/gui/dist/assets/App-dnkJ85te.css +1 -0
  5. package/gui/dist/assets/{Tray-DCd-A1az.js → Tray-7z1COnmz.js} +1 -1
  6. package/gui/dist/assets/index-uchD-ngf.js +94 -0
  7. package/gui/dist/assets/{quota-summary-ChvmKGOk.js → quota-summary-BY_10IEz.js} +1 -1
  8. package/gui/dist/index.html +1 -1
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic/cache-retention.ts +117 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -4
  12. package/src/adapters/anthropic-rejection-memo.ts +71 -0
  13. package/src/adapters/anthropic-replay.ts +135 -0
  14. package/src/adapters/anthropic-tool-catalog.ts +11 -0
  15. package/src/adapters/anthropic.ts +86 -48
  16. package/src/adapters/coding-agent/protocol.ts +6 -0
  17. package/src/adapters/google-http.ts +3 -2
  18. package/src/adapters/google-progress-history.ts +53 -0
  19. package/src/adapters/google-vertex-endpoint.ts +67 -0
  20. package/src/adapters/google.ts +52 -30
  21. package/src/adapters/openai-responses/compaction-search-history.ts +27 -0
  22. package/src/adapters/openai-responses/passthrough.ts +17 -12
  23. package/src/adapters/openai-responses/reasoning.ts +68 -1
  24. package/src/adapters/registry.ts +1 -1
  25. package/src/adapters/upstream-http-error.ts +3 -2
  26. package/src/adapters/zed.ts +7 -4
  27. package/src/bridge/internal.ts +17 -3
  28. package/src/bridge/response-json.ts +4 -4
  29. package/src/bridge/sse.ts +17 -10
  30. package/src/claude/inbound-content-options.ts +66 -0
  31. package/src/claude/inbound.ts +12 -26
  32. package/src/claude/outbound.ts +18 -5
  33. package/src/cli/account-api.ts +13 -1
  34. package/src/cli/account-extended.ts +17 -5
  35. package/src/cli/account-key-quota.ts +4 -1
  36. package/src/cli/account.ts +1 -1
  37. package/src/cli/capabilities-agents-routing.ts +45 -8
  38. package/src/cli/capabilities-base.ts +5 -5
  39. package/src/cli/capabilities-provider-models.ts +5 -4
  40. package/src/cli/combo-result.ts +8 -2
  41. package/src/cli/combo.ts +17 -0
  42. package/src/cli/cross-home-owner.ts +16 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/loopback-family.ts +31 -0
  45. package/src/cli/message-command.ts +4 -1
  46. package/src/cli/message-remote-args.ts +69 -0
  47. package/src/cli/message-remote-command.ts +77 -0
  48. package/src/cli/provider-runtime.ts +69 -1
  49. package/src/cli/registry.ts +3 -3
  50. package/src/codex/account-pause.ts +6 -1
  51. package/src/codex/account-runtime-state.ts +5 -0
  52. package/src/codex/account-store.ts +30 -3
  53. package/src/codex/auth-api/account-list.ts +3 -0
  54. package/src/codex/auth-api/main-account-probe.ts +3 -2
  55. package/src/codex/auth-context.ts +9 -4
  56. package/src/codex/catalog/aggregation.ts +15 -0
  57. package/src/codex/catalog/auto-review.ts +2 -1
  58. package/src/codex/catalog/build-entries.ts +17 -7
  59. package/src/codex/catalog/derive-entry.ts +3 -1
  60. package/src/codex/catalog/metadata.ts +2 -0
  61. package/src/codex/catalog/model-visibility.ts +0 -2
  62. package/src/codex/catalog/ownership.ts +71 -0
  63. package/src/codex/catalog/parsing.ts +12 -3
  64. package/src/codex/catalog/retained-sync.ts +2 -1
  65. package/src/codex/catalog/routed-gather.ts +3 -1
  66. package/src/codex/catalog/routed-removal.ts +4 -4
  67. package/src/codex/convergence.ts +2 -1
  68. package/src/codex/low-quota-desktop-notifier.ts +125 -0
  69. package/src/codex/low-quota-protection.ts +57 -31
  70. package/src/codex/main-account-cache.ts +77 -3
  71. package/src/codex/main-account.ts +6 -2
  72. package/src/codex/native-residue.ts +2 -3
  73. package/src/codex/quota.ts +26 -2
  74. package/src/codex/websocket-activity.ts +161 -0
  75. package/src/codex/websocket-registry.ts +25 -1
  76. package/src/combos/failover.ts +2 -1
  77. package/src/combos/jev-model-backend.ts +30 -7
  78. package/src/combos/jev-model-request.ts +20 -0
  79. package/src/combos/jev-quota-activation.ts +153 -0
  80. package/src/combos/jev-quota-config.ts +62 -0
  81. package/src/combos/jev-quota-route.ts +26 -0
  82. package/src/combos/jev-quota.ts +88 -0
  83. package/src/combos/jev.ts +25 -12
  84. package/src/combos/types.ts +27 -0
  85. package/src/compatibility/index.ts +3 -0
  86. package/src/compatibility/openai-responses.ts +8 -1
  87. package/src/compatibility/opengateway-responses.ts +61 -0
  88. package/src/config/account-auto-pause-revisions.ts +136 -0
  89. package/src/config/diagnostics.ts +13 -0
  90. package/src/config/live-reconcile.ts +9 -0
  91. package/src/config/opencode-zen-upgrade.ts +9 -8
  92. package/src/config/persist-unlocked.ts +10 -1
  93. package/src/config/removed-opencode-free.ts +135 -0
  94. package/src/config/schema/account-auto-pause.ts +14 -0
  95. package/src/config/schema/config-schema.ts +15 -0
  96. package/src/config/schema/leaf-validators.ts +7 -0
  97. package/src/config.ts +10 -3
  98. package/src/generated/compatibility-version.json +411 -175
  99. package/src/images/loop.ts +101 -27
  100. package/src/lib/account-auto-pause-response.ts +16 -0
  101. package/src/lib/bun-runtime.ts +18 -1
  102. package/src/lib/bun-transpiler-cache-scope.d.mts +8 -0
  103. package/src/lib/bun-transpiler-cache-scope.mjs +68 -0
  104. package/src/lib/decision-quota-sink.ts +76 -0
  105. package/src/lib/request-failure-model.ts +2 -0
  106. package/src/lib/state-store-registrations.ts +25 -0
  107. package/src/link/ssh-argv.ts +3 -0
  108. package/src/messaging/envelope.ts +6 -3
  109. package/src/messaging/remote-auth.ts +83 -0
  110. package/src/messaging/remote-bridge.ts +270 -0
  111. package/src/messaging/remote-contract.ts +108 -0
  112. package/src/messaging/remote-enrollment-recovery.ts +59 -0
  113. package/src/messaging/remote-enrollment.ts +244 -0
  114. package/src/messaging/remote-envelope.ts +40 -0
  115. package/src/messaging/remote-files.ts +117 -0
  116. package/src/messaging/remote-owner.ts +169 -0
  117. package/src/messaging/remote-ports.ts +56 -0
  118. package/src/messaging/remote-process.ts +131 -0
  119. package/src/messaging/remote-replies.ts +98 -0
  120. package/src/messaging/remote-rpc-admission.ts +32 -0
  121. package/src/messaging/remote-send.ts +72 -0
  122. package/src/messaging/remote-store.ts +111 -0
  123. package/src/messaging/remote-tunnels.ts +65 -0
  124. package/src/messaging/rpc.ts +13 -1
  125. package/src/oauth/account-auto-pause-policy.ts +66 -0
  126. package/src/oauth/account-auto-pause.ts +171 -0
  127. package/src/oauth/account-quota-rank.ts +2 -1
  128. package/src/oauth/anthropic-account-refusal.ts +6 -0
  129. package/src/oauth/anthropic-model-family.ts +8 -0
  130. package/src/oauth/anthropic-model-quota.ts +2 -6
  131. package/src/oauth/api-key-pool-preserve.ts +37 -0
  132. package/src/oauth/generic-account-failover.ts +7 -4
  133. package/src/oauth/index.ts +5 -35
  134. package/src/oauth/store.ts +35 -8
  135. package/src/oauth/types.ts +2 -0
  136. package/src/plugins/upstream-hooks.ts +14 -1
  137. package/src/protocols/encoders/adapter-events.ts +5 -5
  138. package/src/protocols/encoders/messages.ts +2 -2
  139. package/src/providers/api-key-pause.ts +32 -0
  140. package/src/providers/api-key-resolve.ts +10 -4
  141. package/src/providers/api-key-selection.ts +6 -1
  142. package/src/providers/api-keys.ts +8 -4
  143. package/src/providers/claude-cli-usage.ts +5 -0
  144. package/src/providers/key-failover.ts +11 -11
  145. package/src/providers/model-rename-fields.ts +1 -0
  146. package/src/providers/opencode-zen-rate-limit.ts +12 -73
  147. package/src/providers/quota/account-cache.ts +15 -1
  148. package/src/providers/quota/anthropic-account-quota.ts +2 -1
  149. package/src/providers/quota/anthropic-cooldown-recovery.ts +22 -4
  150. package/src/providers/quota/anthropic-decision-windows.ts +47 -0
  151. package/src/providers/quota/antigravity.ts +39 -6
  152. package/src/providers/quota/report-cache.ts +14 -0
  153. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  154. package/src/providers/quota-decision-publication.ts +77 -0
  155. package/src/providers/quota-decision-snapshot.ts +145 -0
  156. package/src/providers/quota-routing-cache.ts +3 -0
  157. package/src/providers/quota-types.ts +6 -1
  158. package/src/providers/quota.ts +3 -2
  159. package/src/providers/registry/entries-extended.ts +8 -52
  160. package/src/providers/registry/model-seeds.ts +1 -1
  161. package/src/providers/registry.ts +1 -2
  162. package/src/providers/stale-vision-classification-migration.ts +1 -1
  163. package/src/responses/parser.ts +18 -10
  164. package/src/responses/reasoning-replay-cache.ts +50 -4
  165. package/src/responses/web-search-replay.ts +138 -0
  166. package/src/routing/analytics.ts +7 -15
  167. package/src/routing/compatibility/behavior.ts +4 -1
  168. package/src/server/auth-cors.ts +7 -0
  169. package/src/server/background-lifecycle.ts +2 -1
  170. package/src/server/chat-native.ts +28 -13
  171. package/src/server/claude-messages.ts +4 -2
  172. package/src/server/index/live-sideband.ts +3 -2
  173. package/src/server/index/optional-listeners.ts +6 -0
  174. package/src/server/index/serve-options.ts +11 -5
  175. package/src/server/index/websocket-handler.ts +18 -4
  176. package/src/server/index.ts +7 -7
  177. package/src/server/lifecycle.ts +13 -1
  178. package/src/server/management/combo-routes.ts +17 -2
  179. package/src/server/management/oauth-account-routes.ts +10 -1
  180. package/src/server/management/provider-completion-test.ts +289 -0
  181. package/src/server/management/provider-routes.ts +9 -1
  182. package/src/server/management/route-registry.ts +2 -0
  183. package/src/server/messages-native.ts +34 -7
  184. package/src/server/messages-shared.ts +4 -1
  185. package/src/server/request-log.ts +7 -2
  186. package/src/server/responses/account-auto-pause-recovery.ts +26 -0
  187. package/src/server/responses/adapter-continuation.ts +34 -18
  188. package/src/server/responses/adapter-delivery.ts +53 -6
  189. package/src/server/responses/adapter-dispatch.ts +104 -29
  190. package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
  191. package/src/server/responses/combo-session-recall.ts +25 -0
  192. package/src/server/responses/combo-stream-preflight.ts +6 -3
  193. package/src/server/responses/combo-tool-loop-pin.ts +70 -0
  194. package/src/server/responses/compact.ts +8 -5
  195. package/src/server/responses/compaction-recovery.ts +2 -0
  196. package/src/server/responses/context-overflow.ts +141 -2
  197. package/src/server/responses/core-codex-account.ts +9 -3
  198. package/src/server/responses/core-combo.ts +34 -7
  199. package/src/server/responses/core-lifetime.ts +3 -2
  200. package/src/server/responses/core-options.ts +2 -0
  201. package/src/server/responses/core-replay.ts +5 -1
  202. package/src/server/responses/core.ts +12 -9
  203. package/src/server/responses/empty-completion-guard.ts +2 -0
  204. package/src/server/responses/fetch-helpers.ts +35 -4
  205. package/src/server/responses/jev-model-invoke.ts +13 -14
  206. package/src/server/responses/native-steering-log.ts +2 -2
  207. package/src/server/responses/passthrough-delivery.ts +3 -2
  208. package/src/server/responses/passthrough-dispatch.ts +30 -33
  209. package/src/server/responses/passthrough-execution.ts +3 -2
  210. package/src/server/responses/passthrough-observations.ts +38 -0
  211. package/src/server/responses/request-compression.ts +98 -0
  212. package/src/server/responses/request-prepare.ts +1 -0
  213. package/src/server/responses/request-transport.ts +32 -3
  214. package/src/server/responses/response-effects.ts +1 -0
  215. package/src/server/responses/response-tool-ownership.ts +21 -0
  216. package/src/server/responses/run-turn-execution.ts +2 -0
  217. package/src/server/responses/sidecar-execution.ts +59 -10
  218. package/src/server/responses/sidecar-send-budget.ts +2 -2
  219. package/src/server/responses/terminal-guard.ts +2 -0
  220. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  221. package/src/server/startup-action-control.ts +2 -2
  222. package/src/server/startup-health-cache.ts +2 -2
  223. package/src/server/windows-tray-control.ts +2 -2
  224. package/src/server/ws-bridge.ts +2 -0
  225. package/src/service/definition.ts +9 -1
  226. package/src/types/account-auto-pause.ts +15 -0
  227. package/src/types/config.ts +23 -1
  228. package/src/types/opencode-zen.ts +9 -3
  229. package/src/types/provider.ts +9 -5
  230. package/src/types/request.ts +30 -4
  231. package/src/types/wire.ts +4 -2
  232. package/src/types.ts +2 -0
  233. package/src/usage/cost.ts +17 -1
  234. package/src/usage/expected-prices.ts +3 -1
  235. package/src/usage/jev-stats.ts +4 -0
  236. package/src/usage/log.ts +2 -0
  237. package/src/usage/summary.ts +16 -3
  238. package/src/usage/telemetry-contract.ts +1 -0
  239. package/src/web-search/anthropic-history.ts +59 -0
  240. package/src/web-search/loop.ts +212 -73
  241. package/src/web-search/progress-stream.ts +14 -1
  242. package/src/web-search/run-turn-loop.ts +9 -3
  243. package/src/web-search/sidecar-search-replay.ts +96 -0
  244. package/gui/dist/assets/App-ZfYbLHkB.js +0 -58
  245. package/gui/dist/assets/App-wT3mXqFz.css +0 -1
  246. package/gui/dist/assets/index-CfYDMj34.js +0 -94
@@ -1089,7 +1089,11 @@ export interface OcxConfig {
1089
1089
  modelCacheTtlMs?: number;
1090
1090
  /** Evictable retained app-state budget in MiB. Default 256; valid 64..4096. */
1091
1091
  appOwnedMemoryBudgetMb?: number;
1092
- /** Anthropic prompt-cache retention: "short" = 5-min ephemeral (default), "long" = 1-hour extended, "none" = disabled. */
1092
+ /**
1093
+ * Anthropic prompt-cache retention: "short" = 5-min ephemeral, "long" = 1-hour extended,
1094
+ * "none" = disabled. Unset = "long" on first-party api.anthropic.com, "short" on
1095
+ * Anthropic-compatible endpoints (adapters/anthropic/cache-retention.ts).
1096
+ */
1093
1097
  cacheRetention?: "none" | "short" | "long";
1094
1098
  /** Web-search sidecar: route web_search for non-OpenAI models through a gpt-mini via ChatGPT passthrough. */
1095
1099
  webSearchSidecar?: OcxWebSearchSidecarConfig;
@@ -1113,6 +1117,9 @@ export interface OcxConfig {
1113
1117
  codexAccounts?: CodexAccount[];
1114
1118
  /** Account ids administratively excluded from future pool selection until resumed. */
1115
1119
  pausedCodexAccountIds?: string[];
1120
+ /** Internal policy epochs; null marks malformed disk input and suppresses automatic pause. */
1121
+ accountAutoPausePolicyRevisions?: Record<string, string> | null;
1122
+ codexAccountPauseState?: Record<string, { revision: string; details?: import("./account-auto-pause").AccountPauseDetails }>;
1116
1123
  /**
1117
1124
  * Account ids, `__main__` included, allowed to keep serving from ChatGPT credits once one of
1118
1125
  * their usage windows is full. Every other account is skipped by selection at 100% until its
@@ -1293,6 +1300,13 @@ export interface OcxConfig {
1293
1300
  * that work today — on Azure and custom Responses gateways as well, whose limits are unknown.
1294
1301
  */
1295
1302
  maxUpstreamBodyBytes?: number;
1303
+ /**
1304
+ * Opt-in compression of native ChatGPT Responses HTTP request bodies (#7247): "gzip" or "zstd".
1305
+ * Omitted, "off", or any other value = identity, the default. Applies only at the final HTTP send
1306
+ * to chatgpt.com Codex responses/compact, to string bodies from 1 KiB to 16 MiB; WebSocket turns
1307
+ * and every other destination are unchanged. Compression runs synchronously on the event loop.
1308
+ */
1309
+ upstreamRequestCompression?: "off" | "gzip" | "zstd";
1296
1310
  /**
1297
1311
  * Opt-in ceiling, in bytes, on a decompressed INBOUND data-plane request body (#3573).
1298
1312
  *
@@ -1454,6 +1468,8 @@ export interface OcxComboConfig {
1454
1468
  nativeAlias?: boolean;
1455
1469
  /** Display-only label for the public catalog row. Required for native aliases. */
1456
1470
  displayName?: string;
1471
+ /** Display-only description for the public catalog row; unset keeps the generated text (#7229). */
1472
+ description?: string;
1457
1473
  /**
1458
1474
  * `strategy: "jev"` only: provider id of the decision service. Omitted or `"jev"` uses the
1459
1475
  * canonical TypeSafe endpoint; any other id must name a configured `adapter: "jev-decision"`
@@ -1469,6 +1485,10 @@ export interface OcxComboConfig {
1469
1485
  * whose first call may include a cold model load.
1470
1486
  */
1471
1487
  decisionTimeoutMs?: number;
1488
+ /** JEV-only advisory loaded quota evidence; off/unset leaves request bytes unchanged. */
1489
+ decisionQuotaSignals?: boolean | null;
1490
+ /** Partial 0..100 tier overrides; defaults limited 70/nearlyExhausted 90. null clears. */
1491
+ decisionQuotaTiers?: import("../combos/jev-quota-config").JevQuotaTiers | null;
1472
1492
  }
1473
1493
 
1474
1494
  export type OcxRoutingUnknownEvidenceMode = "allow" | "penalize" | "exclude";
@@ -1757,6 +1777,8 @@ export interface CodexLowQuotaProtectionConfig {
1757
1777
  actions: {
1758
1778
  pause: boolean;
1759
1779
  notify: boolean;
1780
+ /** Opt-in: also hand each notice to this host's OS notifier (macOS, Linux). Requires notify. */
1781
+ desktop?: boolean;
1760
1782
  };
1761
1783
  windows: {
1762
1784
  short: boolean;
@@ -1,14 +1,14 @@
1
- /** Fixed-key OpenCode Zen destinations exposing the native Gemini endpoint. */
1
+ /** Fixed-key OpenCode Zen destinations supporting native model protocols. */
2
2
  export interface OpenCodeZenDestination {
3
3
  readonly baseUrl?: unknown;
4
4
  readonly authMode?: unknown;
5
5
  readonly googleMode?: unknown;
6
6
  }
7
7
 
8
- export function openCodeZenBaseUrl(provider: OpenCodeZenDestination | undefined): string | undefined {
8
+ /** Match the official Zen destination and API-key authentication. */
9
+ export function openCodeZenKeyBaseUrl(provider: OpenCodeZenDestination | undefined): string | undefined {
9
10
  if (!provider || typeof provider.baseUrl !== "string") return undefined;
10
11
  if (provider.authMode !== undefined && provider.authMode !== "key") return undefined;
11
- if (provider.googleMode !== undefined && provider.googleMode !== "ai-studio") return undefined;
12
12
  try {
13
13
  const url = new URL(provider.baseUrl.trim());
14
14
  const pathname = url.pathname.replace(/\/+$/, "");
@@ -20,6 +20,12 @@ export function openCodeZenBaseUrl(provider: OpenCodeZenDestination | undefined)
20
20
  }
21
21
  }
22
22
 
23
+ /** Gemini's native endpoint applies only in AI Studio mode. */
24
+ export function openCodeZenBaseUrl(provider: OpenCodeZenDestination | undefined): string | undefined {
25
+ if (provider?.googleMode !== undefined && provider.googleMode !== "ai-studio") return undefined;
26
+ return openCodeZenKeyBaseUrl(provider);
27
+ }
28
+
23
29
  /** Keep the selected destination; Zen omits AI Studio's /v1beta segment. */
24
30
  export function openCodeZenGeminiEndpoint(provider: OpenCodeZenDestination, modelId: string): string | undefined {
25
31
  const baseUrl = openCodeZenBaseUrl(provider);
@@ -1,4 +1,5 @@
1
1
  import type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode } from "./wire";
2
+ import type { AccountAutoPausePolicy, AccountPauseDetails } from "./account-auto-pause";
2
3
  import type { AnthropicAccountPoolConfig } from "./anthropic-account-pool";
3
4
 
4
5
  /**
@@ -448,8 +449,9 @@ export interface OcxProviderConfig {
448
449
  */
449
450
  preserveResponsesMessageMetadata?: boolean;
450
451
  /**
451
- * Opt in to lossy hosted-search history notes on ordinary canonical Codex Lite requests.
452
- * Default off; only literal true enables it, and non-Lite/native compact stay unchanged.
452
+ * Opt in to lossy hosted-search history notes on canonical Codex Lite ordinary turns and
453
+ * native v2 compaction triggers, even when search is declared. Default off; only literal true
454
+ * enables it. History without a search declaration is always projected, independent of this.
453
455
  */
454
456
  projectResponsesLiteHostedSearchHistory?: boolean;
455
457
  /**
@@ -567,7 +569,9 @@ export interface OcxProviderConfig {
567
569
  * entry so routing stays single-key; managed via /api/providers/keys. A legacy bare
568
570
  * `apiKey` seeds a one-entry pool on first management touch.
569
571
  */
570
- apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number }>;
572
+ apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number; disabled?: boolean; pauseDetails?: AccountPauseDetails; pauseRevision?: string }>;
573
+ /** Explicit opt-in permanent account refusal policy; default off. */
574
+ accountAutoPause?: AccountAutoPausePolicy;
571
575
  /**
572
576
  * Optional proactive ordering for `apiKeyPool` when the committed key is already
573
577
  * cooling. Deliberately NOT named like the OAuth `accountPoolStrategy`: an API key
@@ -1110,9 +1114,9 @@ export interface OcxProviderConfig {
1110
1114
  googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
1111
1115
  /** Google tool-schema compatibility policy. Omitted preserves compatible report-only behavior. */
1112
1116
  googleToolSchemaPolicy?: "compatible" | "reject-lossy";
1113
- /** Vertex AI GCP project id (or GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env). */
1117
+ /** Vertex AI GCP project id. API keys use it to select the project endpoint; ADC falls back to GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env. */
1114
1118
  project?: string;
1115
- /** Vertex AI location, e.g. "us-central1" or "global" (or GOOGLE_CLOUD_LOCATION env). */
1119
+ /** Vertex AI location, e.g. "us-central1" or "global". API-key project endpoints default to "global"; ADC falls back to GOOGLE_CLOUD_LOCATION env. */
1116
1120
  location?: string;
1117
1121
  /**
1118
1122
  * Cursor adapter only: MCP servers opencodex starts/connects and exposes to the Cursor agent
@@ -46,6 +46,14 @@ export interface OcxReasoningReplayScopeRef {
46
46
  current?: Readonly<OcxReasoningReplayIdentity>;
47
47
  }
48
48
 
49
+ /** One replayed hosted search cell, rebuilt as the `web_search` call/result pair `callId`. */
50
+ export interface OcxReplayedWebSearchCell {
51
+ /** The client-visible cell id; absent when the cell carried no usable id (no recorded result). */
52
+ cellId?: string;
53
+ callId: string;
54
+ failed: boolean;
55
+ }
56
+
49
57
  export interface OcxParsedRequest {
50
58
  modelId: string;
51
59
  /** Client-facing model selector retained for Anthropic routes after wire-model normalization. */
@@ -68,6 +76,11 @@ export interface OcxParsedRequest {
68
76
  * them, and only for api.anthropic.com; every other destination never sees them.
69
77
  */
70
78
  _anthropicServerTools?: readonly Record<string, unknown>[];
79
+ /**
80
+ * Completed server-tool exchanges from the Claude client's history, keyed by the block that
81
+ * followed them (`call:<tool_use id>` / `text:<text>`). Same audience as `_anthropicServerTools`.
82
+ */
83
+ _anthropicServerReplay?: ReadonlyMap<string, readonly Record<string, unknown>[]>;
71
84
  /**
72
85
  * Boundary between replayed history and this turn's newly appended input. Usually the
73
86
  * items the proxy restored from local previous_response_id state; also set when the
@@ -147,6 +160,12 @@ export interface OcxParsedRequest {
147
160
  * executes searches via the gpt-5.6-luna sidecar (see src/web-search). Absent when not requested.
148
161
  */
149
162
  _webSearch?: Record<string, unknown>;
163
+ /**
164
+ * Replayed hosted `web_search_call` cells the parser rebuilt as `web_search` call/result pairs
165
+ * (src/responses/web-search-replay.ts). The web-search sidecar swaps in the result it recorded for
166
+ * a cell, or removes the pairs when it does not run (src/web-search/sidecar-search-replay.ts).
167
+ */
168
+ _replayedWebSearchCells?: OcxReplayedWebSearchCell[];
150
169
  /** Hosted image_generation tool config stashed for the image bridge sidecar (see src/images). */
151
170
  _imageGeneration?: { toolNames: Set<string>; originalTool?: Record<string, unknown> };
152
171
  /**
@@ -414,6 +433,9 @@ export interface OcxProviderContinuationState {
414
433
  [provider: string]: Record<string, unknown> | undefined;
415
434
  }
416
435
 
436
+ /** One Anthropic server-side call as the API returned it: `server_tool_use` + its `*_tool_result`. */
437
+ export interface AnthropicServerBlocks { use: Record<string, unknown>; result: Record<string, unknown> }
438
+
417
439
  export type AdapterEvent =
418
440
  | { type: "heartbeat"; replayUnsafe?: true; preflightReady?: true }
419
441
  | { type: "text_delta"; text: string; phase?: OcxMessagePhase;
@@ -440,11 +462,12 @@ export type AdapterEvent =
440
462
  // "Searching the web" spinner, then `end` once it resolves. The bridge maps begin → an
441
463
  // output_item.added(in_progress) and end → the matching output_item.done(completed|failed) under
442
464
  // the SAME output index, so the activity animates instead of flashing completed instantly.
443
- | { type: "web_search_call_begin"; id: string }
465
+ // `itemId` lets the loop name the client-visible cell so it can record the result for replay.
466
+ | { type: "web_search_call_begin"; id: string; itemId?: string }
444
467
  | {
445
- type: "web_search_call_end"; id: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[];
446
- /** Anthropic's server-side web_fetch (adapters/anthropic.ts): the opened page and its `web_fetch_tool_result` content, verbatim. */
447
- fetch?: { url: string; result: unknown };
468
+ type: "web_search_call_end"; id: string; itemId?: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[];
469
+ /** Anthropic's server-side call (adapters/anthropic.ts): its `server_tool_use` and result block, verbatim. */
470
+ anthropic?: AnthropicServerBlocks;
448
471
  }
449
472
  | {
450
473
  type: "done";
@@ -502,6 +525,8 @@ export interface OcxUrlCitation {
502
525
  * - `cachedInputTokens` is cache READ tokens only (a subset of `inputTokens`).
503
526
  * - `cacheReadInputTokens`/`cacheCreationInputTokens` carry the read/write split when
504
527
  * the provider reports both; reads mirror `cachedInputTokens`.
528
+ * - `cacheCreation1hInputTokens` is the 1-hour-TTL subset of `cacheCreationInputTokens`
529
+ * (Anthropic `cache_creation.ephemeral_1h_input_tokens`), priced at the 1h write tier.
505
530
  * - `totalTokens` = inputTokens + outputTokens. Never re-add cache detail on top.
506
531
  */
507
532
  export interface OcxUsage {
@@ -519,6 +544,7 @@ export interface OcxUsage {
519
544
  cachedInputTokens?: number;
520
545
  cacheReadInputTokens?: number;
521
546
  cacheCreationInputTokens?: number;
547
+ cacheCreation1hInputTokens?: number;
522
548
  reasoningOutputTokens?: number;
523
549
  estimated?: boolean;
524
550
  /**
package/src/types/wire.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isOpenCodeGoGeminiDestination, OPENCODE_GO_BASE_URL, OPENCODE_GO_MODEL_WIRES, OPENCODE_GO_LEGACY_NAME_PINS, type OpenCodeGoDestination } from "./opencode-go";
2
- import { openCodeZenBaseUrl, type OpenCodeZenDestination } from "./opencode-zen";
2
+ import { openCodeZenBaseUrl, openCodeZenKeyBaseUrl, type OpenCodeZenDestination } from "./opencode-zen";
3
3
 
4
4
  /**
5
5
  * Accepted values for the per-provider upstream HTTP-version pin (#1668). Shared by the
@@ -106,7 +106,9 @@ const CREDENTIAL_PINNED_ADAPTERS: Readonly<Record<string, Readonly<Record<string
106
106
 
107
107
  function prefixPinsFor(providerName: string, provider: WirePinProvider | undefined): Readonly<Record<string, string>> | undefined {
108
108
  if (Object.hasOwn(CREDENTIAL_PINNED_ADAPTERS, providerName)) return CREDENTIAL_PINNED_ADAPTERS[providerName];
109
- if (providerName === "opencode-zen" && openCodeZenBaseUrl(provider)) return { "gemini-": "google" };
109
+ if (providerName === "opencode-zen" && openCodeZenKeyBaseUrl(provider)) {
110
+ return openCodeZenBaseUrl(provider) ? { "gemini-": "google", "claude-": "anthropic" } : { "claude-": "anthropic" };
111
+ }
110
112
  if (!provider || !Object.hasOwn(WIRE_ADAPTER_PIN_PREFIXES, providerName)) return undefined;
111
113
  const rule = WIRE_ADAPTER_PIN_PREFIXES[providerName]!;
112
114
  return normalizedEndpoint(provider.baseUrl) === rule.endpoint ? rule.prefixes : undefined;
package/src/types.ts CHANGED
@@ -41,6 +41,7 @@ export {
41
41
  export type {
42
42
  OcxReasoningReplayIdentity,
43
43
  OcxReasoningReplayScopeRef,
44
+ OcxReplayedWebSearchCell,
44
45
  OcxParsedRequest,
45
46
  OcxContext,
46
47
  OcxMessage,
@@ -61,6 +62,7 @@ export type {
61
62
  OcxProviderContinuationOwner,
62
63
  OcxProviderContinuationState,
63
64
  AdapterEvent,
65
+ AnthropicServerBlocks,
64
66
  OcxUrlCitation,
65
67
  OcxUsage,
66
68
  } from "./types/request";
package/src/usage/cost.ts CHANGED
@@ -65,6 +65,8 @@ export interface CostTokens {
65
65
  output: number;
66
66
  cacheRead: number;
67
67
  cacheWrite: number;
68
+ /** The 1-hour-TTL subset of `cacheWrite`, when the provider reports the split. */
69
+ cacheWrite1h?: number;
68
70
  }
69
71
 
70
72
  export interface CostBreakdown {
@@ -163,22 +165,35 @@ export function normalizeCostTokens(usage: OcxUsage): CostTokens | null {
163
165
  for (const cacheRead of candidates) {
164
166
  if (![input, output, cacheRead, cacheWrite].every(finiteNonNegative)) return null;
165
167
  if (cacheRead + cacheWrite > input) continue;
168
+ const cacheWrite1h = usage.cacheCreation1hInputTokens;
166
169
  return {
167
170
  input: Math.max(0, input - cacheRead - cacheWrite),
168
171
  output,
169
172
  cacheRead,
170
173
  cacheWrite,
174
+ ...(typeof cacheWrite1h === "number" && finiteNonNegative(cacheWrite1h) && cacheWrite1h > 0
175
+ ? { cacheWrite1h: Math.min(cacheWrite1h, cacheWrite) } : {}),
171
176
  };
172
177
  }
173
178
  return null;
174
179
  }
175
180
 
181
+ /**
182
+ * Price of a 1-hour cache write relative to the 5-minute rate that Cost4.cacheWrite records.
183
+ * Anthropic bills 5m writes at 1.25x and 1h writes at 2x base input, so 2 / 1.25. Scaling the
184
+ * 5m rate (rather than input) keeps every tier multiplier already applied to the Cost4 and keeps
185
+ * a free-write row (cacheWrite 0) free.
186
+ */
187
+ export const CACHE_WRITE_1H_OVER_5M = 2 / 1.25;
188
+
176
189
  /** jawcode unit convention: USD per 1M tokens (jawcode stats/db.ts calculateCatalogCost). */
177
190
  export function calculateCost(tokens: CostTokens, cost4: Cost4): CostBreakdown {
178
191
  const input = cost4.input * tokens.input / 1_000_000;
179
192
  const output = cost4.output * tokens.output / 1_000_000;
180
193
  const cacheRead = cost4.cacheRead * tokens.cacheRead / 1_000_000;
181
- const cacheWrite = cost4.cacheWrite * tokens.cacheWrite / 1_000_000;
194
+ const write1h = Math.min(tokens.cacheWrite1h ?? 0, tokens.cacheWrite);
195
+ const cacheWrite = (cost4.cacheWrite * (tokens.cacheWrite - write1h)
196
+ + cost4.cacheWrite * CACHE_WRITE_1H_OVER_5M * write1h) / 1_000_000;
182
197
  return { input, output, cacheRead, cacheWrite, total: input + output + cacheRead + cacheWrite };
183
198
  }
184
199
 
@@ -656,6 +671,7 @@ export function estimateComboCost(
656
671
  tokens.output += est.tokens.output;
657
672
  tokens.cacheRead += est.tokens.cacheRead;
658
673
  tokens.cacheWrite += est.tokens.cacheWrite;
674
+ if (est.tokens.cacheWrite1h) tokens.cacheWrite1h = (tokens.cacheWrite1h ?? 0) + est.tokens.cacheWrite1h;
659
675
  cost.input += est.cost.input;
660
676
  cost.output += est.cost.output;
661
677
  cost.cacheRead += est.cost.cacheRead;
@@ -125,7 +125,9 @@ const GLM_52: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0
125
125
  const GLM_53: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 };
126
126
  const GLM_53_FLASH: Cost4 = { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 };
127
127
  const QWEN38_MAX: Cost4 = { input: 2, output: 6, cacheRead: 0, cacheWrite: 0 };
128
- // Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate.
128
+ // Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate;
129
+ // writes the usage reports as 1-hour (cache_creation.ephemeral_1h_input_tokens) are priced at
130
+ // 2x base input through CACHE_WRITE_1H_OVER_5M in cost.ts.
129
131
  const CLAUDE_SONNET_46: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
130
132
  const CLAUDE_OPUS_46: Cost4 = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
131
133
  // Claude Fable 5.1: 10 / 50, 5m cache write 12.50. Cache hits are 0.025x base input
@@ -1,3 +1,4 @@
1
+ import { normalizeJevQuotaSummary, type JevQuotaDecisionSummary } from "../combos/jev-quota-config";
1
2
  import type { OcxComboDefaultEffort } from "../types";
2
3
  import type { PersistedUsageEntry } from "./log";
3
4
  import { usageDisplayTotalTokens } from "./totals";
@@ -39,6 +40,7 @@ export interface PersistedJevDecisionV1 {
39
40
  };
40
41
  gate: JevDecisionGate;
41
42
  latencyMs: number;
43
+ quota?: JevQuotaDecisionSummary;
42
44
  confidence?: number;
43
45
  chosenProbability?: number;
44
46
  usage?: {
@@ -182,6 +184,7 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
182
184
  const confidence = probability(value.confidence);
183
185
  const chosenProbability = probability(value.chosenProbability);
184
186
  const usage = normalizedDecisionUsage(value.usage);
187
+ const quota = normalizeJevQuotaSummary(value.quota);
185
188
  const backend = value.backend === "typesafe" || value.backend === "systemone" || value.backend === "model"
186
189
  ? value.backend
187
190
  : undefined;
@@ -196,6 +199,7 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
196
199
  },
197
200
  gate: gate as JevDecisionGate,
198
201
  latencyMs,
202
+ ...(quota ? { quota } : {}),
199
203
  ...(confidence !== undefined ? { confidence } : {}),
200
204
  ...(chosenProbability !== undefined ? { chosenProbability } : {}),
201
205
  ...(usage ? { usage } : {}),
package/src/usage/log.ts CHANGED
@@ -581,6 +581,7 @@ function normalizeUsageValue(usage: OcxUsage | undefined): OcxUsage | undefined
581
581
  ...(typeof usage.cachedInputTokens === "number" ? { cachedInputTokens: usage.cachedInputTokens } : {}),
582
582
  ...(typeof usage.cacheReadInputTokens === "number" ? { cacheReadInputTokens: usage.cacheReadInputTokens } : {}),
583
583
  ...(typeof usage.cacheCreationInputTokens === "number" ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {}),
584
+ ...(typeof usage.cacheCreation1hInputTokens === "number" ? { cacheCreation1hInputTokens: usage.cacheCreation1hInputTokens } : {}),
584
585
  ...(typeof usage.reasoningOutputTokens === "number" ? { reasoningOutputTokens: usage.reasoningOutputTokens } : {}),
585
586
  ...(isNonNegativeFiniteNumber(usage.providerCredits) ? { providerCredits: usage.providerCredits } : {}),
586
587
  ...(usage.estimated ? { estimated: true } : {}),
@@ -625,6 +626,7 @@ function normalizeAttemptUsage(raw: unknown): OcxUsage | null {
625
626
  "cachedInputTokens",
626
627
  "cacheReadInputTokens",
627
628
  "cacheCreationInputTokens",
629
+ "cacheCreation1hInputTokens",
628
630
  "reasoningOutputTokens",
629
631
  "providerCredits",
630
632
  ] as const) {
@@ -520,6 +520,8 @@ function projectedComboUsage(
520
520
  let cachedInputTokens = 0;
521
521
  let cacheReadInputTokens = 0;
522
522
  let cacheCreationInputTokens = 0;
523
+ let cacheCreation1hInputTokens = 0;
524
+ let hasCacheWrite1h = false;
523
525
  let reasoningOutputTokens = 0;
524
526
  let hasUsage = false;
525
527
  let hasCacheTelemetry = false;
@@ -540,6 +542,10 @@ function projectedComboUsage(
540
542
  cacheReadInputTokens += cache.read;
541
543
  }
542
544
  if (typeof cache.creation === "number") cacheCreationInputTokens += cache.creation;
545
+ if (typeof attempt.usage.cacheCreation1hInputTokens === "number") {
546
+ hasCacheWrite1h = true;
547
+ cacheCreation1hInputTokens += attempt.usage.cacheCreation1hInputTokens;
548
+ }
543
549
  if (typeof attempt.usage.reasoningOutputTokens === "number") {
544
550
  hasReasoningTelemetry = true;
545
551
  reasoningOutputTokens += attempt.usage.reasoningOutputTokens;
@@ -559,6 +565,7 @@ function projectedComboUsage(
559
565
  inputTokens,
560
566
  outputTokens,
561
567
  ...(hasCacheTelemetry ? { cachedInputTokens, cacheReadInputTokens, cacheCreationInputTokens } : {}),
568
+ ...(hasCacheWrite1h ? { cacheCreation1hInputTokens } : {}),
562
569
  ...(hasReasoningTelemetry ? { reasoningOutputTokens } : {}),
563
570
  ...(estimated ? { estimated: true } : {}),
564
571
  }
@@ -665,8 +672,12 @@ function addEstimatedCost(
665
672
  entry: Pick<PersistedUsageEntry, "usageStatus" | "usage" | "attempts">,
666
673
  costInfo: EntryCostInfo,
667
674
  ): void {
668
- if (entry.usageStatus === "unreported" || entry.usageStatus === "unsupported"
669
- || (!entry.usage && !entry.attempts?.length)) {
675
+ // A row of attempts reads `unreported` when any leg lacks usage. Once one leg is measured the row
676
+ // is metered and the attempt-level pricing decides, matching the model and provider breakdowns.
677
+ const measuredAttempt = entry.attempts?.some(attempt => attempt.usage && isMeasuredStatus(attempt.usageStatus));
678
+ if (!measuredAttempt
679
+ && (entry.usageStatus === "unreported" || entry.usageStatus === "unsupported"
680
+ || (!entry.usage && !entry.attempts?.length))) {
670
681
  totals.unmeteredRequests += 1;
671
682
  return;
672
683
  }
@@ -822,7 +833,9 @@ function blankRequestCounts(): UsageRequestCounts {
822
833
  function spentNothing(attempt: NonNullable<PersistedUsageEntry["attempts"]>[number] | undefined): boolean {
823
834
  if (!attempt || attempt.usage) return false;
824
835
  if (attempt.sendCount === 0 || attempt.locallyAnswered === true) return true;
825
- return attempt.status >= 400 && attempt.streamAborted !== true && attempt.firstOutputMs === undefined;
836
+ // A 499 is the client hanging up on a send upstream may already be generating for.
837
+ return attempt.status >= 400 && attempt.status !== 499
838
+ && attempt.streamAborted !== true && attempt.firstOutputMs === undefined;
826
839
  }
827
840
 
828
841
  function bumpRequestCounts(counts: UsageRequestCounts, facts: number, amount = 1): void {
@@ -32,6 +32,7 @@ export const ATTEMPT_RECOVERY_KIND_ROSTER = Object.freeze([
32
32
  "rate-limit-429",
33
33
  "anthropic-oauth-429",
34
34
  "oauth-account-429",
35
+ "oauth-account-402",
35
36
  "image-413",
36
37
  "console-go-upload-retry",
37
38
  "opaque-blob-rejection",
@@ -0,0 +1,59 @@
1
+ import type { AdapterEvent, OcxAssistantContentPart, OcxThinkingContent, OcxToolCall } from "../types";
2
+
3
+ /** Rebuild one validated Anthropic model response without changing its search arguments or order. */
4
+ export function anthropicSearchAssistantContent(events: readonly AdapterEvent[]): OcxAssistantContentPart[] {
5
+ const content: OcxAssistantContentPart[] = [];
6
+ let thinking: OcxThinkingContent | undefined;
7
+ let text: Extract<OcxAssistantContentPart, { type: "text" }> | undefined;
8
+ let call: OcxToolCall | undefined;
9
+ let argumentsText = "";
10
+ for (const event of events) {
11
+ if (event.type === "heartbeat") continue;
12
+ // A signature is a replacement snapshot until the next semantic block starts.
13
+ if (thinking?.signature !== undefined && event.type !== "thinking_signature") thinking = undefined;
14
+ if (event.type !== "text_delta") text = undefined;
15
+ switch (event.type) {
16
+ case "thinking_delta":
17
+ if (!thinking) { thinking = { type: "thinking", thinking: "" }; content.push(thinking); }
18
+ thinking.thinking += event.thinking;
19
+ break;
20
+ case "thinking_signature":
21
+ if (!thinking) { thinking = { type: "thinking", thinking: "" }; content.push(thinking); }
22
+ thinking.signature = event.signature;
23
+ break;
24
+ case "redacted_thinking":
25
+ thinking = undefined;
26
+ content.push({ type: "thinking", thinking: "", redacted: [event.data] });
27
+ break;
28
+ case "text_delta":
29
+ thinking = undefined;
30
+ if (event.anthropicThinkingSignature) {
31
+ text = undefined;
32
+ content.push({ type: "thinking", thinking: event.text, signature: event.anthropicThinkingSignature });
33
+ break;
34
+ }
35
+ if (!text) { text = { type: "text", text: "" }; content.push(text); }
36
+ text.text += event.text;
37
+ break;
38
+ case "tool_call_start":
39
+ thinking = undefined;
40
+ call = { type: "toolCall", id: event.id, name: event.name, arguments: {},
41
+ ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
42
+ content.push(call);
43
+ argumentsText = "";
44
+ break;
45
+ case "tool_call_delta":
46
+ argumentsText += event.arguments;
47
+ break;
48
+ case "tool_call_end": {
49
+ if (!call) throw new Error("search history has an unpaired tool call");
50
+ const args: unknown = JSON.parse(argumentsText || "{}");
51
+ if (!args || typeof args !== "object" || Array.isArray(args)) throw new Error("search history tool input is not an object");
52
+ call.arguments = args as Record<string, unknown>;
53
+ call = undefined;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ return content;
59
+ }