@linxiraos/pi-ai 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +4 -2
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/auth/cascade.d.ts +5 -3
- package/dist/types/auth/pool.d.ts +3 -3
- package/dist/types/auth/select.d.ts +5 -1
- package/dist/types/auth/types.d.ts +15 -5
- package/dist/types/auth/usage-cache.d.ts +3 -1
- package/dist/types/auth-gateway/clients.d.ts +67 -0
- package/dist/types/auth-gateway/dispatch.d.ts +28 -10
- package/dist/types/auth-gateway/http.d.ts +2 -0
- package/dist/types/auth-gateway/index.d.ts +1 -0
- package/dist/types/auth-gateway/server.d.ts +9 -4
- package/dist/types/auth-gateway/session-state.d.ts +6 -0
- package/dist/types/auth-gateway/types.d.ts +16 -2
- package/dist/types/dialect/coercion.d.ts +6 -0
- package/dist/types/error/classes.d.ts +1 -1
- package/dist/types/error/flags.d.ts +15 -0
- package/dist/types/error/index.d.ts +1 -0
- package/dist/types/error/network-cause.d.ts +40 -0
- package/dist/types/images/shared.d.ts +26 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/judgment/native.d.ts +51 -3
- package/dist/types/judgment/systemone.d.ts +9 -2
- package/dist/types/judgment/text.d.ts +11 -5
- package/dist/types/judgment/types.d.ts +5 -1
- package/dist/types/judgment/wire.d.ts +16 -3
- package/dist/types/provider-session-state.d.ts +2 -2
- package/dist/types/providers/anthropic-images.d.ts +7 -0
- package/dist/types/providers/anthropic-signature.d.ts +12 -2
- package/dist/types/providers/anthropic-state.d.ts +7 -0
- package/dist/types/providers/anthropic.d.ts +21 -6
- package/dist/types/providers/connection-prewarm.d.ts +15 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor-pi-args.d.ts +13 -0
- package/dist/types/providers/cursor.d.ts +7 -0
- package/dist/types/providers/openai-codex-responses.d.ts +6 -0
- package/dist/types/providers/openai-responses.d.ts +9 -0
- package/dist/types/providers/openai-shared.d.ts +30 -0
- package/dist/types/providers/systemone-server.d.ts +7 -3
- package/dist/types/providers/transform-messages.d.ts +26 -5
- package/dist/types/registry/oauth/google-antigravity.d.ts +1 -1
- package/dist/types/stream.d.ts +0 -2
- package/dist/types/types.d.ts +33 -9
- package/dist/types/utils/dsml-leak.d.ts +4 -2
- package/dist/types/utils/narration.d.ts +9 -0
- package/dist/types/utils/openai-http.d.ts +2 -0
- package/dist/types/utils/request-body.d.ts +11 -0
- package/dist/types/utils/request-compression.d.ts +21 -0
- package/dist/types/utils/tool-call-arguments.d.ts +2 -1
- package/dist/types/utils/transport-fetch.d.ts +8 -0
- package/dist/types/utils.d.ts +1 -1
- package/package.json +8 -8
- package/src/auth/cascade.ts +23 -13
- package/src/auth/pool.ts +3 -3
- package/src/auth/refresh.ts +39 -12
- package/src/auth/select.ts +8 -2
- package/src/auth/types.ts +16 -5
- package/src/auth/usage-cache.ts +23 -7
- package/src/auth/usage.ts +2 -4
- package/src/auth-broker/remote-store.ts +31 -6
- package/src/auth-gateway/clients.ts +318 -0
- package/src/auth-gateway/dispatch.ts +66 -35
- package/src/auth-gateway/http.ts +11 -5
- package/src/auth-gateway/index.ts +1 -0
- package/src/auth-gateway/routes/embeddings.ts +4 -10
- package/src/auth-gateway/routes/images.ts +6 -10
- package/src/auth-gateway/routes/rerank.ts +4 -10
- package/src/auth-gateway/routes/speech.ts +10 -10
- package/src/auth-gateway/routes/systemone.ts +22 -12
- package/src/auth-gateway/routes/transcriptions.ts +4 -2
- package/src/auth-gateway/routes/video.ts +12 -15
- package/src/auth-gateway/server.ts +136 -53
- package/src/auth-gateway/session-state.ts +14 -0
- package/src/auth-gateway/types.ts +16 -2
- package/src/dialect/coercion.ts +16 -0
- package/src/dialect/hermes.ts +7 -11
- package/src/dialect/qwen3.ts +3 -11
- package/src/error/classes.ts +3 -2
- package/src/error/flags.ts +24 -3
- package/src/error/index.ts +1 -0
- package/src/error/network-cause.ts +179 -0
- package/src/images/codex-format-directive.md +3 -0
- package/src/images/google-antigravity.ts +2 -1
- package/src/images/google-generative-ai.ts +4 -2
- package/src/images/openai-hosted.ts +25 -2
- package/src/images/openai-images.ts +11 -6
- package/src/images/shared.ts +77 -3
- package/src/index.ts +1 -0
- package/src/judgment/chat.ts +17 -1
- package/src/judgment/native.ts +182 -12
- package/src/judgment/openai-decisions.ts +27 -6
- package/src/judgment/systemone.ts +131 -10
- package/src/judgment/text.ts +20 -7
- package/src/judgment/types.ts +5 -1
- package/src/judgment/wire.ts +17 -3
- package/src/provider-session-state.ts +2 -2
- package/src/providers/amazon-bedrock.ts +4 -1
- package/src/providers/anthropic-client.ts +1 -1
- package/src/providers/anthropic-images.ts +263 -0
- package/src/providers/anthropic-messages-server.ts +84 -1
- package/src/providers/anthropic-signature.ts +35 -8
- package/src/providers/anthropic-state.ts +13 -0
- package/src/providers/anthropic.ts +479 -397
- package/src/providers/connection-prewarm.ts +78 -0
- package/src/providers/cowork-fetch.ts +5 -1
- package/src/providers/cursor/exec-modern.ts +1 -0
- package/src/providers/cursor-pi-args.ts +19 -0
- package/src/providers/cursor.ts +53 -13
- package/src/providers/devin.ts +10 -4
- package/src/providers/factory-droid/gemini.ts +9 -3
- package/src/providers/google-gemini-cli.ts +11 -1
- package/src/providers/google-shared.ts +2 -6
- package/src/providers/images-server.ts +6 -1
- package/src/providers/ollama.ts +15 -5
- package/src/providers/openai-anthropic-shim.ts +1 -0
- package/src/providers/openai-chat-server.ts +29 -0
- package/src/providers/openai-codex/access-programs.ts +2 -1
- package/src/providers/openai-codex-responses.ts +49 -82
- package/src/providers/openai-completions.ts +117 -19
- package/src/providers/openai-responses.ts +25 -2
- package/src/providers/openai-shared.ts +36 -5
- package/src/providers/pi-native-server.ts +1 -0
- package/src/providers/systemone-server.ts +72 -12
- package/src/providers/transform-messages.ts +95 -51
- package/src/registry/oauth/google-antigravity.ts +1 -1
- package/src/stream.ts +13 -73
- package/src/types.ts +33 -9
- package/src/usage/claude.ts +2 -9
- package/src/usage/github-copilot.ts +26 -4
- package/src/usage/openai-codex.ts +13 -7
- package/src/usage/opencode-go.ts +7 -8
- package/src/usage/xai-oauth.ts +2 -9
- package/src/utils/dsml-leak.ts +30 -14
- package/src/utils/leaked-thinking-stream.ts +10 -0
- package/src/utils/narration.ts +18 -0
- package/src/utils/openai-http.ts +27 -18
- package/src/utils/request-body.ts +27 -0
- package/src/utils/request-compression.ts +202 -0
- package/src/utils/request-debug.ts +23 -9
- package/src/utils/schema/dereference.ts +51 -18
- package/src/utils/schema/draft.ts +10 -2
- package/src/utils/schema/json-schema-validator.ts +4 -10
- package/src/utils/schema/normalize.ts +6 -9
- package/src/utils/tool-call-arguments.ts +2 -1
- package/src/utils/transport-fetch.ts +37 -0
- package/src/utils/validation.ts +26 -16
- package/src/utils.ts +38 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2026-10-18
|
|
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
|
+
- `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))
|
|
9
|
+
- `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))
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- 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))
|
|
14
|
+
- Added `withoutUnverifiedThinking` utility to filter reasoning capability from models when no effort is requested
|
|
15
|
+
- 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))
|
|
16
|
+
- 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))
|
|
17
|
+
- 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))
|
|
18
|
+
- 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)).
|
|
19
|
+
- `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))
|
|
20
|
+
- 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))
|
|
21
|
+
- 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))
|
|
22
|
+
- 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
|
|
23
|
+
- 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))
|
|
24
|
+
- 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))
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- 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))
|
|
29
|
+
- 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))
|
|
30
|
+
- 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))
|
|
31
|
+
- 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))
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- 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))
|
|
36
|
+
- 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))
|
|
37
|
+
- 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))
|
|
38
|
+
- 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))
|
|
39
|
+
- 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)).
|
|
40
|
+
- 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))
|
|
41
|
+
- 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))
|
|
42
|
+
- 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))
|
|
43
|
+
- 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))
|
|
44
|
+
- 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)).
|
|
45
|
+
- 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)).
|
|
46
|
+
- 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))
|
|
47
|
+
- 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))
|
|
48
|
+
- 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))
|
|
49
|
+
- 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))
|
|
50
|
+
- 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))
|
|
51
|
+
- 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))
|
|
52
|
+
- 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))
|
|
53
|
+
- 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))
|
|
54
|
+
- 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))
|
|
55
|
+
- 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))
|
|
56
|
+
- 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))
|
|
57
|
+
- 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))
|
|
58
|
+
- 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))
|
|
59
|
+
- 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))
|
|
60
|
+
- 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))
|
|
61
|
+
- 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))
|
|
62
|
+
- 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))
|
|
63
|
+
- 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))
|
|
64
|
+
- 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))
|
|
65
|
+
- 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))
|
|
66
|
+
- 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))
|
|
67
|
+
- 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))
|
|
68
|
+
- 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)).
|
|
69
|
+
- 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))
|
|
70
|
+
- 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))
|
|
71
|
+
- 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))
|
|
72
|
+
- 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))
|
|
73
|
+
- 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))
|
|
74
|
+
- 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))
|
|
75
|
+
- 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)).
|
|
76
|
+
- 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))
|
|
77
|
+
- 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)).
|
|
78
|
+
- 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)).
|
|
79
|
+
- 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))
|
|
80
|
+
- 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))
|
|
81
|
+
|
|
82
|
+
### Removed
|
|
83
|
+
|
|
84
|
+
- 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.
|
|
85
|
+
|
|
3
86
|
## [1.2.0] - 2026-10-12
|
|
4
87
|
|
|
5
88
|
- 版本线推进至 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** (
|
|
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
|
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -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
|
-
|
|
12
|
-
Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and
|
|
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,
|
|
83
|
-
printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
|
|
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
|
-
* `
|
|
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 (`
|
|
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 (`
|
|
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
|
-
*
|
|
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 `
|
|
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 (`
|
|
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 (`
|
|
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.
|
|
41
|
-
*
|
|
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.
|
|
44
|
-
*
|
|
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(
|
|
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(
|
|
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.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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(
|
|
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
|
|
@@ -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
|
-
/**
|
|
35
|
-
|
|
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 `
|
|
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(
|
|
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
|
|
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
|
}
|
|
@@ -21,3 +21,9 @@ export declare function partialSuffixOverlap(text: string, tag: string): number;
|
|
|
21
21
|
export declare function partialSuffixOverlapAny(text: string, tags: readonly string[]): number;
|
|
22
22
|
export declare function normalizeKimiFunctionName(rawId: string): string;
|
|
23
23
|
export declare function asRecord(value: unknown): Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Extract a COMPLETE tool name from a partial in-band JSON body.
|
|
26
|
+
* Returns undefined until the name string is fully closed, so streaming
|
|
27
|
+
* scanners never start a tool call under a truncated name.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseCompleteToolName(body: string): string | undefined;
|