@bitkyc08/opencodex 2.79.0 → 2.81.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 (245) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/App-7yhoys8a.js +54 -0
  3. package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
  4. package/gui/dist/assets/index-RsoaAxXY.js +94 -0
  5. package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
  6. package/gui/dist/index.html +1 -1
  7. package/gui/dist/provider-icons/claude-green.svg +1 -0
  8. package/package.json +3 -2
  9. package/src/adapters/anthropic-model-contract.ts +8 -2
  10. package/src/adapters/anthropic.ts +7 -2
  11. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  12. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  13. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  14. package/src/adapters/coding-agent/protocol.ts +122 -17
  15. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  16. package/src/adapters/coding-agent/turn.ts +101 -44
  17. package/src/adapters/cursor/catalog.ts +8 -0
  18. package/src/adapters/cursor/effort-map.ts +2 -0
  19. package/src/adapters/cursor/live-transport.ts +5 -3
  20. package/src/adapters/cursor/native-exec-fs.ts +12 -9
  21. package/src/adapters/cursor/native-exec.ts +13 -5
  22. package/src/adapters/cursor/protobuf-request.ts +13 -7
  23. package/src/adapters/cursor/request-builder.ts +23 -10
  24. package/src/adapters/cursor/tool-definitions.ts +9 -6
  25. package/src/adapters/cursor/tool-guidance.ts +29 -8
  26. package/src/adapters/cursor/tool-naming.ts +40 -7
  27. package/src/adapters/cursor/tool-wording.ts +46 -0
  28. package/src/adapters/cursor.ts +3 -0
  29. package/src/adapters/devin/live-models.ts +2 -0
  30. package/src/adapters/devin.ts +25 -11
  31. package/src/adapters/kiro-retry.ts +9 -4
  32. package/src/adapters/openai-chat/messages.ts +7 -4
  33. package/src/adapters/physical-send.ts +8 -5
  34. package/src/adapters/qoder/adapter.ts +41 -6
  35. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  36. package/src/adapters/zed.ts +16 -1
  37. package/src/claude/inbound-cache-stabilize.ts +4 -9
  38. package/src/claude/inbound.ts +10 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-listener.ts +71 -4
  41. package/src/cli/account-anthropic-threshold.ts +14 -5
  42. package/src/cli/account-api.ts +3 -1
  43. package/src/cli/account-extended.ts +13 -9
  44. package/src/cli/account-policy-dto.ts +1 -1
  45. package/src/cli/account-policy.ts +13 -4
  46. package/src/cli/account.ts +3 -3
  47. package/src/cli/capabilities-accounts.ts +2 -2
  48. package/src/cli/capabilities-agents-routing.ts +24 -0
  49. package/src/cli/capabilities-integrations.ts +16 -0
  50. package/src/cli/codex-shim-autorestore.ts +3 -0
  51. package/src/cli/dispatch.ts +10 -1
  52. package/src/cli/help.ts +4 -1
  53. package/src/cli/index.ts +11 -5
  54. package/src/cli/integration-plan-dto.ts +1 -0
  55. package/src/cli/integration-preview.ts +8 -1
  56. package/src/cli/integrations.ts +32 -3
  57. package/src/cli/message-args.ts +34 -0
  58. package/src/cli/message-command.ts +43 -0
  59. package/src/cli/message-runtime.ts +6 -0
  60. package/src/cli/observe.ts +3 -1
  61. package/src/cli/provider.ts +29 -6
  62. package/src/cli/registry.ts +24 -2
  63. package/src/clients/config-export/commandcode.ts +131 -0
  64. package/src/clients/config-export/contracts.ts +12 -0
  65. package/src/clients/config-export.ts +64 -1
  66. package/src/codex/auth-api/account-list.ts +11 -3
  67. package/src/codex/auth-api/main-account-probe.ts +25 -8
  68. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  69. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  70. package/src/codex/auth-api/routes.ts +1 -0
  71. package/src/codex/autostart-health.ts +3 -3
  72. package/src/codex/catalog/auto-review.ts +13 -6
  73. package/src/codex/catalog/build-entries.ts +15 -7
  74. package/src/codex/catalog/control-plane.ts +45 -0
  75. package/src/codex/catalog/effort.ts +11 -1
  76. package/src/codex/catalog/gather-capture.ts +5 -3
  77. package/src/codex/catalog/parsing.ts +2 -1
  78. package/src/codex/catalog/provider-models.ts +15 -9
  79. package/src/codex/control-plane-models.ts +6 -0
  80. package/src/codex/journal.ts +36 -5
  81. package/src/codex/shim-overlay.ts +9 -1
  82. package/src/combos/jev-decision-contract.ts +25 -2
  83. package/src/combos/jev.ts +6 -3
  84. package/src/combos/types.ts +1 -1
  85. package/src/config/diagnostics.ts +27 -16
  86. package/src/config/load-degrade.ts +34 -0
  87. package/src/config/schema/anthropic-account-pool.ts +143 -0
  88. package/src/config/schema/config-schema.ts +7 -1
  89. package/src/config/schema/leaf-validators.ts +4 -0
  90. package/src/config.ts +2 -0
  91. package/src/generated/compatibility-version.json +347 -199
  92. package/src/generated/model-metadata.ts +5 -4
  93. package/src/images/loop.ts +2 -1
  94. package/src/integrations/catalog-refresh.ts +1 -1
  95. package/src/integrations/mutation-plan.ts +7 -0
  96. package/src/integrations/registry.ts +8 -0
  97. package/src/integrations/state.ts +9 -2
  98. package/src/integrations/writer.ts +8 -2
  99. package/src/lib/lab-live-route-production.ts +9 -1
  100. package/src/lib/request-execution-budget.ts +236 -58
  101. package/src/lib/spend-pool-alias-validation.ts +79 -0
  102. package/src/lib/spend-pool-continuity.ts +34 -0
  103. package/src/lib/spend-reservation-ledger.ts +412 -50
  104. package/src/lib/state-store-registrations.ts +20 -3
  105. package/src/lib/upstream-retry.ts +17 -5
  106. package/src/lib/workflow-budget.ts +53 -11
  107. package/src/messaging/budget.ts +45 -0
  108. package/src/messaging/discovery.ts +57 -0
  109. package/src/messaging/envelope.ts +49 -0
  110. package/src/messaging/input.ts +37 -0
  111. package/src/messaging/rpc.ts +196 -0
  112. package/src/messaging/send.ts +58 -0
  113. package/src/messaging/socket.ts +72 -0
  114. package/src/messaging/types.ts +29 -0
  115. package/src/oauth/anthropic-account-refusal.ts +51 -17
  116. package/src/oauth/anthropic-account-threshold.ts +8 -2
  117. package/src/oauth/anthropic-continuity.ts +15 -1
  118. package/src/oauth/anthropic-model-quota.ts +120 -48
  119. package/src/oauth/anthropic-model-routes.ts +11 -3
  120. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  121. package/src/oauth/anthropic-pool-config.ts +30 -0
  122. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  123. package/src/oauth/anthropic-routing.ts +1202 -866
  124. package/src/oauth/anthropic-send-ownership.ts +41 -0
  125. package/src/oauth/anthropic.ts +4 -1
  126. package/src/oauth/generic-account-failover.ts +3 -1
  127. package/src/oauth/health.ts +8 -6
  128. package/src/oauth/index.ts +90 -173
  129. package/src/oauth/kiro.ts +22 -9
  130. package/src/oauth/login-cli.ts +2 -0
  131. package/src/oauth/model-discovery-auth.ts +43 -0
  132. package/src/oauth/pool-kernel.ts +11 -5
  133. package/src/oauth/pool-settings-capability.ts +5 -3
  134. package/src/oauth/store-anthropic-instance.ts +87 -0
  135. package/src/oauth/store.ts +67 -9
  136. package/src/oauth/token-guardian.ts +2 -0
  137. package/src/oauth/types.ts +2 -0
  138. package/src/protocols/plan-snapshot.ts +5 -0
  139. package/src/protocols/settings.ts +20 -5
  140. package/src/providers/anthropic-instance-id.ts +47 -0
  141. package/src/providers/anthropic-instance.ts +48 -0
  142. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  143. package/src/providers/derive.ts +2 -0
  144. package/src/providers/kiro-models.ts +3 -0
  145. package/src/providers/label.ts +1 -1
  146. package/src/providers/model-rename-fields.ts +2 -0
  147. package/src/providers/quota/account-cache.ts +37 -11
  148. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  149. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  150. package/src/providers/quota/report-cache.ts +24 -1
  151. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  152. package/src/providers/quota.ts +22 -32
  153. package/src/providers/registry/entries-core.ts +35 -30
  154. package/src/providers/registry/model-ids.ts +1 -0
  155. package/src/providers/registry/model-seeds.ts +7 -4
  156. package/src/providers/registry/types.ts +3 -0
  157. package/src/providers/registry.ts +3 -3
  158. package/src/responses/spill-store.ts +5 -1
  159. package/src/responses/state/metrics.ts +87 -0
  160. package/src/responses/state/snapshot-codec.ts +6 -0
  161. package/src/responses/state/spill-queue.ts +1 -0
  162. package/src/responses/state/unforced-store-false.ts +55 -0
  163. package/src/responses/state.ts +50 -66
  164. package/src/router.ts +28 -4
  165. package/src/routing/compatibility/assemble.ts +1 -1
  166. package/src/routing/quota.ts +7 -3
  167. package/src/server/auth-cors.ts +5 -0
  168. package/src/server/chat-completions.ts +6 -1
  169. package/src/server/chat-native.ts +45 -25
  170. package/src/server/claude-messages.ts +16 -2
  171. package/src/server/gui-static.ts +38 -4
  172. package/src/server/index/spend-ledger-lifecycle.ts +10 -4
  173. package/src/server/index.ts +16 -16
  174. package/src/server/inference/context.ts +1 -1
  175. package/src/server/lifecycle.ts +1 -1
  176. package/src/server/management/agent-settings-routes.ts +30 -6
  177. package/src/server/management/anthropic-account-threshold.ts +12 -9
  178. package/src/server/management/anthropic-pool-settings.ts +72 -0
  179. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  180. package/src/server/management/config-routes.ts +27 -7
  181. package/src/server/management/decision-routes.ts +2 -1
  182. package/src/server/management/integration-routes.ts +25 -2
  183. package/src/server/management/oauth-account-routes.ts +79 -98
  184. package/src/server/management/provider-instance-ownership.ts +44 -0
  185. package/src/server/management/provider-routes.ts +21 -28
  186. package/src/server/management/route-registry.ts +3 -1
  187. package/src/server/management/routing-profile-routes.ts +4 -4
  188. package/src/server/management/system-restart.ts +44 -14
  189. package/src/server/management/system-routes.ts +24 -2
  190. package/src/server/management/vision-sidecar-options.ts +9 -6
  191. package/src/server/management/web-search-sidecar-options.ts +51 -2
  192. package/src/server/management-auth.ts +6 -0
  193. package/src/server/messages-native-eligibility.ts +3 -2
  194. package/src/server/messages-native-oauth.ts +86 -41
  195. package/src/server/messages-native-scope.ts +4 -1
  196. package/src/server/messages-native-selector.ts +39 -0
  197. package/src/server/messages-native.ts +84 -25
  198. package/src/server/request-log.ts +5 -0
  199. package/src/server/responses/adapter-continuation.ts +29 -17
  200. package/src/server/responses/adapter-dispatch.ts +47 -17
  201. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  202. package/src/server/responses/combo-requested-effort.ts +14 -0
  203. package/src/server/responses/compact.ts +25 -8
  204. package/src/server/responses/compaction-recovery.ts +1 -1
  205. package/src/server/responses/core-combo-native.ts +4 -1
  206. package/src/server/responses/core-combo.ts +21 -18
  207. package/src/server/responses/core-normalize.ts +1 -0
  208. package/src/server/responses/core-options.ts +3 -1
  209. package/src/server/responses/core-replay.ts +5 -0
  210. package/src/server/responses/passthrough-delivery.ts +7 -3
  211. package/src/server/responses/passthrough-dispatch.ts +21 -8
  212. package/src/server/responses/request-prepare.ts +37 -5
  213. package/src/server/responses/request-send-budget.ts +74 -37
  214. package/src/server/responses/request-spend.ts +325 -33
  215. package/src/server/responses/request-transport.ts +132 -52
  216. package/src/server/responses/run-turn-execution.ts +11 -3
  217. package/src/server/responses/sidecar-execution.ts +96 -14
  218. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  219. package/src/server/workflow-refusal.ts +30 -0
  220. package/src/service/desktop-startup.ts +100 -3
  221. package/src/service/ownership-mutation-lease.d.mts +1 -1
  222. package/src/service/ownership-mutation-lease.mjs +5 -7
  223. package/src/sidecar/anthropic-binding.ts +79 -0
  224. package/src/sidecar/auth.ts +76 -2
  225. package/src/types/anthropic-account-pool.ts +33 -0
  226. package/src/types/config.ts +12 -28
  227. package/src/types/provider.ts +5 -0
  228. package/src/types/tools.ts +7 -0
  229. package/src/types.ts +4 -0
  230. package/src/usage/cost.ts +3 -2
  231. package/src/usage/expected-prices.ts +66 -14
  232. package/src/vision/anthropic-describe.ts +16 -3
  233. package/src/vision/index.ts +3 -0
  234. package/src/vision/plan.ts +19 -6
  235. package/src/web-search/alpha-search.ts +7 -1
  236. package/src/web-search/anthropic-executor.ts +16 -4
  237. package/src/web-search/header-deadline.ts +67 -0
  238. package/src/web-search/index.ts +7 -3
  239. package/src/web-search/loop.ts +32 -10
  240. package/src/web-search/passthrough-bridge.ts +4 -1
  241. package/src/web-search/sidecar-providers.ts +4 -3
  242. package/gui/dist/assets/App-BOUa-ek3.js +0 -54
  243. package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
  244. package/gui/dist/assets/index-DwHNDF4s.js +0 -94
  245. package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
@@ -17,6 +17,73 @@ import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
17
17
  import { isCodexAccountUsable } from "../codex/account-usability";
18
18
  import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "../codex/account-id";
19
19
  import { getAccountSet } from "../oauth/store";
20
+ import { configuredAnthropicInstance, type AnthropicInstanceId } from "../providers/anthropic-instance";
21
+ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
22
+
23
+ /**
24
+ * The parent request's pool for an inheriting helper. A missing or disabled row keeps the parent's
25
+ * identity, so a pool removed mid-request refuses instead of discovering another pool; only a present
26
+ * custom row that is not the builtin instance (an unmarked custom `anthropic2`) stops inheritance.
27
+ */
28
+ export function inheritedAnthropicInstance(config: OcxConfig, parentProviderName: string | undefined): AnthropicInstanceId | undefined {
29
+ if (!isAnthropicInstanceId(parentProviderName)) return undefined;
30
+ const row = config.providers?.[parentProviderName];
31
+ if (!row || row.disabled === true) return parentProviderName;
32
+ return configuredAnthropicInstance(config, parentProviderName);
33
+ }
34
+
35
+ export class AnthropicHelperUnavailableError extends Error {
36
+ readonly code = "anthropic_helper_unavailable";
37
+ constructor(readonly instance: AnthropicInstanceId) {
38
+ super(`The selected ${instance} helper account pool is unavailable`);
39
+ this.name = "AnthropicHelperUnavailableError";
40
+ }
41
+ }
42
+ export type AnthropicHelperContext = {
43
+ backendFamily: string;
44
+ anthropicInstance?: AnthropicInstanceId;
45
+ parentProviderName?: string;
46
+ };
47
+
48
+ /**
49
+ * Availability of one selected pool: the configured builtin row plus at least one stored account the
50
+ * pool could select. Which account actually sends (model route, cooldown, manual choice) is decided
51
+ * by snapshot resolution at send time, exactly as the main request path does, so a paused or
52
+ * reauth-pending ACTIVE account does not make a pool with another usable account unavailable.
53
+ */
54
+ export function resolveAnthropicSidecarAuth(config: OcxConfig, instance: AnthropicInstanceId):
55
+ { anthropicProviderName: AnthropicInstanceId; anthropicProvider: OcxProviderConfig } | undefined {
56
+ const provider = config.providers[instance];
57
+ if (!provider || provider.disabled || provider.authMode !== "oauth" || provider.adapter !== "anthropic"
58
+ || configuredAnthropicInstance(config, instance) !== instance) return undefined;
59
+ if (!getAccountSet(instance)?.accounts.some(row => row.paused !== true && row.needsReauth !== true)) return undefined;
60
+ return { anthropicProviderName: instance, anthropicProvider: provider };
61
+ }
62
+
63
+ /**
64
+ * Explicit target, then the parent's CONFIGURED builtin instance. The parent name alone is not
65
+ * enough: a custom provider that happens to be named `anthropic2` is not Pool 2 and is never inherited.
66
+ */
67
+ export function resolveAnthropicHelperInstance(config: OcxConfig, context: AnthropicHelperContext): AnthropicInstanceId | undefined {
68
+ if (context.backendFamily !== "anthropic") return undefined;
69
+ const instance = context.anthropicInstance ?? inheritedAnthropicInstance(config, context.parentProviderName);
70
+ if (instance && !resolveAnthropicSidecarAuth(config, instance)) throw new AnthropicHelperUnavailableError(instance);
71
+ return instance;
72
+ }
73
+
74
+ /**
75
+ * Planner boundary for the typed refusal: an unavailable selected pool means "this helper does not
76
+ * run", never "fail the main request" and never "discover another pool". Other errors propagate.
77
+ */
78
+ export function withAnthropicHelperRefusal<T>(surface: string, resolve: () => T): T | undefined {
79
+ try {
80
+ return resolve();
81
+ } catch (error) {
82
+ if (!(error instanceof AnthropicHelperUnavailableError)) throw error;
83
+ console.warn(`[${surface}] ${error.message}; the request continues without this helper`);
84
+ return undefined;
85
+ }
86
+ }
20
87
 
21
88
  export interface SidecarAuthState {
22
89
  /** ChatGPT login usable: canonical forward provider AND a live stored credential. */
@@ -63,6 +130,10 @@ function findAnthropicAuthProvider(
63
130
  config: OcxConfig,
64
131
  ): { providerName: string; provider: OcxProviderConfig } | undefined {
65
132
  for (const [providerName, provider] of Object.entries(config.providers)) {
133
+ // Never adds Pool 2 automatically. The OAuth store key `anthropic2` belongs to the builtin pool:
134
+ // its token path and the token guardian refuse an unmarked row of that name, so offering such a
135
+ // custom row here would advertise credentials no helper executor will spend.
136
+ if (providerName === "anthropic2") continue;
66
137
  if (provider.disabled === true) continue;
67
138
  if (provider.adapter !== "anthropic" || provider.authMode !== "oauth") continue;
68
139
  const set = getAccountSet(providerName);
@@ -72,8 +143,11 @@ function findAnthropicAuthProvider(
72
143
  return undefined;
73
144
  }
74
145
 
75
- export function resolveSidecarAuth(config: OcxConfig): SidecarAuthState {
76
- const anthropic = findAnthropicAuthProvider(config);
146
+ export function resolveSidecarAuth(config: OcxConfig, instance?: AnthropicInstanceId): SidecarAuthState {
147
+ const exact = instance ? resolveAnthropicSidecarAuth(config, instance) : undefined;
148
+ if (instance && !exact) throw new AnthropicHelperUnavailableError(instance);
149
+ const anthropic = exact ? { providerName: exact.anthropicProviderName, provider: exact.anthropicProvider }
150
+ : findAnthropicAuthProvider(config);
77
151
  return {
78
152
  isCodexAuth: hasUsableCodexLogin(config),
79
153
  isAnthropicAuth: anthropic !== undefined,
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Anthropic OAuth account-pool settings. One shape, two locations:
3
+ * `config.anthropicAccountPool` for the `anthropic` instance and
4
+ * `config.providers.anthropic2.anthropicAccountPool` for `anthropic2`. Neither inherits from the other.
5
+ *
6
+ * Leaf module: `config.ts` and `provider.ts` import these types, never the reverse.
7
+ */
8
+ export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-first";
9
+
10
+ export type OcxAccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization";
11
+
12
+ export interface AnthropicModelRoute {
13
+ name: string;
14
+ match: string;
15
+ accounts: string[];
16
+ fallback?: boolean;
17
+ }
18
+
19
+ export interface AnthropicAccountPoolConfig {
20
+ enabled?: boolean;
21
+ /** Preserve native Claude Messages while the pool is enabled. Default true; false selects legacy translation. */
22
+ nativeMessages?: boolean;
23
+ /** Usage % threshold for new-session auto-pick. Default 80. 0 = disabled (affinity/active only). */
24
+ autoSwitchThreshold?: number;
25
+ /** New-session rotation strategy. Default quota (today's behaviour). */
26
+ strategy?: OcxAccountPoolRotationStrategy;
27
+ /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
28
+ stickyLimit?: number;
29
+ /** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */
30
+ quotaWindow?: OcxAccountPoolQuotaWindow;
31
+ /** Ordered model allowlists; inactive while the pool is disabled. Stored account IDs only. */
32
+ routes?: AnthropicModelRoute[];
33
+ }
@@ -1,12 +1,8 @@
1
1
  import type { OcxProviderConfig } from "./provider";
2
2
  import type { CodexAccount } from "./accounts";
3
-
4
- export interface AnthropicModelRoute {
5
- name: string;
6
- match: string;
7
- accounts: string[];
8
- fallback?: boolean;
9
- }
3
+ import type { AnthropicAccountPoolConfig, OcxAccountPoolRotationStrategy } from "./anthropic-account-pool";
4
+ import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
5
+ export type { AnthropicAccountPoolConfig, AnthropicModelRoute, OcxAccountPoolRotationStrategy, OcxAccountPoolQuotaWindow } from "./anthropic-account-pool";
10
6
 
11
7
  /** Public inference API exposure. Responses and Chat Completions are always served. */
12
8
  export interface OcxApiSurfacesConfig {
@@ -222,9 +218,9 @@ export interface OcxClaudeCodeConfig {
222
218
  /** Optional roster-style model forced on subagents at the next routed Claude Code launch. */
223
219
  subagentModelForce?: string;
224
220
  /** Claude-originated web-search override. Unset fields inherit the global sidecar settings. */
225
- webSearchSidecar?: { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa"; model?: string };
221
+ webSearchSidecar?: { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa"; model?: string; anthropicInstance?: AnthropicInstanceId };
226
222
  /** Claude-originated vision override. Unset fields inherit the global sidecar settings. */
227
- visionSidecar?: { backend?: "openai" | "anthropic" | "routed"; model?: string };
223
+ visionSidecar?: { backend?: "openai" | "anthropic" | "routed"; model?: string; anthropicInstance?: AnthropicInstanceId };
228
224
  /** Persisted Claude Desktop four-family routing profile. */
229
225
  desktopProfile?: OcxClaudeDesktopProfile;
230
226
  /**
@@ -1052,6 +1048,8 @@ export interface OcxConfig {
1052
1048
  * upgrade against a number nobody chose.
1053
1049
  */
1054
1050
  spend?: OcxSpendConfig;
1051
+ /** Historical salted pool aliases; h(pool,P) cannot map to Q != P for any configured provider P. */
1052
+ spendPoolAliases?: Record<string, string>;
1055
1053
  /** Opt-in per-account activation of newly reset Codex quota windows. */
1056
1054
  codexQuotaAutoRefresh?: Record<string, {
1057
1055
  fiveHour?: boolean;
@@ -1216,21 +1214,7 @@ export interface OcxConfig {
1216
1214
  * upstream has just rate-limited only ever runs after a refusal, so stranding it while a
1217
1215
  * second logged-in account sits idle is a defect rather than a configuration choice.
1218
1216
  */
1219
- anthropicAccountPool?: {
1220
- enabled?: boolean;
1221
- /** Preserve native Claude Messages while the pool is enabled. Default true; false selects legacy translation. */
1222
- nativeMessages?: boolean;
1223
- /** Usage % threshold for new-session auto-pick. Default 80. 0 = disabled (affinity/active only). */
1224
- autoSwitchThreshold?: number;
1225
- /** New-session rotation strategy. Default quota (today's behaviour). */
1226
- strategy?: OcxAccountPoolRotationStrategy;
1227
- /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
1228
- stickyLimit?: number;
1229
- /** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */
1230
- quotaWindow?: OcxAccountPoolQuotaWindow;
1231
- /** Ordered model allowlists; inactive while the pool is disabled. Stored account IDs only. */
1232
- routes?: AnthropicModelRoute[];
1233
- };
1217
+ anthropicAccountPool?: AnthropicAccountPoolConfig;
1234
1218
  /**
1235
1219
  * Generic OAuth multi-account PROACTIVE account preference (#2568, #695).
1236
1220
  *
@@ -1262,10 +1246,6 @@ export interface OcxConfig {
1262
1246
  corsAllowOrigins?: string[];
1263
1247
  }
1264
1248
 
1265
- export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-first";
1266
-
1267
- export type OcxAccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization";
1268
-
1269
1249
  export type OcxComboStrategy = "failover" | "round-robin" | "random" | "least-used" | "reset-window" | "jev";
1270
1250
  export type OcxComboDefaultEffort = "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
1271
1251
  export type OcxComboDefaultEffortMode = "fallback" | "force";
@@ -1526,6 +1506,8 @@ export interface OcxSearchConfig {
1526
1506
  }
1527
1507
 
1528
1508
  export interface OcxVisionSidecarConfig {
1509
+ /** Explicit Anthropic account pool; unset inherits the request's instance. */
1510
+ anthropicInstance?: AnthropicInstanceId;
1529
1511
  /** Master switch. Default: enabled when the selected backend has a usable credential. */
1530
1512
  enabled?: boolean;
1531
1513
  /**
@@ -1545,6 +1527,8 @@ export interface OcxVisionSidecarConfig {
1545
1527
  }
1546
1528
 
1547
1529
  export interface OcxWebSearchSidecarConfig {
1530
+ /** Explicit Anthropic account pool; unset inherits the request's instance. */
1531
+ anthropicInstance?: AnthropicInstanceId;
1548
1532
  /** Master switch. Default: enabled when a forward (ChatGPT) provider exists and the caller is logged in. */
1549
1533
  enabled?: boolean;
1550
1534
  /**
@@ -1,4 +1,5 @@
1
1
  import type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode } from "./wire";
2
+ import type { AnthropicAccountPoolConfig } from "./anthropic-account-pool";
2
3
 
3
4
  /**
4
5
  * Per-provider proactive-refresh policy. The guardian only ever touches a provider whose EFFECTIVE
@@ -274,6 +275,10 @@ export interface ModelCapabilities {
274
275
  }
275
276
 
276
277
  export interface OcxProviderConfig {
278
+ /** Explicit builtin Pool 2 provenance; endpoints never imply ownership. */
279
+ anthropicOAuthInstance?: "anthropic2";
280
+ /** Independent Anthropic pool settings; valid only on providers.anthropic2. */
281
+ anthropicAccountPool?: AnthropicAccountPoolConfig;
277
282
  /** Optional browser-compatible outbound TLS profile; disabled by default. */
278
283
  tlsProfile?: "antigravity-browser";
279
284
  /** Optional short provider namespace used only at request/catalog presentation time. */
@@ -399,6 +399,13 @@ export function isAllowedToolChoice(value: OcxToolChoice | undefined): value is
399
399
  return typeof value === "object" && value !== null && "allowedTools" in value;
400
400
  }
401
401
 
402
+ export function requiresToolCall(choice: OcxToolChoice | undefined): boolean {
403
+ return choice === "required"
404
+ || (typeof choice === "object" && choice !== null && (
405
+ "name" in choice || ("mode" in choice && choice.mode === "required")
406
+ ));
407
+ }
408
+
402
409
  /** Compile the request's tool-choice policy into a reusable advertisement/restoration predicate. */
403
410
  export function toolChoiceToolPredicate(
404
411
  choice: OcxToolChoice | undefined,
package/src/types.ts CHANGED
@@ -16,6 +16,7 @@ export {
16
16
  resolveToolChoiceWireName,
17
17
  modelInList,
18
18
  isAllowedToolChoice,
19
+ requiresToolCall,
19
20
  toolChoiceToolPredicate,
20
21
  declaresCodeModeExec,
21
22
  toolRestrictsCallers,
@@ -23,6 +24,9 @@ export {
23
24
  } from "./types/tools";
24
25
 
25
26
  export type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode } from "./types/wire";
27
+ export type { AnthropicInstanceId } from "./providers/anthropic-instance-id";
28
+ export { ANTHROPIC_INSTANCE_IDS, ANTHROPIC_PRIMARY_INSTANCE, ANTHROPIC_POOL2_INSTANCE, isAnthropicInstanceId } from "./providers/anthropic-instance-id";
29
+ export type { AnthropicAccountPoolConfig, AnthropicModelRoute } from "./types/anthropic-account-pool";
26
30
  export {
27
31
  UPSTREAM_HTTP_VERSION_VALUES,
28
32
  REASONING_SUMMARY_DELIVERY_VALUES,
package/src/usage/cost.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  cursorFastPriceSupported,
31
31
  findContextTier,
32
32
  isLongContext,
33
+ pricingFamilyProvider,
33
34
  type Cost4,
34
35
  type ExpectedPriceOverlay,
35
36
  type ExpectedPriceStatus,
@@ -262,7 +263,7 @@ function resolveMatchedPriceExact(
262
263
  if (userOverlay) return userOverlay;
263
264
  if (!cursorFastPriceSupported(provider, modelId)) return null;
264
265
  const verifiedOverride = overlays === EXPECTED_PRICE_OVERLAYS
265
- ? findVerifiedPriceOverride(provider, modelId)
266
+ ? findVerifiedPriceOverride(pricingFamilyProvider(provider), modelId)
266
267
  : undefined;
267
268
  if (verifiedOverride && verifiedOverride.status !== "unverified" && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
268
269
  return {
@@ -291,7 +292,7 @@ function resolveMatchedPriceExact(
291
292
  ...cursorFastPriceProvenance(provider, modelId),
292
293
  };
293
294
  }
294
- const overlay = findExpectedPriceOverlay(provider, modelId, overlays);
295
+ const overlay = findExpectedPriceOverlay(overlays === EXPECTED_PRICE_OVERLAYS ? pricingFamilyProvider(provider) : provider, modelId, overlays);
295
296
  if (!overlay || !validCost4(overlay.cost4) || !hasNonZeroCost(overlay.cost4)) {
296
297
  if (options.allowModelLevelFallback === false) return null;
297
298
  const fallback = resolveModelLevelPrice(provider, modelId);
@@ -1,4 +1,15 @@
1
+ import { CURSOR_CAPABILITIES } from "../adapters/cursor/catalog";
1
2
  import { normalizeCursorClaudeId } from "../adapters/cursor/claude-id";
3
+ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
4
+
5
+ /**
6
+ * Provider key the compiled price catalogs are written under. Both Anthropic OAuth instances run
7
+ * the same models at the same published prices, so Pool 2 reads Pool 1's rows. Callers apply this
8
+ * only to the built-in catalogs; operator-supplied overlays and tiers stay keyed exactly.
9
+ */
10
+ export function pricingFamilyProvider(provider: string): string {
11
+ return isAnthropicInstanceId(provider) ? "anthropic" : provider;
12
+ }
2
13
 
3
14
  /**
4
15
  * Expected-price overlay for models whose jawcode cost rows are missing or all-zero
@@ -123,13 +134,23 @@ const CURSOR_OPUS_48 = CLAUDE_OPUS_46;
123
134
  // 0.1x most families use. 1M context and 128K output at one flat rate (no long-context tier).
124
135
  const CLAUDE_OPUS_55: Cost4 = { input: 4, output: 20, cacheRead: 0.2, cacheWrite: 5 };
125
136
  // Claude Sonnet 5.5 (claude-sonnet-5-5, released 2026-09-28): the Sonnet 5 tuple, 2 / 10, 5m cache
126
- // write 2.50, cache hit at the standard 0.1x (0.20). No fast mode and no long-context tier.
127
- const CLAUDE_SONNET_55: Cost4 = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
137
+ // write 2.50; cache hits cut to 0.05x (0.10) on 2026-10-08. No fast mode or long-context tier.
138
+ const CLAUDE_SONNET_55: Cost4 = { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 2.5 };
139
+ // Haiku 5.5 (2026-10-07): <=100K prompt base tuple; every rate is 5x above 100K.
140
+ const CLAUDE_HAIKU_55: Cost4 = { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 };
128
141
  const ANTHROPIC_PRICING = "https://platform.claude.com/docs/en/about-claude/pricing (official; 5m cache-write tier)";
129
142
  const CLAUDE_OPUS_5_SOURCE = `anthropic official Claude Opus 5 ${ANTHROPIC_PRICING}`;
130
143
  const CLAUDE_OPUS_55_SOURCE = `anthropic official Claude Opus 5.5 ${ANTHROPIC_PRICING}; cache hit = 0.05x base input`;
131
- const CLAUDE_SONNET_55_SOURCE = `anthropic official Claude Sonnet 5.5 ${ANTHROPIC_PRICING}`;
132
- const CURSOR_SONNET_55_PRICING = "https://cursor.com/docs/models/claude-sonnet-5-5 (same list rate as Anthropic)";
144
+ const CLAUDE_SONNET_55_SOURCE = `anthropic official Claude Sonnet 5.5 ${ANTHROPIC_PRICING}; cache hit = 0.05x base input (2026-10-08 cut)`;
145
+ const CLAUDE_HAIKU_55_SOURCE = `anthropic official Claude Haiku 5.5 ${ANTHROPIC_PRICING}; >100K prompt prices every token category at 5x`;
146
+ const CURSOR_HAIKU_55_PRICING = "https://cursor.com/docs/models/claude-haiku-5-5 (same base and >100K rates as Anthropic)";
147
+ const DEVIN_HAIKU_55_SOURCE = `derived (preemptive): Devin live catalog and modelCostData do not list claude-haiku-5-5 yet; Anthropic reference price ${ANTHROPIC_PRICING}`;
148
+ const VENICE_55_SOURCE = "derived: Venice listing on https://models.dev/api.json (2026-10-08), 1.25x Anthropic list price";
149
+ // Exact lookup must cover regular effort ids as well as every spelling Cursor can emit.
150
+ const CURSOR_HAIKU_55_IDS = ["claude-haiku-5-5", "claude-haiku-5.5", "claude-5.5-haiku"].flatMap(base =>
151
+ [base, ...CURSOR_CAPABILITIES["claude-haiku-5-5"]!.variants.regular!.levels.map(level => `${base}-${level}`)],
152
+ );
153
+ const CURSOR_SONNET_55_PRICING = `https://cursor.com/docs/models/claude-sonnet-5-5 (same list rate as Anthropic; cache hit 0.05x) ${ANTHROPIC_PRICING}`;
133
154
  const DEVIN_SONNET_55_SOURCE = `derived (preemptive): Devin's live catalog and modelCostData table do not list claude-sonnet-5-5 yet; Anthropic list price shown as estimate ${ANTHROPIC_PRICING}`;
134
155
  const CURSOR_OPUS_55_PRICING = "https://cursor.com/docs/models/claude-opus-5-5 (Cursor Other Models pool; same list rate as Anthropic, Fast Mode billed separately)";
135
156
  const CURSOR_OPUS_48_FAST_PRICING = "https://cursor.com/docs/models/claude-opus-4-8";
@@ -291,11 +312,26 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
291
312
  { provider: "anthropic-apikey", modelId: "claude-opus-5-5", cost4: CLAUDE_OPUS_55, source: CLAUDE_OPUS_55_SOURCE, verifiedAt: "2026-09-23", status: "verified" },
292
313
  // Cursor canonicalizes every Opus 5.5 spelling (thinking/effort/fast suffixes) onto this row.
293
314
  { provider: "cursor", modelId: "claude-opus-5-5", cost4: CLAUDE_OPUS_55, source: CURSOR_OPUS_55_PRICING, verifiedAt: "2026-09-23", status: "verified" },
315
+ // Haiku 5.5: official Anthropic and Cursor rates; reseller/preemptive rows remain estimates.
316
+ ...["anthropic", "anthropic-apikey"].map((provider): ExpectedPriceOverlay => ({
317
+ provider, modelId: "claude-haiku-5-5", cost4: CLAUDE_HAIKU_55,
318
+ source: CLAUDE_HAIKU_55_SOURCE, verifiedAt: "2026-10-08", status: "verified",
319
+ })),
320
+ ...CURSOR_HAIKU_55_IDS.map((modelId): ExpectedPriceOverlay => ({
321
+ provider: "cursor", modelId, cost4: CLAUDE_HAIKU_55,
322
+ source: CURSOR_HAIKU_55_PRICING, verifiedAt: "2026-10-08", status: "verified",
323
+ })),
324
+ ...["devin", "devin-cli"].map((provider): ExpectedPriceOverlay => ({
325
+ provider, modelId: "claude-haiku-5-5", cost4: CLAUDE_HAIKU_55,
326
+ source: DEVIN_HAIKU_55_SOURCE, verifiedAt: "2026-10-08", status: "verified-derived",
327
+ })),
328
+ { provider: "venice", modelId: "claude-haiku-5-5", cost4: { input: 0.125, output: 0.625, cacheRead: 0.0125, cacheWrite: 0.15625 }, source: VENICE_55_SOURCE, verifiedAt: "2026-10-08", status: "verified-derived" },
329
+ { provider: "venice", modelId: "claude-sonnet-5-5", cost4: { input: 2.5, output: 12.5, cacheRead: 0.125, cacheWrite: 3.125 }, source: VENICE_55_SOURCE, verifiedAt: "2026-10-08", status: "verified-derived" },
294
330
  // Claude Sonnet 5.5. Same layering as Opus 5.5: the anthropic bundle row wins for the bare id,
295
331
  // these cover account-label namespaces, and Cursor publishes the list rate on its model page.
296
- { provider: "anthropic", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CLAUDE_SONNET_55_SOURCE, verifiedAt: "2026-09-29", status: "verified" },
297
- { provider: "anthropic-apikey", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CLAUDE_SONNET_55_SOURCE, verifiedAt: "2026-09-29", status: "verified" },
298
- { provider: "cursor", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CURSOR_SONNET_55_PRICING, verifiedAt: "2026-09-29", status: "verified" },
332
+ { provider: "anthropic", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CLAUDE_SONNET_55_SOURCE, verifiedAt: "2026-10-08", status: "verified" },
333
+ { provider: "anthropic-apikey", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CLAUDE_SONNET_55_SOURCE, verifiedAt: "2026-10-08", status: "verified" },
334
+ { provider: "cursor", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: CURSOR_SONNET_55_PRICING, verifiedAt: "2026-10-08", status: "verified" },
299
335
  // MiniMax M2.1 highspeed — published PAYG price (verified).
300
336
  { provider: "minimax", modelId: "MiniMax-M2.1-highspeed", cost4: MINIMAX_M21_HIGHSPEED, source: MINIMAX_PRICING, verifiedAt: "2026-07-20", status: "verified" },
301
337
  { provider: "minimax-cn", modelId: "MiniMax-M2.1-highspeed", cost4: MINIMAX_M21_HIGHSPEED, source: MINIMAX_PRICING, verifiedAt: "2026-07-20", status: "verified" },
@@ -371,10 +407,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
371
407
  // Alias without the Antigravity "-thinking" suffix (if logs/UI ever surface it).
372
408
  { provider: "google-antigravity", modelId: "claude-opus-4-6", cost4: CLAUDE_OPUS_46, source: `anthropic official Claude Opus 4.6 ${ANTHROPIC_PRICING}`, verifiedAt: "2026-07-23", status: "verified" },
373
409
  // Antigravity uses subscription quota; these are derived Anthropic reference estimates.
374
- { provider: "google-antigravity", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
375
- { provider: "google-antigravity", modelId: "claude-sonnet-5-5-low", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
376
- { provider: "google-antigravity", modelId: "claude-sonnet-5-5-medium", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
377
- { provider: "google-antigravity", modelId: "claude-sonnet-5-5-high", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
410
+ { provider: "google-antigravity", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price (cache hit 0.05x); ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-08", status: "verified-derived" },
411
+ { provider: "google-antigravity", modelId: "claude-sonnet-5-5-low", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price (cache hit 0.05x); ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-08", status: "verified-derived" },
412
+ { provider: "google-antigravity", modelId: "claude-sonnet-5-5-medium", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price (cache hit 0.05x); ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-08", status: "verified-derived" },
413
+ { provider: "google-antigravity", modelId: "claude-sonnet-5-5-high", cost4: CLAUDE_SONNET_55, source: `derived: Anthropic Claude Sonnet 5.5 reference price (cache hit 0.05x); ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-08", status: "verified-derived" },
378
414
  { provider: "google-antigravity", modelId: "claude-opus-5-5", cost4: CLAUDE_OPUS_55, source: `derived: Anthropic Claude Opus 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
379
415
  { provider: "google-antigravity", modelId: "claude-opus-5-5-low", cost4: CLAUDE_OPUS_55, source: `derived: Anthropic Claude Opus 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
380
416
  { provider: "google-antigravity", modelId: "claude-opus-5-5-medium", cost4: CLAUDE_OPUS_55, source: `derived: Anthropic Claude Opus 5.5 reference price; ${ANTHROPIC_PRICING}`, verifiedAt: "2026-10-03", status: "verified-derived" },
@@ -482,7 +518,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
482
518
  { provider: "devin-cli", modelId: "claude-opus-5", cost4: CLAUDE_OPUS_46, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
483
519
  { provider: "devin-cli", modelId: "claude-opus-5-5", cost4: CLAUDE_OPUS_55, source: `derived: live Devin catalog lists claude-opus-5-5 but Devin's modelCostData table does not yet; Anthropic list price shown as estimate ${ANTHROPIC_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
484
520
  { provider: "devin-cli", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
485
- { provider: "devin-cli", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: DEVIN_SONNET_55_SOURCE, verifiedAt: "2026-09-29", status: "verified-derived" },
521
+ { provider: "devin-cli", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: DEVIN_SONNET_55_SOURCE, verifiedAt: "2026-10-08", status: "verified-derived" },
486
522
  { provider: "devin-cli", modelId: "claude-sonnet-5", cost4: DEVIN_SONNET_5, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
487
523
  { provider: "devin-cli", modelId: "glm-5-3", cost4: GLM_53, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
488
524
  { provider: "devin-cli", modelId: "kimi-k3", cost4: DEVIN_KIMI_K3, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
@@ -501,7 +537,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
501
537
  { provider: "devin", modelId: "claude-opus-4-8", cost4: CLAUDE_OPUS_46, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
502
538
  { provider: "devin", modelId: "claude-opus-5-5", cost4: CLAUDE_OPUS_55, source: `derived: live Devin catalog lists claude-opus-5-5 but Devin's modelCostData table does not yet; Anthropic list price shown as estimate ${ANTHROPIC_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
503
539
  { provider: "devin", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
504
- { provider: "devin", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: DEVIN_SONNET_55_SOURCE, verifiedAt: "2026-09-29", status: "verified-derived" },
540
+ { provider: "devin", modelId: "claude-sonnet-5-5", cost4: CLAUDE_SONNET_55, source: DEVIN_SONNET_55_SOURCE, verifiedAt: "2026-10-08", status: "verified-derived" },
505
541
  { provider: "devin", modelId: "claude-sonnet-5", cost4: DEVIN_SONNET_5, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
506
542
  { provider: "devin", modelId: "glm-5-2", cost4: GLM_52, source: `enterprise list column (self-serve shows an unannounced 0 promo); ${DEVIN_PRICING}`, verifiedAt: "2026-09-13", status: "verified-derived" },
507
543
  { provider: "devin", modelId: "kimi-k2-7", cost4: DEVIN_KIMI_K27, source: DEVIN_PRICING, verifiedAt: "2026-09-13", status: "verified-derived" },
@@ -705,6 +741,8 @@ export interface ContextTier {
705
741
  const OPENAI_LONG_CONTEXT: Cost4 = { input: 2, output: 1.5, cacheRead: 2, cacheWrite: 2 };
706
742
  /** xAI and MiniMax double every rate uniformly past their thresholds. */
707
743
  const UNIFORM_DOUBLE: Cost4 = { input: 2, output: 2, cacheRead: 2, cacheWrite: 2 };
744
+ // Haiku 5.5 reprices the whole request above 100K (official pricing, read 2026-10-08).
745
+ const HAIKU_55_LONG_CONTEXT: Cost4 = { input: 5, output: 5, cacheRead: 5, cacheWrite: 5 };
708
746
 
709
747
  const OPENAI_PRICING_DOC = "https://developers.openai.com/api/docs/pricing";
710
748
  const OPENAI_CONTEXT_MODELS = [
@@ -723,6 +761,19 @@ const OPENAI_CONTEXT_MODELS = [
723
761
  ];
724
762
 
725
763
  export const CONTEXT_TIERS: readonly ContextTier[] = [
764
+ // Reference bands on preemptive/subscription surfaces follow their reference price, not billing.
765
+ ...[
766
+ ...["anthropic", "anthropic-apikey", "opencode-go", "opencode-zen", "venice", "github-copilot", "devin", "devin-cli", "claude-cli"].map(provider => [provider, "claude-haiku-5-5"]),
767
+ ...["", "global.", "us.", "eu.", "jp.", "au."].map(prefix => ["amazon-bedrock", `${prefix}anthropic.claude-haiku-5-5`]),
768
+ ...["openrouter", "vercel-ai-gateway", "zenmux"].map(provider => [provider, "anthropic/claude-haiku-5.5"]),
769
+ ["cloudflare-ai-gateway", "anthropic/claude-haiku-5-5"], ["kiro", "claude-haiku-5.5"],
770
+ ...CURSOR_HAIKU_55_IDS.map(modelId => ["cursor", modelId]),
771
+ ].map(([provider, modelId]): ContextTier => ({
772
+ provider, modelId, thresholdInputTokens: 100_000, inclusive: false,
773
+ multiplier: HAIKU_55_LONG_CONTEXT,
774
+ source: provider === "cursor" ? CURSOR_HAIKU_55_PRICING : ANTHROPIC_PRICING,
775
+ verifiedAt: "2026-10-08",
776
+ })),
726
777
  // API-reference estimates do not apply subscription-only exemptions or multipliers.
727
778
  ...["openai", "openai-apikey"].flatMap(provider =>
728
779
  OPENAI_CONTEXT_MODELS.map((modelId): ContextTier => ({
@@ -811,7 +862,8 @@ export function findContextTier(
811
862
  modelId: string,
812
863
  tiers: readonly ContextTier[] = CONTEXT_TIERS,
813
864
  ): ContextTier | undefined {
814
- return tiers.find(tier => tier.provider === provider && tier.modelId === modelId);
865
+ const familyProvider = tiers === CONTEXT_TIERS ? pricingFamilyProvider(provider) : provider;
866
+ return tiers.find(tier => tier.provider === familyProvider && tier.modelId === modelId);
815
867
  }
816
868
 
817
869
  /** Whether a raw input-token count crosses the tier's published boundary. */
@@ -1,3 +1,6 @@
1
+ import { resolveAnthropicHelperSnapshot, fetchAnthropicHelper, anthropicHelperMessagesUrl } from "../sidecar/anthropic-binding";
2
+ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
3
+ import type { OAuthAccessSnapshot } from "../oauth";
1
4
  import type { OcxConfig, OcxProviderConfig } from "../types";
2
5
  import { sidecarThinkingOff } from "../adapters/anthropic-model-contract";
3
6
  import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fingerprint";
@@ -159,8 +162,15 @@ export async function describeImageAnthropic(
159
162
  if (!image.block) return { text: "", error: image.error ?? "invalid image" };
160
163
 
161
164
  let token: string;
165
+ let snapshot: OAuthAccessSnapshot | undefined;
166
+ const capturedTarget = provider.baseUrl;
162
167
  try {
163
- token = await getAnthropicSidecarAccessToken(providerName, settings.model, config);
168
+ if (config && isAnthropicInstanceId(providerName)) {
169
+ snapshot = await resolveAnthropicHelperSnapshot(config, providerName, settings.model);
170
+ token = snapshot.accessToken;
171
+ } else {
172
+ token = await getAnthropicSidecarAccessToken(providerName, settings.model, config);
173
+ }
164
174
  } catch (error) {
165
175
  return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` };
166
176
  }
@@ -195,13 +205,16 @@ export async function describeImageAnthropic(
195
205
 
196
206
  // Anthropic image blocks have no detail field, but detail remains part of the cache identity.
197
207
  void detail;
198
- const base = provider.baseUrl.replace(/\/v1\/?$/, "");
208
+ const url = anthropicHelperMessagesUrl(provider.baseUrl);
209
+ const dispatch = (target: string, init: RequestInit) => snapshot && config
210
+ ? fetchAnthropicHelper(config, snapshot, settings.model, capturedTarget, target, init)
211
+ : fetch(target, init);
199
212
  const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
200
213
  const sidecarExit = sidecarEnter("vision");
201
214
  const startedAt = Date.now();
202
215
  try {
203
216
  const res = await fetchWithResetRetry(
204
- recovery => fetch(`${base}/v1/messages`, applyUpstreamRecoveryInit({
217
+ recovery => dispatch(url, applyUpstreamRecoveryInit({
205
218
  method: "POST",
206
219
  redirect: "manual",
207
220
  headers,
@@ -217,6 +217,9 @@ function descriptionIdentity(job: ImageJob, plan: VisionPlan): { key: string; pe
217
217
  key: JSON.stringify([
218
218
  plan.backend,
219
219
  plan.settings.model,
220
+ // Explicit identity: the resolved pool and reasoning. Settings that only gate or bound the call
221
+ // (enabled, timeoutMs, maxDescriptionsPerTurn) never split the cache.
222
+ ...(plan.backend === "anthropic" ? [plan.anthropicSidecar?.providerName ?? null, plan.settings.reasoning] : []),
220
223
  ...(plan.backend === "openai" ? [plan.settings.reasoning] : []),
221
224
  job.detail ?? "high",
222
225
  imageHash,
@@ -11,7 +11,7 @@ import {
11
11
  modelAcceptsImageInput,
12
12
  } from "./eligibility";
13
13
  import { normalizeVisionReasoningForModel } from "./reasoning";
14
- import { resolveSidecarAuth } from "../sidecar/auth";
14
+ import { inheritedAnthropicInstance, resolveSidecarAuth, resolveAnthropicHelperInstance, resolveAnthropicSidecarAuth, withAnthropicHelperRefusal } from "../sidecar/auth";
15
15
  import { DEFAULT_VISION_TIMEOUT_MS, MAX_VISION_TIMEOUT_MS, MIN_VISION_TIMEOUT_MS } from "./timeout-bounds";
16
16
  import { carriesImages } from "./image-rewrite";
17
17
 
@@ -50,8 +50,9 @@ export interface AnthropicVisionProvider {
50
50
  * First enabled Anthropic OAuth provider whose active stored account is not marked for reauth.
51
51
  * Delegates to the shared sidecar auth module (#2188) — same predicate as web-search.
52
52
  */
53
- export function findAnthropicVisionProvider(config: OcxConfig): AnthropicVisionProvider | undefined {
54
- const auth = resolveSidecarAuth(config);
53
+ export function findAnthropicVisionProvider(config: OcxConfig, parentProviderName?: string): AnthropicVisionProvider | undefined {
54
+ const instance = resolveAnthropicHelperInstance(config, { backendFamily: "anthropic", anthropicInstance: config.visionSidecar?.anthropicInstance, parentProviderName });
55
+ const auth = resolveSidecarAuth(config, instance);
55
56
  if (!auth.isAnthropicAuth || !auth.anthropicProviderName || !auth.anthropicProvider) return undefined;
56
57
  return { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider, config };
57
58
  }
@@ -68,6 +69,16 @@ export function resolveVisionBackend(
68
69
  return anthropicSidecar ? "anthropic" : "openai";
69
70
  }
70
71
 
72
+ /** Existing family preference, with a builtin parent contributing its own auth availability. */
73
+ function preferredVisionBackend(config: OcxConfig, parentProviderName?: string): "openai" | "anthropic" {
74
+ const cfg = config.visionSidecar ?? {};
75
+ const requested = cfg.anthropicInstance ?? inheritedAnthropicInstance(config, parentProviderName);
76
+ const exact = requested ? resolveAnthropicSidecarAuth(config, requested) : undefined;
77
+ const legacy = findAnthropicVisionProvider({ ...config, visionSidecar: { ...cfg, anthropicInstance: undefined } });
78
+ return resolveVisionBackend(cfg.backend, exact
79
+ ? { providerName: exact.anthropicProviderName, provider: exact.anthropicProvider, config } : legacy);
80
+ }
81
+
71
82
  /** Native model used by the OpenAI vision helper, including its bounded default. */
72
83
  export function resolveOpenAiVisionModel(config: Pick<OcxConfig, "visionSidecar">): string {
73
84
  const configured = config.visionSidecar?.model;
@@ -170,7 +181,7 @@ export function shouldResolveOpenAiVisionSidecar(
170
181
  const cfg = config.visionSidecar ?? {};
171
182
  if (cfg.enabled === false) return false;
172
183
  if (usableRoutedVisionModel(config)) return false;
173
- return resolveVisionBackend(cfg.backend, findAnthropicVisionProvider(config)) === "openai";
184
+ return preferredVisionBackend(config, providerName) === "openai";
174
185
  }
175
186
 
176
187
  export interface VisionPlan {
@@ -241,8 +252,10 @@ export function planVisionSidecar(
241
252
  }
242
253
  // A non-dispatchable routed configuration keeps the legacy backend fallback below.
243
254
 
244
- const anthropicSidecar = findAnthropicVisionProvider(config);
245
- const backend = resolveVisionBackend(cfg.backend, anthropicSidecar);
255
+ const backend = preferredVisionBackend(config, options.providerName);
256
+ // An unavailable selected pool degrades to "no plan" (images stripped); it never fails the request.
257
+ const anthropicSidecar = backend === "anthropic"
258
+ ? withAnthropicHelperRefusal("vision", () => findAnthropicVisionProvider(config, options.providerName)) : undefined;
246
259
  // A namespaced routed model must never reach the forward/OAuth executors
247
260
  // (they POST the string verbatim); the effective-model resolver falls back
248
261
  // to each side's default in that case.
@@ -1,3 +1,4 @@
1
+ import { AnthropicHelperUnavailableError } from "../sidecar/auth";
1
2
  /**
2
3
  * Serve Codex's built-in `/v1/alpha/search` when no ChatGPT forward provider exists.
3
4
  *
@@ -101,7 +102,12 @@ export function resolveAlphaSearchSidecar(config: OcxConfig): AlphaSearchSidecar
101
102
  if (backend === "openai") return { status: "unconfigured" };
102
103
  switch (backend) {
103
104
  case "anthropic": {
104
- const found = findAnthropicSidecarProvider(config);
105
+ let found;
106
+ try { found = findAnthropicSidecarProvider(config); }
107
+ catch (error) {
108
+ if (error instanceof AnthropicHelperUnavailableError) return { status: "missing-credential", backend };
109
+ throw error;
110
+ }
105
111
  return found
106
112
  ? { status: "ready", sidecar: { backend, providerName: found.providerName, provider: found.provider, config } }
107
113
  : { status: "missing-credential", backend };
@@ -1,3 +1,6 @@
1
+ import { resolveAnthropicHelperSnapshot, fetchAnthropicHelper, anthropicHelperMessagesUrl } from "../sidecar/anthropic-binding";
2
+ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
3
+ import type { OAuthAccessSnapshot } from "../oauth";
1
4
  import type { OcxConfig, OcxProviderConfig } from "../types";
2
5
  import { publicOAuthAuthenticationErrorMessage } from "../oauth";
3
6
  import { getAnthropicSidecarAccessToken } from "../oauth/anthropic-routing";
@@ -167,11 +170,17 @@ export async function runAnthropicWebSearch(
167
170
  abortSignal?: AbortSignal,
168
171
  config?: OcxConfig,
169
172
  ): Promise<SidecarOutcome> {
170
- const base = provider.baseUrl.replace(/\/v1\/?$/, "");
171
- const url = `${base}/v1/messages`;
173
+ const url = anthropicHelperMessagesUrl(provider.baseUrl);
172
174
  let token: string;
175
+ let snapshot: OAuthAccessSnapshot | undefined;
176
+ const capturedTarget = provider.baseUrl;
173
177
  try {
174
- token = await getAnthropicSidecarAccessToken(providerName, settings.model, config);
178
+ if (config && isAnthropicInstanceId(providerName)) {
179
+ snapshot = await resolveAnthropicHelperSnapshot(config, providerName, settings.model);
180
+ token = snapshot.accessToken;
181
+ } else {
182
+ token = await getAnthropicSidecarAccessToken(providerName, settings.model, config);
183
+ }
175
184
  } catch (e) {
176
185
  return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
177
186
  }
@@ -205,6 +214,9 @@ export async function runAnthropicWebSearch(
205
214
  stream: true,
206
215
  };
207
216
 
217
+ const dispatch = (target: string, init: RequestInit) => snapshot && config
218
+ ? fetchAnthropicHelper(config, snapshot, settings.model, capturedTarget, target, init)
219
+ : fetch(target, init);
208
220
  const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
209
221
  const sidecarExit = sidecarEnter("web-search");
210
222
  const t0 = Date.now();
@@ -212,7 +224,7 @@ export async function runAnthropicWebSearch(
212
224
  const res = await fetchWithResetRetry(
213
225
  // The replay needs `keepalive: false` to leave the half-closed pooled socket; Bun has
214
226
  // ignored a bare `Connection: close` (oven-sh/bun#20492).
215
- recovery => fetch(url, applyUpstreamRecoveryInit({
227
+ recovery => dispatch(url, applyUpstreamRecoveryInit({
216
228
  method: "POST",
217
229
  redirect: "manual",
218
230
  headers,