@linxiraos/pi-ai 1.1.26 → 1.1.27

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 (82) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/README.md +15 -0
  3. package/dist/types/auth/cascade.d.ts +20 -5
  4. package/dist/types/auth/types.d.ts +12 -1
  5. package/dist/types/auth-broker/refresher.d.ts +6 -1
  6. package/dist/types/auth-gateway/dispatch.d.ts +6 -2
  7. package/dist/types/auth-gateway/index.d.ts +1 -0
  8. package/dist/types/auth-gateway/routes/embeddings.d.ts +2 -2
  9. package/dist/types/auth-gateway/routes/images.d.ts +3 -3
  10. package/dist/types/auth-gateway/routes/rerank.d.ts +2 -2
  11. package/dist/types/auth-gateway/routes/speech.d.ts +2 -2
  12. package/dist/types/auth-gateway/routes/systemone.d.ts +2 -2
  13. package/dist/types/auth-gateway/routes/transcriptions.d.ts +2 -2
  14. package/dist/types/auth-gateway/routes/video.d.ts +4 -4
  15. package/dist/types/auth-gateway/server.d.ts +20 -3
  16. package/dist/types/auth-gateway/stdio.d.ts +12 -0
  17. package/dist/types/error/flags.d.ts +15 -0
  18. package/dist/types/index.d.ts +1 -1
  19. package/dist/types/providers/anthropic.d.ts +15 -2
  20. package/dist/types/providers/aws-credentials.d.ts +4 -8
  21. package/dist/types/providers/connect-error-detail.d.ts +9 -0
  22. package/dist/types/providers/openai-responses.d.ts +32 -3
  23. package/dist/types/providers/openai-shared.d.ts +14 -1
  24. package/dist/types/providers/transform-messages.d.ts +11 -0
  25. package/dist/types/registry/oauth/snowflake.d.ts +3 -0
  26. package/dist/types/registry/snowflake.d.ts +4 -0
  27. package/dist/types/types.d.ts +40 -3
  28. package/dist/types/utils/dsml-leak.d.ts +29 -0
  29. package/dist/types/utils/idle-iterator.d.ts +11 -0
  30. package/dist/types/utils/stream-markup-healing.d.ts +4 -4
  31. package/dist/types/utils/tool-call-arguments.d.ts +8 -0
  32. package/dist/types/utils.d.ts +6 -0
  33. package/package.json +6 -6
  34. package/src/auth/blocks.ts +4 -1
  35. package/src/auth/cascade.ts +45 -9
  36. package/src/auth/health.ts +1 -0
  37. package/src/auth/select.ts +3 -1
  38. package/src/auth/types.ts +12 -2
  39. package/src/auth-broker/refresher.ts +8 -1
  40. package/src/auth-gateway/dispatch.ts +6 -2
  41. package/src/auth-gateway/index.ts +1 -0
  42. package/src/auth-gateway/routes/embeddings.ts +2 -2
  43. package/src/auth-gateway/routes/images.ts +4 -4
  44. package/src/auth-gateway/routes/rerank.ts +2 -2
  45. package/src/auth-gateway/routes/speech.ts +2 -2
  46. package/src/auth-gateway/routes/systemone.ts +6 -2
  47. package/src/auth-gateway/routes/transcriptions.ts +2 -2
  48. package/src/auth-gateway/routes/video.ts +7 -7
  49. package/src/auth-gateway/server.ts +144 -118
  50. package/src/auth-gateway/stdio.ts +132 -0
  51. package/src/dialect/deepseek.ts +57 -6
  52. package/src/error/flags.ts +50 -24
  53. package/src/images/google-antigravity.ts +0 -1
  54. package/src/index.ts +1 -1
  55. package/src/providers/anthropic.ts +115 -46
  56. package/src/providers/aws-credentials.ts +63 -3
  57. package/src/providers/connect-error-detail.ts +21 -0
  58. package/src/providers/cursor.ts +81 -33
  59. package/src/providers/devin.ts +3 -1
  60. package/src/providers/factory-droid.ts +2 -0
  61. package/src/providers/gitlab-duo.ts +1 -0
  62. package/src/providers/google-gemini-cli.ts +4 -0
  63. package/src/providers/openai-anthropic-shim.ts +1 -0
  64. package/src/providers/openai-codex-responses.ts +58 -6
  65. package/src/providers/openai-completions.ts +3 -0
  66. package/src/providers/openai-responses.ts +349 -6
  67. package/src/providers/openai-shared.ts +113 -29
  68. package/src/providers/pi-native-server.ts +1 -0
  69. package/src/providers/transform-messages.ts +2 -2
  70. package/src/registry/hooks/custom.ts +2 -0
  71. package/src/registry/oauth/snowflake.ts +183 -0
  72. package/src/registry/registry.ts +2 -0
  73. package/src/registry/snowflake.ts +149 -0
  74. package/src/stream.ts +80 -35
  75. package/src/types.ts +41 -3
  76. package/src/usage/claude.ts +3 -0
  77. package/src/utils/dsml-leak.ts +109 -0
  78. package/src/utils/idle-iterator.ts +80 -35
  79. package/src/utils/stream-markup-healing.ts +4 -4
  80. package/src/utils/thinking-loop.ts +18 -10
  81. package/src/utils/tool-call-arguments.ts +16 -0
  82. package/src/utils.ts +19 -4
package/CHANGELOG.md CHANGED
@@ -2,9 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [1.1.26] - 2026-10-03
5
+ ## [1.1.27] - 2026-10-07
6
6
 
7
- - 版本线推进至 1.1.26;本版无独立用户可见变化。
7
+ - 版本线推进至 1.1.27;本版无独立用户可见变化。
8
+
9
+ ### Changed
10
+
11
+ - Reduced CPU spent on thinking-loop detection while streaming long reasoning ([#14284](https://github.com/can1357/oh-my-pi/pull/14284) by [@abilliontokens](https://github.com/abilliontokens)).
12
+ - Added `recordAffinity: false` to credential resolution options, selecting as the session would without pinning the choice to that session ([#14512](https://github.com/can1357/oh-my-pi/pull/14512) by [@will-bogusz](https://github.com/will-bogusz))
13
+
14
+ ### Fixed
15
+
16
+ - Fixed replayed Responses and Codex history, including persisted Codex user/developer and assistant items, sending `detail: "original"` images to endpoints whose `supportsImageDetailOriginal` is off ([#13687](https://github.com/can1357/oh-my-pi/pull/13687) by [@alphastorm](https://github.com/alphastorm)).
17
+ - Fixed thinking in turns kept after Anthropic native compaction being rejected or dropped on the next request ([#14251](https://github.com/can1357/oh-my-pi/pull/14251) by [@will-bogusz](https://github.com/will-bogusz))
18
+ - Fixed Claude usage being re-polled every 10 seconds while Anthropic rate-limits the account; a failed refresh now waits a minute before trying again ([#14515](https://github.com/can1357/oh-my-pi/pull/14515) by [@will-bogusz](https://github.com/will-bogusz))
19
+ - Fixed Devin requests skipping the `onPayload` hook, so payload capture now sees each Devin chat request and a returned replacement is what gets sent ([#14506](https://github.com/can1357/oh-my-pi/pull/14506) by [@will-bogusz](https://github.com/will-bogusz))
20
+ - An OpenAI Responses turn whose connection drops mid-stream can now recover the finished answer from the provider (on hosts that store results, such as Muse Code) instead of re-running the whole turn and discarding the reasoning already done. Storage is opt-in: pass `storeResponses: true`, set `PI_MUSE_STORE_RESPONSES=1`, or set a process-wide default with `configureProviderStoreResponses` ([#14293](https://github.com/can1357/oh-my-pi/pull/14293) and [#14534](https://github.com/can1357/oh-my-pi/pull/14534) by [@abilliontokens](https://github.com/abilliontokens)).
21
+ - Fixed the auth broker exiting when a background OAuth refresh sweep cannot read the credential store; the failure is now logged and the next sweep retries ([#14538](https://github.com/can1357/oh-my-pi/issues/14538))
22
+ - Fixed Anthropic OAuth billing headers changing during developer-first sessions and side turns, preserving the prompt-cache prefix ([#14495](https://github.com/can1357/oh-my-pi/issues/14495)).
23
+ - Fixed OpenAI-compatible chat-completions gateways recording completed turns as client-cancelled because the connection closed before their `[DONE]` sentinel arrived ([#14481](https://github.com/can1357/oh-my-pi/issues/14481)).
8
24
 
9
25
  ## [1.1.25] - 2026-10-03
10
26
 
@@ -13,3 +29,10 @@
13
29
  ## [1.1.24] - 2026-10-03
14
30
 
15
31
  - 网关身份头统一 `x-zeta-*`(消费方同步);新增 `extractHttpStatusFromError`/`extractRetryHint` 导出。
32
+
33
+ ### Fixed
34
+
35
+ - Fixed Antigravity chat and image requests sending an outdated client version when the model list came from cache, which could make newer models such as Claude Opus 5.5 unavailable.
36
+ - When a DeepSeek model writes a broken DSML tool call (for example with the opening `<|DSML|tool_calls>` and `<|DSML|invoke>` tags missing), its closing tags are now kept in the streamed text instead of being dropped. This lets the agent remove exactly the broken call while keeping any text after it ([#14202](https://github.com/can1357/oh-my-pi/pull/14202) by [@H4vC](https://github.com/H4vC)).
37
+
38
+ Older entries are archived in [packages/ai/CHANGELOG.md@a20cc0d04b64](https://github.com/can1357/oh-my-pi/blob/a20cc0d04b64f4a68fcc559cbd258743d984c50e/packages/ai/CHANGELOG.md).
package/README.md CHANGED
@@ -77,6 +77,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
77
77
  - **QwenCloud Token Plan** (supports `/login alibaba-token-plan`, `ALIBABA_TOKEN_PLAN_API_KEY`, or `BAILIAN_TOKEN_PLAN_API_KEY`; interactive login first selects a region — International (Singapore, default), China (Beijing) for 百炼 Token Plan keys, or a custom base URL — since region keys are non-interchangeable, then optionally stores a `home.qwencloud.com` Cookie request header for best-effort 5-hour and 7-day quota reporting)
78
78
  To enable quota reporting, sign in to the Token Plan dashboard, copy the `Cookie` request-header value from a `home.qwencloud.com` request in browser developer tools, and paste it at the second login prompt. Press Enter to skip; the Cookie is sensitive and session-lived, so rerun login when it expires.
79
79
  - **Cloudflare AI Gateway** (supports `/login cloudflare-ai-gateway`, or `CLOUDFLARE_AI_GATEWAY_API_KEY` with `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_GATEWAY_ID`)
80
+ - **Snowflake Cortex** (browser `/login snowflake`, or `SNOWFLAKE_ACCOUNT` + `SNOWFLAKE_PAT`; account-billed Cortex REST API)
80
81
  - **Ollama** (local OpenAI-compatible runtime; optional `OLLAMA_API_KEY`)
81
82
  - **Ollama Cloud** (hosted native Ollama API; requires `OLLAMA_CLOUD_API_KEY`)
82
83
  - **llama.cpp** (local OpenAI and Anthropic compatible inference server)
@@ -961,6 +962,7 @@ In Node.js environments, you can set environment variables to avoid passing API
961
962
  | ZenMux | `ZENMUX_API_KEY` |
962
963
  | vLLM | `VLLM_API_KEY` |
963
964
  | Cloudflare AI Gateway | `CLOUDFLARE_AI_GATEWAY_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_GATEWAY_ID` |
965
+ | Snowflake Cortex | `SNOWFLAKE_PAT` + `SNOWFLAKE_ACCOUNT` |
964
966
  | GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` |
965
967
 
966
968
  `/login cloudflare-ai-gateway` collects and stores the gateway token, account ID, and gateway ID. For environment configuration, set all three Cloudflare values above. OMP derives provider endpoints from the account and gateway IDs.
@@ -1029,9 +1031,22 @@ Several providers support OAuth authentication (some also support static API key
1029
1031
  - **Google Gemini CLI** (Gemini 2.0/2.5 via Google Cloud Code Assist; free tier or paid subscription)
1030
1032
  - **Antigravity** (Free Gemini 3, Claude, GPT-OSS via Google Cloud)
1031
1033
  - **Qwen Portal** (Qwen OAuth token or API key)
1034
+ - **Snowflake Cortex** (Snowflake account OAuth with PKCE and refresh, subject to the account's local-application integration)
1032
1035
 
1033
1036
  For paid Cloud Code Assist subscriptions, set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` to your project ID.
1034
1037
 
1038
+ ### Snowflake Cortex
1039
+
1040
+ `/login snowflake` prompts for an account identifier (for example, `myorg-myaccount`), an HTTPS Snowflake account URL, or a Snowsight account link. It opens Snowflake's built-in `SNOWFLAKE$LOCAL_APPLICATION` OAuth flow using PKCE and a `127.0.0.1` callback (port 54551, with an ephemeral-port fallback). The stored account selects the inference endpoint; OAuth credentials cannot be sent to a different account through a custom model endpoint.
1041
+
1042
+ The default inference role must hold either `SNOWFLAKE.CORTEX_USER` or the narrower, REST-only `SNOWFLAKE.CORTEX_REST_API_USER` (recommended for least privilege). `CORTEX_REST_API_USER` cannot be granted to a user directly; grant it to a custom role, and revoke `CORTEX_USER` from users who already hold it so the narrower control applies. The Cortex REST API is not available in China-region (`.snowflakecomputing.cn`) accounts. An administrator can check local-app availability with `SHOW SECURITY INTEGRATIONS LIKE 'SNOWFLAKE$LOCAL_APPLICATION'` and enable the integration or refresh issuance if necessary. When Snowflake issues a refresh token, omp refreshes the access token automatically. Without one, login remains valid until expiry; then sign in again or use the PAT environment alternative.
1043
+
1044
+ For PAT authentication, set both `SNOWFLAKE_ACCOUNT` and `SNOWFLAKE_PAT`. Prefer a short-lived PAT restricted to a dedicated inference role and the required user-scoped network allowlist; do not disable network-policy enforcement. See [Snowflake PAT setup](https://docs.snowflake.com/en/user-guide/programmatic-access-tokens) and [local-application OAuth](https://docs.snowflake.com/en/user-guide/oauth-local-applications).
1045
+
1046
+ Claude models use `/api/v2/cortex/v1/messages`; OpenAI models use `/api/v2/cortex/v1/chat/completions`. Both reuse omp's streaming transports and local tool execution. The 15 bundled models are a documented roster, not an account entitlement check: availability still depends on Cortex REST API enablement, role grants, model allowlists, and region. If authentication succeeds but Cortex returns `003001` ("This account is not allowed to access this endpoint"), Snowflake has not enabled Cortex REST for the account. [Self-service trial accounts disable AI features until a credit card is added](https://docs.snowflake.com/en/user-guide/admin-trial-account#label-trial-account-ai-features) (adding one does not end the trial); REST access can remain disabled even after AI SQL functions work, in which case Snowflake support must enable it. Cross-region inference requires an account administrator's explicit decision. `omp models snowflake --json` lists available models only after credentials are configured.
1047
+
1048
+ This provider uses the [public, account-billed Cortex REST API](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-rest-api). CoCo-subscription coverage is unverified. Catalog prices are per-million-token estimates from [Consumption Table 6(b)](https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf) at [$2 per AI Credit](https://docs.snowflake.com/en/user-guide/snowflake-cortex/pricing) for on-demand global routing, not your contract price. Output is capped at the REST API's documented 16,384-token maximum; context windows are conservative AI_COMPLETE limits, not verified REST maxima. Cortex compatibility rules limit explicit cache retention to five minutes. Paste an account identifier or current Snowsight link; legacy `app.snowflake.com/<region>/<locator>` links are rejected because they don't map uniformly to an account host.
1049
+
1035
1050
  ### Vertex AI (ADC)
1036
1051
 
1037
1052
  Vertex AI models use Application Default Credentials (ADC):
@@ -12,6 +12,13 @@ export declare class KeyOverrides {
12
12
  configKey(provider: string): string | undefined;
13
13
  /** Config value consulted only after stored OAuth/login credentials. */
14
14
  fallbackKey(provider: string): string | undefined;
15
+ /**
16
+ * Config value consulted only after every other source, including stored
17
+ * api_key credentials — the lowest cascade tier. The OMP compatibility
18
+ * overlay uses it so an upstream key is a pure fallback: any credential
19
+ * the user later stores locally wins without needing to evict the mirror.
20
+ */
21
+ mirrorKey(provider: string): string | undefined;
15
22
  /** Resolve a config value (env var name, "!command", literal) to the secret. */
16
23
  resolve(config: string): Promise<string | undefined>;
17
24
  /**
@@ -37,12 +44,17 @@ export declare class KeyOverrides {
37
44
  * credentials (at the env-var tier). Providers that own a `/login` flow use
38
45
  * this so their default key reference (e.g. an unset env-var name, which
39
46
  * resolves to its literal text) cannot shadow the key the user logged in with.
47
+ *
48
+ * `mirror: true` ranks the value below everything else, under stored
49
+ * api_key credentials — a read-only compatibility mirror (upstream OMP
50
+ * agent.db key) that must never shadow a key the user stores locally.
40
51
  */
41
52
  setConfig(provider: string, apiKeyConfig: string, options?: {
42
53
  fallback?: boolean;
54
+ mirror?: boolean;
43
55
  }): void;
44
56
  /**
45
- * Remove a single config-sourced API key (override or fallback).
57
+ * Remove a single config-sourced API key (override, fallback, or mirror).
46
58
  */
47
59
  removeConfig(provider: string): void;
48
60
  /**
@@ -68,7 +80,7 @@ export interface KeyCascadeDeps {
68
80
  rotate: LimitsApi["rotate"];
69
81
  sourceLabel?: string;
70
82
  }
71
- /** The provider auth precedence cascade (runtime → config → OAuth → login key → env → stored key). */
83
+ /** The provider auth precedence cascade (runtime → config → OAuth → login key → env → stored key → compat mirror). */
72
84
  export declare class KeyCascade implements KeysApi {
73
85
  #private;
74
86
  constructor(deps: KeyCascadeDeps);
@@ -94,8 +106,8 @@ export declare class KeyCascade implements KeysApi {
94
106
  /**
95
107
  * Classify where a provider's auth comes from, following the same precedence
96
108
  * as {@link KeyCascade.get}: runtime override → config override →
97
- * stored OAuth → login-stored api_key → config fallback → env var → stored api_key.
98
- * Returns undefined when no auth is configured.
109
+ * stored OAuth → login-stored api_key → config fallback → env var →
110
+ * stored api_key → compat mirror. Returns undefined when no auth is configured.
99
111
  *
100
112
  * Compact, structured counterpart to {@link KeyCascade.describe}; `env`
101
113
  * selects dedicated-only, alias-aware, or no environment fallback.
@@ -114,7 +126,8 @@ export declare class KeyCascade implements KeysApi {
114
126
  /**
115
127
  * Get API key for a provider.
116
128
  * Priority (first match wins): runtime override, config override, OAuth,
117
- * login API key, environment variable, then another stored API key.
129
+ * login API key, environment variable, another stored API key, then the
130
+ * lowest-tier OMP compat mirror.
118
131
  */
119
132
  get(provider: string, sessionId?: string, options?: AuthApiKeyOptions, onCredentialId?: (id: number, identity?: OAuthRequestIdentity) => void): Promise<string | undefined>;
120
133
  /**
@@ -143,6 +156,7 @@ export declare class KeyCascade implements KeysApi {
143
156
  * 4. API key persisted by a successful `/login`.
144
157
  * 5. Env var — overrides a stored static api_key (e.g. a stale broker copy).
145
158
  * 6. Stored api_key credential.
159
+ * 7. OMP compatibility mirror (upstream agent.db fallback key).
146
160
  *
147
161
  * The string is purely informational; consumers must not parse it.
148
162
  */
@@ -151,6 +165,7 @@ export declare class KeyCascade implements KeysApi {
151
165
  removeRuntime(provider: string): void;
152
166
  setConfig(provider: string, apiKeyConfig: string, options?: {
153
167
  fallback?: boolean;
168
+ mirror?: boolean;
154
169
  }): void;
155
170
  removeConfig(provider: string): void;
156
171
  clearConfig(): void;
@@ -220,6 +220,8 @@ export interface CheckCredentialsOptions {
220
220
  completionProbe?: CompletionProbe;
221
221
  /** Per-credential completion probe timeout (ms). Defaults to `timeoutMs`. */
222
222
  completionTimeoutMs?: number;
223
+ /** Providers whose credentials are skipped entirely (not probed, not reported). */
224
+ excludeProviders?: ReadonlySet<string>;
223
225
  }
224
226
  /**
225
227
  * Sentinel value placed in OAuth `refresh` fields when a credential is shared
@@ -424,6 +426,8 @@ export type AuthApiKeyOptions = {
424
426
  forceRefresh?: boolean;
425
427
  /** Explicit provider-401 recovery; generic force refreshes leave this unset. */
426
428
  refreshReason?: OAuthRefreshReason;
429
+ /** When false, select as `sessionId` would without recording the choice as that session's sticky credential. */
430
+ recordAffinity?: boolean;
427
431
  };
428
432
  /** Non-secret identity bound to the OAuth credential selected for one request attempt. */
429
433
  export interface OAuthRequestIdentity {
@@ -808,6 +812,8 @@ export interface KeysApi {
808
812
  * 4. API key persisted by a successful `/login`
809
813
  * 5. Environment variable
810
814
  * 6. Stored API key (e.g. a broker-migrated copy) — last resort, so an explicit env var wins
815
+ * 7. OMP compatibility mirror — below every stored credential, so a key the
816
+ * user saves locally always wins over the upstream fallback
811
817
  */
812
818
  get(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined>;
813
819
  /** Resolve a bearer together with its durable stored credential row id, when known. */
@@ -865,12 +871,17 @@ export interface KeysApi {
865
871
  * `fallback: true` ranks the value below stored OAuth and `/login`
866
872
  * credentials instead, so a provider's default key reference cannot shadow
867
873
  * a key the user logged in with.
874
+ *
875
+ * `mirror: true` ranks the value below everything else, under stored
876
+ * api_key credentials — a read-only compatibility mirror (upstream OMP
877
+ * agent.db key) that must never shadow a locally stored key.
868
878
  */
869
879
  setConfig(provider: string, apiKeyConfig: string, options?: {
870
880
  fallback?: boolean;
881
+ mirror?: boolean;
871
882
  }): void;
872
883
  /**
873
- * Remove a single config-sourced API key (override or fallback).
884
+ * Remove a single config-sourced API key (override, fallback, or mirror).
874
885
  */
875
886
  removeConfig(provider: string): void;
876
887
  /**
@@ -20,6 +20,11 @@ export declare class AuthBrokerRefresher {
20
20
  start(): void;
21
21
  stop(): void;
22
22
  getSchedule(): AuthBrokerRefresherSchedule;
23
- /** Run one sweep. Exposed for tests. */
23
+ /**
24
+ * Run one sweep. Exposed for tests.
25
+ *
26
+ * Never rejects: callers fire it unawaited from a timer, so a failed sweep
27
+ * (e.g. an unreadable credential store) is logged and retried next interval.
28
+ */
24
29
  tick(): Promise<void>;
25
30
  }
@@ -5,8 +5,9 @@ import type { Api, FetchImpl, Model, Usage } from "../types.js";
5
5
  import type { ClientUsageIdentity } from "../usage.js";
6
6
  import type { AuthGatewayServerOptions } from "./types.js";
7
7
  export type ModelResolver = (modelId: string) => Model<Api> | undefined;
8
- export interface AuthGatewayBootOptions extends AuthGatewayServerOptions {
9
- /** Source of credentials. Caller wires this to a broker-backed AuthStorage. */
8
+ /** What the gateway's routes need, whatever transport carries the requests. */
9
+ export interface AuthGatewayRouteOptions {
10
+ /** Source of credentials: broker-backed for `serve`, the CLI's own for `stdio`. */
10
11
  storage: AuthStorage;
11
12
  /**
12
13
  * Resolve a client-requested model id to a pi-ai Model. Caller supplies
@@ -19,6 +20,9 @@ export interface AuthGatewayBootOptions extends AuthGatewayServerOptions {
19
20
  /** Upstream transport for every provider call; defaults to global `fetch`. Test seam. */
20
21
  fetch?: FetchImpl;
21
22
  }
23
+ /** The HTTP server's options: the routes' plus its listener and inbound auth. */
24
+ export interface AuthGatewayBootOptions extends AuthGatewayServerOptions, AuthGatewayRouteOptions {
25
+ }
22
26
  /**
23
27
  * The client's own session key, or `undefined` when it sent none. A blank key
24
28
  * counts as none: honouring it would collapse every caller that sends an empty
@@ -1,5 +1,6 @@
1
1
  export * from "./dispatch.js";
2
2
  export * from "./http.js";
3
3
  export * from "./session-state.js";
4
+ export * from "./stdio.js";
4
5
  export * from "./server.js";
5
6
  export * from "./types.js";
@@ -1,3 +1,3 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
2
  /** OpenAI-compatible `POST /v1/embeddings` gateway handler. */
3
- export declare function handleEmbeddings(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
3
+ export declare function handleEmbeddings(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,3 +1,3 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
2
- export declare function handleImageGenerations(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
3
- export declare function handleImageEdits(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
+ export declare function handleImageGenerations(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
3
+ export declare function handleImageEdits(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,3 +1,3 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
2
  /** OpenRouter-compatible `POST /v1/rerank` gateway handler. */
3
- export declare function handleRerank(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
3
+ export declare function handleRerank(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,2 +1,2 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
2
- export declare function handleSpeech(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
+ export declare function handleSpeech(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,2 +1,2 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
2
- export declare function handleSystemOne(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
+ export declare function handleSystemOne(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,3 +1,3 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
2
  /** OpenAI-compatible `POST /v1/audio/transcriptions` gateway handler. */
3
- export declare function handleTranscriptions(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
3
+ export declare function handleTranscriptions(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
@@ -1,7 +1,7 @@
1
- import { type AuthGatewayBootOptions } from "../dispatch.js";
1
+ import { type AuthGatewayRouteOptions } from "../dispatch.js";
2
2
  /** OpenRouter-compatible `POST /v1/videos` asynchronous video submit handler. */
3
- export declare function handleVideoSubmit(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response>;
3
+ export declare function handleVideoSubmit(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string): Promise<Response>;
4
4
  /** OpenRouter-compatible `GET /v1/videos/:id` asynchronous video poll handler. */
5
- export declare function handleVideoPoll(bootOpts: AuthGatewayBootOptions, req: Request, peer: string, gatewayId: string): Promise<Response>;
5
+ export declare function handleVideoPoll(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string, gatewayId: string): Promise<Response>;
6
6
  /** OpenRouter-compatible `GET /v1/videos/:id/content` streaming video content handler. */
7
- export declare function handleVideoContent(bootOpts: AuthGatewayBootOptions, req: Request, peer: string, gatewayId: string): Promise<Response>;
7
+ export declare function handleVideoContent(bootOpts: AuthGatewayRouteOptions, req: Request, peer: string, gatewayId: string): Promise<Response>;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zeta-c auth-gateway HTTP server.
2
+ * zeta auth-gateway router and HTTP server.
3
3
  *
4
4
  * Accepts any provider-format request (OpenAI chat-completions, Anthropic
5
5
  * messages, OpenAI Responses) and dispatches through pi-ai's `streamSimple()`
@@ -23,8 +23,25 @@
23
23
  * POST /v1/audio/transcriptions → speech-to-text, multipart or JSON base64 in (routes/transcriptions)
24
24
  *
25
25
  * Chat routes live in this file; every other modality is a `routes/*` module
26
- * built on the shared plumbing in `dispatch.ts`.
26
+ * built on the shared plumbing in `dispatch.ts`. {@link createAuthGatewayRouter}
27
+ * answers the `/v1/*` routes for any transport: {@link startAuthGateway}
28
+ * serves them over HTTP behind bearer auth, `stdio.ts` over JSON lines.
27
29
  */
28
- import { type AuthGatewayBootOptions } from "./dispatch.js";
30
+ import { type AuthGatewayBootOptions, type AuthGatewayRouteOptions } from "./dispatch.js";
29
31
  import type { AuthGatewayServerHandle } from "./types.js";
32
+ /** The gateway's `/v1/*` routes, for a transport that has already admitted the caller. */
33
+ 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>;
36
+ /**
37
+ * Closes the retained provider session state (Codex WebSockets, GitLab Duo
38
+ * workflows), whose sockets and timers would otherwise keep the process alive.
39
+ */
40
+ close(): void;
41
+ }
42
+ /**
43
+ * The gateway's routes over `opts`, owning their per-session provider state:
44
+ * two routers in one process never share (or tear down) each other's.
45
+ */
46
+ export declare function createAuthGatewayRouter(opts: AuthGatewayRouteOptions): AuthGatewayRouter;
30
47
  export declare function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle;
@@ -0,0 +1,12 @@
1
+ export interface AuthGatewayStdioOptions {
2
+ /** Request lines. */
3
+ input: ReadableStream<Uint8Array>;
4
+ /** Writes one response line, newline included. */
5
+ write(line: string): void;
6
+ /** Answers one request; never rejects (the gateway router's `route`, or a wrapper around it). */
7
+ route(req: Request): Promise<Response>;
8
+ /** Reported on the ready line. */
9
+ version?: string;
10
+ }
11
+ /** Serves `opts.input` until it ends and every request it carried is answered. */
12
+ export declare function serveAuthGatewayStdio(opts: AuthGatewayStdioOptions): Promise<void>;
@@ -43,6 +43,21 @@ export declare function isResponsesRequestBodyReadTimeout(message: {
43
43
  errorMessage?: string;
44
44
  requestBodyReadTimeoutFullReplay?: boolean;
45
45
  }): boolean;
46
+ /**
47
+ * Codex code for the experimental native turn lane refusing a mid-response
48
+ * `response.steer`; the server then drops the in-flight response and closes
49
+ * the socket.
50
+ */
51
+ export declare const CODEX_NATIVE_LANE_STEER_REJECTED_CODE = "unsupported_native_inflight_message";
52
+ /**
53
+ * A Codex turn the native turn lane dropped because omp steered it. The
54
+ * rejection answers our own `response.steer`, not the model's health: the
55
+ * provider stops steering the session, so the same model replays cleanly.
56
+ */
57
+ export declare function isCodexSteerRejection(message: {
58
+ api?: Api;
59
+ errorMessage?: string;
60
+ }): boolean;
46
61
  export declare const TRANSIENT_TRANSPORT_PATTERN: RegExp;
47
62
  export declare const ANTHROPIC_ACCOUNT_POLICY_PATTERN: RegExp;
48
63
  /** Whether an error message represents an Anthropic account-scoped permission/policy denial. */
@@ -1,7 +1,7 @@
1
1
  export { type Type, type } from "@linxiraos/pi-omptype";
2
2
  export * from "./api-registry.js";
3
3
  export type * from "./auth-broker/index.js";
4
- export type { AuthGatewayBootOptions, ModelResolver } from "./auth-gateway/dispatch.js";
4
+ export type { AuthGatewayBootOptions, AuthGatewayRouteOptions, ModelResolver } from "./auth-gateway/dispatch.js";
5
5
  export * from "./auth-gateway/types.js";
6
6
  export * from "./auth-retry.js";
7
7
  export * from "./auth-storage.js";
@@ -1,4 +1,5 @@
1
- import type { AnthropicOutputEffort, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
1
+ import type { Effort } from "@linxiraos/pi-catalog/effort";
2
+ import type { AnthropicCompactionRequest, AnthropicOutputEffort, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
2
3
  import { type AnthropicFetchOptions, type AnthropicMessagesClientLike } from "./anthropic-client.js";
3
4
  import { type FallbackParam, type MessageParam, type TextBlockParam } from "./anthropic-wire.js";
4
5
  import { resolvesToOfficialAnthropicEndpoint, supportsAnthropicCompaction, supportsAnthropicCompactionOnClient } from "./anthropic-compaction.js";
@@ -209,7 +210,7 @@ export type AnthropicSystemBlock = {
209
210
  type SystemBlockOptions = {
210
211
  includeClaudeCodeInstruction?: boolean;
211
212
  extraInstructions?: string[];
212
- /** Text of the first user message — used as fingerprint seed for the billing header. */
213
+ /** Text of the first conversational wire user turn, used as the billing fingerprint seed. */
213
214
  firstUserMessageText?: string;
214
215
  /** Cache lifetime shared by the OAuth system breakpoint and later message breakpoints. */
215
216
  cacheControl?: AnthropicCacheControl;
@@ -234,6 +235,16 @@ export declare function budgetThinkingOutput(maxTokens: number | undefined, budg
234
235
  maxTokens: number;
235
236
  budgetTokens: number;
236
237
  };
238
+ /**
239
+ * The effort whose thinking allowance an on-demand compaction request over
240
+ * `messages` gets. It sends no effort change of its own, so once an earlier
241
+ * request recorded an effort, the one it left in force applies, even when
242
+ * `requested` differs or is off. When that request named none, the API's
243
+ * per-model default applies, which is not known here: the request gets the
244
+ * largest effort's allowance (`max_tokens` is a cap, not spend). Without a
245
+ * recorded effort the request sets its own.
246
+ */
247
+ export declare function resolveAnthropicCompactionEffort(model: Model<"anthropic-messages">, messages: readonly Message[], requested: Effort | undefined): Effort | undefined;
237
248
  /**
238
249
  * Whether the conversation has stopped being thinking-led, so routes with
239
250
  * `compat.stripThinkingHistory` drop the budget `thinking` config and replay
@@ -269,5 +280,7 @@ export declare function convertAnthropicMessages(messages: Message[], model: Mod
269
280
  dropAllThinking?: boolean;
270
281
  droppedThinkingBlocks?: ReadonlySet<string>;
271
282
  credentialId?: number;
283
+ /** On-demand compaction request: it ends inside the conversation, so it gets nothing live requests lack there. */
284
+ compactionRequest?: AnthropicCompactionRequest;
272
285
  }): AnthropicMessageParam[];
273
286
  export declare function normalizeAnthropicToolSchema(schema: unknown): unknown;
@@ -29,14 +29,10 @@ export interface CredentialResolveOptions {
29
29
  fetch?: FetchImpl;
30
30
  }
31
31
  export declare function resolveAwsCredentials(opts?: CredentialResolveOptions): Promise<ResolvedCredentials>;
32
- /** POSIX-shell-style tokenizer used by the AWS CLI for `credential_process`.
33
- *
34
- * Outside quotes a backslash escapes the next character. Inside single quotes
35
- * everything is literal (no escapes, cannot contain `'`). Inside double quotes
36
- * a backslash only escapes `$`, `` ` ``, `"`, and `\` — every other backslash
37
- * is preserved verbatim, which is what makes Windows paths like
38
- * `"C:\Program Files\tool\auth.exe"` survive tokenization. */
39
- export declare function tokenizeCredentialProcessCommand(cmd: string): string[];
32
+ /** Split a `credential_process` command into argv the way botocore's
33
+ * `compat_shell_split` does: Windows command-line (CRT) rules on `win32`,
34
+ * POSIX shell rules everywhere else. */
35
+ export declare function tokenizeCredentialProcessCommand(cmd: string, platform?: NodeJS.Platform): string[];
40
36
  /** Test/diagnostic helper — drops cached credentials. */
41
37
  export declare function clearAwsCredentialCache(): void;
42
38
  /**
@@ -4,6 +4,15 @@
4
4
  * usable is present.
5
5
  */
6
6
  export declare function summarizeConnectErrorDetails(details: unknown): string | undefined;
7
+ /**
8
+ * True when Cursor's `aiserver.v1.ErrorDetails` entry declares the failure
9
+ * retryable. Its JSON `debug` form carries `details.isRetryable` — set on
10
+ * upstream outages such as ERROR_OPENAI ("Unable to reach the model
11
+ * provider") whose bare `code: message` text (`unavailable: Error`)
12
+ * classifies as a hard failure on its own. Other detail types are ignored
13
+ * even when they share the `debug.details` shape.
14
+ */
15
+ export declare function hasRetryableCursorErrorDetail(details: unknown): boolean;
7
16
  /**
8
17
  * Formats a Connect end-stream error object into a diagnosable message.
9
18
  * The "Connect error code: message" prefix is preserved exactly; detail
@@ -16,9 +16,9 @@ export interface OpenAIResponsesOptions extends StreamOptions {
16
16
  /**
17
17
  * Stateful turns: chain via `previous_response_id` + delta input instead of
18
18
  * replaying the full transcript. Forces `store: true` (the platform only
19
- * resolves stored responses). Defaults ON against the official OpenAI API
20
- * and OFF for other Responses endpoints; `PI_OPENAI_STATEFUL` overrides the
21
- * default, and `false` here vetoes everything. Requires `sessionId` +
19
+ * resolves stored responses). Precedence: this option, then
20
+ * `PI_OPENAI_STATEFUL`, then `compat.statefulResponses`, then ON against the
21
+ * official OpenAI API and OFF elsewhere. Requires `sessionId` +
22
22
  * `providerSessionState`. Falls back to a full replay whenever history
23
23
  * mutates or the server reports a stale id.
24
24
  */
@@ -60,6 +60,27 @@ export interface OpenAIResponsesOptions extends StreamOptions {
60
60
  /** Opt-in GPT-5.6+ prompt-cache policy. Unsupported explicit mode fails locally. */
61
61
  promptCache?: OpenAIPromptCacheOptions;
62
62
  }
63
+ /**
64
+ * Poll a stored Responses result until it reaches an adoptable terminal state.
65
+ * 404s, 5xx, and transport failures mean "not yet" (an in-flight run only
66
+ * becomes retrievable once the server finishes it); any other status, a
67
+ * mismatched id, or a failed/cancelled run ends the poll immediately. The
68
+ * whole recovery is bounded by one wall-clock deadline: each request's timeout
69
+ * is clamped to the time left, and no sleep may cross the deadline, so a
70
+ * stalled host cannot hold an already-failed turn past it. Returns the
71
+ * terminal object, or undefined on give-up/abort.
72
+ *
73
+ * @internal Exported for tests.
74
+ */
75
+ export declare function pollOpenAIResponsesResultForCompletion(args: {
76
+ fetchImpl: NonNullable<StreamOptions["fetch"]>;
77
+ url: string;
78
+ headers: Record<string, string>;
79
+ responseId: string;
80
+ signal?: AbortSignal;
81
+ /** Sleep between polls; defaults to a real wait honoring `signal`. */
82
+ wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
83
+ }): Promise<Record<string, unknown> | undefined>;
63
84
  interface OpenAIResponsesProviderSessionState extends ProviderSessionState, OpenAIStrictToolsState, OpenAIReasoningEffortFallbackState {
64
85
  nativeHistoryReplayWarmed: boolean;
65
86
  /** Stateful `previous_response_id` chain baselines, keyed by baseUrl/model/session. */
@@ -85,6 +106,14 @@ interface OpenAIResponsesChainState {
85
106
  /** Set once chaining is judged unsupported for this session (circuit breaker). */
86
107
  disabled: boolean;
87
108
  }
109
+ /**
110
+ * Set the process-wide storage default per provider id, used when a request
111
+ * leaves `storeResponses` unset. Hosts call this from their settings so every
112
+ * request follows the setting, including direct `streamSimple`/`completeSimple`
113
+ * side calls that do not pass per-request options. `PI_MUSE_STORE_RESPONSES`
114
+ * still overrides it.
115
+ */
116
+ export declare function configureProviderStoreResponses(byProvider: Readonly<Record<string, boolean>> | undefined): void;
88
117
  /**
89
118
  * Drop the account-bound half of every retained `openai-responses` record in
90
119
  * `states`: the stateful `previous_response_id` chain baselines.
@@ -78,7 +78,13 @@ export declare function applyOpenAIServiceTier(params: {
78
78
  * proxy can never skew those costs.
79
79
  */
80
80
  export declare function applyOpenAIResponsesServiceTierCost(model: Pick<Model, "provider" | "serviceTierCost">, usage: AssistantMessage["usage"], responseServiceTier: unknown, requestServiceTier: ServiceTier | null | undefined): void;
81
- /** Reconcile token-price estimates with a gateway's authoritative account charge. */
81
+ /**
82
+ * Reconcile token-price estimates with a gateway's authoritative account charge.
83
+ * BYOK turns (`is_byok: true`) price from the provider spend in
84
+ * `cost_details.upstream_inference_cost` plus whatever credits charge
85
+ * OpenRouter reports in `cost` (its BYOK fee is plan-dependent and can be $0),
86
+ * so both are covered.
87
+ */
82
88
  export declare function applyProviderReportedCost(model: Pick<Model, "provider">, usage: Usage, rawUsage: unknown): void;
83
89
  export interface OpenAIUsageAccountingInput {
84
90
  promptTokens: number;
@@ -470,6 +476,13 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
470
476
  supportsImageDetailOriginal: boolean;
471
477
  systemRole?: "system" | "developer";
472
478
  nativeHistory?: {
479
+ /**
480
+ * Replay same-provider native history. `false` marks a cold provider
481
+ * session (#489): native items are withheld except remote-compaction
482
+ * history and assistant turns that carry no server-issued state
483
+ * ({@link isColdReplayableResponsesTurn}); other turns are rebuilt from
484
+ * message content.
485
+ */
473
486
  replay: boolean;
474
487
  filterReasoning: boolean;
475
488
  };
@@ -1,4 +1,15 @@
1
1
  import type { Api, AssistantMessage, Message, Model } from "../types.js";
2
+ /**
3
+ * Maximum tool-call id length the strictest replay provider accepts.
4
+ *
5
+ * Anthropic requires `^[a-zA-Z0-9_-]+$` with a 64-char cap; Google and Codex
6
+ * `normalizeToolCallId` implementations cap individual id segments to the same
7
+ * 64-char ceiling. Replacement ids minted here flow back through
8
+ * `convertAnthropicMessages` (and friends) unchanged, so the `_dupN` suffix
9
+ * MUST not push a normalized id past this bound.
10
+ */
11
+ export declare const MAX_TOOL_CALL_ID_LENGTH = 64;
12
+ export declare function appendDuplicateSuffix(originalId: string, suffix: string, maxLength: number): string;
2
13
  /**
3
14
  * Whether a tool-call name cannot belong to any declared tool: missing, empty,
4
15
  * longer than OpenAI's 128-character replay limit, or containing whitespace or
@@ -0,0 +1,3 @@
1
+ import type { OAuthController, OAuthCredentials } from "./types.js";
2
+ export declare function loginSnowflake(ctrl: OAuthController): Promise<OAuthCredentials>;
3
+ export declare function refreshSnowflakeToken(credentials: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials>;
@@ -0,0 +1,4 @@
1
+ import type { ProviderTransport } from "./build.js";
2
+ /** Normalize account identifiers and Snowsight links without accepting arbitrary credential-receiving hosts. */
3
+ export declare function normalizeSnowflakeAccountUrl(input: string): string;
4
+ export declare const snowflakeTransport: ProviderTransport;