@oh-my-pi/pi-ai 15.7.6 → 15.8.2

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.8.2] - 2026-06-03
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `opencode-zen/minimax-m3-free` (and forward-compat `opencode-zen/minimax-m3`) and `opencode-go/minimax-m3` being routed to `anthropic-messages` despite the OpenCode Zen/Go gateways only serving these ids at `/v1/chat/completions`, which surfaced raw MiniMax/tool-call markup (`<invoke name="bash">`, `<tool_call>`, `<description>`, `<cwd>`, `<|minimax|>`) in the UI. Resolver overrides now pin these ids to `openai-completions` and the bundled `models.json` entries are flipped to match. ([#1617](https://github.com/can1357/oh-my-pi/issues/1617))
10
+ - Fixed MiniMax Coding Plan China login opening the international `platform.minimax.io` subscription page instead of the China `platform.minimaxi.com` page.
11
+
12
+ ## [15.8.0] - 2026-06-02
13
+ ### Added
14
+
15
+ - Added `AnthropicMessagesClient` and related Anthropic wire types/errors via `anthropic-client` export so callers can build a standalone Anthropic Messages client without depending on `@anthropic-ai/sdk`
16
+ - Added `parseClaudeRateLimitHeaders` and `AuthStorage.ingestUsageHeaders` so Anthropic rate-limit response headers can warm the per-credential usage cache with throttling while preserving per-tier data from the last full usage report.
17
+
18
+ ### Changed
19
+
20
+ - Changed Anthropic request handling to use the package-local `AnthropicMessagesClient` implementation instead of `@anthropic-ai/sdk` as the default transport
21
+ - Updated the `AnthropicOptions.client` surface to accept any `AnthropicMessagesClientLike` implementation with `messages.create`, enabling custom compatible clients
22
+ - Changed generated OAuth metadata `user_id` to use a deterministic `device_id` derived from the install ID instead of a random value
23
+ - `claudeCodeVersion` bumped to `2.1.148` to match current Claude Code release.
24
+ - `X-Stainless-Package-Version` updated to `0.94.0` (matches the bundled `@anthropic-ai/sdk` version); `X-Stainless-Runtime-Version` pinned to `v24.3.0` (Bun version bundled with CC 2.1.148); `X-Stainless-Os` header key corrected to `X-Stainless-OS`.
25
+ - `createClaudeBillingHeader` now emits a deterministic billing header (`cc_version=<claudeCodeVersion>.<suffix>; cc_entrypoint=cli; cch=00000;`), where `<suffix>` is the first 3 hex chars of `SHA-256(salt + msg[4] + msg[7] + msg[20] + version)` instead of random bytes. The fingerprint seed is taken from the first **user** message (skipping synthetic/developer injections), mirroring Claude Code's `computeFingerprintFromMessages`.
26
+ - `cch` attestation implemented: `cch=00000` is a placeholder that, for OAuth requests, `wrapFetchForCch` rewrites on the wire to `XXHash64(body, 0x4D659218E32A3268) & 0xFFFFF` formatted as 5 lowercase hex chars, computed in-place via `Bun.hash.xxHash64`. The rewrite is anchored to the `system[0]` billing-header prefix so user content is never mutated, and is installed only when a billing-header prefix is present (OAuth turns).
27
+ - `anthropic-beta` header set for OAuth model discovery and Claude usage-API requests expanded to add `context-1m-2025-08-07`, `redact-thinking-2026-02-12`, `mid-conversation-system-2026-04-07`, `advanced-tool-use-2025-11-20`, `effort-2025-11-24`, and `extended-cache-ttl-2025-04-11`. The usage-API `user-agent` is bumped to `claude-cli/2.1.158 (external, cli)`.
28
+ - Reasoning models now append `effort-2025-11-24` to the per-request `Anthropic-Beta` header (matches Claude Code).
29
+ - `buildAnthropicSystemBlocks` (CC-instruction mode) now emits the same 3-block layout as Claude Code: billing header (never cached), system instruction (cached), all user content merged into one block with `\n\n` (cached). Previously emitted one block per item with cache only on the last, which fingerprinted the caller by block count.
30
+ - `applyPromptCaching` now matches Claude Code's breakpoint layout: 2 system (instruction + merged content) + 2 message, with no tool breakpoint. The tool breakpoint was redundant — tools follow system in the token sequence, so when system changes the tool cache prefix also changes. The instruction block (system[1]) is stable across every request and now gets its own guaranteed-hit breakpoint.
31
+ - `applyPromptCaching` now caches the last two messages regardless of role instead of the last two *user* messages. The penultimate assistant message (tool calls + response from the previous turn) is larger and more recently created than the penultimate user message, making it the higher-value cache target.
32
+ - OAuth scope set expanded: added `user:sessions:claude_code`, `user:mcp_servers`, `user:file_upload`. `AUTHORIZE_URL` stays at `claude.ai/oauth/authorize` and `TOKEN_URL` stays at `api.anthropic.com/v1/oauth/token` — the `platform.claude.com` equivalents are CC's console-credential flow and do not grant `user:inference`, which OMP requires for direct OAuth-token inference.
33
+ - Token refresh POST now sends `anthropic-beta: oauth-2025-04-20` and `User-Agent: anthropic-sdk-typescript/0.94.0 userOAuthProvider` (CC sends these on refresh but not on the initial code exchange).
34
+
35
+ ### Fixed
36
+
37
+ - Fixed tool argument validation to wrap a plain string in a singleton array when the schema requires an array, allowing tool-level path/list normalization to recover from bare string arguments.
38
+ - Restored `eager_input_streaming` and strict flags on OAuth Anthropic tool definitions when model compatibility allows eager streaming.
39
+ - Fixed OAuth stream calls with injected custom clients missing a `beta` client by falling back to `client.messages.create` instead of requiring `client.beta.messages.create`
40
+ - Fixed direct use of internal API client typing so retry/timeouts and malformed-error classification remain compatible while not requiring the external SDK
41
+ - Fixed Cursor provider requests failing with `Cannot send empty user message to Cursor API` after tool-result history by selecting the latest user/developer turn instead of assuming the final context message is the active user turn.
42
+ - Fixed Anthropic web search dropping `ANTHROPIC_CUSTOM_HEADERS` when `CLAUDE_CODE_USE_FOUNDRY` was unset, causing 401s from corporate API gateways. `resolveAnthropicCustomHeadersForBaseUrl` now forwards the parsed headers whenever the base URL is non-Anthropic (or Foundry is enabled), and `buildAnthropicSearchHeaders` threads them through `buildAnthropicHeaders` so the search and streaming paths behave identically ([#1693](https://github.com/can1357/oh-my-pi/issues/1693)).
43
+ - Fixed OpenCode Go Anthropic-format models such as `qwen3.7-max` sending Anthropic `X-Api-Key` auth alongside the OpenCode bearer token, avoiding spurious Alibaba `401 Invalid API-key provided` errors. ([#1661](https://github.com/can1357/oh-my-pi/issues/1661))
44
+ - Fixed OAuth token exchange and refresh flows to fetch Claude CLI bootstrap identity when token responses omit account information, so `accountId` and `email` are now recovered when available
45
+ - Fixed Anthropic thinking traces being lost on direct OAuth requests. OAuth requests no longer send `redact-thinking-2026-02-12` unless thinking is explicitly hidden, Opus 4.7+ adaptive thinking opts into `display: "summarized"`, and the top user-facing thinking tier now sends Anthropic's `output_config.effort = "max"` rather than the next-lower `"xhigh"` tier.
46
+
47
+ ### Removed
48
+
49
+ - Removed the `@anthropic-ai/sdk` runtime dependency. The Anthropic provider now uses the package-local `AnthropicMessagesClient` and hand-maintained wire types in `providers/anthropic-wire.ts`; the SDK was only ever used for URL assembly, auth-header injection, bounded retries, the pre-response timeout, and HTTP-error-to-status mapping, all of which are reproduced with identical observable behavior.
50
+
5
51
  ## [15.7.5] - 2026-06-01
6
52
 
7
53
  ### Added
@@ -43,6 +89,7 @@
43
89
  - Fixed OpenCode-Go dynamic model refresh downgrading `qwen3.7-max` from Anthropic Messages to OpenAI-compatible transport, which caused `401 Model qwen3.7-max is not supported for format oa-compat` after `/v1/models` cache refreshes.
44
90
 
45
91
  ## [15.5.12] - 2026-05-29
92
+
46
93
  ### Removed
47
94
 
48
95
  - Removed ANTML stream markup healing for `antml:function_calls` and `antml:thinking` envelopes, so Anthropic-compatible providers no longer parse those tags into `toolCall`/`thinking` events
@@ -96,13 +143,12 @@
96
143
  - Fixed `streamSimple` to retry on usage-limit errors (including message-only error events) before any content is emitted, so `onAuthError` can rotate credentials automatically
97
144
  - Fixed auth-gateway error classification to extract embedded status codes and use word-boundary matching, so `GenerateContentRequest` and similar messages are no longer misreported as rate-limit errors
98
145
  - Fixed `checkCredentials` to handle `completionProbe` exceptions by recording the failure in `CredentialHealthResult.completion.reason` while still returning the usage probe result
99
-
100
- ### Fixed
101
-
102
146
  - Fixed Google Vertex's bundled model list to use the authoritative models.dev catalog, including MaaS entries such as `deepseek-ai/deepseek-v3.2-maas` and removing retired Gemini 1.5 fallbacks. ([#1456](https://github.com/can1357/oh-my-pi/issues/1456))
103
147
 
104
148
  ## [15.5.7] - 2026-05-27
149
+
105
150
  ### Added
151
+
106
152
  - `SimpleStreamOptions.openrouterVariant` (`"nitro"`, `"floor"`, `"online"`, `"exacto"`, …) — when set, appends `:<variant>` to OpenRouter model IDs at request time, leaving ids that already carry an explicit `:suffix` untouched. Plumbed through `openai-completions` and the pi-native gateway forwarder.
107
153
 
108
154
  - xAI Grok OAuth (SuperGrok Subscription) provider in `/login`. Loopback PKCE flow on `127.0.0.1:56121`; the token unlocks Grok-4.x chat. Ported from NousResearch/hermes-agent (MIT).
@@ -119,6 +165,7 @@
119
165
  - Fixed OpenRouter DeepSeek V4 tool-call follow-up requests replaying normalized `reasoning` as-is instead of DeepSeek's required `reasoning_content`, which caused HTTP 400 errors in thinking mode. ([#1445](https://github.com/can1357/oh-my-pi/issues/1445))
120
166
 
121
167
  ## [15.5.6] - 2026-05-27
168
+
122
169
  ### Added
123
170
 
124
171
  - Added `PI_CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS` to control how long an idle Codex WebSocket stays eligible for reuse, with `0` disabling the check
@@ -136,11 +183,14 @@
136
183
  - Added `PI_CODEX_WEBSOCKET_PING_INTERVAL_MS` to configure the interval for Codex WebSocket protocol ping heartbeats
137
184
  - Added `PI_CODEX_WEBSOCKET_PONG_TIMEOUT_MS` to configure the Codex WebSocket pong timeout used to detect unresponsive connections
138
185
  - Added `PI_CODEX_WEBSOCKET_MESSAGE_QUEUE_CAPACITY` to configure the maximum buffered Codex WebSocket inbound queue size before transport fallback
186
+ - Added `parseStreamingJsonThrottled` to `@oh-my-pi/pi-ai/utils/json-parse` — a per-delta wrapper around `parseStreamingJson` that skips re-parses until the buffer has grown by `minGrowthBytes` (default 256). Wired into the streaming hot path of every provider's tool-call argument accumulator (`anthropic`, `amazon-bedrock`, `openai-completions`, `openai-codex-responses`, `openai-responses-shared`) so per-delta cost is O(N) in total buffer length instead of O(N²). Each provider's `toolcall_end` still runs a final unthrottled parse, so the published `block.arguments` is unchanged.
187
+ - Added named-tool routing support to Google providers: `GoogleSharedStreamOptions.toolChoice` and `GoogleGeminiCliOptions.toolChoice` now accept `{ mode: "ANY"; allowedFunctionNames: [string, ...string[]] }` in addition to the string forms. `mapGoogleToolChoice` converts `ToolChoice` objects of shape `{ type: "tool" | "function", name }` to the wire form. Mirrors the equivalent Anthropic mapper.
139
188
 
140
189
  ### Changed
141
190
 
142
191
  - Improved Codex WebSocket timeout diagnostics to include last event type and time since last progress event
143
192
  - Enhanced Codex WebSocket error classification to recognize ping, pong, send, and queue-overflow failures as retryable
193
+ - Changed `mapGoogleToolChoice` to be exported from `@oh-my-pi/pi-ai/stream` so callers can build the wire-shape allow-list directly without re-deriving it.
144
194
 
145
195
  ### Fixed
146
196
 
@@ -149,21 +199,10 @@
149
199
  - Fixed Codex WebSocket pong timeout detection by tracking pong events and failing the connection when no pong is received within the configured timeout
150
200
  - Fixed Anthropic streaming to suppress hallucinated meta-prompt thinking blocks (the recent "I don't see any current rewritten thinking..." regression). When the marker phrase `rewritten thinking` appears in a streamed thinking summary the block is collapsed to a plain `Thinking...` placeholder and its signature is dropped so subsequent turns can't re-anchor on the garbled chain.
151
201
  - Fixed Codex WebSocket silent stalls by adding protocol pings, inbound queue bounding, clearer idle-timeout diagnostics, and SDK retry clamping for first-event timeouts.
152
-
153
- ### Fixed
154
-
155
202
  - Fixed Synthetic model discovery to treat the provider `/models` response as authoritative so deprecated bundled IDs are pruned from the runtime cache, and changed Synthetic login validation to avoid probing a specific model ([#1417](https://github.com/can1357/oh-my-pi/issues/1417)).
156
203
 
157
- ### Added
158
-
159
- - Added `parseStreamingJsonThrottled` to `@oh-my-pi/pi-ai/utils/json-parse` — a per-delta wrapper around `parseStreamingJson` that skips re-parses until the buffer has grown by `minGrowthBytes` (default 256). Wired into the streaming hot path of every provider's tool-call argument accumulator (`anthropic`, `amazon-bedrock`, `openai-completions`, `openai-codex-responses`, `openai-responses-shared`) so per-delta cost is O(N) in total buffer length instead of O(N²). Each provider's `toolcall_end` still runs a final unthrottled parse, so the published `block.arguments` is unchanged.
160
- - Added named-tool routing support to Google providers: `GoogleSharedStreamOptions.toolChoice` and `GoogleGeminiCliOptions.toolChoice` now accept `{ mode: "ANY"; allowedFunctionNames: [string, ...string[]] }` in addition to the string forms. `mapGoogleToolChoice` converts `ToolChoice` objects of shape `{ type: "tool" | "function", name }` to the wire form. Mirrors the equivalent Anthropic mapper.
161
-
162
- ### Changed
163
-
164
- - Changed `mapGoogleToolChoice` to be exported from `@oh-my-pi/pi-ai/stream` so callers can build the wire-shape allow-list directly without re-deriving it.
165
-
166
204
  ## [15.5.0] - 2026-05-26
205
+
167
206
  ### Added
168
207
 
169
208
  - Added `zhipu-coding-plan` provider for Zhipu (智谱) BigModel's domestic coding-plan SKU at `https://open.bigmodel.cn/api/coding/paas/v4`, with dynamic model discovery (`ZHIPU_API_KEY`), zai-format thinking, `reasoning_content` field, and OAuth login flow ([#1340](https://github.com/can1357/oh-my-pi/issues/1340)).
@@ -191,6 +230,7 @@
191
230
  - Fixed OpenCode Zen `big-pickle` follow-up requests replaying assistant tool-call turns without DeepSeek-required `reasoning_content`, which caused HTTP 400 errors in thinking mode.
192
231
 
193
232
  ## [15.4.1] - 2026-05-26
233
+
194
234
  ### Added
195
235
 
196
236
  - Added `isOpenAICompletionsProgressChunk` export to identify real progress chunks vs. keepalives in OpenAI completions streams
@@ -224,6 +264,7 @@
224
264
  - Fixed z.ai/GLM-via-OpenRouter subagent stalls where no-op keepalive chunks reset the idle watchdog indefinitely by filtering non-progress items before resetting the deadline
225
265
 
226
266
  ## [15.4.0] - 2026-05-26
267
+
227
268
  ### Breaking Changes
228
269
 
229
270
  - Removed `findAnthropicAuth` from `anthropic-auth` and replaced store-driven auth discovery with `buildAnthropicAuthConfig`, requiring callers to provide an already-resolved API key before building Anthropic auth config
@@ -260,6 +301,7 @@
260
301
  - Fixed `pi-ai login moonshot` failing with `invalid temperature: only 1 is allowed for this model` (HTTP 400) because the API-key validator probed `kimi-k2.5` with `temperature: 0`. Moonshot login now validates against `GET /v1/models`, matching the DeepSeek/Fireworks/NanoGPT/ZenMux pattern and authenticating the key without invoking model-specific parameter restrictions.
261
302
 
262
303
  ## [15.3.2] - 2026-05-25
304
+
263
305
  ### Added
264
306
 
265
307
  - Added `GET /v1/snapshot/stream` for live auth-broker snapshot updates via SSE with `snapshot`, `entry`, and `removed` event frames
@@ -316,6 +358,7 @@
316
358
  - Fixed `/btw` (and IRC background replies) returning a `BedrockException` 400 (`The toolConfig field must be defined when using toolUse and toolResult content blocks.`) on LiteLLM → Bedrock once the session has tool-call history. Two source fixes in `buildParams`: (1) `if (context.tools)` → `if (context.tools?.length)` so an explicit `context.tools = []` (the /btw opt-out) never routes through `convertTools` and never emits an empty `"tools"` array; (2) `else if (hasToolHistory(...))` → `else if (context.tools === undefined && hasToolHistory(...))` so the Anthropic-proxy sentinel that injects `tools: []` for tool-history turns is suppressed when the caller explicitly opted out, preventing it from re-introducing the empty array. As defence-in-depth, `tool_choice: "none"` is also dropped when the resolved tools list is missing or empty. ([#1227](https://github.com/can1357/oh-my-pi/issues/1227))
317
359
 
318
360
  ## [15.1.8] - 2026-05-20
361
+
319
362
  ### Added
320
363
 
321
364
  - Added Fireworks Fire Pass as a separate `firepass` provider with API-key login flow, bundled `kimi-k2.6-turbo` model entry (Kimi K2.6 Turbo), and wire-id translation from the friendly catalog id to the `accounts/fireworks/routers/kimi-k2p6-turbo` router endpoint. Fire Pass keys (`fpk_…`) authorize only the dedicated router and reject `/v1/models`, so login validation pings chat completions against the router id directly. Extended the openai-completions Kimi-family safety net so the firepass entry inherits the per-Fireworks-docs "always send `max_tokens`" default ([Kimi K2 guide](https://docs.fireworks.ai/models/kimi-k2)); the router's accepted `reasoning_effort` set includes `xhigh`, so it is forwarded verbatim rather than remapped. See https://docs.fireworks.ai/firepass.
@@ -327,6 +370,7 @@
327
370
  - Fixed Perplexity OAuth credentials being treated as expired one hour after login. `getJwtExpiry` was fabricating `expires = now + 1h` whenever the JWT had no `exp` claim (the common case — Perplexity sessions are server-side). Once the hour elapsed, `getOAuthApiKey` would mark the cred expired and the search provider's loader would silently skip it, surfacing as "logged out". Logins with no `exp` now persist a far-future sentinel; `getOAuthApiKey` also normalizes any stale `expires` written by older builds.
328
371
 
329
372
  ## [15.1.7] - 2026-05-19
373
+
330
374
  ### Added
331
375
 
332
376
  - Added Anthropic realization of `serviceTier: "priority"`. The anthropic-messages provider now sets `speed: "fast"` on the request and appends the `fast-mode-2026-02-01` beta to `Anthropic-Beta` whenever the caller passes `serviceTier: "priority"`. When the server rejects an unsupported model with `invalid_request_error`, the provider transparently retries the same turn without the fast-mode signal (mirroring the strict-tools fallback pattern), persists the disable via a new `providerSessionState.fastModeDisabled` flag so subsequent requests in the session skip the field, and surfaces the action via the new `AssistantMessage.disabledFeatures` array (id `"priority"`) so callers can sync user-facing toggles. A new `clearAnthropicFastModeFallback(providerSessionState)` helper lets callers re-arm priority after the auto-fallback fired.
@@ -345,6 +389,7 @@
345
389
  - Fixed `pi-ai login <provider>` crashing with `Unknown provider` for providers that only the `auth-storage` `login()` switch knew about (perplexity, alibaba-coding-plan, gitlab-duo, huggingface, opencode-zen/go, lm-studio, ollama, cerebras, fireworks, qianfan, synthetic, venice, litellm, moonshot, together, cloudflare/vercel ai gateways, vllm, qwen-portal, nvidia, xiaomi, and any custom OAuth provider). The CLI now delegates to `SqliteAuthCredentialStore.login()` instead of duplicating a smaller switch, so the auth-broker `omp auth-broker login <provider>` flow works for every registered OAuth provider.
346
390
 
347
391
  ## [15.1.4] - 2026-05-19
392
+
348
393
  ### Changed
349
394
 
350
395
  - Updated auth-gateway format and pi-native request handling to invalidate the failed API key and retry the provider request with a replacement key when authentication fails
@@ -358,6 +403,7 @@
358
403
  - Added `credential_process` support to the Bedrock provider's AWS credential resolver so profiles delegating to external brokers (`aws-vault`, `granted`, in-house tools) resolve instead of falling through to `Unable to resolve AWS credentials`. Parses the AWS SDK `Version: 1` JSON envelope, honors `Expiration` in the per-profile cache, propagates `AbortSignal` to the spawned helper, routes Windows `.cmd`/`.bat` helpers through `cmd.exe /c`, and ships a POSIX-shell-style tokenizer that preserves backslashes inside double quotes so Windows paths survive ([#1142](https://github.com/can1357/oh-my-pi/issues/1142))
359
404
 
360
405
  ## [15.1.3] - 2026-05-17
406
+
361
407
  ### Breaking Changes
362
408
 
363
409
  - Changed `AuthBrokerClient.fetchSnapshot()` to return status-based results (`200` or `304`) instead of always returning a raw snapshot body, so callers now need to branch on `status`
@@ -443,6 +489,7 @@
443
489
  - Hardened auth-gateway bearer-token checks with constant-time comparison to avoid timing-side-channel leaks
444
490
 
445
491
  ## [15.1.2] - 2026-05-15
492
+
446
493
  ### Breaking Changes
447
494
 
448
495
  - Rejected draft-07 tuple and dependency keywords (`items` arrays, `dependencies`, `additionalItems`) in JSON Schema validation
@@ -511,12 +558,14 @@
511
558
  - Fixed mock provider auto-generated tool-call IDs to use a per-instance counter (now reset by `reset()`), so test order no longer affects IDs across `createMockModel()` instances
512
559
 
513
560
  ## [15.0.2] - 2026-05-15
561
+
514
562
  ### Fixed
515
563
 
516
564
  - Fixed `StreamOptions.fetch` typing to accept fetch-compatible override functions that do not expose `preconnect`, allowing custom fetch implementations to be used without type errors across runtimes
517
565
  - Fixed Moonshot Kimi K2.6 forced tool calls to send `thinking: { type: "disabled" }`, avoiding `tool_choice 'specified' is incompatible with thinking enabled` 400s while preserving the requested named tool ([#1077](https://github.com/can1357/oh-my-pi/issues/1077)).
518
566
 
519
567
  ## [15.0.1] - 2026-05-14
568
+
520
569
  ### Breaking Changes
521
570
 
522
571
  - Increased the minimum Bun runtime version to `>=1.3.14` for the `@aws-?` package
@@ -536,22 +585,22 @@
536
585
 
537
586
  - Fixed OAuth credentials being silently disabled when two omp processes (or any two `AuthStorage` instances sharing a `agent.db`) race on token refresh. Anthropic rotates refresh tokens on every use, so the loser's `invalid_grant` response previously soft-deleted the row that the winner just rotated, forcing the user to `/login` again. `#tryOAuthCredential` now re-reads the row from disk before declaring a definitive failure: if the persisted `refresh` differs from the snapshot it tried, the peer-rotated credential is reloaded and the request retries against the fresh token instead of disabling the live row.
538
587
  - Closed a remaining race window in OAuth refresh-failure handling: between re-reading the credential row to check for peer rotation and the subsequent soft-delete, another process could still complete a refresh and rotate the row, leaving us to disable the freshly-rotated credential by `id`. The disable now runs as a single CAS update conditioned on the row's `data` still matching the snapshot we tried to refresh, and on `disabled_cause IS NULL`. If the CAS reports 0 rows changed (peer rotation, or row already disabled by a concurrent failure on the same snapshot), we reload from disk and retry instead of mutating the wrong row or emitting a spurious `credential_disabled` event.
588
+ - Lazy built-in provider streams now enforce the shared idle watchdog and abort stalled provider requests, so session auto-retry can continue after transient network drops instead of remaining stuck. Caller aborts still terminate as aborted.
589
+
539
590
  ### Changed
540
- - Lowered the default steady-state stream idle timeout from 120s to 30s while preserving the existing environment overrides.
541
591
 
542
- ### Fixed
543
- - Lazy built-in provider streams now enforce the shared idle watchdog and abort stalled provider requests, so session auto-retry can continue after transient network drops instead of remaining stuck. Caller aborts still terminate as aborted.
592
+ - Lowered the default steady-state stream idle timeout from 120s to 30s while preserving the existing environment overrides.
544
593
 
545
594
  ## [14.9.3] - 2026-05-10
546
595
 
547
596
  ### Fixed
597
+
548
598
  - Anthropic provider now retries generic transient connect failures (`unable to connect`, `fetch failed`, `connection error`, etc.) by falling back to the shared `isRetryableError` allowlist after the provider-specific patterns. Previously these errors bypassed the hand-curated regex in `isProviderRetryableError` and aborted the stream on the first attempt, while the OpenAI SDK and Codex `fetchWithRetry` paths already handled them.
549
599
 
550
600
  ## [14.9.0] - 2026-05-10
551
601
 
552
- ### Added
553
-
554
602
  ### Fixed
603
+
555
604
  - Fixed silent forwarding of image content (for example Python plot output rendered in the terminal) to models without vision support, which produced opaque 404 errors from upstream. Image blocks are now stripped and replaced with a `[image omitted: model does not support vision]` placeholder for non-vision models, including tool-result payloads ([#967](https://github.com/can1357/oh-my-pi/issues/967), [#968](https://github.com/can1357/oh-my-pi/issues/968)).
556
605
 
557
606
  - Added `AuthStorage` `onCredentialDisabled` callback (sync or async) so embedders can react when a credential is automatically disabled (e.g. OAuth refresh fails with `invalid_grant`) — useful for surfacing a banner or auto-launching a re-login flow instead of letting the credential silently disappear. Sync throws and async rejections are both caught and logged so a misbehaving subscriber cannot break the disable path.
@@ -568,6 +617,7 @@
568
617
  ## [14.8.0] - 2026-05-09
569
618
 
570
619
  ### Fixed
620
+
571
621
  - Fixed Gemini 3 Pro thinking metadata so `medium` effort is rejected with the expected error instead of being silently accepted: `ThinkingConfig` now carries an optional explicit `levels` list that survives `expandEffortRange`, letting non-contiguous supported sets (e.g. `[low, high]`) round-trip through enrichment.
572
622
  - Fixed Kimi Code OAuth expiry handling to refresh access tokens 5 minutes before server expiry, avoiding daily 401s from using tokens right up to the cutoff.
573
623
  - Fixed OpenAI Responses custom tool replay to preserve custom tool call item IDs with the `ctc_` prefix instead of rewriting them as `fc_` function-call IDs ([#977](https://github.com/can1357/oh-my-pi/issues/977)).
@@ -581,6 +631,7 @@
581
631
  ### Changed
582
632
 
583
633
  - Changed OpenAI Responses, Azure OpenAI Responses, and OpenAI Codex providers to omit `reasoning.summary` from requests when `reasoningSummary` is explicitly `null` (previously fell back to `"auto"`).
634
+
584
635
  ## [14.7.5] - 2026-05-07
585
636
 
586
637
  ### Added
@@ -600,6 +651,7 @@
600
651
  - Fixed local Ollama model discovery to apply `/api/show` thinking and vision capabilities in addition to native context windows ([#928](https://github.com/can1357/oh-my-pi/issues/928)).
601
652
 
602
653
  ## [14.7.0] - 2026-05-04
654
+
603
655
  ### Breaking Changes
604
656
 
605
657
  - Changed `Context.systemPrompt` from a string to `string[]`, so callers must now pass an array of prompts instead of a single string
@@ -631,6 +683,7 @@
631
683
  - Fixed OpenAI Codex websocket continuations to retry with full context when `previous_response_id` expires server-side instead of surfacing `previous_response_not_found`.
632
684
 
633
685
  ## [14.6.2] - 2026-05-03
686
+
634
687
  ### Added
635
688
 
636
689
  - Added `EventStream.fail(err)` method to terminate the async iterator with an error, enabling consumers to catch stream-level failures via `for await` without hanging
@@ -668,6 +721,7 @@
668
721
  - Fixed OpenAI Codex websocket append reuse after `response.completed` terminal events.
669
722
 
670
723
  ## [14.5.14] - 2026-05-01
724
+
671
725
  ### Added
672
726
 
673
727
  - Added package-level `google-gemini-headers` exports (`getGeminiCliHeaders`, `getGeminiCliUserAgent`, `getAntigravityHeaders`, `extractRetryDelay`, and `ANTIGRAVITY_SYSTEM_INSTRUCTION`) for header and retry handling reuse without importing full Google providers
@@ -927,6 +981,7 @@
927
981
  - Added `thinkingSignature` field to thinking content blocks to preserve the original reasoning field name (e.g., `reasoning_text`, `reasoning_content`) for accurate follow-up requests
928
982
  - Added first-event timeout detection for streaming responses to abort stuck requests before user-visible content arrives
929
983
  - Added `PI_STREAM_FIRST_EVENT_TIMEOUT_MS` environment variable to configure first-event timeout (defaults to 15 seconds or idle timeout, whichever is lower)
984
+ - Added Vercel AI Gateway to `/login` providers for interactive API key setup
930
985
 
931
986
  ### Changed
932
987
 
@@ -936,13 +991,6 @@
936
991
 
937
992
  - Fixed Anthropic stream timeout errors to be properly retried by recognizing first-event timeout messages
938
993
  - Fixed stream stall detection to distinguish between first-event timeouts and idle timeouts, enabling faster recovery for stuck connections
939
-
940
- ### Added
941
-
942
- - Added Vercel AI Gateway to `/login` providers for interactive API key setup
943
-
944
- ### Fixed
945
-
946
994
  - Fixed `omp commit` failing with HTTP 400 errors when using reasoning-enabled models on OpenAI-compatible endpoints that don't support the `developer` role (e.g., GitHub Copilot, custom proxies). Now falls back to `system` role when `developer` is unsupported.
947
995
 
948
996
  ## [13.17.0] - 2026-03-30
@@ -2832,19 +2880,16 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
2832
2880
  ### Added
2833
2881
 
2834
2882
  - **`agentLoopContinue` function**: Continue an agent loop from existing context without adding a new user message. Validates that the last message is `user` or `toolResult`. Useful for retry after context overflow or resuming from manually-added tool results.
2835
-
2836
- ### Breaking Changes
2837
-
2838
- - Removed provider-level tool argument validation. Validation now happens in `agentLoop` via `executeToolCalls`, allowing models to retry on validation errors. For manual tool execution, use `validateToolCall(tools, toolCall)` or `validateToolArguments(tool, toolCall)`.
2839
-
2840
- ### Added
2841
-
2842
2883
  - Added `validateToolCall(tools, toolCall)` helper that finds the tool by name and validates arguments.
2843
2884
 
2844
2885
  - **OpenAI compatibility overrides**: Added `compat` field to `Model` for `openai-completions` API, allowing explicit configuration of provider quirks (`supportsStore`, `supportsDeveloperRole`, `supportsReasoningEffort`, `maxTokensField`). Falls back to URL-based detection if not set. Useful for LiteLLM, custom proxies, and other non-standard endpoints. ([#133](https://github.com/badlogic/pi-mono/issues/133), thanks @fink-andreas for the initial idea and PR)
2845
2886
 
2846
2887
  - **xhigh reasoning level**: Added `xhigh` to `ReasoningEffort` type for OpenAI codex-max models. For non-OpenAI providers (Anthropic, Google), `xhigh` is automatically mapped to `high`. ([#143](https://github.com/badlogic/pi-mono/issues/143))
2847
2888
 
2889
+ ### Breaking Changes
2890
+
2891
+ - Removed provider-level tool argument validation. Validation now happens in `agentLoop` via `executeToolCalls`, allowing models to retry on validation errors. For manual tool execution, use `validateToolCall(tools, toolCall)` or `validateToolArguments(tool, toolCall)`.
2892
+
2848
2893
  ### Changed
2849
2894
 
2850
2895
  - **Updated SDK versions**: OpenAI SDK 5.21.0 → 6.10.0, Anthropic SDK 0.61.0 → 0.71.2, Google GenAI SDK 1.30.0 → 1.31.0
@@ -2873,4 +2918,4 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
2873
2918
 
2874
2919
  ## [0.9.4] - 2025-11-26
2875
2920
 
2876
- Initial release with multi-provider LLM support.
2921
+ Initial release with multi-provider LLM support.
@@ -542,6 +542,10 @@ export declare class AuthStorage {
542
542
  * Logout from a provider.
543
543
  */
544
544
  logout(provider: string): Promise<void>;
545
+ ingestUsageHeaders(provider: Provider, headers: Record<string, string>, options?: {
546
+ sessionId?: string;
547
+ baseUrl?: string;
548
+ }): boolean;
545
549
  fetchUsageReports(options?: {
546
550
  baseUrlResolver?: (provider: Provider) => string | undefined;
547
551
  /** Caller's cancel signal; only rejects this caller, never the shared upstream fetch. */
@@ -11,6 +11,7 @@ export * from "./models";
11
11
  export * from "./provider-details";
12
12
  export * from "./provider-models";
13
13
  export * from "./providers/anthropic";
14
+ export * from "./providers/anthropic-client";
14
15
  export * from "./providers/azure-openai-responses";
15
16
  export type * from "./providers/cursor";
16
17
  export * from "./providers/gitlab-duo";
@@ -0,0 +1,99 @@
1
+ import type { FetchImpl } from "../types";
2
+ import type { MessageCreateParamsStreaming } from "./anthropic-wire";
3
+ /** Per-request options accepted by {@link AnthropicMessages.create}. */
4
+ export interface AnthropicRequestOptions {
5
+ signal?: AbortSignal;
6
+ /** Pre-response timeout in milliseconds. */
7
+ timeout?: number;
8
+ /** Per-request retry budget override. */
9
+ maxRetries?: number;
10
+ }
11
+ /**
12
+ * Extra `RequestInit` fields merged into every fetch call. Bun extends
13
+ * `RequestInit` with a `tls` option used for the Claude Code TLS profile and
14
+ * Foundry mTLS.
15
+ */
16
+ export type AnthropicFetchOptions = RequestInit & {
17
+ tls?: {
18
+ rejectUnauthorized?: boolean;
19
+ serverName?: string;
20
+ ciphers?: string;
21
+ ca?: string | string[];
22
+ cert?: string;
23
+ key?: string;
24
+ };
25
+ };
26
+ export interface AnthropicClientOptions {
27
+ /** Sent as `X-Api-Key` unless the header is already present in `defaultHeaders`. */
28
+ apiKey?: string | null;
29
+ /** Sent as `Authorization: Bearer <token>` unless the header is already present in `defaultHeaders`. */
30
+ authToken?: string | null;
31
+ baseURL?: string | null;
32
+ maxRetries?: number;
33
+ /** Pre-response timeout in milliseconds. Defaults to 10 minutes. */
34
+ timeout?: number;
35
+ defaultHeaders?: Record<string, string>;
36
+ fetch?: FetchImpl;
37
+ fetchOptions?: AnthropicFetchOptions;
38
+ }
39
+ /** Non-2xx response from the Anthropic API. */
40
+ export declare class AnthropicApiError extends Error {
41
+ readonly status: number;
42
+ readonly headers: Headers;
43
+ readonly requestId: string | null;
44
+ constructor(status: number, message: string, headers: Headers);
45
+ static fromResponse(response: Response): Promise<AnthropicApiError>;
46
+ }
47
+ /** Network-level failure (DNS, TLS, socket reset) after retries were exhausted. */
48
+ export declare class AnthropicConnectionError extends Error {
49
+ constructor(cause: unknown);
50
+ }
51
+ /** No response headers arrived within the configured request timeout. */
52
+ export declare class AnthropicConnectionTimeoutError extends Error {
53
+ constructor();
54
+ }
55
+ /**
56
+ * Lazy in-flight request handle. The HTTP request starts on the first
57
+ * `asResponse()` call; subsequent calls return the same promise.
58
+ *
59
+ * Shape-compatible with the SDK's `APIPromise.asResponse()` so
60
+ * `getAnthropicStreamResponse` treats internal and injected clients uniformly.
61
+ */
62
+ export declare class AnthropicApiRequest {
63
+ #private;
64
+ constructor(start: () => Promise<Response>);
65
+ asResponse(): Promise<Response>;
66
+ }
67
+ /**
68
+ * `messages` resource. `create` lives on the prototype so tests can intercept
69
+ * every outgoing request with `vi.spyOn(AnthropicMessages.prototype, "create")`.
70
+ */
71
+ export declare class AnthropicMessages {
72
+ #private;
73
+ constructor(client: AnthropicMessagesClient, path: string);
74
+ create(params: MessageCreateParamsStreaming, options?: AnthropicRequestOptions): AnthropicApiRequest;
75
+ }
76
+ /**
77
+ * Structural interface satisfied by both {@link AnthropicMessagesClient} and
78
+ * SDK-style clients (e.g. `AnthropicVertex`), so callers can inject an
79
+ * alternative Messages-API client via `AnthropicOptions.client`.
80
+ */
81
+ export interface AnthropicMessagesClientLike {
82
+ messages: {
83
+ create(params: MessageCreateParamsStreaming, options?: AnthropicRequestOptions): unknown;
84
+ };
85
+ beta?: {
86
+ messages: {
87
+ create(params: MessageCreateParamsStreaming, options?: AnthropicRequestOptions): unknown;
88
+ };
89
+ };
90
+ }
91
+ export declare class AnthropicMessagesClient implements AnthropicMessagesClientLike {
92
+ #private;
93
+ readonly messages: AnthropicMessages;
94
+ readonly beta: {
95
+ readonly messages: AnthropicMessages;
96
+ };
97
+ constructor(options: AnthropicClientOptions);
98
+ request(path: string, params: MessageCreateParamsStreaming, options?: AnthropicRequestOptions): AnthropicApiRequest;
99
+ }
@@ -7,8 +7,8 @@
7
7
  * Used by `anthropic-messages.ts:parseRequest` to validate the inbound JSON
8
8
  * before walking it into pi-ai's canonical `Context`.
9
9
  */
10
- import type { ContentBlockParam, ImageBlockParam, MessageCreateParams, MessageParam, TextBlockParam, Tool, ToolChoice } from "@anthropic-ai/sdk/resources/messages";
11
10
  import * as z from "zod/v4";
11
+ import type { ContentBlockParam, ImageBlockParam, MessageCreateParams, MessageParam, TextBlockParam, Tool, ToolChoice } from "./anthropic-wire";
12
12
  export declare const cacheControlSchema: z.ZodObject<{
13
13
  type: z.ZodLiteral<"ephemeral">;
14
14
  ttl: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"1h">, z.ZodLiteral<"5m">]>>;