@oh-my-pi/pi-ai 18.1.22 → 18.2.1

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 (62) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/THIRD-PARTY-NOTICES.txt +54 -25
  3. package/dist/types/auth-broker/remote-store.d.ts +17 -0
  4. package/dist/types/auth-gateway/index.d.ts +1 -0
  5. package/dist/types/auth-gateway/session-state.d.ts +65 -0
  6. package/dist/types/auth-storage.d.ts +16 -0
  7. package/dist/types/error/body-error.d.ts +15 -0
  8. package/dist/types/error/flags.d.ts +16 -0
  9. package/dist/types/error/index.d.ts +1 -0
  10. package/dist/types/oneshot-retry.d.ts +6 -0
  11. package/dist/types/providers/anthropic-signature.d.ts +25 -0
  12. package/dist/types/providers/openai-codex/request-transformer.d.ts +27 -0
  13. package/dist/types/providers/openai-shared.d.ts +20 -3
  14. package/dist/types/registry/oauth/perplexity.d.ts +1 -7
  15. package/dist/types/registry/oauth/types.d.ts +8 -0
  16. package/dist/types/stream.d.ts +2 -0
  17. package/dist/types/types.d.ts +10 -2
  18. package/dist/types/usage.d.ts +8 -0
  19. package/dist/types/utils/block-symbols.d.ts +36 -0
  20. package/dist/types/utils/openai-http.d.ts +2 -0
  21. package/dist/types/utils/retry-after.d.ts +2 -0
  22. package/dist/types/utils/schema/wire.d.ts +4 -5
  23. package/dist/types/utils.d.ts +9 -0
  24. package/package.json +6 -6
  25. package/src/auth-broker/remote-store.ts +73 -8
  26. package/src/auth-broker/wire-schemas.ts +1 -0
  27. package/src/auth-gateway/index.ts +1 -0
  28. package/src/auth-gateway/server.ts +48 -11
  29. package/src/auth-gateway/session-state.ts +114 -0
  30. package/src/auth-storage.ts +144 -13
  31. package/src/error/body-error.ts +310 -0
  32. package/src/error/flags.ts +63 -13
  33. package/src/error/index.ts +1 -0
  34. package/src/error/retryable.ts +2 -0
  35. package/src/oneshot-retry.ts +13 -3
  36. package/src/providers/anthropic-messages-server.ts +24 -3
  37. package/src/providers/anthropic-signature.ts +103 -0
  38. package/src/providers/anthropic.ts +111 -15
  39. package/src/providers/cursor.ts +7 -1
  40. package/src/providers/devin.ts +82 -28
  41. package/src/providers/openai-chat-server.ts +4 -0
  42. package/src/providers/openai-codex/request-transformer.ts +36 -0
  43. package/src/providers/openai-codex-responses.ts +35 -12
  44. package/src/providers/openai-completions.ts +45 -12
  45. package/src/providers/openai-reasoning-fallback.ts +6 -6
  46. package/src/providers/openai-responses-server.ts +2 -1
  47. package/src/providers/openai-responses.ts +25 -4
  48. package/src/providers/openai-shared.ts +201 -51
  49. package/src/registry/oauth/perplexity.ts +94 -28
  50. package/src/registry/oauth/types.ts +9 -0
  51. package/src/stream.ts +29 -7
  52. package/src/types.ts +10 -1
  53. package/src/usage/claude.ts +33 -0
  54. package/src/usage/google-antigravity.ts +8 -2
  55. package/src/usage.ts +3 -0
  56. package/src/utils/block-symbols.ts +57 -0
  57. package/src/utils/openai-http.ts +39 -3
  58. package/src/utils/retry-after.ts +12 -0
  59. package/src/utils/schema/normalize.ts +3 -3
  60. package/src/utils/schema/stamps.ts +33 -45
  61. package/src/utils/schema/wire.ts +9 -7
  62. package/src/utils.ts +67 -22
package/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.1] - 2026-09-15
6
+
7
+ ### Added
8
+
9
+ - Added support for Cerebras Qwen 3.8-27b with improved reasoning effort control
10
+ - Added optional host browser-session callbacks for Perplexity SSO login, keeping browser automation out of pi-ai and preserving email and authenticator-code login.
11
+
12
+ ### Fixed
13
+
14
+ - Fixed the auth-gateway sending a model's own reasoning back to Anthropic as demoted plain text, which tripped the `reasoning_extraction` classifier on Fable, leaked reasoning into visible answers on Opus, Sonnet and Haiku, and broke the prompt cache prefix on every tool-calling turn. Replayed assistant turns now carry the model id the request resolves to and a `stopReason` derived from the turn's own tool calls, so same-model thinking blocks keep their signatures and replay natively ([#12115](https://github.com/can1357/oh-my-pi/pull/12115) by [@Zhu-Aemon](https://github.com/Zhu-Aemon)).
15
+ - Fixed custom OpenAI-compatible Responses streams crashing on omitted delta payloads or reasoning-summary fields, and recovered text delivered only in completed snapshots ([#11863](https://github.com/can1357/oh-my-pi/pull/11863) by [@moodiness](https://github.com/moodiness)).
16
+ - Fixed streaming CPU blowup on long Responses turns: per-delta content-index lookups are now O(1) instead of re-scanning the accumulated content blocks, eliminating the quadratic work that could freeze the TUI for tens of seconds to minutes while a subagent streams ([#10605](https://github.com/can1357/oh-my-pi/issues/10605)).
17
+ - Fixed sessions permanently wedged by `400 Invalid signature in thinking block` after a failover proxy swapped upstream models mid-conversation (e.g. Claude -> GLM -> Claude): when the unsigned-demotion retry fails identically, the anthropic-messages transport now retries once with replayed thinking dropped and pins that mode for the session, so the conversation continues without starting a new session ([#12006](https://github.com/can1357/oh-my-pi/pull/12006) by [@Damin-Lee](https://github.com/Damin-Lee)).
18
+ - Fixed OpenAI Codex backend rejecting requests with HTTP 400 (`string_above_max_length`) when replaying tool call IDs exceeding 64 characters or containing composite delimiters (`|`, `\n`) by sanitizing and deterministically clamping call IDs on the wire ([#11342](https://github.com/can1357/oh-my-pi/pull/11342)).
19
+ - Fixed OpenRouter multi-turn tool-call sessions failing with `400 Referenced reasoning item ... was not found or has expired` on Meta Muse Spark models by suppressing reasoning reconstruction when history is filtered and synthetic replay is disallowed, while preserving Anthropic and DeepSeek replay ([#10966](https://github.com/can1357/oh-my-pi/issues/10966)).
20
+ - The auth gateway now keeps provider session state per session, so a model reached through it stops re-learning the same rejection every turn. Sticky fallbacks such as strict-tools and fast mode previously did nothing on the `pi-native` transport used by containerized and robomp deployments, because the state cannot cross the wire and the gateway kept none of its own ([#12058](https://github.com/can1357/oh-my-pi/pull/12058) by [@camjac251](https://github.com/camjac251)).
21
+ - Fixed full OpenAI Responses request-body timeout recovery so the exact HTTP 408 is surfaced for a changed-request recovery instead of repeated unchanged transport retries when eligible tool-result history can be safely elided ([#11878](https://github.com/can1357/oh-my-pi/pull/11878) by [@hellofrommorgan](https://github.com/hellofrommorgan)).
22
+ - Fixed Codex sessions producing unrelated visible output on later turns after a progress-only response. ([#11466](https://github.com/can1357/oh-my-pi/issues/11466))
23
+ - Fixed OpenRouter reasoning models (e.g. Meta Muse Spark) rejecting every turn with `400 Provider returned error` after the session history contains a tool-call turn from another provider, by no longer sending a fabricated reasoning item id ([#11791](https://github.com/can1357/oh-my-pi/pull/11791) by [@brndnmtthws](https://github.com/brndnmtthws)).
24
+ - Fixed statusless stream-drop diagnostics (stream disconnected/closed before `response.completed`, upstream stream interrupted or ended before its terminal chunk, socket disconnected before the secure TLS handshake) classifying as terminal errors, so they now retry like their status-tagged twins instead of settling the turn ([#11805](https://github.com/can1357/oh-my-pi/issues/11805)).
25
+ - OpenAI-compatible endpoints that report `ReasoningEffort` in CamelCase now trigger effort-downgrade retries instead of terminating turns with HTTP 400 ([#11804](https://github.com/can1357/oh-my-pi/issues/11804)).
26
+ - Fixed openai-responses replay wedging a repaired orphan tool-result note between another call's `function_call` and `function_call_output`, which broke round pairing on strict validators (e.g. DeepSeek) with `400 No tool output found for tool call …`: orphan-output/call repair now runs before the interleaved-message hoist, so any injected note is relocated out of the tool-call batch ([#11473](https://github.com/can1357/oh-my-pi/issues/11473)).
27
+ - A stale Anthropic tier block (`tier:fable`, `tier:mythos`) is now cleared once a live usage report shows headroom on both the tier row and the shared windows, instead of idling a usable account until the reported reset. Healing requires a live report, and a credential held by an unscoped block spends no usage request on a probe that cannot lift it ([#11334](https://github.com/can1357/oh-my-pi/pull/11334) by [@AshishKumar4](https://github.com/AshishKumar4)).
28
+ - A running session now picks up credentials another process committed: adding an account in a second terminal is visible to credential selection and rotation without restarting the session, and a session's pinned account is re-resolved by row id so a row another process deleted cannot hand its slot to a sibling ([#11329](https://github.com/can1357/oh-my-pi/pull/11329) by [@AshishKumar4](https://github.com/AshishKumar4)).
29
+ - Fixed rate-limit/overload failures that arrive *inside* an HTTP 200 body (Azure, LiteLLM-style aggregators, and reverse proxies that already committed to the stream) not advancing `retry.fallbackChains`: a `{"error":{…}}`/`{"code":429}` chunk or a plain-text throttle frame (`429 Too Many Requests`, an nginx page) is now classified as a retryable 429/5xx through the same path an HTTP-status 429 takes, so a busy provider backs off and fails over instead of ending the session. Only bodies the provider actually reported are used: no status is inferred from error wording, and an unreadable body can no longer consume a credential.
30
+ - Fixed tool schema normalization and cycle detection for frozen, sealed, and nonextensible schemas.
31
+ - Reduced memory retained by `complete()` and `completeSimple()` while streaming responses.
32
+ - Antigravity quota summaries now identify Claude/GPT routing copies as one shared upstream pool while preserving model-specific quota selection ([#11268](https://github.com/can1357/oh-my-pi/issues/11268)).
33
+ - Fixed the auth-gateway rejecting `content: null` on `/v1/responses` and `/v1/chat/completions` message items with a 400; Codex and other OpenAI clients that emit null content on empty turns now work, matching OpenAI's tolerance ([#10956](https://github.com/can1357/oh-my-pi/issues/10956)).
34
+ - Fixed Azure GPT-6 Astra Chat Completions requests with function tools sending a non-`none` reasoning effort, which Azure rejects with HTTP 400 ([#11052](https://github.com/can1357/oh-my-pi/issues/11052)).
35
+ - Fixed Z.AI and Zhipu usage-limit credential blocks and oneshot completion retries (titles, summaries, classifiers) resolving eight hours late when provider responses omit the reset timestamp timezone ([#11014](https://github.com/can1357/oh-my-pi/issues/11014)).
36
+ - Fixed provider requests failing with `ENOENT` when another process removes a stale shared concurrency lock during acquisition.
37
+ - Fixed Devin-hosted Gemini models rejecting turns that include nullable tool parameters by normalizing tool schemas to Gemini's supported JSON Schema dialect ([#8647](https://github.com/can1357/oh-my-pi/issues/8647), [#10233](https://github.com/can1357/oh-my-pi/pull/10233) by [@will-bogusz](https://github.com/will-bogusz)).
38
+ - Fixed Devin gateway failures leaking raw proxy HTML into turn errors; HTTP status and retry metadata remain available for recovery ([#10233](https://github.com/can1357/oh-my-pi/pull/10233) by [@will-bogusz](https://github.com/will-bogusz)).
39
+
40
+ ## [18.2.0] - 2026-09-15
41
+
42
+ ### Added
43
+
44
+ - Assistant turns from Anthropic-compatible hosts (direct, or via OpenRouter's `reasoning_details`) carry `upstreamModel`, the serving model id recovered from the signed thinking block, so callers can detect a router substituting a different model than requested.
45
+
46
+ ### Fixed
47
+
48
+ - Fixed OpenCode Go window-limit 429s (`5-hour`/`Weekly`/`Monthly usage limit reached. Resets in …`) not pinning the exhausted credential to the server-stated reset; the window phrasing is now covered by a regression test over the rotation classifier. ([#12091](https://github.com/can1357/oh-my-pi/pull/12091) by [@H4vC](https://github.com/H4vC))
49
+
5
50
  ## [18.1.22] - 2026-09-14
6
51
 
7
52
  ### Fixed
@@ -10843,7 +10843,7 @@ RUST RUNTIME DEPENDENCY LICENSES
10843
10843
 
10844
10844
  Generated from Cargo.lock by cargo-about 0.8.2 in locked, offline, workspace, all-feature, all-target mode, then restricted using cargo metadata to normal and build edges reachable from the workspace (development-only edges are excluded). cargo-deny independently evaluates the complete all-target graph.
10845
10845
 
10846
- Native Opus (audiopus/audiopus_sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10846
+ Native Opus (opus/opusic-sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10847
10847
 
10848
10848
  -------------------------------------------------------------------------------
10849
10849
  License: Apache License 2.0 (Apache-2.0)
@@ -12509,6 +12509,58 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
12509
12509
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
12510
12510
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12511
12511
 
12512
+ -------------------------------------------------------------------------------
12513
+ License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
+ Used by:
12515
+ - opusic-sys 0.7.5 (https://github.com/DoumanAsh/opusic-sys)
12516
+
12517
+ Full license text:
12518
+
12519
+ Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
12520
+ Jean-Marc Valin, Timothy B. Terriberry,
12521
+ CSIRO, Gregory Maxwell, Mark Borgerding,
12522
+ Erik de Castro Lopo, Mozilla, Amazon
12523
+
12524
+ Redistribution and use in source and binary forms, with or without
12525
+ modification, are permitted provided that the following conditions
12526
+ are met:
12527
+
12528
+ - Redistributions of source code must retain the above copyright
12529
+ notice, this list of conditions and the following disclaimer.
12530
+
12531
+ - Redistributions in binary form must reproduce the above copyright
12532
+ notice, this list of conditions and the following disclaimer in the
12533
+ documentation and/or other materials provided with the distribution.
12534
+
12535
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the
12536
+ names of specific contributors, may be used to endorse or promote
12537
+ products derived from this software without specific prior written
12538
+ permission.
12539
+
12540
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12541
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12542
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12543
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
12544
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
12545
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
12546
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
12547
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
12548
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
12549
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
12550
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12551
+
12552
+ Opus is subject to the royalty-free patent licenses which are
12553
+ specified at:
12554
+
12555
+ Xiph.Org Foundation:
12556
+ https://datatracker.ietf.org/ipr/1524/
12557
+
12558
+ Microsoft Corporation:
12559
+ https://datatracker.ietf.org/ipr/1914/
12560
+
12561
+ Broadcom Corporation:
12562
+ https://datatracker.ietf.org/ipr/1526/
12563
+
12512
12564
  -------------------------------------------------------------------------------
12513
12565
  License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
12566
  Used by:
@@ -13290,29 +13342,6 @@ jurisdiction of the Federal Courts of the Northern District of
13290
13342
  California and the state courts of the State of California, with
13291
13343
  venue lying in Santa Clara County, California.
13292
13344
 
13293
- -------------------------------------------------------------------------------
13294
- License: ISC License (ISC)
13295
- Used by:
13296
- - audiopus_sys 0.2.2 (https://github.com/lakelezz/audiopus_sys.git)
13297
-
13298
- Full license text:
13299
-
13300
- ISC License
13301
-
13302
- Copyright (c) 2019, Lakelezz
13303
-
13304
- Permission to use, copy, modify, and/or distribute this software for any
13305
- purpose with or without fee is hereby granted, provided that the above
13306
- copyright notice and this permission notice appear in all copies.
13307
-
13308
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13309
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13310
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13312
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13313
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13314
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
13315
-
13316
13345
  -------------------------------------------------------------------------------
13317
13346
  License: ISC License (ISC)
13318
13347
  Used by:
@@ -18734,7 +18763,7 @@ DEALINGS IN THE SOFTWARE.
18734
18763
  -------------------------------------------------------------------------------
18735
18764
  License: MIT License (MIT)
18736
18765
  Used by:
18737
- - opus 0.3.1 (https://github.com/SpaceManiac/opus-rs)
18766
+ - opus 0.4.0 (https://github.com/SpaceManiac/opus-rs)
18738
18767
 
18739
18768
  Full license text:
18740
18769
 
@@ -51,6 +51,23 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
51
51
  get snapshot(): SnapshotResponse;
52
52
  /** Re-hydrate the in-memory snapshot from the broker. */
53
53
  refreshSnapshot(): Promise<SnapshotResponse>;
54
+ /**
55
+ * Stateful probe for broker-side credential changes, mirroring
56
+ * {@link SqliteAuthCredentialStore.pollExternalChanges} so long-lived broker
57
+ * clients (notably `auth-gateway serve`) pick up logins/logouts made by
58
+ * another process without a restart.
59
+ *
60
+ * Compares a local content revision, not the broker's numeric generation:
61
+ * generation is an in-memory counter that resets when the broker process
62
+ * restarts, so a reconnecting stream can deliver a different credential set
63
+ * under a repeated (or lower) generation. {@link #refreshCredentialRevision}
64
+ * bumps the revision whenever the applied credential material actually
65
+ * changes, catching those cases too. Records foreground activity first: a
66
+ * low-traffic client's background sync parks after `#backgroundIdleMs`, and
67
+ * without this wakeup it would never fetch the new snapshot to report in the
68
+ * first place.
69
+ */
70
+ pollExternalChanges(): boolean;
54
71
  listAuthCredentials(provider?: string): StoredAuthCredential[];
55
72
  /** Broker-backed disabled tombstones; empty against brokers predating the endpoint. */
56
73
  listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
@@ -1,3 +1,4 @@
1
1
  export * from "./http.js";
2
+ export * from "./session-state.js";
2
3
  export * from "./server.js";
3
4
  export * from "./types.js";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Server-owned provider session state for the auth-gateway.
3
+ *
4
+ * `SimpleStreamOptions.providerSessionState` is how a provider keeps what it
5
+ * learned about an endpoint across turns of one conversation: Anthropic's
6
+ * sticky `strictToolsDisabled` / `fastModeDisabled` /
7
+ * `replayUnsignedThinkingDisabled` flags and dropped-thinking-prefix set,
8
+ * OpenAI's strict-tools and reasoning-effort fallbacks, Codex's WebSocket and
9
+ * turn-state sessions. An in-process omp session owns that `Map` for its whole
10
+ * lifetime, so a grammar-too-large 400 or a fast-mode rejection costs one
11
+ * wasted round-trip per session rather than one per turn.
12
+ *
13
+ * The map is deliberately non-serializable — `Set`/`Map` fields, live sockets,
14
+ * a `close()` method — so `pi-native-client` strips it from the wire and
15
+ * `pi-native-server` never accepts it. Gateway clients therefore cannot bring
16
+ * their own, and without a server-side owner every containerized / robomp turn
17
+ * re-learns every lesson from a fresh upstream rejection.
18
+ *
19
+ * A plain `Map<sessionId, …>` in a long-lived server process is a leak: nothing
20
+ * ever reclaims an entry, and the entries own timers and sockets. This store is
21
+ * an LRU with a hard entry ceiling that calls `close()` on everything it drops
22
+ * and on everything it still holds at shutdown.
23
+ */
24
+ import type { Api, Model, ProviderSessionState } from "../types.js";
25
+ /**
26
+ * Retained logical sessions. Each entry is a handful of small provider records
27
+ * plus, for Codex, a WebSocket session — cheap to keep, but not free, so the
28
+ * ceiling is what turns "one entry per session id forever" into a bounded cost.
29
+ * Eviction is least-recently-used, so the ceiling only ever drops sessions that
30
+ * have been quiet longer than the 256 most recent ones.
31
+ */
32
+ export declare const AUTH_GATEWAY_MAX_SESSION_STATES = 256;
33
+ /**
34
+ * Bounded per-session provider state, owned by one gateway server instance.
35
+ *
36
+ * Two gateways in the same process get separate stores, so neither can hand a
37
+ * request another gateway's learned state or close it out from under one.
38
+ */
39
+ export declare class AuthGatewaySessionStateStore {
40
+ #private;
41
+ constructor(max?: number);
42
+ /** Retained logical sessions. */
43
+ get size(): number;
44
+ /**
45
+ * The provider-session map for one logical session on one model, created on
46
+ * first use and returned by reference so provider mutations persist into the
47
+ * next request.
48
+ *
49
+ * Keyed by session + provider + model id. The session is the identity that
50
+ * matters — it is the same identity used for credential stickiness and
51
+ * prefix-cache keying — but a client is free to reuse one session id across
52
+ * models, and the coarsest provider entries do not separate models
53
+ * themselves (`openai-responses` keys its strict-tools / history-replay
54
+ * record by provider alone, Antigravity by a single constant), so the model
55
+ * belongs in the key here. Endpoint is deliberately absent: every provider
56
+ * whose learning is endpoint-specific already sub-keys it internally
57
+ * (`anthropic-messages:${baseUrl}\0${modelId}`,
58
+ * `openai-completions:${provider}:${baseUrl}:${modelId}`), and repeating it
59
+ * would only fragment the map. NUL separates the components so none of them
60
+ * can forge the boundary.
61
+ */
62
+ acquire(sessionId: string, model: Model<Api>): Map<string, ProviderSessionState>;
63
+ /** Close and drop every retained state. Called when the gateway shuts down. */
64
+ close(): void;
65
+ }
@@ -502,6 +502,11 @@ export { isDefinitiveOAuthFailure } from "./error/auth-classify.js";
502
502
  * the usage report reveals. Callers that wait the account out (instead of
503
503
  * rotating) must sleep until this, not the error-text hint alone.
504
504
  *
505
+ * `requestedBlockedUntilMs` (epoch ms) is this mark call's initial deadline,
506
+ * before usage-report correction and longest-wins merging. Callers use it to
507
+ * distinguish the call's replaceable heuristic from a longer merged block
508
+ * that credential selection will continue enforcing.
509
+ *
505
510
  * `priorBlockedUntilMs` (epoch ms) is the live block deadline the map already
506
511
  * stored for this credential before this call. The merged `blockedUntilMs`
507
512
  * masks a pre-existing block shorter than this call's own heuristic
@@ -527,6 +532,8 @@ export interface UsageLimitMarkResult {
527
532
  switched: boolean;
528
533
  retryAtMs?: number;
529
534
  blockedUntilMs?: number;
535
+ /** This mark call's initial deadline, before report correction and merging. */
536
+ requestedBlockedUntilMs?: number;
530
537
  priorBlockedUntilMs?: number;
531
538
  priorBlockedUntilTimed?: boolean;
532
539
  reportResetAtMs?: number;
@@ -1116,6 +1123,15 @@ export declare class AuthStorage {
1116
1123
  pinSessionOAuthAccount(provider: string, sessionId: string, credentialId: number, options?: {
1117
1124
  lastUsedAtMs?: number;
1118
1125
  }): boolean;
1126
+ /**
1127
+ * Copy every stored credential affinity from one live session to another.
1128
+ *
1129
+ * The target receives its own sticky entries, so request resolution, usage
1130
+ * blocking, credential rotation, metadata, and persisted pins all continue
1131
+ * through the target session id without retaining a live dependency on the
1132
+ * source session.
1133
+ */
1134
+ inheritSessionCredentials(sourceSessionId: string, targetSessionId: string): number;
1119
1135
  /**
1120
1136
  * Resolve every stored OAuth credential for `provider` independently.
1121
1137
  *
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Build the classified error for an in-band failure frame, or `undefined` when
3
+ * the frame is not a retryable in-band failure (in which case the caller keeps
4
+ * its existing handling and message).
5
+ *
6
+ * @param frame decoded SSE `data:` payload, or the `{ error, response }` subset of one
7
+ */
8
+ export declare function createInBandProviderError(frame: unknown): Error | undefined;
9
+ /**
10
+ * Build the classified error for a non-JSON SSE frame: gateways and reverse
11
+ * proxies that answer `data: 429 Too Many Requests` or an HTML throttle page
12
+ * instead of an OpenAI envelope. `undefined` when the text is not recognisable
13
+ * as a throttle, so genuinely malformed payloads keep failing loudly.
14
+ */
15
+ export declare function createInBandProviderErrorFromText(text: string): Error | undefined;
@@ -34,6 +34,15 @@ export declare const PYTHON_HTTP2_STREAM_RESET_PATTERN: RegExp;
34
34
  export declare const PYTHON_HTTP_INCOMPLETE_CHUNK_PATTERN: RegExp;
35
35
  /** reqwest body-frame failures forwarded by the Codex HTTP proxy. */
36
36
  export declare const CODEX_HTTP_BODY_READ_ERROR_PATTERN: RegExp;
37
+ /** Exact HTTP request-body-read timeout diagnostic. */
38
+ export declare function isRequestBodyReadTimeout(status: number | undefined, message: string | undefined): boolean;
39
+ /** Exact pre-output Responses 408 that needs a changed-request recovery path. */
40
+ export declare function isResponsesRequestBodyReadTimeout(message: {
41
+ api?: Api;
42
+ errorStatus?: number;
43
+ errorMessage?: string;
44
+ requestBodyReadTimeoutFullReplay?: boolean;
45
+ }): boolean;
37
46
  export declare const TRANSIENT_TRANSPORT_PATTERN: RegExp;
38
47
  /**
39
48
  * Local llama.cpp / Ollama deterministic tool-call argument JSON parse failure.
@@ -131,6 +140,13 @@ export declare function stringify(id: number | undefined): string;
131
140
  * low-signal to trust once detached from a live transport `Error`, which keeps the broad pattern.
132
141
  */
133
142
  export declare function isTransientStreamParseError(error: unknown): boolean;
143
+ /**
144
+ * Transport stream-drop diagnostic (see {@link STREAM_DROP_PATTERN}). Unlike
145
+ * {@link isTransientStreamParseError}, one pattern serves both the live `Error`
146
+ * and the persisted-string forms: the phrasings are high-signal enough to trust
147
+ * detached from a transport `Error`.
148
+ */
149
+ export declare function isTransientStreamDropError(error: unknown): boolean;
134
150
  /** Any malformed stream-envelope error (prefix-tagged or out-of-order events). */
135
151
  export declare function isStreamEnvelopeError(error: unknown): boolean;
136
152
  /** Stream-envelope errors safe to retry against the provider (event ordering only). */
@@ -2,6 +2,7 @@ export * from "./abort.js";
2
2
  export * from "./auth.js";
3
3
  export * from "./auth-classify.js";
4
4
  export * from "./aws.js";
5
+ export * from "./body-error.js";
5
6
  export * from "./classes.js";
6
7
  export * from "./finalize.js";
7
8
  export * from "./flags.js";
@@ -63,6 +63,12 @@ export interface OneshotRetryOptions {
63
63
  * Thrown errors need no wiring — headers are recovered from the error itself.
64
64
  */
65
65
  getResponseHeaders?: () => HeadersLike;
66
+ /**
67
+ * Provider id of the model being retried. Selects the catalog-declared
68
+ * timezone for a timezone-naive absolute reset stamp (Z.AI/Zhipu report
69
+ * Beijing time), so an over-cap wait is not misread as UTC and discarded.
70
+ */
71
+ provider?: string;
66
72
  /** Observability hook. Fires immediately before sleeping. */
67
73
  onRetry?: (info: OneshotRetryInfo) => void;
68
74
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Recovers the serving model id Anthropic embeds in thinking-block signatures.
3
+ *
4
+ * A signature is a base64 protobuf: outer `{1: version, 2: envelope}`, envelope
5
+ * `{1: header, 2..5: nonce/ciphertext/tag}`, header
6
+ * `{1: schema, 5: hash, 6: model, 8: "thinking", 11: org uuid, 15: profile, 21: unix ts}`.
7
+ * Field 6 names the model that actually produced the block — a concrete
8
+ * snapshot (`claude-haiku-4-5-20251001`) or an internal codename
9
+ * (`numbat-v6-…`) — which lets a caller notice a gateway serving something other
10
+ * than the requested model. The v4 outer format (`CAQS…`) ships a header
11
+ * without field 6; the decoder then yields nothing.
12
+ */
13
+ /**
14
+ * Model id embedded in an Anthropic thinking signature, or undefined when the
15
+ * signature is malformed, uses a format without a cleartext header, or names
16
+ * nothing resembling a model id.
17
+ */
18
+ export declare function servedModelFromAnthropicSignature(signature: string): string | undefined;
19
+ /**
20
+ * Model id from an OpenRouter reasoning item — a `reasoning_details` entry on
21
+ * chat completions or a Responses `reasoning` output item — which forwards the
22
+ * upstream Anthropic signature as `{ format: "anthropic-claude-v1", signature }`.
23
+ * Undefined for other formats.
24
+ */
25
+ export declare function servedModelFromOpenRouterReasoning(item: unknown): string | undefined;
@@ -74,6 +74,33 @@ export interface RequestBody {
74
74
  * explicit option; normal inference defaults to the full Responses contract.
75
75
  */
76
76
  export declare function resolveCodexResponsesLite(requested: boolean | undefined): boolean;
77
+ /**
78
+ * Repair both halves of unpaired tool exchanges so the Responses input grammar
79
+ * stays valid — the API rejects either orphan with a 400:
80
+ *
81
+ * - `function_call_output` / `custom_tool_call_output` with no matching call →
82
+ * folded into an assistant message (`400 No tool call found for … output`).
83
+ * Regression of #472 / #1351.
84
+ * - `function_call` / `custom_tool_call` with no matching `*_output` → a
85
+ * placeholder output is synthesized immediately after the call
86
+ * (`400 No tool output found for function call …`). Hit when the user
87
+ * branches/navigates the session tree to a node that ends on a tool call (the
88
+ * tool-result child is dropped from the reconstructed history) or when a turn
89
+ * is aborted/crashes after the call streamed but before its result persisted.
90
+ */
91
+ /**
92
+ * Sanitize an OpenAI Responses/Codex tool call ID to <= 64 characters and valid charset.
93
+ * Composite IDs with '|' or '\n' have their secondary/item part stripped.
94
+ * Hashing is anchored on the canonical base part so assistant and result composites
95
+ * with different item halves stay identical. Short lossy changes include a hash suffix
96
+ * to preserve collision resistance across distinct IDs.
97
+ */
98
+ export declare function sanitizeCodexCallId(rawCallId: string): string;
99
+ /**
100
+ * In-place mutates the `call_id` property on every input item in the array to conform
101
+ * to the OpenAI Responses/Codex 64-character limit and valid charset constraints.
102
+ */
103
+ export declare function sanitizeInputCallIds(input: InputItem[]): void;
77
104
  /**
78
105
  * Structural view of a Responses-style body mutated by the Lite rewrite.
79
106
  * Loose (`unknown`) property types let the turn transformer (`RequestBody`)
@@ -6,7 +6,7 @@ import { kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-sy
6
6
  import type { AssistantMessageEventStream } from "../utils/event-stream.js";
7
7
  import type { CapturedHttpErrorResponse } from "../utils/http-inspector.js";
8
8
  import type { ChatCompletionCreateParamsStreaming } from "./openai-chat-wire.js";
9
- import type { InputItem } from "./openai-codex/request-transformer.js";
9
+ import { type InputItem } from "./openai-codex/request-transformer.js";
10
10
  import type { ResponseComputerToolCall, ResponseContentPartAddedEvent, ResponseCreateParamsStreaming, ResponseInput, ResponseInputContent, ResponseInputItem, ResponseOutputItem, ResponseOutputMessage, ResponseReasoningItem, ResponseStatus, ResponseStreamEvent } from "./openai-responses-wire.js";
11
11
  /**
12
12
  * Keyless-provider sentinel. Custom providers configured with `auth: none`
@@ -222,6 +222,21 @@ export interface OpenAIExtraBodyOptions {
222
222
  export declare function applyOpenAIExtraBody<P extends object>(params: P & {
223
223
  venice_parameters?: Record<string, unknown>;
224
224
  }, extraBody: Record<string, unknown> | undefined, options?: OpenAIExtraBodyOptions): void;
225
+ /**
226
+ * Normalize `content: null` to `[]` in place across a list of inbound wire
227
+ * message items.
228
+ *
229
+ * Codex (and other OpenAI clients) emit `content: null` on empty message items
230
+ * during multi-turn/tool turns; OpenAI tolerates it. Normalizing before the
231
+ * auth-gateway's request schema validation lets the item take the same path as
232
+ * an explicit empty content array — for both validation and any native
233
+ * history-replay clone — instead of 400ing. Shared by the `/v1/responses`
234
+ * (`input[]`) and `/v1/chat/completions` (`messages[]`) routes.
235
+ *
236
+ * `isEligible` lets a route skip items whose content is not array-typed, e.g.
237
+ * the chat `function` role whose content is `string | null`. See issue #10956.
238
+ */
239
+ export declare function coerceNullMessageContentInPlace(items: unknown, isEligible?: (item: Record<string, unknown>) => boolean): void;
225
240
  /**
226
241
  * Chat Completions streaming request body shaped by the OpenAI-family providers.
227
242
  * (binary `thinking`, Qwen `enable_thinking`/`chat_template_kwargs`, Venice
@@ -273,7 +288,7 @@ export interface ChatCompletionsReasoningOptions {
273
288
  thinkingBudgets?: Partial<Record<Effort, number>>;
274
289
  }
275
290
  export type OpenAICompatEndpoint = "chat-completions" | "responses";
276
- export type OpenAIReasoningDisableReason = "caller" | "forced-tool-choice" | "tool-choice" | "not-requested";
291
+ export type OpenAIReasoningDisableReason = "caller" | "forced-tool-choice" | "tool-choice" | "tools" | "not-requested";
277
292
  export type OpenAICompatPolicyCompat = ResolvedOpenAISharedCompat & Partial<ResolvedOpenAICompat> & Partial<ResolvedOpenAIResponsesCompat>;
278
293
  export interface ResolveOpenAICompatPolicyOptions {
279
294
  endpoint: OpenAICompatEndpoint;
@@ -281,6 +296,7 @@ export interface ResolveOpenAICompatPolicyOptions {
281
296
  reasoning?: string;
282
297
  disableReasoning?: boolean;
283
298
  toolChoice?: unknown;
299
+ hasTools?: boolean;
284
300
  strictResponsesPairing?: boolean;
285
301
  includeEncryptedReasoning?: boolean;
286
302
  filterReasoningHistory?: boolean;
@@ -351,6 +367,7 @@ export declare function resolveOpenAICompatPolicy<TApi extends Api>(model: Model
351
367
  export declare function applyChatCompletionsCompatPolicy(params: OpenAICompletionsParams, policy: OpenAICompatPolicy): void;
352
368
  export declare function applyChatCompletionsReasoningParams(params: OpenAICompletionsParams, model: Model<"openai-completions">, compat: ResolvedOpenAICompat, options: (ChatCompletionsReasoningOptions & {
353
369
  toolChoice?: unknown;
370
+ hasTools?: boolean;
354
371
  }) | undefined): void;
355
372
  export declare function disableChatCompletionsReasoningForDialect(params: OpenAICompletionsParams, compat: ResolvedOpenAICompat): void;
356
373
  /**
@@ -550,7 +567,7 @@ type ResponsesToolCallBlock = ToolCall & {
550
567
  [kStreamingPartialJson]: string;
551
568
  [kStreamingLastParseLen]?: number;
552
569
  };
553
- export declare function appendReasoningSummaryPart(item: ResponseReasoningItem, part: ResponseReasoningItem["summary"][number]): void;
570
+ export declare function appendReasoningSummaryPart(item: ResponseReasoningItem, part: ResponseReasoningItem["summary"][number] | undefined): void;
554
571
  /**
555
572
  * Response-global accumulator for the sequential-cutoff summary contract.
556
573
  *
@@ -1,9 +1,3 @@
1
1
  import type { OAuthController, OAuthCredentials } from "./types.js";
2
- /**
3
- * Login to Perplexity.
4
- *
5
- * Tries auto-extraction from the desktop app, then runs HTTP email OTP login.
6
- *
7
- * No browser/manual token paste fallback is used.
8
- */
2
+ /** Prefer legacy app borrowing, then offer browser SSO when the host supports it. */
9
3
  export declare function loginPerplexity(ctrl: OAuthController): Promise<OAuthCredentials>;
@@ -63,12 +63,20 @@ export interface OAuthProviderInfo {
63
63
  */
64
64
  storeCredentialsAs?: string;
65
65
  }
66
+ /** Sign-in URL and accepted cookies for an isolated, host-owned browser. */
67
+ export type OAuthBrowserSessionRequest = {
68
+ url: string;
69
+ /** Cookie names in preference order; return the first non-empty matching value. */
70
+ cookieNames: readonly string[];
71
+ };
66
72
  export interface OAuthController {
67
73
  onAuth?(info: OAuthAuthInfo): void;
68
74
  onProgress?(message: string): void;
69
75
  /** Request pasted callback input; stop any visible prompt when `signal` aborts. */
70
76
  onManualCodeInput?(signal?: AbortSignal): Promise<string>;
71
77
  onPrompt?(prompt: OAuthPrompt): Promise<string>;
78
+ /** Complete browser login and return one matching cookie value privately. Reject on cancellation or failure. */
79
+ onBrowserSession?(request: OAuthBrowserSessionRequest, signal?: AbortSignal): Promise<string>;
72
80
  signal?: AbortSignal;
73
81
  fetch?: FetchImpl;
74
82
  }
@@ -17,6 +17,8 @@ export declare const __providerInFlightForTesting: {
17
17
  setHeartbeatWriter(writer: ((writeProviderInFlightInfo: () => Promise<void>) => Promise<void>) | undefined): void;
18
18
  setLeaseRemover(remover: ((leasePath: string) => Promise<void>) | undefined): void;
19
19
  setWaitObserver(observer: ((provider: string) => void) | undefined): void;
20
+ setLockCreatedObserver(observer: ((lockDir: string) => Promise<void>) | undefined): void;
21
+ setLockIdentifiedObserver(observer: ((lockDir: string) => Promise<void>) | undefined): void;
20
22
  providerDir(provider: string): string;
21
23
  lockDir(provider: string): string;
22
24
  captureStaleLockRelease(provider: string): Promise<(() => Promise<void>) | null>;
@@ -839,7 +839,13 @@ export interface AssistantMessage {
839
839
  * providers that expose no such field.
840
840
  */
841
841
  upstreamProvider?: string;
842
- /** Provider-reported concrete model when a router selected one for this turn. */
842
+ /**
843
+ * Concrete model that produced this turn when it is knowable independently
844
+ * of the requested id: reported by a router that selected one, or recovered
845
+ * from a signed thinking block (Anthropic signatures name the serving
846
+ * model). Compared against `model` to notice a gateway serving something
847
+ * other than what was requested.
848
+ */
843
849
  upstreamModel?: string;
844
850
  usage: Usage;
845
851
  stopReason: StopReason;
@@ -847,6 +853,8 @@ export interface AssistantMessage {
847
853
  errorMessage?: string;
848
854
  /** Stable recovery-classification text when errorMessage includes display-only diagnostics. */
849
855
  errorClassificationMessage?: string;
856
+ /** True only when an exact request-body-read timeout failed on a full Responses replay, not a previous-response delta. */
857
+ requestBodyReadTimeoutFullReplay?: boolean;
850
858
  /** Per-tool abort messages used when an aborted assistant turn needs different placeholder results per tool call. */
851
859
  toolCallAbortMessages?: Record<string, string>;
852
860
  /** HTTP status surfaced by the provider when the request failed. Populated by every provider's catch block alongside `errorMessage` so consumers (auth retry, telemetry, UI) can branch without regex-scraping the message. */
@@ -967,7 +975,7 @@ export interface CursorTodoSnapshot {
967
975
  * provider persists this value verbatim. When no handler is registered at all,
968
976
  * the provider falls back to its own summary-only result.
969
977
  */
970
- export type CursorTodoSyncHandler = (snapshot: CursorTodoSnapshot | null, toolCallId: string, error: string | null) => ToolResultMessage;
978
+ export type CursorTodoSyncHandler = (snapshot: CursorTodoSnapshot | null, toolCallId: string, error: string | null, origin?: "read" | "update") => ToolResultMessage;
971
979
  export interface CursorShellStreamCallbacks {
972
980
  onStdout(data: string): void;
973
981
  onStderr(data: string): void;
@@ -43,6 +43,8 @@ export interface UsageScope {
43
43
  tier?: string;
44
44
  windowId?: string;
45
45
  shared?: boolean;
46
+ /** Stable identity shared by routing-specific copies of one upstream quota. */
47
+ sharedGroup?: string;
46
48
  }
47
49
  /** Normalized limit entry for a single window or quota bucket. */
48
50
  export interface UsageLimit {
@@ -241,6 +243,7 @@ export declare const usageScopeSchema: import("@oh-my-pi/omptype").FluentType<{
241
243
  projectId?: string | undefined;
242
244
  provider: string;
243
245
  shared?: boolean | undefined;
246
+ sharedGroup?: string | undefined;
244
247
  tier?: string | undefined;
245
248
  windowId?: string | undefined;
246
249
  }, {
@@ -250,6 +253,7 @@ export declare const usageScopeSchema: import("@oh-my-pi/omptype").FluentType<{
250
253
  projectId?: string | undefined;
251
254
  provider: string;
252
255
  shared?: boolean | undefined;
256
+ sharedGroup?: string | undefined;
253
257
  tier?: string | undefined;
254
258
  windowId?: string | undefined;
255
259
  }>;
@@ -272,6 +276,7 @@ export declare const usageLimitSchema: import("@oh-my-pi/omptype").FluentType<{
272
276
  projectId?: string | undefined;
273
277
  provider: string;
274
278
  shared?: boolean | undefined;
279
+ sharedGroup?: string | undefined;
275
280
  tier?: string | undefined;
276
281
  windowId?: string | undefined;
277
282
  };
@@ -302,6 +307,7 @@ export declare const usageLimitSchema: import("@oh-my-pi/omptype").FluentType<{
302
307
  projectId?: string | undefined;
303
308
  provider: string;
304
309
  shared?: boolean | undefined;
310
+ sharedGroup?: string | undefined;
305
311
  tier?: string | undefined;
306
312
  windowId?: string | undefined;
307
313
  };
@@ -359,6 +365,7 @@ export declare const usageReportSchema: import("@oh-my-pi/omptype").FluentType<{
359
365
  projectId?: string | undefined;
360
366
  provider: string;
361
367
  shared?: boolean | undefined;
368
+ sharedGroup?: string | undefined;
362
369
  tier?: string | undefined;
363
370
  windowId?: string | undefined;
364
371
  };
@@ -404,6 +411,7 @@ export declare const usageReportSchema: import("@oh-my-pi/omptype").FluentType<{
404
411
  projectId?: string | undefined;
405
412
  provider: string;
406
413
  shared?: boolean | undefined;
414
+ sharedGroup?: string | undefined;
407
415
  tier?: string | undefined;
408
416
  windowId?: string | undefined;
409
417
  };