@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
@@ -0,0 +1,67 @@
1
+ import { clearableDeadline } from "../lib/abort";
2
+ import { sendTrackingRequestSlot } from "../providers/request-pacing";
3
+ import type { ProviderFetch } from "../server/responses/fetch-helpers";
4
+
5
+ /** One iteration's cumulative header budget; only local pacing admission pauses its clock. */
6
+ export function pacingHeaderDeadline(timeoutMs: number, parent: AbortSignal) {
7
+ const controller = new AbortController();
8
+ const signal = AbortSignal.any([parent, controller.signal]);
9
+ let remainingMs = timeoutMs;
10
+ let armedAt = performance.now();
11
+ let current = clearableDeadline(remainingMs, parent);
12
+ const timeoutReason = current.timeoutReason;
13
+ let pacingWaits = 0;
14
+ let cleared = false;
15
+ const relayExpiry = () => {
16
+ if (current.didExpire()) controller.abort(timeoutReason);
17
+ };
18
+ current.signal.addEventListener("abort", relayExpiry, { once: true });
19
+
20
+ const pause = () => {
21
+ if (cleared || pacingWaits++ > 0) return;
22
+ remainingMs = Math.max(0, remainingMs - (performance.now() - armedAt));
23
+ current.clear();
24
+ current.signal.removeEventListener("abort", relayExpiry);
25
+ // A delayed timer task must not let an already-spent budget escape into the queue.
26
+ if (remainingMs === 0 && !signal.aborted) controller.abort(timeoutReason);
27
+ };
28
+ const resume = () => {
29
+ if (cleared || --pacingWaits > 0 || signal.aborted) return;
30
+ armedAt = performance.now();
31
+ current = clearableDeadline(remainingMs, parent);
32
+ current.signal.addEventListener("abort", relayExpiry, { once: true });
33
+ };
34
+
35
+ return {
36
+ signal,
37
+ didExpire: () => signal.aborted && signal.reason === timeoutReason,
38
+ clear: () => {
39
+ cleared = true;
40
+ current.clear();
41
+ current.signal.removeEventListener("abort", relayExpiry);
42
+ },
43
+ /** Preserve the pacing seams used by adapter physical-send helpers, without double admission. */
44
+ pacedFetch(executor: typeof globalThis.fetch): typeof globalThis.fetch {
45
+ const pacing = executor as ProviderFetch;
46
+ if (!pacing.waitForPacing) return executor;
47
+ const waitForPacing: NonNullable<ProviderFetch["waitForPacing"]> = async (sendSignal) => {
48
+ pause();
49
+ try {
50
+ return await pacing.waitForPacing!(sendSignal ? AbortSignal.any([signal, sendSignal]) : signal);
51
+ } finally {
52
+ resume();
53
+ }
54
+ };
55
+ const unpacedFetch = pacing.unpacedFetch ?? executor;
56
+ const wrapped = async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
57
+ const slot = await waitForPacing(init?.signal ?? undefined);
58
+ return sendTrackingRequestSlot(slot, () => {
59
+ signal.throwIfAborted();
60
+ init?.signal?.throwIfAborted();
61
+ return unpacedFetch(input, init);
62
+ });
63
+ };
64
+ return Object.assign(wrapped, { preconnect: executor.preconnect, waitForPacing, unpacedFetch }) as ProviderFetch;
65
+ },
66
+ };
67
+ }
@@ -6,7 +6,7 @@ import type { CodexAuthPolicyConfig } from "../codex/auth-context";
6
6
  import { isCodexReserveRequestEligible } from "../codex/loopback-target";
7
7
  import type { DataPlaneAdmission } from "../server/auth-cors";
8
8
  import type { ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
9
- import { resolveSidecarAuth } from "../sidecar/auth";
9
+ import { resolveSidecarAuth, resolveAnthropicHelperInstance, withAnthropicHelperRefusal } from "../sidecar/auth";
10
10
  import { validateXaiSearchOptions, type XaiSearchOptions } from "./xai-executor";
11
11
  import type { OcxWebSearchSidecarConfig } from "../types";
12
12
  import { DEFAULT_STALL_TIMEOUT_SEC } from "../stall-timeout";
@@ -168,11 +168,15 @@ export function planWebSearch(
168
168
  const connectTimeoutMs = config.connectTimeoutMs ?? 200_000;
169
169
  // Shared auth state (#2188): presence only — backend PREFERENCE stays with
170
170
  // resolveSidecarBackend's explicit-or-openai contract.
171
- const auth = resolveSidecarAuth(config);
171
+ const backend = resolveSidecarBackend(cfg.backend);
172
+ // Only the anthropic backend can be refused; an unavailable selected pool means no plan, so the
173
+ // main request takes the normal path instead of failing or borrowing another pool.
174
+ const auth = withAnthropicHelperRefusal("web-search", () => resolveSidecarAuth(config, resolveAnthropicHelperInstance(config,
175
+ { backendFamily: backend, anthropicInstance: cfg.anthropicInstance, parentProviderName: options.providerName })));
176
+ if (!auth) return undefined;
172
177
  const anthropicSidecar = auth.isAnthropicAuth && auth.anthropicProviderName && auth.anthropicProvider
173
178
  ? { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider, config }
174
179
  : undefined;
175
- const backend = resolveSidecarBackend(cfg.backend);
176
180
  const maxSearches = cfg.maxSearchesPerTurn ?? DEFAULT_MAX_SEARCHES;
177
181
  const stallTimeoutSec = webSearchStallTimeoutSec(
178
182
  config.stallTimeoutSec,
@@ -1,6 +1,7 @@
1
1
  import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "../adapters/base";
2
2
  import type { AdapterEvent, OcxConfig, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxProviderOpaqueToolCallMetadata, OcxThinkingContent, OcxUsage, RateLimitRetryPolicy } from "../types";
3
3
  import { namespacedToolName, toolChoiceToolPredicate } from "../types";
4
+ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
4
5
  import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
5
6
  import type { AttemptRecoveryKind } from "../usage/log";
6
7
  import { isTruncatedStopReason } from "../responses/truncated-stop-reason";
@@ -11,7 +12,7 @@ import { runXaiWebSearch, type XaiSearchOptions } from "./xai-executor";
11
12
  import { runGeminiWebSearch } from "./gemini-executor";
12
13
  import { runExaWebSearch } from "./exa-executor";
13
14
  import type { WebSearchBackendId } from "./index";
14
- import { clearableDeadline } from "../lib/abort";
15
+ import { pacingHeaderDeadline } from "./header-deadline";
15
16
  import { redactSecretString } from "../lib/redact";
16
17
  import { readBoundedResponseBody } from "../lib/bounded-body";
17
18
  import { applyUpstreamRecoveryInit, fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
@@ -353,6 +354,8 @@ export interface WebSearchLoopDeps {
353
354
  | { adapter: ProviderAdapter; recoveryKind: AttemptRecoveryKind }
354
355
  | null
355
356
  | Promise<{ adapter: ProviderAdapter; recoveryKind: AttemptRecoveryKind } | null>;
357
+ /** Release a credential-hop permit if this iteration ended before the adapter used it. */
358
+ onIterationEnd?: () => void;
356
359
  /** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
357
360
  retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
358
361
  /** Called only when the final bridged Responses stream reaches completed or incomplete. */
@@ -455,10 +458,10 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
455
458
  ...(recoveringEmptyAnswer ? { options: { ...parsed.options, toolChoice: "none" as const } } : {}),
456
459
  context: { ...parsed.context, messages: iterMessages, tools: recoveringEmptyAnswer ? [] : allTools },
457
460
  };
458
- // One cumulative header deadline spans every pool-key 429 rotation in this model iteration.
461
+ // One cumulative header deadline spans key rotations and reset sends, excluding local pacing.
459
462
  // clear() stops only its timer after final headers; the direct turn signal remains attached to
460
463
  // the returned response body through AbortSignal.any().
461
- let headerDeadline = clearableDeadline(connectTimeoutMs, signal);
464
+ let headerDeadline = pacingHeaderDeadline(connectTimeoutMs, signal);
462
465
  try {
463
466
  /**
464
467
  * Build and fetch one web-search iteration on the given adapter, under the iteration
@@ -491,7 +494,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
491
494
  cachedRequest = request;
492
495
  cachedAdapter = requestAdapter;
493
496
  }
494
- const requestFetch = deps.fetchForRequest?.(request, iterParsed) ?? routedProviderFetch;
497
+ const requestFetch = headerDeadline.pacedFetch(deps.fetchForRequest?.(request, iterParsed) ?? routedProviderFetch);
495
498
  let response: Response;
496
499
  try {
497
500
  if (requestAdapter.fetchResponse) {
@@ -501,6 +504,8 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
501
504
  timeoutMs: connectTimeoutMs,
502
505
  returnRawErrors: true,
503
506
  stream: true,
507
+ sendBudget: deps.incomingMeta.sendBudget,
508
+ onPhysicalSend: deps.incomingMeta.onPhysicalSend,
504
509
  executor: requestFetch,
505
510
  });
506
511
  } else {
@@ -563,7 +568,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
563
568
  // The deliberate backoff must not consume the cumulative response-header deadline:
564
569
  // start a fresh one so the replay gets a new connect budget (504 stays reserved for real
565
570
  // upstream latency).
566
- headerDeadline = clearableDeadline(connectTimeoutMs, signal);
571
+ headerDeadline = pacingHeaderDeadline(connectTimeoutMs, signal);
567
572
  // Stall-watchdog seam between bounded retry fetches.
568
573
  yield { type: "heartbeat" };
569
574
  prepared = await fetchOnce(adapter, "rate-limit-429");
@@ -571,18 +576,34 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
571
576
  // 429 key-failover parity with the normal routed path: rotate pool keys until one responds
572
577
  // or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
573
578
  while ((prepared.response.status === 429
574
- || (prepared.response.status === 403 && deps.incomingMeta?.providerName === "anthropic" && !accountRefusalOutputStarted)
579
+ || (prepared.response.status === 403
580
+ && (isAnthropicInstanceId(deps.incomingMeta?.providerName) || deps.incomingMeta?.providerName === "google-antigravity")
581
+ && !accountRefusalOutputStarted)
575
582
  || (iterParsed._kiroAuthContext && (prepared.response.status === 400 || prepared.response.status === 403))) && deps.on429) {
576
583
  const rotated = await deps.on429(prepared.response.headers.get("retry-after"), prepared.response.headers,
577
584
  iterParsed, prepared.response);
578
585
  if (!rotated) break;
579
- // Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
580
- // it, but proceed immediately to the rotated fetch under the SAME deadline signal.
581
- try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
586
+ const retainRefusal = prepared.response.status === 403
587
+ && deps.incomingMeta?.providerName === "google-antigravity" && rotated.recoveryKind === "oauth-account-403";
588
+ // Keep the bounded refusal readable until the sibling actually returns headers.
589
+ // Other rotation paths retain their existing cancellation boundary.
590
+ if (!retainRefusal) {
591
+ try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
592
+ }
582
593
  adapter = rotated.adapter;
583
594
  // Stall-watchdog seam between bounded retry fetches (audit 011 B3).
584
595
  yield { type: "heartbeat" };
585
- prepared = await fetchOnce(adapter, rotated.recoveryKind);
596
+ let replacement: IterationResponse;
597
+ try {
598
+ replacement = await fetchOnce(adapter, rotated.recoveryKind);
599
+ } catch (error) {
600
+ if (!retainRefusal || signal.aborted || headerDeadline.didExpire() || isTranslatorBudgetExceededError(error)) throw error;
601
+ break; // Format the original 403 safely; replacement diagnostics are not client errors.
602
+ }
603
+ if (retainRefusal) {
604
+ try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
605
+ }
606
+ prepared = replacement;
586
607
  }
587
608
 
588
609
  // Final headers have arrived. Clear only the deadline timer before ANY body read.
@@ -623,6 +644,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
623
644
  throw new LoopError(502, `Provider unreachable: ${error instanceof Error ? error.message : String(error)}`);
624
645
  } finally {
625
646
  headerDeadline.clear();
647
+ deps.onIterationEnd?.();
626
648
  }
627
649
  };
628
650
 
@@ -76,6 +76,7 @@ import {
76
76
  import { providerDestinationConfigError } from "../lib/destination-policy";
77
77
  import { redactSecretString } from "../lib/redact";
78
78
  import { rememberBridgeSearchReplay } from "../responses/bridge-search-replay-cache";
79
+ import { withAnthropicHelperRefusal } from "../sidecar/auth";
79
80
 
80
81
  /** Canonical Ollama Cloud origin. The only origin the "ollama" backend derives on its own. */
81
82
  export const OLLAMA_CLOUD_ORIGIN = "https://ollama.com";
@@ -246,12 +247,14 @@ export function resolvePassthroughWebSearchBridgeAuth(
246
247
  backend: ProviderWebSearchBridgeBackend | undefined,
247
248
  config: OcxConfig,
248
249
  openAiSidecar?: ResolvedOpenAiForwardSidecar,
250
+ parentProviderName?: string,
249
251
  ): PassthroughWebSearchBridgeAuth {
250
252
  switch (backend) {
251
253
  case "openai":
252
254
  return openAiSidecar ? { openAiSidecar } : {};
253
255
  case "anthropic": {
254
- const anthropic = findAnthropicSidecarProvider(config);
256
+ // An unavailable selected pool leaves the bridge disarmed; it never fails the passthrough turn.
257
+ const anthropic = withAnthropicHelperRefusal("web-search", () => findAnthropicSidecarProvider(config, parentProviderName));
255
258
  return anthropic ? { anthropic } : {};
256
259
  }
257
260
  case "xai": {
@@ -5,7 +5,7 @@
5
5
  * evaluating when the bridge asks for these names).
6
6
  */
7
7
  import type { OcxConfig, OcxProviderConfig, OcxWebSearchSidecarConfig } from "../types";
8
- import { resolveSidecarAuth } from "../sidecar/auth";
8
+ import { resolveSidecarAuth, resolveAnthropicHelperInstance } from "../sidecar/auth";
9
9
  import { getAccountSet } from "../oauth/store";
10
10
  import type { XaiSearchOptions } from "./xai-executor";
11
11
 
@@ -46,8 +46,9 @@ export interface AnthropicSidecarProvider {
46
46
  * Delegates to the shared sidecar auth module (#2188) so web-search and vision
47
47
  * cannot drift on what "Anthropic auth present" means.
48
48
  */
49
- export function findAnthropicSidecarProvider(config: OcxConfig): AnthropicSidecarProvider | undefined {
50
- const auth = resolveSidecarAuth(config);
49
+ export function findAnthropicSidecarProvider(config: OcxConfig, parentProviderName?: string): AnthropicSidecarProvider | undefined {
50
+ const instance = resolveAnthropicHelperInstance(config, { backendFamily: "anthropic", anthropicInstance: config.webSearchSidecar?.anthropicInstance, parentProviderName });
51
+ const auth = resolveSidecarAuth(config, instance);
51
52
  if (!auth.isAnthropicAuth || !auth.anthropicProviderName || !auth.anthropicProvider) return undefined;
52
53
  return { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider, config };
53
54
  }