@linxiraos/pi-ai 1.2.1 → 1.3.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 (153) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +4 -2
  3. package/THIRD-PARTY-NOTICES.txt +55 -5
  4. package/dist/types/auth/cascade.d.ts +5 -3
  5. package/dist/types/auth/pool.d.ts +3 -3
  6. package/dist/types/auth/select.d.ts +5 -1
  7. package/dist/types/auth/types.d.ts +15 -5
  8. package/dist/types/auth/usage-cache.d.ts +3 -1
  9. package/dist/types/auth-gateway/clients.d.ts +67 -0
  10. package/dist/types/auth-gateway/dispatch.d.ts +28 -10
  11. package/dist/types/auth-gateway/http.d.ts +2 -0
  12. package/dist/types/auth-gateway/index.d.ts +1 -0
  13. package/dist/types/auth-gateway/server.d.ts +9 -4
  14. package/dist/types/auth-gateway/session-state.d.ts +6 -0
  15. package/dist/types/auth-gateway/types.d.ts +16 -2
  16. package/dist/types/dialect/coercion.d.ts +6 -0
  17. package/dist/types/dialect/deepseek.d.ts +13 -0
  18. package/dist/types/error/classes.d.ts +1 -1
  19. package/dist/types/error/flags.d.ts +15 -0
  20. package/dist/types/error/index.d.ts +1 -0
  21. package/dist/types/error/network-cause.d.ts +40 -0
  22. package/dist/types/images/shared.d.ts +26 -2
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/judgment/native.d.ts +62 -3
  25. package/dist/types/judgment/systemone.d.ts +9 -2
  26. package/dist/types/judgment/text.d.ts +11 -5
  27. package/dist/types/judgment/types.d.ts +37 -6
  28. package/dist/types/judgment/wire.d.ts +18 -3
  29. package/dist/types/provider-session-state.d.ts +2 -2
  30. package/dist/types/providers/anthropic-images.d.ts +7 -0
  31. package/dist/types/providers/anthropic-signature.d.ts +12 -2
  32. package/dist/types/providers/anthropic-state.d.ts +7 -0
  33. package/dist/types/providers/anthropic.d.ts +21 -6
  34. package/dist/types/providers/connection-prewarm.d.ts +15 -0
  35. package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
  36. package/dist/types/providers/cursor-pi-args.d.ts +13 -0
  37. package/dist/types/providers/cursor.d.ts +7 -0
  38. package/dist/types/providers/ollama.d.ts +6 -1
  39. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  40. package/dist/types/providers/openai-responses.d.ts +9 -0
  41. package/dist/types/providers/openai-shared.d.ts +30 -0
  42. package/dist/types/providers/systemone-server.d.ts +7 -3
  43. package/dist/types/providers/transform-messages.d.ts +28 -5
  44. package/dist/types/registry/oauth/google-antigravity.d.ts +1 -1
  45. package/dist/types/stream.d.ts +0 -2
  46. package/dist/types/types.d.ts +33 -9
  47. package/dist/types/usage/muse-code.d.ts +1 -1
  48. package/dist/types/utils/dsml-leak.d.ts +17 -13
  49. package/dist/types/utils/narration.d.ts +9 -0
  50. package/dist/types/utils/openai-http.d.ts +2 -0
  51. package/dist/types/utils/request-body.d.ts +11 -0
  52. package/dist/types/utils/request-compression.d.ts +21 -0
  53. package/dist/types/utils/tool-call-arguments.d.ts +2 -1
  54. package/dist/types/utils/transport-fetch.d.ts +8 -0
  55. package/dist/types/utils.d.ts +1 -1
  56. package/package.json +7 -7
  57. package/src/auth/cascade.ts +23 -13
  58. package/src/auth/pool.ts +3 -3
  59. package/src/auth/refresh.ts +39 -12
  60. package/src/auth/select.ts +8 -2
  61. package/src/auth/types.ts +16 -5
  62. package/src/auth/usage-cache.ts +23 -7
  63. package/src/auth/usage.ts +2 -4
  64. package/src/auth-broker/remote-store.ts +31 -6
  65. package/src/auth-gateway/clients.ts +318 -0
  66. package/src/auth-gateway/dispatch.ts +66 -35
  67. package/src/auth-gateway/http.ts +11 -5
  68. package/src/auth-gateway/index.ts +1 -0
  69. package/src/auth-gateway/routes/embeddings.ts +4 -10
  70. package/src/auth-gateway/routes/images.ts +6 -10
  71. package/src/auth-gateway/routes/rerank.ts +4 -10
  72. package/src/auth-gateway/routes/speech.ts +10 -10
  73. package/src/auth-gateway/routes/systemone.ts +22 -12
  74. package/src/auth-gateway/routes/transcriptions.ts +4 -2
  75. package/src/auth-gateway/routes/video.ts +12 -15
  76. package/src/auth-gateway/server.ts +136 -53
  77. package/src/auth-gateway/session-state.ts +14 -0
  78. package/src/auth-gateway/types.ts +16 -2
  79. package/src/dialect/coercion.ts +16 -0
  80. package/src/dialect/deepseek.ts +344 -81
  81. package/src/dialect/hermes.ts +7 -11
  82. package/src/dialect/qwen3.ts +3 -11
  83. package/src/error/classes.ts +3 -2
  84. package/src/error/flags.ts +24 -3
  85. package/src/error/index.ts +1 -0
  86. package/src/error/network-cause.ts +179 -0
  87. package/src/images/codex-format-directive.md +3 -0
  88. package/src/images/google-antigravity.ts +2 -1
  89. package/src/images/google-generative-ai.ts +4 -2
  90. package/src/images/openai-hosted.ts +25 -2
  91. package/src/images/openai-images.ts +11 -6
  92. package/src/images/shared.ts +77 -3
  93. package/src/index.ts +1 -0
  94. package/src/judgment/chat.ts +17 -1
  95. package/src/judgment/native.ts +238 -12
  96. package/src/judgment/openai-decisions.ts +30 -6
  97. package/src/judgment/systemone.ts +153 -11
  98. package/src/judgment/text.ts +26 -7
  99. package/src/judgment/types.ts +46 -6
  100. package/src/judgment/wire.ts +19 -3
  101. package/src/provider-session-state.ts +2 -2
  102. package/src/providers/amazon-bedrock.ts +4 -1
  103. package/src/providers/anthropic-client.ts +1 -1
  104. package/src/providers/anthropic-images.ts +263 -0
  105. package/src/providers/anthropic-messages-server.ts +84 -1
  106. package/src/providers/anthropic-signature.ts +35 -8
  107. package/src/providers/anthropic-state.ts +13 -0
  108. package/src/providers/anthropic.ts +479 -397
  109. package/src/providers/connection-prewarm.ts +78 -0
  110. package/src/providers/cowork-fetch.ts +5 -1
  111. package/src/providers/cursor/exec-modern.ts +1 -0
  112. package/src/providers/cursor-pi-args.ts +19 -0
  113. package/src/providers/cursor.ts +53 -13
  114. package/src/providers/devin.ts +10 -4
  115. package/src/providers/factory-droid/gemini.ts +9 -3
  116. package/src/providers/google-gemini-cli.ts +11 -1
  117. package/src/providers/google-shared.ts +2 -6
  118. package/src/providers/images-server.ts +6 -1
  119. package/src/providers/ollama.ts +23 -5
  120. package/src/providers/openai-anthropic-shim.ts +1 -0
  121. package/src/providers/openai-chat-server.ts +29 -0
  122. package/src/providers/openai-codex/access-programs.ts +2 -1
  123. package/src/providers/openai-codex-responses.ts +49 -82
  124. package/src/providers/openai-completions.ts +117 -19
  125. package/src/providers/openai-responses.ts +25 -2
  126. package/src/providers/openai-shared.ts +36 -5
  127. package/src/providers/pi-native-server.ts +1 -0
  128. package/src/providers/systemone-server.ts +72 -12
  129. package/src/providers/transform-messages.ts +104 -55
  130. package/src/registry/oauth/google-antigravity.ts +1 -1
  131. package/src/stream.ts +13 -73
  132. package/src/types.ts +33 -9
  133. package/src/usage/claude.ts +2 -9
  134. package/src/usage/github-copilot.ts +26 -4
  135. package/src/usage/muse-code.ts +115 -29
  136. package/src/usage/openai-codex.ts +13 -7
  137. package/src/usage/opencode-go.ts +7 -8
  138. package/src/usage/xai-oauth.ts +2 -9
  139. package/src/utils/dsml-leak.ts +88 -33
  140. package/src/utils/leaked-thinking-stream.ts +10 -0
  141. package/src/utils/narration.ts +18 -0
  142. package/src/utils/openai-http.ts +27 -18
  143. package/src/utils/request-body.ts +27 -0
  144. package/src/utils/request-compression.ts +202 -0
  145. package/src/utils/request-debug.ts +23 -9
  146. package/src/utils/schema/dereference.ts +51 -18
  147. package/src/utils/schema/draft.ts +10 -2
  148. package/src/utils/schema/json-schema-validator.ts +4 -10
  149. package/src/utils/schema/normalize.ts +6 -9
  150. package/src/utils/tool-call-arguments.ts +2 -1
  151. package/src/utils/transport-fetch.ts +37 -0
  152. package/src/utils/validation.ts +26 -16
  153. package/src/utils.ts +38 -23
package/CHANGELOG.md CHANGED
@@ -1,5 +1,103 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.1] - 2026-10-19
4
+
5
+ ### Breaking Changes
6
+
7
+ - `isOfficialCodexApiUrl` moved from `@linxiraos/pi-ai` to `@linxiraos/pi-catalog/wire/codex`, next to `CODEX_BASE_URL`, so the catalog's compat resolver classifies Codex endpoints with the same check ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
8
+
9
+ ### Added
10
+
11
+ - Added `nativeJudgmentImageBounds()`: the image bounds a native judge enforces, its wire's transport limits tightened by the catalog's model bounds
12
+ - Added Box judgment questions (`BoxQuestion`/`BoxAnswer`, TypeSafe Jev 1.14+ direct or through OpenRouter): each names the request image it searches by `image` index, and comes back as that image's edges as 0–1 fractions; text, OpenAI Decisions, and Workers AI judges reject them
13
+
14
+ ### Fixed
15
+
16
+ - Fixed Muse Code usage going stale whenever its 5-hour window sits idle: the idle window now reports as unused, and weekly usage carries over from the last active window until its reset ([#16193](https://github.com/can1357/oh-my-pi/pull/16193) by [@eggpeat](https://github.com/eggpeat))
17
+ - Fixed DeepSeek tool calls never running when the model writes them as chat-template tokens with `_` in place of `▁`, a missing `|`, or no per-call close (e.g. `<|tool_calls_begin|>…<|tool_calls_end|>`): they now run as tool calls, and any that still cannot be parsed are removed from history and reported to the model as a failed call ([#16216](https://github.com/can1357/oh-my-pi/pull/16216) by [@H4vC](https://github.com/H4vC))
18
+
19
+ ## [1.3.0] - 2026-10-18
20
+
21
+ ### Fixed
22
+
23
+ - Fixed every request failing behind a TLS-inspecting proxy that refuses compressed uploads: a 400/403/415 whose body is not JSON, or the Anthropic API's "request body is not valid JSON" after a proxy drops `Content-Encoding`, now resends the body uncompressed and stops compressing for that host ([#16108](https://github.com/can1357/oh-my-pi/pull/16108) by [@alphastorm](https://github.com/alphastorm))
24
+ - Fixed Claude on Google Antigravity failing every turn with "image exceeds 5 MB maximum" once a large image entered the conversation; images are now downscaled to the host's limits before sending, as on the Anthropic API ([#15444](https://github.com/can1357/oh-my-pi/pull/15444) by [@will-bogusz](https://github.com/will-bogusz))
25
+ - Fixed Codex cybersecurity policy errors incorrectly retrying without Daybreak and disabling it for subsequent requests, while preserving retries without Daybreak when access is rejected ([#15472](https://github.com/can1357/oh-my-pi/pull/15472) by [@taibenvenuti](https://github.com/taibenvenuti))
26
+ - Fixed Codex-hosted image generation ignoring the requested `aspect_ratio`/`image_size`, and `3:2`/`2:3` requests sending no size to OpenAI image models ([#15601](https://github.com/can1357/oh-my-pi/pull/15601) by [@chulmin-dev](https://github.com/chulmin-dev))
27
+ - Fixed Claude subagents failing to return structured results when `yield` arguments arrived as invalid JSON ([#15605](https://github.com/can1357/oh-my-pi/pull/15605) by [@farnoy](https://github.com/farnoy))
28
+ - Fixed turns stopping instead of retrying when a gateway reports its upstream WebSocket closed before `response.completed` ([#15628](https://github.com/can1357/oh-my-pi/pull/15628) by [@nqrwhal](https://github.com/nqrwhal)).
29
+ - Fixed Claude on Google Vertex ignoring `cacheRetention: "long"`: prompt caches now get the 1h TTL instead of expiring after 5 minutes, and the `extended-cache-ttl` beta (which Vertex rejects) is never sent there ([#15505](https://github.com/can1357/oh-my-pi/pull/15505) by [@hpost](https://github.com/hpost))
30
+ - Fixed Gemini tool continuations failing on OpenAI-compatible gateways that return `reasoning_signature`, including restored sessions ([#15678](https://github.com/can1357/oh-my-pi/pull/15678) by [@Zzz141592653](https://github.com/Zzz141592653))
31
+ - Fixed Cursor sessions failing every rebuilt request with `PROVIDER_ERROR` after the server re-sent a tool call: the call is recorded once, and histories that already repeat it replay it once with its latest result ([#15749](https://github.com/can1357/oh-my-pi/issues/15749))
32
+ - Fixed Cursor turns cut off mid-stream failing every retry with `PROVIDER_ERROR` until you sent a new message; the retry now starts a fresh Cursor conversation ([#15830](https://github.com/can1357/oh-my-pi/issues/15830))
33
+ - Fixed malformed image-generation inputs being forwarded to providers or reported as credential/upstream errors ([#15765](https://github.com/can1357/oh-my-pi/pull/15765) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
34
+ - Fixed broker-backed auth-gateway processes retaining a cache entry for every distinct client session indefinitely under sustained traffic ([#15841](https://github.com/can1357/oh-my-pi/issues/15841)).
35
+ - Fixed turns ending on a pinned error when a provider reports "temporarily unavailable" or finishes with an `unknown`, `other` or `unspecified` finish reason: these now retry automatically ([#15424](https://github.com/can1357/oh-my-pi/pull/15424) by [@lockwo](https://github.com/lockwo))
36
+ - Fixed parallel tool calls failing with "Tool call arguments are not valid JSON" on some OpenAI-compatible gateways, such as Gemini ([#15682](https://github.com/can1357/oh-my-pi/pull/15682) by [@Zzz141592653](https://github.com/Zzz141592653))
37
+ - Fixed Anthropic fallback-credit redemption losing its credit on errors that only mention `400` in their text, such as an overload message ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
38
+ - Fixed Anthropic fallback-credit redemption sending an extra `fallback-credit-2026-06-01` beta when the refused request used a newer one ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
39
+ - Fixed Anthropic fallback-credit redemptions failing when the refused turn included unsigned thinking ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
40
+ - Fixed turns ending without automatic retry when a gateway forwards Go's `use of closed network connection` transport error; it now retries like other dropped connections ([#15385](https://github.com/can1357/oh-my-pi/issues/15385))
41
+ - Fixed Codex SSE streams failing with an abort instead of retrying when the first event stalls ([#15542](https://github.com/can1357/oh-my-pi/issues/15542))
42
+ - Fixed Codex (ChatGPT subscription) requests with reasoning disabled, such as judges, titles, commit messages, and thinking `off`, leaving effort to the server default; they now send the lowest supported effort, or `none` where the model accepts it ([#15514](https://github.com/can1357/oh-my-pi/issues/15514))
43
+ - Fixed a Codex account whose token OpenAI rejects (401/403) continuing to show its last quota as healthy; `omp usage` now drops the stale report and `omp auth-gateway check` reports the credential as failing ([#15439](https://github.com/can1357/oh-my-pi/issues/15439))
44
+ - Fixed usage reports being served indefinitely while their provider keeps failing; the last successful report is now shown for at most 24 hours after it was fetched ([#15439](https://github.com/can1357/oh-my-pi/issues/15439))
45
+ - Fixed Responses requests 400ing (DeepSeek: "The `reasoning_text` in the thinking mode must be passed back to the API") after a tool call whose arguments were invalid JSON: the call is now replayed with its parse diagnostic instead of being dropped and leaving a trailing `[Orphan tool result]` note ([#15568](https://github.com/can1357/oh-my-pi/issues/15568))
46
+ - Fixed DeepSeek on OpenAI-compatible gateways leaving doubled-pipe DSML markup (`</||DSML||parameter>`) in assistant text and reasoning ([#15485](https://github.com/can1357/oh-my-pi/issues/15485))
47
+ - Fixed sessions failing every request with `400 … thinking or redacted_thinking blocks in the latest assistant message cannot be modified` after a response fell back to another model mid-turn (e.g. through a gateway that opts into Anthropic server-side fallback); the turn now replays one model's reasoning, and a session already stuck on such a turn recovers by dropping that turn's reasoning and retrying ([#15654](https://github.com/can1357/oh-my-pi/pull/15654) by [@abilliontokens](https://github.com/abilliontokens))
48
+ - Fixed `omp auth-gateway`'s Anthropic `/v1/messages` stream misnumbering content blocks after a redacted reasoning block, which misrouted every later delta in index-addressed clients ([#15654](https://github.com/can1357/oh-my-pi/pull/15654) by [@abilliontokens](https://github.com/abilliontokens))
49
+ - Fixed error results from non-first-party providers regaining partial output the provider had discarded; an error with empty content now stays empty ([#14516](https://github.com/can1357/oh-my-pi/issues/14516), [#14572](https://github.com/can1357/oh-my-pi/pull/14572) by [@oleg494](https://github.com/oleg494))
50
+ - Fixed the auth gateway's `POST /v1/audio/speech` answering 502 for requests the speech adapter rejects (unsupported format, over-long input); they now get 400 `invalid_request_error` ([#14508](https://github.com/can1357/oh-my-pi/pull/14508) by [@F0Rextasy](https://github.com/F0Rextasy))
51
+ - Fixed a single-string tool call with two or more mislabelled string fields silently dispatching whichever field came first in the JSON; such ambiguous calls now fail validation and the model sees its own arguments back ([#14414](https://github.com/can1357/oh-my-pi/pull/14414) by [@F0Rextasy](https://github.com/F0Rextasy))
52
+ - Fixed GLM-5.2 on Ollama Cloud losing its reasoning between turns; Ollama Cloud now keeps assistant thinking in history for models verified to accept it ([#14440](https://github.com/can1357/oh-my-pi/pull/14440) by [@Dante-dan](https://github.com/Dante-dan))
53
+ - Fixed GPT Image generation and edits failing on gateways that validate request parameters, such as LiteLLM; `response_format` is no longer sent to GPT Image models ([#14619](https://github.com/can1357/oh-my-pi/pull/14619) by [@jimhester](https://github.com/jimhester))
54
+ - Fixed OpenRouter errors such as "Access denied by security policy" dropping the routed upstream's own explanation from `error.metadata.raw` ([#10906](https://github.com/can1357/oh-my-pi/issues/10906), [#14435](https://github.com/can1357/oh-my-pi/pull/14435) by [@F0Rextasy](https://github.com/F0Rextasy))
55
+ - Fixed GitHub Copilot usage reporting an AI-credit allowance as "Premium Requests" in request units; the unit each billing item reports is now honoured ([#13849](https://github.com/can1357/oh-my-pi/issues/13849), [#14501](https://github.com/can1357/oh-my-pi/pull/14501) by [@F0Rextasy](https://github.com/F0Rextasy))
56
+ - Fixed an OpenAI-compatible server's `finish_reason: "repetition"` or `stop_reason: "repetition_detected"` not being treated as a thinking loop, so the degenerate partial reasoning was kept and replayed into the next turn ([#12525](https://github.com/can1357/oh-my-pi/issues/12525), [#14535](https://github.com/can1357/oh-my-pi/pull/14535) by [@F0Rextasy](https://github.com/F0Rextasy))
57
+ - Fixed JSON Schema dereferencing rewriting literal values in `const`, `enum`, `default`, examples, and custom annotations, or treating property names as schema keywords ([#15927](https://github.com/can1357/oh-my-pi/pull/15927) by [@jchanghong023](https://github.com/jchanghong023)).
58
+ - Fixed Anthropic requests hanging forever on Bun 1.3 when the upstream accepted the request but never sent headers: aborting the request (including the first-event timeout) now fails it, so the turn retries instead of waiting until the user interrupts it ([#16010](https://github.com/can1357/oh-my-pi/pull/16010) by [@ca-io-systems](https://github.com/ca-io-systems))
59
+ - Fixed the auth gateway's `POST /v1/chat/completions` streams being dropped (`terminated`) after ~255 s without upstream output, e.g. while xAI generates a large tool call's arguments, which arrive only when complete; the stream now sends an SSE `: keepalive` comment every 15 s ([#15940](https://github.com/can1357/oh-my-pi/pull/15940) by [@radkawar](https://github.com/radkawar))
60
+ - Fixed Anthropic models with mid-conversation system messages (e.g. Claude Opus 5.5) receiving `@file` mention contents and hidden extension messages as a trailing `system` message instead of ending the request on the user's turn ([#15881](https://github.com/can1357/oh-my-pi/issues/15881))
61
+ - Fixed Ollama and Ollama Cloud streaming errors being silently accepted as successful responses ([#16039](https://github.com/can1357/oh-my-pi/pull/16039) by [@mottopanikeiku](https://github.com/mottopanikeiku))
62
+ - Fixed the hermes in-band scanner exposing a truncated tool name for streaming tool calls: `toolStart` now waits for the complete JSON name, so a stream that splits or stops mid-name never reports a partial tool name ([#16064](https://github.com/can1357/oh-my-pi/pull/16064) by [@adi-IL](https://github.com/adi-IL))
63
+ - Fixed `keys.peek()` rotating through stored accounts on each call; it now returns the first account whose token has not expired, every time, so model discovery keeps reading one account's cached catalog ([#16061](https://github.com/can1357/oh-my-pi/pull/16061) by [@gchai](https://github.com/gchai))
64
+ - Fixed local JSON Schema references with URI-percent-encoded, JSON Pointer-escaped, or empty definition names resolving incorrectly during dereferencing, strict conversion, and value validation ([#15926](https://github.com/can1357/oh-my-pi/pull/15926) by [@jchanghong023](https://github.com/jchanghong023)).
65
+ - Fixed network failures showing a generic "Connection error."; offline, connection-refused, and TLS-verification failures now name the host and the cause ([#16036](https://github.com/can1357/oh-my-pi/pull/16036) by [@jorgoose](https://github.com/jorgoose))
66
+ - Fixed Jev image limits rejecting inputs for unrelated native judges and redundant TypeSafe image judgments when only detail changes ([#16002](https://github.com/can1357/oh-my-pi/pull/16002) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
67
+ - Fixed SIGTERM interrupting an in-flight OAuth token rotation before its replacement refresh token was saved ([#15845](https://github.com/can1357/oh-my-pi/issues/15845)).
68
+ - Fixed sessions getting stuck on HTTP 400 after replaying tool names unsupported by the target provider, including invented `proc://*` calls and Google tool names sent to Bedrock ([#15839](https://github.com/can1357/oh-my-pi/issues/15839))
69
+
70
+ ### Breaking Changes
71
+
72
+ - `resolveGatewayApiKey` and `buildGatewayApiKeyResolver` (`@oh-my-pi/pi-ai/auth-gateway`) take the request's route options (`{ storage, client? }`) instead of an `AuthStorage`, their resolver callback receives the resolved credential, and `resolveGatewayAccount` takes that credential ([#15900](https://github.com/can1357/oh-my-pi/pull/15900) by [@alphastorm](https://github.com/alphastorm))
73
+ - `PI_CODEX_ZSTD` is replaced by `PI_REQUEST_COMPRESSION`; set `PI_REQUEST_COMPRESSION=0` to send Codex (and every other) request body uncompressed ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
74
+
75
+ ### Added
76
+
77
+ - Added support for `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` on the `anthropic` provider: when set, requests omit pre-release fields (`context_management`, beta tool fields such as `strict` and `defer_loading`, task budgets, structured-output format, `thinking.block_binding`, and Anthropic server compaction) along with their beta headers, so `ANTHROPIC_BASE_URL` gateways that reject them keep working ([#15458](https://github.com/can1357/oh-my-pi/pull/15458) by [@H4vC](https://github.com/H4vC))
78
+ - Added `withoutUnverifiedThinking` utility to filter reasoning capability from models when no effort is requested
79
+ - Added `reachesOfficialEndpoint(model)`: whether a request to the model reaches the official endpoint its compat was built for, following `ANTHROPIC_BASE_URL` and Foundry's `FOUNDRY_BASE_URL` reroutes of first-party Anthropic models ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
80
+ - Added `AssistantMessage.elidedOutputTokens`, the visible output tokens a history rewrite (shake) has removed from the turn, which `usage.output` still counts ([#15446](https://github.com/can1357/oh-my-pi/pull/15446) by [@will-bogusz](https://github.com/will-bogusz))
81
+ - Exported `shouldDropTruncatedThinkingOnlyAssistant` from `providers/transform-messages`, `isColdReplayableResponsesTurn` from `providers/openai-shared` and the new `openAIResponsesReplaysNativeHistory` from `providers/openai-responses`, so local token estimates can follow which turns replay drops and whether a session replays native history ([#15447](https://github.com/can1357/oh-my-pi/pull/15447) by [@will-bogusz](https://github.com/will-bogusz))
82
+ - Added image support for native OpenAI Decisions and Workers AI judgments, prompted vision chat, and auth-gateway System One requests; images exceeding a judgment API's documented caps (e.g. Clef's 4 images, 4 MiB each) are rejected locally ([#15471](https://github.com/can1357/oh-my-pi/pull/15471) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
83
+ - `isSurvivingAssistant` in `providers/transform-messages`: whether an assistant turn reaches a replay, malformed tool calls dropped first, so callers outside `transformMessages` pick the same latest turn ([#15791](https://github.com/can1357/oh-my-pi/pull/15791) by [@will-bogusz](https://github.com/will-bogusz))
84
+ - Added scoped clients to `startAuthGateway` (`clients`, `setClients`): each client bearer is limited to its own models and OAuth accounts, with no fallback to other accounts or API keys, and request log lines name the client and the credential a request started on ([#5098](https://github.com/can1357/oh-my-pi/issues/5098), [#15900](https://github.com/can1357/oh-my-pi/pull/15900) by [@alphastorm](https://github.com/alphastorm))
85
+ - Added experimental native image judgments for TypeSafe Jev's `jev-latest`, `jev-preview`, `jev-1.13.0`, and `jev-1.14.0`, plus OpenRouter's `typesafe/jev-1.13`, `typesafe/jev-1.14`, and `~typesafe/jev-latest` models; images outside Jev's documented size bounds fall through to a vision chat judge instead of failing ([#15887](https://github.com/can1357/oh-my-pi/pull/15887) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy))
86
+ - Added `ThinkingContent.narration`, set on Anthropic and Bedrock thinking blocks whose signature marks them as between-tool narration, and `presentNarrationAsAnswer(message)` to show those blocks as reply text while replay keeps them as signed thinking
87
+ - Added `keys.peek(provider, { includeExpired: true })` to name the stored account behind an expired OAuth token for reads that never send it, such as keying an offline model-cache lookup ([#16061](https://github.com/can1357/oh-my-pi/pull/16061) by [@gchai](https://github.com/gchai))
88
+ - Added `prewarmModelConnection(model)` (`providers/connection-prewarm`), which opens the model's API connection ahead of a request using the same transport and TLS settings so the request reuses it instead of paying the connection handshake ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
89
+
90
+ ### Changed
91
+
92
+ - Anthropic requests to the official API now gzip the request body, so long conversations reach the model sooner on slow uplinks; set `PI_REQUEST_COMPRESSION=0` to disable it ([#16044](https://github.com/can1357/oh-my-pi/pull/16044) by [@H4vC](https://github.com/H4vC))
93
+ - GitHub Copilot requests (chat completions, Responses, and Claude messages) now send zstd-compressed bodies, cutting upload size by roughly 60–75% on long conversations; set `PI_REQUEST_COMPRESSION=0` to disable it ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
94
+ - A rejected request-body encoding now only disables compression for that host, and only after the uncompressed resend succeeds, so a genuine 400 no longer turns compression off ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
95
+ - Connection warm-up now covers every catalog provider, not only the official Anthropic API: it re-warms the origin a model's requests actually reach (Copilot plan hosts, `MOONSHOT_BASE_URL`, regional hosts), throttled per host; custom providers, built-in provider ids routed to a gateway, local hosts, and Anthropic gateways are never probed ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
96
+
97
+ ### Removed
98
+
99
+ - Removed the `toolNamespacesInfo` stream option; Codex requests no longer send `tool_namespaces_info` turn metadata, and `code_mode_tool_names`/`tool_namespaces_info` are no longer reserved client metadata keys.
100
+
3
101
  ## [1.2.0] - 2026-10-12
4
102
 
5
103
  - 版本线推进至 1.2.0;本版无独立用户可见变化。
package/README.md CHANGED
@@ -84,7 +84,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
84
84
  - **vLLM** (OpenAI-compatible server; `VLLM_API_KEY` for secured deployments)
85
85
  - **GitHub Copilot** (requires OAuth, see below)
86
86
  - **Google Gemini CLI** (requires OAuth, see below)
87
- - **Antigravity** (requires OAuth, see below)
87
+ - **Google Antigravity** (requires OAuth, see below)
88
88
  - **Any OpenAI-compatible API**: LM Studio, custom proxies, etc.
89
89
 
90
90
  ## Installation
@@ -565,6 +565,8 @@ if (message.stopReason === "error" || message.stopReason === "aborted") {
565
565
  }
566
566
  ```
567
567
 
568
+ If the provider discards partial output by returning an error message with empty `content`, the final result remains empty. Leaked-thinking healing does not reconstruct discarded blocks from earlier streaming deltas.
569
+
568
570
  ### Aborting Requests
569
571
 
570
572
  The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
@@ -1029,7 +1031,7 @@ Several providers support OAuth authentication (some also support static API key
1029
1031
  - **OpenAI Codex** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
1030
1032
  - **GitHub Copilot** (Copilot subscription)
1031
1033
  - **Google Gemini CLI** (Gemini 2.0/2.5 via Google Cloud Code Assist; free tier or paid subscription)
1032
- - **Antigravity** (Free Gemini 3, Claude, GPT-OSS via Google Cloud)
1034
+ - **Google Antigravity** (Gemini, Claude, GPT-OSS via Google Cloud)
1033
1035
  - **Qwen Portal** (Qwen OAuth token or API key)
1034
1036
  - **Snowflake Cortex** (Snowflake account OAuth with PKCE and refresh, subject to the account's local-application integration)
1035
1037
 
@@ -8,8 +8,8 @@ no rights in third-party code, fonts, data, or marks identified below.
8
8
  PYTHON DEPENDENCY DISTRIBUTION ROLE
9
9
  ===================================
10
10
 
11
- omp-rpc has no third-party runtime dependencies. robomp declares FastAPI,
12
- Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and omp-rpc
11
+ zeta-rpc has no third-party runtime dependencies. robomp declares FastAPI,
12
+ Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and zeta-rpc
13
13
  as ordinary installer-resolved dependencies. The Python wheels do not vendor
14
14
  those projects; each dependency remains a separately installed distribution
15
15
  carrying its own license metadata and files. Container/system assemblers must
@@ -79,9 +79,9 @@ uutils coreutils (https://github.com/uutils/coreutils), MIT
79
79
  --------------------------------------------------------------------------------
80
80
  Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
81
81
  b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
82
- cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
83
- printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee, touch, tr,
84
- truncate, uname, uniq, wc, whoami, yes.
82
+ cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nl, nproc,
83
+ paste, printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
84
+ touch, tr, truncate, uname, uniq, wc, whoami, yes.
85
85
 
86
86
  Copyright (c) uutils developers
87
87
 
@@ -824,6 +824,56 @@ public licenses.
824
824
 
825
825
  Creative Commons may be contacted at creativecommons.org.
826
826
 
827
+ -------------------------------------------------------------------------------
828
+ scripts/brand/fonts/ANSI Shadow.flf, scripts/brand/fonts/mini.flf
829
+
830
+ Two FIGlet font files from the standard FIGlet font distribution — the
831
+ figlet.org font set shipped by figlet.js
832
+ (https://github.com/patorjk/figlet.js/tree/master/fonts), fetched from
833
+ figlet.js 1.8.0 — vendored so the welcome/splash brand art
834
+ (`packages/tui/src/prompt/welcome.ts`) is generated reproducibly and offline
835
+ by `bun scripts/brand/generate-logo-art.ts`. Both files are unmodified and
836
+ retain their original FIGlet headers. They are build-time inputs only: no font
837
+ file or font data is compiled into, or shipped with, a Zeta release artifact.
838
+ The fonts' own headers carry their terms:
839
+
840
+ - `ANSI Shadow.flf` — "ANSI Shadow". Author unknown ("Font Author: ?" in the
841
+ header); a TheDraw-archive font converted with the FIGlet font editor
842
+ (http://patorjk.com/figfont-editor). Distributed under the FIGlet font
843
+ license as carried by the standard FIGlet font set: free to use and
844
+ redistribute with the header intact.
845
+ - `mini.flf` — "mini" by Glenn Chappell (4/93), from figlet release 2.1
846
+ (12 Aug 1994), "Includes ISO Latin-1"; modified by Paul Burton
847
+ <solution@earthlink.net> 12/96. Header grant: "Permission is hereby given to
848
+ modify this font, as long as the modifier's name is placed on a comment
849
+ line."
850
+
851
+ FIGlet renderer — `figlet` 1.8.0 (https://github.com/patorjk/figlet.js), a
852
+ root devDependency used only to generate the art at build time; it is not
853
+ imported by any runtime bundle or shipped artifact.
854
+
855
+ MIT License
856
+
857
+ Copyright (C) 2014-present Patrick Gillespie and other contributors
858
+
859
+ Permission is hereby granted, free of charge, to any person obtaining a copy
860
+ of this software and associated documentation files (the "Software"), to deal
861
+ in the Software without restriction, including without limitation the rights
862
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
863
+ copies of the Software, and to permit persons to whom the Software is
864
+ furnished to do so, subject to the following conditions:
865
+
866
+ The above copyright notice and this permission notice shall be included in
867
+ all copies or substantial portions of the Software.
868
+
869
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
870
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
871
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
872
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
873
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
874
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
875
+ SOFTWARE.
876
+
827
877
  BUN / NPM DEPENDENCY INVENTORY
828
878
  ==============================
829
879
  This inventory is traversed from bun.lock, not from the host node_modules tree.
@@ -2,7 +2,7 @@ import { type ApiKeyResolver, type ResolvedApiKey } from "../auth-retry.js";
2
2
  import type { SessionAffinity } from "./affinity.js";
3
3
  import type { CredentialPool } from "./pool.js";
4
4
  import type { CredentialSelector } from "./select.js";
5
- import type { AuthApiKeyOptions, AuthCredential, AuthSource, AuthSourceOptions, KeysApi, LimitsApi } from "./types.js";
5
+ import type { AuthApiKeyOptions, AuthPeekOptions, AuthCredential, AuthSource, AuthSourceOptions, KeysApi, LimitsApi } from "./types.js";
6
6
  /** Runtime (--api-key) and config (models.yml) key overrides plus the config-value resolver. */
7
7
  export declare class KeyOverrides {
8
8
  #private;
@@ -126,9 +126,11 @@ export declare class KeyCascade implements KeysApi {
126
126
  * Used for model discovery where we only need to know if credentials exist
127
127
  * and get a best-effort token. GitHub Copilot's peek must preserve
128
128
  * enterprise routing metadata because discovery needs a structured
129
- * credential to reach the correct host.
129
+ * credential to reach the correct host. With several stored accounts every
130
+ * peek returns the same one, so credential-scoped discovery caches stay on
131
+ * one namespace.
130
132
  */
131
- peek(provider: string): Promise<string | undefined>;
133
+ peek(provider: string, options?: AuthPeekOptions): Promise<string | undefined>;
132
134
  /**
133
135
  * Resolve a bearer together with the stored row that supplied it, for a request.
134
136
  * Same precedence as {@link KeyCascade.get}, but rejects with
@@ -49,7 +49,7 @@ export declare class CredentialPool implements CredentialsApi {
49
49
  * The store is shared across every zeta process, but the pool is an
50
50
  * in-process cache refreshed only by this process's own writes. Without
51
51
  * this a long-running session ranks a stale pool for its whole lifetime:
52
- * `zeta-c auth` in another terminal is invisible, rotation reports no usable
52
+ * `zetacode auth` in another terminal is invisible, rotation reports no usable
53
53
  * sibling while a freshly added account sits unblocked in SQLite, and the
54
54
  * turn degrades to the fallback chain. The auth-broker path already polls;
55
55
  * direct-store sessions had no equivalent.
@@ -178,7 +178,7 @@ export declare class CredentialPool implements CredentialsApi {
178
178
  */
179
179
  snapshot(): AuthCredentialSnapshot;
180
180
  /**
181
- * Disabled credential tombstones for display surfaces (`zeta-c usage`,
181
+ * Disabled credential tombstones for display surfaces (`zetacode usage`,
182
182
  * broker `GET /v1/credentials/disabled`). Empty when the backing store
183
183
  * keeps no tombstones or the remote broker predates the endpoint.
184
184
  */
@@ -187,7 +187,7 @@ export declare class CredentialPool implements CredentialsApi {
187
187
  * Force the backing store to revalidate its credential snapshot, then
188
188
  * reload. Remote broker stores re-fetch the snapshot; local stores are
189
189
  * always current, so only the reload runs. Callers that pair live
190
- * per-credential data with stored identities (`zeta-c usage`) use this so a
190
+ * per-credential data with stored identities (`zetacode usage`) use this so a
191
191
  * disk-cached snapshot cannot misattribute fresh reports.
192
192
  */
193
193
  revalidate(): Promise<void>;
@@ -55,7 +55,11 @@ export declare class CredentialSelector {
55
55
  /**
56
56
  * Selects a credential of the specified type for a provider.
57
57
  * Returns both the credential and its index in the original array (for updates/removal).
58
- * Uses deterministic hashing for session stickiness and skips blocked credentials when possible.
58
+ * With `sessionId` the scan starts at the session's hashed index; without one it starts at the
59
+ * first stored credential and never advances round-robin. Model discovery peeks through this,
60
+ * and credential-scoped catalogs (e.g. GitHub Copilot) key their cache rows on the credential
61
+ * picked, so a rotating pick would read another account's cache namespace on every pass.
62
+ * Blocked credentials are skipped when possible.
59
63
  */
60
64
  selectByType<T extends AuthCredential["type"]>(provider: string, type: T, sessionId?: string, filter?: (credential: AuthCredential) => boolean): {
61
65
  credential: Extract<AuthCredential, {
@@ -80,7 +80,7 @@ export interface StoredCredentialBlock {
80
80
  /**
81
81
  * Identity slice of a disabled (soft-deleted) credential tombstone — cause and
82
82
  * account identity only, never token material. Surfaced so auto-disabled
83
- * accounts (e.g. an expired Anthropic OAuth grant) stay visible in `zeta-c usage`
83
+ * accounts (e.g. an expired Anthropic OAuth grant) stay visible in `zetacode usage`
84
84
  * instead of silently vanishing until the user notices missing quota.
85
85
  */
86
86
  export interface DisabledCredentialSummary {
@@ -631,6 +631,15 @@ export interface AuthSourceOptions {
631
631
  */
632
632
  env?: "dedicated" | "aliases" | "none";
633
633
  }
634
+ /** Options for {@link KeysApi.peek}. */
635
+ export interface AuthPeekOptions {
636
+ /**
637
+ * Fall back to an expired OAuth access token when no stored account holds an
638
+ * unexpired one. Only for reads that never send the token, e.g. keying an
639
+ * offline model-cache lookup on the account it names.
640
+ */
641
+ includeExpired?: boolean;
642
+ }
634
643
  /** Where a provider's auth comes from ({@link CredentialOrigin}) plus whether any leg holds a concrete key. */
635
644
  export interface AuthSource extends CredentialOrigin {
636
645
  /** False when the only auth is an ambient AUTHENTICATED_SENTINEL source (AWS profile, ADC). */
@@ -746,7 +755,7 @@ export interface CredentialsApi {
746
755
  * Force the backing store to revalidate its credential snapshot, then
747
756
  * reload. Remote broker stores re-fetch the snapshot; local stores are
748
757
  * always current, so only the reload runs. Callers that pair live
749
- * per-credential data with stored identities (`zeta-c usage`) use this so a
758
+ * per-credential data with stored identities (`zetacode usage`) use this so a
750
759
  * disk-cached snapshot cannot misattribute fresh reports.
751
760
  */
752
761
  revalidate(): Promise<void>;
@@ -805,7 +814,7 @@ export interface CredentialsApi {
805
814
  */
806
815
  disable(id: number, disabledCause: string): Promise<boolean>;
807
816
  /**
808
- * Disabled credential tombstones for display surfaces (`zeta-c usage`,
817
+ * Disabled credential tombstones for display surfaces (`zetacode usage`,
809
818
  * broker `GET /v1/credentials/disabled`). Empty when the backing store
810
819
  * keeps no tombstones or the remote broker predates the endpoint.
811
820
  */
@@ -852,9 +861,10 @@ export interface KeysApi {
852
861
  * Peek at API key for a provider without refreshing OAuth tokens.
853
862
  * Used for model discovery where we only need to know if credentials exist
854
863
  * and get a best-effort token. For GitHub Copilot we preserve enterprise
855
- * routing metadata so discovery can hit the correct host.
864
+ * routing metadata so discovery can hit the correct host. Repeated peeks
865
+ * return the same stored credential; they never rotate accounts.
856
866
  */
857
- peek(provider: string): Promise<string | undefined>;
867
+ peek(provider: string, options?: AuthPeekOptions): Promise<string | undefined>;
858
868
  /** Resolve the first active auth source by precedence without refreshing credentials. */
859
869
  source(provider: string, options?: AuthSourceOptions): AuthSource | undefined;
860
870
  /**
@@ -2,7 +2,7 @@ import type { AuthCredentialStore } from "./store.js";
2
2
  import type { AuthCredential, OAuthCredential } from "./types.js";
3
3
  import type { CredentialPool } from "./pool.js";
4
4
  import type { Provider } from "../types.js";
5
- import type { UsageCredential, UsageProvider } from "../usage.js";
5
+ import type { UsageCredential, UsageProvider, UsageReport } from "../usage.js";
6
6
  /** Minimum interval between non-exhausted header snapshots; used by UsageService. */
7
7
  export declare const USAGE_HEADER_INGEST_INTERVAL_MS = 60000;
8
8
  /**
@@ -56,6 +56,8 @@ export declare class UsageCache {
56
56
  failureKey(reportKey: string): string;
57
57
  get<T>(key: string): UsageCacheEntry<T> | undefined;
58
58
  getStale<T>(key: string): UsageCacheEntry<T> | undefined;
59
+ /** Last successful report under `key` while inside its retention window; the failure-path fallback. */
60
+ lastGood(key: string): UsageReport | null;
59
61
  set<T>(key: string, entry: UsageCacheEntry<T>): void;
60
62
  deletePrefix(prefix: string): boolean;
61
63
  markForceRefresh(provider?: Provider): void;
@@ -0,0 +1,67 @@
1
+ import type { SessionsApi } from "../auth/types.js";
2
+ import type { Api, Model } from "../types.js";
3
+ /** One scoped client. */
4
+ export interface AuthGatewayClient {
5
+ /** Operator-chosen name, logged with the client's requests. */
6
+ name: string;
7
+ /** Hex SHA-256 digest of the client's bearer token. */
8
+ tokenSha256: string;
9
+ /** `provider/id` or `provider/*` grants; absent allows every served model, `[]` none. */
10
+ models?: readonly string[];
11
+ /** Provider id → OAuth identity keys the client's requests to that provider may use. */
12
+ accounts?: Readonly<Record<string, readonly string[]>>;
13
+ }
14
+ /** An admitted scoped client, as the gateway's routes see it. */
15
+ export interface AuthGatewayClientScope {
16
+ readonly name: string;
17
+ /** False when the client may call every served model. */
18
+ readonly limitsModels: boolean;
19
+ /** Whether the client may call `model`. */
20
+ allows(model: Model<Api>): boolean;
21
+ /**
22
+ * Session id the client's credential lookups for `provider` resolve under:
23
+ * its own restricted session when `accounts` lists the provider, otherwise
24
+ * the request's `sessionId`. Throws for a provider `accounts` omits, and for
25
+ * a bound provider once the restriction is lifted, so a lookup can never
26
+ * resolve unrestricted on the client's behalf.
27
+ */
28
+ credentialSessionId(provider: string, sessionId: string): string;
29
+ /**
30
+ * Hold the client's account restriction while a request uses it; call the
31
+ * returned release once the request can no longer resolve a credential. A
32
+ * reload that retires the client lifts the restriction only after every
33
+ * hold is released. Take the first hold in the same tick as
34
+ * `authenticate`, before a reload can retire the client.
35
+ */
36
+ retain(): () => void;
37
+ }
38
+ /**
39
+ * Parse a clients file: a JSON object mapping each client name to
40
+ * `{ tokenSha256, models?, accounts? }`. Any malformed entry or unknown field
41
+ * rejects the whole file, since a misspelled `models` would otherwise allow
42
+ * every model. Errors name the offending field, never its value.
43
+ */
44
+ export declare function parseAuthGatewayClients(text: string): AuthGatewayClient[];
45
+ /** Hex SHA-256 digest of a bearer token, as `tokenSha256` records it. */
46
+ export declare function hashAuthGatewayToken(token: string): string;
47
+ type RestrictionSessions = Pick<SessionsApi, "restrict" | "unrestrict">;
48
+ /**
49
+ * The scoped clients one gateway admits, looked up by the digest of the
50
+ * presented bearer.
51
+ */
52
+ export declare class AuthGatewayClientTable {
53
+ #private;
54
+ constructor(sessions: RestrictionSessions, gatewayTokens: Iterable<string>);
55
+ /**
56
+ * Replace the admitted clients. An invalid table throws and changes
57
+ * nothing. An unchanged client keeps its scope; a new or changed one is
58
+ * restricted before its token is admitted; the scopes left out are retired.
59
+ * Ignored once the table is closed.
60
+ */
61
+ set(clients: readonly AuthGatewayClient[]): void;
62
+ /** The client `token` authenticates, if any. */
63
+ authenticate(token: string): AuthGatewayClientScope | undefined;
64
+ /** Admit no more clients and retire every scope. */
65
+ close(): void;
66
+ }
67
+ export {};
@@ -3,6 +3,7 @@ import type { AuthStorage } from "../auth-storage.js";
3
3
  import { type GatewayErrorClassification } from "../error/gateway.js";
4
4
  import type { Api, FetchImpl, Model, Usage } from "../types.js";
5
5
  import type { ClientUsageIdentity } from "../usage.js";
6
+ import type { AuthGatewayClientScope } from "./clients.js";
6
7
  import type { AuthGatewayServerOptions } from "./types.js";
7
8
  export type ModelResolver = (modelId: string) => Model<Api> | undefined;
8
9
  /** What the gateway's routes need, whatever transport carries the requests. */
@@ -21,7 +22,15 @@ export interface AuthGatewayRouteOptions {
21
22
  excludeProviders?: ReadonlySet<string>;
22
23
  /** Upstream transport for every provider call; defaults to global `fetch`. Test seam. */
23
24
  fetch?: FetchImpl;
25
+ /**
26
+ * The scoped client that sent this request, set per request by the HTTP
27
+ * server: it narrows `resolveModel` / `listModels`, and every credential
28
+ * lookup below resolves under its {@link AuthGatewayClientScope.credentialSessionId}.
29
+ */
30
+ client?: AuthGatewayClientScope;
24
31
  }
32
+ /** The route options a request's credential lookups read. */
33
+ export type AuthGatewayCredentialOptions = Pick<AuthGatewayRouteOptions, "storage" | "client">;
25
34
  /** The HTTP server's options: the routes' plus its listener and inbound auth. */
26
35
  export interface AuthGatewayBootOptions extends AuthGatewayServerOptions, AuthGatewayRouteOptions {
27
36
  }
@@ -37,13 +46,20 @@ export declare function normalizeClientSessionKey(clientKey: string | undefined)
37
46
  *
38
47
  * `markUsageLimitReached` and the auth-retry resolver switch a session to a
39
48
  * sibling credential, so the provider state retained for that session can
40
- * outlive the account that taught it. OAuth rows expose an account id / email
41
- * that survives token refresh — fingerprinting the bearer instead would look
49
+ * outlive the account that taught it. A stored credential keeps its durable
50
+ * row id across token refreshes — fingerprinting the bearer instead would look
42
51
  * like a rotation every time a token refreshes and discard the retained
43
- * lessons for nothing. Key-based rows fall back to a hash of the key, never
44
- * the key itself: this value is held for the lifetime of the entry.
52
+ * lessons for nothing. The id comes from the selection itself, not the
53
+ * session's current pin, which a concurrent request on the same session may
54
+ * have moved. Unstored keys fall back to a hash of the key, never the key
55
+ * itself: this value is held for the lifetime of the entry.
45
56
  */
46
- export declare function resolveGatewayAccount(storage: AuthStorage, provider: string, sessionId: string, apiKey: string): string;
57
+ export declare function resolveGatewayAccount(resolved: ResolvedApiKey): string;
58
+ /** Who sent a request and which stored credential it started on, for the `auth-gateway request` log line. */
59
+ export declare function gatewayRequestLogFields(opts: AuthGatewayCredentialOptions, resolved: ResolvedApiKey): {
60
+ client?: string;
61
+ credentialId?: number;
62
+ };
47
63
  /**
48
64
  * Resolve the credential for one request from broker-backed storage.
49
65
  *
@@ -53,7 +69,7 @@ export declare function resolveGatewayAccount(storage: AuthStorage, provider: st
53
69
  * stored token before dispatch. Storage failures map through
54
70
  * {@link classifyGatewayError}; a provider without any credential is a 401.
55
71
  */
56
- export declare function resolveGatewayApiKey(storage: AuthStorage, model: Model<Api>, sessionId: string, signal: AbortSignal, peer: string): Promise<ResolvedApiKey | GatewayErrorClassification>;
72
+ export declare function resolveGatewayApiKey(opts: AuthGatewayCredentialOptions, model: Model<Api>, sessionId: string, signal: AbortSignal, peer: string): Promise<ResolvedApiKey | GatewayErrorClassification>;
57
73
  /**
58
74
  * Build the {@link ApiKeyResolver} handed to a pi-ai client for a gateway
59
75
  * request. Drives the central a/b/c auth-retry policy server-side:
@@ -67,11 +83,13 @@ export declare function resolveGatewayApiKey(storage: AuthStorage, model: Model<
67
83
  * `lastKey` tracks the most recent bearer so the switch step invalidates the
68
84
  * credential that actually failed; a retry context's `previousKey` wins, since
69
85
  * concurrent operations sharing one resolver (split judgment chunks) each know
70
- * the bearer they sent. `onResolvedKey` observes every rotation; routes that
71
- * retain provider session state use it to re-key the account lease, one-shot
72
- * routes pass `undefined`.
86
+ * the bearer they sent. Every step resolves under the scoped client's
87
+ * credential session when `opts.client` binds the provider, so refresh,
88
+ * blocking and sibling rotation stay inside its accounts. `onResolvedKey`
89
+ * observes every rotation; routes that retain provider session state use it
90
+ * to re-key the account lease, one-shot routes pass `undefined`.
73
91
  */
74
- export declare function buildGatewayApiKeyResolver(storage: AuthStorage, model: Model<Api>, sessionId: string, initialKey: ResolvedApiKey, requestSignal: AbortSignal, format: string, peer: string, onResolvedKey?: (apiKey: string) => void): ApiKeyResolver;
92
+ export declare function buildGatewayApiKeyResolver(opts: AuthGatewayCredentialOptions, model: Model<Api>, sessionId: string, initialKey: ResolvedApiKey, requestSignal: AbortSignal, format: string, peer: string, onResolvedKey?: (resolved: ResolvedApiKey) => void): ApiKeyResolver;
75
93
  /**
76
94
  * Attribute one settled upstream request to the originating client via the
77
95
  * broker's observed-usage channel (`AuthStorage.usage.observe`, batched
@@ -24,6 +24,8 @@ export declare function hasMisplacedBearer(req: Request, url: URL, tokens: Reado
24
24
  * of the longer input so length itself doesn't leak via timing.
25
25
  */
26
26
  export declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean;
27
+ /** The token of an `Authorization: Bearer <token>` header, trimmed; `undefined` without one. */
28
+ export declare function readBearerToken(req: Request): string | undefined;
27
29
  export declare function isAuthorized(req: Request, tokens: ReadonlySet<string>): boolean;
28
30
  /**
29
31
  * Extract allow-listed passthrough headers from an inbound request. Keys are
@@ -1,3 +1,4 @@
1
+ export * from "./clients.js";
1
2
  export * from "./dispatch.js";
2
3
  export * from "./http.js";
3
4
  export * from "./session-state.js";
@@ -27,12 +27,17 @@
27
27
  * answers the `/v1/*` routes for any transport: {@link startAuthGateway}
28
28
  * serves them over HTTP behind bearer auth, `stdio.ts` over JSON lines.
29
29
  */
30
+ import { type AuthGatewayClientScope } from "./clients.js";
30
31
  import { type AuthGatewayBootOptions, type AuthGatewayRouteOptions } from "./dispatch.js";
31
32
  import type { AuthGatewayServerHandle } from "./types.js";
32
33
  /** The gateway's `/v1/*` routes, for a transport that has already admitted the caller. */
33
34
  export interface AuthGatewayRouter {
34
- /** Answers one request; `peer` names the caller in logs. Never rejects: a crashed route answers 500. */
35
- route(req: Request, peer: string): Promise<Response>;
35
+ /**
36
+ * Answers one request; `peer` names the caller in logs, `client` is the
37
+ * scoped client the transport admitted, if any. Never rejects: a crashed
38
+ * route answers 500.
39
+ */
40
+ route(req: Request, peer: string, client?: AuthGatewayClientScope): Promise<Response>;
36
41
  /**
37
42
  * Closes the retained provider session state (Codex WebSockets, GitLab Duo
38
43
  * workflows), whose sockets and timers would otherwise keep the process alive.
@@ -40,8 +45,8 @@ export interface AuthGatewayRouter {
40
45
  close(): void;
41
46
  }
42
47
  /**
43
- * The gateway's routes over `opts`, owning their per-session provider state:
48
+ * The gateway's routes over `hostOpts`, owning their per-session provider state:
44
49
  * two routers in one process never share (or tear down) each other's.
45
50
  */
46
- export declare function createAuthGatewayRouter(opts: AuthGatewayRouteOptions): AuthGatewayRouter;
51
+ export declare function createAuthGatewayRouter(hostOpts: AuthGatewayRouteOptions): AuthGatewayRouter;
47
52
  export declare function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle;
@@ -77,6 +77,12 @@ export interface AuthGatewaySessionStateRequest {
77
77
  * resolves a sibling credential.
78
78
  */
79
79
  account: string;
80
+ /**
81
+ * Name of the scoped gateway client that sent the request, if any. A
82
+ * client's conversations never share retained state with another client's,
83
+ * or with unscoped traffic, even when their keys or histories match.
84
+ */
85
+ client?: string;
80
86
  }
81
87
  /**
82
88
  * Bounded per-session provider state, owned by one gateway server instance.
@@ -1,7 +1,8 @@
1
1
  import type { Effort } from "@linxiraos/pi-catalog/effort";
2
+ import type { AuthGatewayClient } from "./clients.js";
2
3
  import type { AssistantMessage, AssistantMessageEventStream, CacheRetention, Context, OpenAIResponseInclude, ServiceTier, TokenTaskBudget } from "../types.js";
3
4
  /**
4
- * Wire types for the zeta-c auth-gateway.
5
+ * Wire types for the zetacode auth-gateway.
5
6
  *
6
7
  * The gateway sits between unauthenticated clients (containerized zeta,
7
8
  * llm-git, …) and the broker. It accepts provider-format HTTP requests
@@ -123,8 +124,14 @@ export interface AuthGatewayFormatModule {
123
124
  export interface AuthGatewayServerOptions {
124
125
  /** Listen address. Default `127.0.0.1:4000`. */
125
126
  bind?: string;
126
- /** Accept any of these bearer tokens. Empty allows unauthenticated calls. */
127
+ /** Accept any of these bearer tokens. Empty allows unauthenticated calls unless `clients` is set. */
127
128
  bearerTokens: string[];
129
+ /**
130
+ * Scoped clients, each admitted by its own bearer and limited to its models
131
+ * and accounts (see `clients.ts`). Any table, even an empty one, requires a
132
+ * bearer on every request. Throws at startup when invalid.
133
+ */
134
+ clients?: readonly AuthGatewayClient[];
128
135
  /** Honor forwarded peer headers only when the connecting proxy is trusted. Default false. */
129
136
  trustProxyHeaders?: boolean;
130
137
  /** Version surfaced on `/healthz`. */
@@ -135,4 +142,11 @@ export interface AuthGatewayServerHandle {
135
142
  port: number;
136
143
  hostname: string;
137
144
  close(): Promise<void>;
145
+ /**
146
+ * Replace the scoped clients. A removed or changed token is refused from
147
+ * the next request on; a request already in flight keeps the models and
148
+ * accounts it was admitted with, retries included, until it finishes.
149
+ * Throws, changing nothing, when the table is invalid. Turns bearer auth on.
150
+ */
151
+ setClients(clients: readonly AuthGatewayClient[]): void;
138
152
  }