@oh-my-pi/pi-ai 15.3.1 → 15.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/dist/types/auth-broker/client.d.ts +21 -1
  3. package/dist/types/auth-broker/remote-store.d.ts +6 -1
  4. package/dist/types/auth-broker/server.d.ts +6 -0
  5. package/dist/types/auth-broker/types.d.ts +36 -0
  6. package/dist/types/auth-broker/wire-schemas.d.ts +148 -0
  7. package/dist/types/auth-gateway/types.d.ts +1 -1
  8. package/dist/types/auth-storage.d.ts +96 -0
  9. package/dist/types/index.d.ts +0 -1
  10. package/dist/types/providers/amazon-bedrock.d.ts +16 -0
  11. package/dist/types/providers/cursor.d.ts +7 -1
  12. package/dist/types/providers/mock.d.ts +0 -2
  13. package/dist/types/providers/openai-responses-shared.d.ts +26 -0
  14. package/dist/types/types.d.ts +31 -8
  15. package/dist/types/utils/abortable-iterator.d.ts +4 -0
  16. package/dist/types/utils/anthropic-auth.d.ts +11 -24
  17. package/dist/types/utils/idle-iterator.d.ts +11 -9
  18. package/dist/types/utils/oauth/index.d.ts +8 -5
  19. package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
  20. package/package.json +2 -2
  21. package/src/auth-broker/client.ts +97 -0
  22. package/src/auth-broker/remote-store.ts +145 -20
  23. package/src/auth-broker/server.ts +191 -1
  24. package/src/auth-broker/types.ts +43 -0
  25. package/src/auth-broker/wire-schemas.ts +38 -0
  26. package/src/auth-gateway/server.ts +27 -1
  27. package/src/auth-gateway/types.ts +1 -1
  28. package/src/auth-storage.ts +353 -32
  29. package/src/index.ts +0 -1
  30. package/src/providers/amazon-bedrock.ts +80 -24
  31. package/src/providers/anthropic.ts +55 -18
  32. package/src/providers/azure-openai-responses.ts +33 -10
  33. package/src/providers/cursor.ts +149 -28
  34. package/src/providers/google-gemini-cli.ts +17 -36
  35. package/src/providers/mock.ts +0 -4
  36. package/src/providers/openai-codex-responses.ts +173 -79
  37. package/src/providers/openai-completions-compat.ts +7 -1
  38. package/src/providers/openai-completions.ts +33 -37
  39. package/src/providers/openai-responses-shared.ts +95 -0
  40. package/src/providers/openai-responses.ts +51 -47
  41. package/src/providers/pi-native-server.ts +1 -0
  42. package/src/providers/register-builtins.ts +61 -8
  43. package/src/providers/transform-messages.ts +25 -0
  44. package/src/stream.ts +6 -2
  45. package/src/types.ts +31 -8
  46. package/src/usage/gemini.ts +15 -13
  47. package/src/usage/google-antigravity.ts +13 -12
  48. package/src/usage/kimi.ts +9 -14
  49. package/src/utils/abortable-iterator.ts +69 -0
  50. package/src/utils/anthropic-auth.ts +22 -143
  51. package/src/utils/idle-iterator.ts +26 -31
  52. package/src/utils/oauth/index.ts +23 -17
  53. package/src/utils/oauth/moonshot.ts +11 -4
  54. package/src/utils/sdk-stream-timeout.ts +43 -0
  55. package/dist/types/utils/h2-fetch.d.ts +0 -22
  56. package/src/utils/h2-fetch.ts +0 -60
package/CHANGELOG.md CHANGED
@@ -2,12 +2,101 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.4.1] - 2026-05-26
6
+ ### Added
7
+
8
+ - Added `isOpenAICompletionsProgressChunk` export to identify real progress chunks vs. keepalives in OpenAI completions streams
9
+ - Added per-provider stream watchdog overrides via `getStreamIdleTimeoutMs(fallbackMs)` and `getStreamFirstEventTimeoutMs(idleTimeoutMs, fallbackMs)` to allow providers like Google Gemini CLI to extend first-event timeouts without affecting global defaults
10
+ - Added `promptCacheKey` to `StreamOptions` and passed it through stream option mapping so callers can specify an explicit prompt-cache key separate from `sessionId`
11
+ - Added `promptCacheKey` support to the native server option whitelist so `promptCacheKey` is accepted by `pi-native-server` streams
12
+ - Restored the per-provider stream watchdog (`iterateWithIdleTimeout`) on top of the abortable iterator. The lazy stream forwarder in `register-builtins` now wraps every provider's event stream with the first-event + steady-state idle watchdog (`PI_STREAM_FIRST_EVENT_TIMEOUT_MS`, `PI_STREAM_IDLE_TIMEOUT_MS`; aliases honored), and Anthropic / OpenAI Completions / OpenAI Responses / Azure OpenAI Responses / Codex SSE re-emit their per-provider progress predicates so empty keepalive frames cannot keep a stalled stream alive. Reverts the partial regression from #1392 that left Codex WebSocket subagent runs hanging silently for hours when the broker dropped frames between deltas. The Codex WebSocket transport additionally now resets `lastProgressAt` only on progress events (not keepalives), giving the 300s WS-internal idle ceiling the same liveness semantics as the SSE path.
13
+
14
+ ### Changed
15
+
16
+ - Enabled OpenAI Codex WebSocket streams to apply `streamIdleTimeoutMs` and `streamFirstEventTimeoutMs` from `StreamOptions` per request instead of fixed internal defaults
17
+ - Changed stream idle watchdog implementation from `iterateUntilAbort` to `iterateWithIdleTimeout`, which now enforces maximum idle gaps between streamed events and distinguishes between first-event and steady-state timeouts
18
+ - Changed Anthropic, OpenAI Responses, OpenAI Completions, Azure OpenAI Responses, and OpenAI Codex Responses providers to use the new idle-timeout iterator with per-provider progress predicates so empty keepalive frames cannot keep a stalled stream alive
19
+ - Changed Codex WebSocket transport to reset `lastProgressAt` only on progress events (not keepalives), giving the 300s WS-internal idle ceiling the same liveness semantics as the SSE path
20
+ - Changed Google Gemini CLI stream forwarding defaults to use a 5-minute first-event floor via per-provider lazy-stream limits to avoid premature first-event timeouts on slow startup
21
+ - Changed OpenAI Responses and OpenAI Codex request handling to keep `sessionId` for provider routing and conversation headers while `promptCacheKey` controls the `prompt_cache_key` payload independently
22
+ - Changed `StreamOptions.streamIdleTimeoutMs` documentation to clarify it is now wired into every built-in provider and the lazy stream forwarder, and that `streamFirstEventTimeoutMs` is honored at both the SDK-request layer and the iterator-watchdog layer
23
+ - Changed OpenAI Responses and OpenAI Codex request handling so `sessionId` continues to drive provider routing and state while `promptCacheKey` controls the `prompt_cache_key` payload
24
+ - Changed Google Gemini CLI stream forwarding defaults to use a 5-minute first-event floor to avoid premature first-event timeouts on slow startup
25
+ - Changed auth-gateway request mapping to preserve incoming `prompt_cache_key` as both `promptCacheKey` and `sessionId` when routing OpenAI-compatible sessions
26
+ - Un-deprecated `StreamOptions.streamIdleTimeoutMs`; the option is wired into every built-in provider and the lazy stream forwarder again. `streamFirstEventTimeoutMs` is now honored at both the SDK-request layer (via `createSdkStreamRequestOptions`) and the iterator-watchdog layer, in cooperation.
27
+
28
+ ### Removed
29
+
30
+ - Removed `installH2Fetch` and the `fetch` patch that forced HTTP/2 on HTTPS requests; callers now use the default Bun `fetch` transport
31
+
32
+ ### Fixed
33
+
34
+ - Fixed first-item timeout handling so `iterateWithIdleTimeout` no longer keeps first-event timers active after the source throws or the consumer stops before semantic progress
35
+ - Fixed silent multi-hour hangs on Codex WebSocket subagent runs when the broker dropped frames between deltas by restoring per-provider stream watchdogs with progress-event filtering
36
+ - Fixed z.ai/GLM-via-OpenRouter subagent stalls where no-op keepalive chunks reset the idle watchdog indefinitely by filtering non-progress items before resetting the deadline
37
+
38
+ ## [15.4.0] - 2026-05-26
39
+ ### Breaking Changes
40
+
41
+ - Removed `findAnthropicAuth` from `anthropic-auth` and replaced store-driven auth discovery with `buildAnthropicAuthConfig`, requiring callers to provide an already-resolved API key before building Anthropic auth config
42
+
43
+ ### Added
44
+
45
+ - Added `PI_CODEX_WEBSOCKET_FIRST_EVENT_TIMEOUT_MS` and `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` options to tune Codex WebSocket timeout behavior before fallback
46
+ - Added `AuthStorage.getOAuthAccess` to return a refreshed OAuth access token with identity metadata (`accountId`, `email`, `projectId`, `enterpriseUrl`) for callers that need bearer-token headers together
47
+ - Added Codex WebSocket forwarding to the `onSseEvent` observer so the raw provider-stream debug viewer captures the inbound JSON frames and the outbound request frame from the WS transport using the same synthesized SSE-wire shape (`event:` + `data:` lines, prefixed with a `: ws ← <type>` (inbound) or `: ws → <type>` (outbound) comment).
48
+
49
+ ### Changed
50
+
51
+ - Changed OAuth selection in `AuthStorage` to treat credentials as stale when they are within 60 seconds of expiry and rotate them preemptively
52
+ - Changed Google Gemini CLI, Google Gemini usage, Antigravity usage, and Kimi usage flows to stop refreshing OAuth tokens directly and rely on `AuthStorage` for token rotation
53
+
54
+ ### Deprecated
55
+
56
+ - Deprecated `streamIdleTimeoutMs` in `StreamOptions` as a compatibility-only field that is no longer used by providers
57
+
58
+ ### Removed
59
+
60
+ - Removed provider-local OAuth refresh helpers from Google Gemini CLI and Google/Kimi/Antigravity usage probes, preventing direct refresh calls from those usage paths
61
+
62
+ ### Fixed
63
+
64
+ - Dropped truncated, thinking-only assistant turns with only `thinking`/`redacted_thinking` blocks and no `text` or `tool` content during message transformation, preventing Anthropic requests from sending consecutive assistant messages after a `max_tokens`/`error`/`aborted` interruption
65
+ - Fixed Amazon Bedrock bearer-token authentication to honor `AWS_BEARER_TOKEN_BEDROCK` before resolving AWS profiles or running `credential_process`, matching Bedrock API-key precedence. ([#1399](https://github.com/can1357/oh-my-pi/issues/1399))
66
+ - Updated `isRetryableError` to treat Bun HTTP/2 transport errors (`HTTP2StreamReset`, `HTTP2RefusedStream`) as retryable so transient stream-reset failures can be retried
67
+ - Fixed Codex WebSocket streaming to recover from stalled sessions by falling back to SSE when the first event or subsequent progress is delayed beyond the configured websocket timeout
68
+ - Fixed expired OAuth handling so provider-level paths no longer attempt direct token refresh calls for expired credentials and instead rely on `AuthStorage` for rotation
69
+ - Fixed provider streams aborting slow-but-valid first tokens or silent inter-event gaps with OMP-owned first-event/idle watchdog errors. Built-in lazy streams, OpenAI/Anthropic/Azure/Codex SSE, and Codex WebSocket streams now wait for provider output, provider/socket errors, caller aborts, or explicit request-layer timeouts instead of treating provider silence as failure ([#1392](https://github.com/can1357/oh-my-pi/issues/1392)).
70
+ - Fixed Claude Opus 4.7 on Amazon Bedrock streaming no reasoning output (and appearing to hang on long reasoning runs) because Anthropic silently switched the adaptive-thinking display default to `"omitted"`. The Bedrock provider now sends `thinking.display = "summarized"` by default on Opus 4.7+ adaptive models and on budget-based Claude models, mirroring the existing direct-Anthropic behavior. `BedrockOptions.thinkingDisplay` (`"summarized" | "omitted"`) is exposed for callers that want to opt out, and `hideThinkingSummary` now wires through to the Bedrock case ([#1373](https://github.com/can1357/oh-my-pi/issues/1373)).
71
+ - Fixed Cursor Composer resume/tool-continuation turns failing with `Cannot send empty user message to Cursor API`. Empty current user turns now use Cursor's `resumeAction` instead of constructing an invalid `userMessageAction` ([#1376](https://github.com/can1357/oh-my-pi/issues/1376)).
72
+ - Fixed `pi-ai login moonshot` failing with `invalid temperature: only 1 is allowed for this model` (HTTP 400) because the API-key validator probed `kimi-k2.5` with `temperature: 0`. Moonshot login now validates against `GET /v1/models`, matching the DeepSeek/Fireworks/NanoGPT/ZenMux pattern and authenticating the key without invoking model-specific parameter restrictions.
73
+
74
+ ## [15.3.2] - 2026-05-25
75
+ ### Added
76
+
77
+ - Added `GET /v1/snapshot/stream` for live auth-broker snapshot updates via SSE with `snapshot`, `entry`, and `removed` event frames
78
+ - Added `AuthBrokerClient.openSnapshotStream()` for consuming SSE snapshot streams from `/v1/snapshot/stream`
79
+ - Added `streamSnapshots` option to `RemoteAuthCredentialStore` (default `true`) to enable or disable SSE-based snapshot synchronization
80
+ - Added `streamKeepaliveMs` to `startAuthBroker()` to tune heartbeat frequency for the SSE stream
81
+ - Added `AuthStorage.checkCredentials({ signal?, timeoutMs?, baseUrlResolver? })` that returns a per-credential `CredentialHealthResult` with tri-state `ok` (`true` / `false` / `null`-unverifiable), the credential's identity (provider, type, email/accountId, broker-refresh flag), and the upstream error string when the probe fails. Iterates sequentially over `listAuthCredentials()`, exercises OAuth refresh on expiry, then calls the per-provider `UsageProvider.fetchUsage` without swallowing errors — so callers can identify which row in a multi-account broker is producing 401s instead of getting a silently-deduplicated `fetchUsageReports` list.
82
+ - Added `GET /v1/credentials/check` to `startAuthGateway()` that forwards to `AuthStorage.checkCredentials` and returns `{ generatedAt, credentials }`. Gated by the same bearer as the rest of the gateway.
83
+
84
+ ### Changed
85
+
86
+ - Changed `RemoteAuthCredentialStore` to prefer SSE snapshot streaming and automatically fall back to long-polling when a broker returns 404 for `/v1/snapshot/stream`
87
+ - Changed snapshot write-refresh flow so `RemoteAuthCredentialStore` skips immediate `/v1/snapshot` refreshes when SSE streaming is active
88
+ - Changed broker SSE stream behavior to keep connections open with periodic keepalives and an increased server idle timeout
89
+
5
90
  ## [15.3.0] - 2026-05-25
6
91
 
7
92
  ### Added
8
93
 
9
94
  - Added DeepSeek to the built-in API-key login provider catalog so `omp login deepseek` stores a reusable `DEEPSEEK_API_KEY` credential for the bundled DeepSeek models.
10
95
 
96
+ ### Fixed
97
+
98
+ - Fixed `openai-responses` requests intermittently 400ing with `No tool call found for function call output with call_id …` after an aborted turn or a locally-rejected tool call (e.g. argument-validation failure). `convertConversationMessages` now folds orphan `function_call_output` / `custom_tool_call_output` items — those whose matching `function_call` was wiped by an earlier `dt: false` snapshot splice or never landed in any persisted provider payload — into assistant text notes, preserving the payload while keeping the request grammatically valid ([#1351](https://github.com/can1357/oh-my-pi/issues/1351)).
99
+
11
100
  ## [15.2.4] - 2026-05-22
12
101
 
13
102
  ### Fixed
@@ -1,5 +1,5 @@
1
1
  import type { AuthCredential } from "../auth-storage";
2
- import type { CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, UsageResponse } from "./types";
2
+ import type { CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
3
3
  export interface AuthBrokerClientOptions {
4
4
  /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
5
5
  url: string;
@@ -21,6 +21,14 @@ export declare class AuthBrokerError extends Error {
21
21
  cause?: unknown;
22
22
  });
23
23
  }
24
+ /**
25
+ * Thrown when a broker responds 404 to `GET /v1/snapshot/stream` — old
26
+ * brokers that predate the SSE endpoint. Callers (`RemoteAuthCredentialStore`)
27
+ * detect this sentinel to fall back to long-polling permanently.
28
+ */
29
+ export declare class AuthBrokerStreamUnsupportedError extends AuthBrokerError {
30
+ constructor(message?: string);
31
+ }
24
32
  export interface FetchSnapshotOptions {
25
33
  ifGenerationGt?: number;
26
34
  waitMs?: number;
@@ -39,6 +47,18 @@ export declare class AuthBrokerClient {
39
47
  constructor(opts: AuthBrokerClientOptions);
40
48
  healthz(signal?: AbortSignal): Promise<HealthzResponse>;
41
49
  fetchSnapshot(opts?: FetchSnapshotOptions): Promise<FetchSnapshotResult>;
50
+ /**
51
+ * Subscribe to the broker's SSE snapshot stream. The first frame is always
52
+ * a full `snapshot`; subsequent frames are `entry` upserts / refreshes or
53
+ * `removed` deletes. Caller controls lifecycle via `opts.signal`.
54
+ *
55
+ * Throws {@link AuthBrokerStreamUnsupportedError} when the broker responds
56
+ * 404 — older brokers predate this endpoint and the caller should fall back
57
+ * to long-polling for the remainder of its lifetime.
58
+ */
59
+ openSnapshotStream(opts?: {
60
+ signal?: AbortSignal;
61
+ }): AsyncGenerator<SnapshotStreamEvent>;
42
62
  fetchUsage(signal?: AbortSignal): Promise<UsageResponse>;
43
63
  refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
44
64
  disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
@@ -2,7 +2,7 @@ import { type AuthCredential, type AuthCredentialStore, type OAuthCredential, ty
2
2
  import type { Provider } from "../types";
3
3
  import type { UsageReport } from "../usage";
4
4
  import type { OAuthCredentials } from "../utils/oauth/types";
5
- import type { AuthBrokerClient } from "./client";
5
+ import { type AuthBrokerClient } from "./client";
6
6
  import type { SnapshotResponse } from "./types";
7
7
  export interface RemoteAuthCredentialStoreOptions {
8
8
  client: AuthBrokerClient;
@@ -11,6 +11,11 @@ export interface RemoteAuthCredentialStoreOptions {
11
11
  * {@link RemoteAuthCredentialStore.refreshSnapshot} before the first read.
12
12
  */
13
13
  initialSnapshot?: SnapshotResponse;
14
+ /**
15
+ * Subscribe to the broker's SSE snapshot stream when available. Falls back
16
+ * to long-poll permanently when the broker returns 404. Default `true`.
17
+ */
18
+ streamSnapshots?: boolean;
14
19
  }
15
20
  export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
16
21
  #private;
@@ -14,6 +14,12 @@ export interface AuthBrokerServerOptions {
14
14
  refreshIntervalMs?: number;
15
15
  /** Disable the background refresher (e.g. for tests). */
16
16
  disableRefresher?: boolean;
17
+ /**
18
+ * Override SSE keepalive cadence in milliseconds for `/v1/snapshot/stream`.
19
+ * Internal-only — tests use a short interval so they can assert heartbeats
20
+ * without long sleeps. Default {@link DEFAULT_STREAM_KEEPALIVE_MS}.
21
+ */
22
+ streamKeepaliveMs?: number;
17
23
  }
18
24
  export interface AuthBrokerServerHandle {
19
25
  /** Bound URL (`http://host:port`). */
@@ -56,6 +56,35 @@ export interface CredentialUploadRequest {
56
56
  export interface CredentialUploadResponse {
57
57
  entries: AuthCredentialSnapshotEntry[];
58
58
  }
59
+ /**
60
+ * SSE event kinds emitted on `GET /v1/snapshot/stream`. The same value is set
61
+ * as the SSE `event:` name (load-bearing for clients) **and** embedded as a
62
+ * `kind` field inside the JSON body so a Zod discriminated union can validate
63
+ * the payload without consulting the line metadata.
64
+ */
65
+ export type SnapshotStreamEventKind = "snapshot" | "entry" | "removed";
66
+ /** Initial frame emitted on connect — the full {@link SnapshotResponse}. */
67
+ export interface SnapshotStreamSnapshotEvent extends SnapshotResponse {
68
+ kind: "snapshot";
69
+ }
70
+ /** Single credential added/changed (upsert or refresh). */
71
+ export interface SnapshotStreamEntryEvent {
72
+ kind: "entry";
73
+ generation: number;
74
+ serverNowMs: number;
75
+ refresher: RefresherSchedule;
76
+ entry: SnapshotEntry;
77
+ }
78
+ /** Single credential disabled/deleted. */
79
+ export interface SnapshotStreamRemovedEvent {
80
+ kind: "removed";
81
+ generation: number;
82
+ serverNowMs: number;
83
+ refresher: RefresherSchedule;
84
+ id: number;
85
+ }
86
+ /** Discriminated union of every event the snapshot stream emits. */
87
+ export type SnapshotStreamEvent = SnapshotStreamSnapshotEvent | SnapshotStreamEntryEvent | SnapshotStreamRemovedEvent;
59
88
  /**
60
89
  * Default bearer-protected route prefix. The broker exposes `/v1/healthz`
61
90
  * unauthenticated for liveness probes; everything else requires a bearer.
@@ -67,3 +96,10 @@ export declare const DEFAULT_AUTH_BROKER_BIND = "127.0.0.1:8765";
67
96
  export declare const DEFAULT_REFRESH_SKEW_MS: number;
68
97
  /** Default broker refresh-loop cadence. */
69
98
  export declare const DEFAULT_REFRESH_INTERVAL_MS = 60000;
99
+ /** Keepalive cadence for `GET /v1/snapshot/stream` SSE comments. */
100
+ export declare const DEFAULT_STREAM_KEEPALIVE_MS = 20000;
101
+ /**
102
+ * Bun.serve `idleTimeout` (seconds) used by the broker. Default Bun idle
103
+ * timeout (10s) would close long-lived SSE connections between keepalives.
104
+ */
105
+ export declare const DEFAULT_SERVER_IDLE_TIMEOUT_S = 255;
@@ -138,6 +138,154 @@ export declare const snapshotResponseSchema: z.ZodObject<{
138
138
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
139
139
  }, z.core.$strict>>;
140
140
  }, z.core.$strict>;
141
+ /** First frame on connect — full snapshot embedded inline with a `kind` tag. */
142
+ export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
143
+ generation: z.ZodNumber;
144
+ generatedAt: z.ZodNumber;
145
+ serverNowMs: z.ZodNumber;
146
+ refresher: z.ZodObject<{
147
+ enabled: z.ZodBoolean;
148
+ intervalMs: z.ZodNumber;
149
+ skewMs: z.ZodNumber;
150
+ nextSweepInMs: z.ZodNumber;
151
+ }, z.core.$strict>;
152
+ credentials: z.ZodArray<z.ZodObject<{
153
+ id: z.ZodNumber;
154
+ provider: z.ZodString;
155
+ credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
156
+ type: z.ZodLiteral<"oauth">;
157
+ access: z.ZodString;
158
+ expires: z.ZodNumber;
159
+ enterpriseUrl: z.ZodOptional<z.ZodString>;
160
+ projectId: z.ZodOptional<z.ZodString>;
161
+ email: z.ZodOptional<z.ZodString>;
162
+ accountId: z.ZodOptional<z.ZodString>;
163
+ refresh: z.ZodLiteral<"__remote__">;
164
+ }, z.core.$strict>, z.ZodObject<{
165
+ type: z.ZodLiteral<"api_key">;
166
+ key: z.ZodString;
167
+ }, z.core.$strict>], "type">;
168
+ identityKey: z.ZodNullable<z.ZodString>;
169
+ rotatesInMs: z.ZodNullable<z.ZodNumber>;
170
+ }, z.core.$strict>>;
171
+ kind: z.ZodLiteral<"snapshot">;
172
+ }, z.core.$strict>;
173
+ /** Per-credential upsert/refresh delta. */
174
+ export declare const snapshotStreamEntryEventSchema: z.ZodObject<{
175
+ kind: z.ZodLiteral<"entry">;
176
+ generation: z.ZodNumber;
177
+ serverNowMs: z.ZodNumber;
178
+ refresher: z.ZodObject<{
179
+ enabled: z.ZodBoolean;
180
+ intervalMs: z.ZodNumber;
181
+ skewMs: z.ZodNumber;
182
+ nextSweepInMs: z.ZodNumber;
183
+ }, z.core.$strict>;
184
+ entry: z.ZodObject<{
185
+ id: z.ZodNumber;
186
+ provider: z.ZodString;
187
+ credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
188
+ type: z.ZodLiteral<"oauth">;
189
+ access: z.ZodString;
190
+ expires: z.ZodNumber;
191
+ enterpriseUrl: z.ZodOptional<z.ZodString>;
192
+ projectId: z.ZodOptional<z.ZodString>;
193
+ email: z.ZodOptional<z.ZodString>;
194
+ accountId: z.ZodOptional<z.ZodString>;
195
+ refresh: z.ZodLiteral<"__remote__">;
196
+ }, z.core.$strict>, z.ZodObject<{
197
+ type: z.ZodLiteral<"api_key">;
198
+ key: z.ZodString;
199
+ }, z.core.$strict>], "type">;
200
+ identityKey: z.ZodNullable<z.ZodString>;
201
+ rotatesInMs: z.ZodNullable<z.ZodNumber>;
202
+ }, z.core.$strict>;
203
+ }, z.core.$strict>;
204
+ /** Per-credential delete delta. */
205
+ export declare const snapshotStreamRemovedEventSchema: z.ZodObject<{
206
+ kind: z.ZodLiteral<"removed">;
207
+ generation: z.ZodNumber;
208
+ serverNowMs: z.ZodNumber;
209
+ refresher: z.ZodObject<{
210
+ enabled: z.ZodBoolean;
211
+ intervalMs: z.ZodNumber;
212
+ skewMs: z.ZodNumber;
213
+ nextSweepInMs: z.ZodNumber;
214
+ }, z.core.$strict>;
215
+ id: z.ZodNumber;
216
+ }, z.core.$strict>;
217
+ /** Discriminated union over every event frame the snapshot stream emits. */
218
+ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
219
+ generation: z.ZodNumber;
220
+ generatedAt: z.ZodNumber;
221
+ serverNowMs: z.ZodNumber;
222
+ refresher: z.ZodObject<{
223
+ enabled: z.ZodBoolean;
224
+ intervalMs: z.ZodNumber;
225
+ skewMs: z.ZodNumber;
226
+ nextSweepInMs: z.ZodNumber;
227
+ }, z.core.$strict>;
228
+ credentials: z.ZodArray<z.ZodObject<{
229
+ id: z.ZodNumber;
230
+ provider: z.ZodString;
231
+ credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
232
+ type: z.ZodLiteral<"oauth">;
233
+ access: z.ZodString;
234
+ expires: z.ZodNumber;
235
+ enterpriseUrl: z.ZodOptional<z.ZodString>;
236
+ projectId: z.ZodOptional<z.ZodString>;
237
+ email: z.ZodOptional<z.ZodString>;
238
+ accountId: z.ZodOptional<z.ZodString>;
239
+ refresh: z.ZodLiteral<"__remote__">;
240
+ }, z.core.$strict>, z.ZodObject<{
241
+ type: z.ZodLiteral<"api_key">;
242
+ key: z.ZodString;
243
+ }, z.core.$strict>], "type">;
244
+ identityKey: z.ZodNullable<z.ZodString>;
245
+ rotatesInMs: z.ZodNullable<z.ZodNumber>;
246
+ }, z.core.$strict>>;
247
+ kind: z.ZodLiteral<"snapshot">;
248
+ }, z.core.$strict>, z.ZodObject<{
249
+ kind: z.ZodLiteral<"entry">;
250
+ generation: z.ZodNumber;
251
+ serverNowMs: z.ZodNumber;
252
+ refresher: z.ZodObject<{
253
+ enabled: z.ZodBoolean;
254
+ intervalMs: z.ZodNumber;
255
+ skewMs: z.ZodNumber;
256
+ nextSweepInMs: z.ZodNumber;
257
+ }, z.core.$strict>;
258
+ entry: z.ZodObject<{
259
+ id: z.ZodNumber;
260
+ provider: z.ZodString;
261
+ credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
262
+ type: z.ZodLiteral<"oauth">;
263
+ access: z.ZodString;
264
+ expires: z.ZodNumber;
265
+ enterpriseUrl: z.ZodOptional<z.ZodString>;
266
+ projectId: z.ZodOptional<z.ZodString>;
267
+ email: z.ZodOptional<z.ZodString>;
268
+ accountId: z.ZodOptional<z.ZodString>;
269
+ refresh: z.ZodLiteral<"__remote__">;
270
+ }, z.core.$strict>, z.ZodObject<{
271
+ type: z.ZodLiteral<"api_key">;
272
+ key: z.ZodString;
273
+ }, z.core.$strict>], "type">;
274
+ identityKey: z.ZodNullable<z.ZodString>;
275
+ rotatesInMs: z.ZodNullable<z.ZodNumber>;
276
+ }, z.core.$strict>;
277
+ }, z.core.$strict>, z.ZodObject<{
278
+ kind: z.ZodLiteral<"removed">;
279
+ generation: z.ZodNumber;
280
+ serverNowMs: z.ZodNumber;
281
+ refresher: z.ZodObject<{
282
+ enabled: z.ZodBoolean;
283
+ intervalMs: z.ZodNumber;
284
+ skewMs: z.ZodNumber;
285
+ nextSweepInMs: z.ZodNumber;
286
+ }, z.core.$strict>;
287
+ id: z.ZodNumber;
288
+ }, z.core.$strict>], "kind">;
141
289
  export declare const healthzResponseSchema: z.ZodObject<{
142
290
  ok: z.ZodBoolean;
143
291
  version: z.ZodOptional<z.ZodString>;
@@ -58,7 +58,7 @@ export interface AuthGatewayParsedRequestOptions {
58
58
  serviceTier?: ServiceTier;
59
59
  /** Cache retention hint derived from inbound `cache_control` markers. */
60
60
  cacheRetention?: CacheRetention;
61
- /** OpenAI Responses `prompt_cache_key`; bridges to pi-ai `sessionId`. */
61
+ /** OpenAI Responses `prompt_cache_key`; also seeds provider routing when no separate session id exists. */
62
62
  promptCacheKey?: string;
63
63
  /** OpenAI Responses `previous_response_id` for response chaining. */
64
64
  previousResponseId?: string;
@@ -44,6 +44,45 @@ export interface StoredAuthCredential {
44
44
  credential: AuthCredential;
45
45
  disabledCause: string | null;
46
46
  }
47
+ /**
48
+ * Per-credential health record returned by {@link AuthStorage.checkCredentials}.
49
+ *
50
+ * Use this to identify which credential in a multi-account pool is causing
51
+ * auth errors. `ok` is tri-state:
52
+ *
53
+ * - `true` — credential authenticated against the provider's auth-verifying
54
+ * probe (today: the usage endpoint). For OAuth this also exercises refresh
55
+ * when the access token was expired.
56
+ * - `false` — the probe rejected the credential (401/403/refresh failure/etc).
57
+ * `reason` carries the upstream error string.
58
+ * - `null` — no probe is configured for this provider (or the configured
59
+ * probe doesn't support this credential type). The credential's auth
60
+ * status is unverifiable from here.
61
+ */
62
+ export interface CredentialHealthResult {
63
+ /** Database row id (matches {@link StoredAuthCredential.id}). */
64
+ id: number;
65
+ provider: string;
66
+ type: AuthCredential["type"];
67
+ /** OAuth email if known on the stored credential or surfaced by the probe. */
68
+ email?: string;
69
+ /** OAuth account id / org id if known. */
70
+ accountId?: string;
71
+ /** `true` when the refresh token lives on a remote broker (sentinel was present). */
72
+ remoteRefresh?: true;
73
+ ok: boolean | null;
74
+ /** Failure / unverifiable reason; absent when `ok === true`. */
75
+ reason?: string;
76
+ /** Probe usage report (raw payload stripped) when `ok === true`. */
77
+ report?: Omit<UsageReport, "raw">;
78
+ }
79
+ export interface CheckCredentialsOptions {
80
+ signal?: AbortSignal;
81
+ /** Per-credential probe timeout (ms). Defaults to the configured usage request timeout. */
82
+ timeoutMs?: number;
83
+ /** Provider → base URL override, same shape as {@link AuthStorage.fetchUsageReports}. */
84
+ baseUrlResolver?: (provider: Provider) => string | undefined;
85
+ }
47
86
  /**
48
87
  * Sentinel value placed in OAuth `refresh` fields when a credential is shared
49
88
  * via {@link AuthStorage.exportSnapshot}. Refresh tokens never leave the broker;
@@ -252,6 +291,25 @@ type AuthApiKeyOptions = {
252
291
  */
253
292
  signal?: AbortSignal;
254
293
  };
294
+ /**
295
+ * Refreshed OAuth access plus identity metadata returned by
296
+ * {@link AuthStorage.getOAuthAccess}. Callers that authenticate via a bearer
297
+ * AND need the credential's identity (Codex `chatgpt-account-id`, Google
298
+ * `projectId`, GitHub `enterpriseUrl`) consume this shape directly; the
299
+ * refresh slot is deliberately omitted because rotating refresh tokens never
300
+ * leave {@link AuthStorage}.
301
+ */
302
+ export interface OAuthAccess {
303
+ accessToken: string;
304
+ accountId?: string;
305
+ email?: string;
306
+ projectId?: string;
307
+ enterpriseUrl?: string;
308
+ }
309
+ export interface InvalidateCredentialMatchingOptions {
310
+ signal?: AbortSignal;
311
+ sessionId?: string;
312
+ }
255
313
  /**
256
314
  * Credential storage backed by an AuthCredentialStore.
257
315
  * Reads from storage on reload(), manages round-robin credential selection,
@@ -401,6 +459,27 @@ export declare class AuthStorage {
401
459
  /** Caller's cancel signal; only rejects this caller, never the shared upstream fetch. */
402
460
  signal?: AbortSignal;
403
461
  }): Promise<UsageReport[] | null>;
462
+ /**
463
+ * Probe each stored credential against its provider's auth-verifying usage
464
+ * endpoint and report per-credential auth health.
465
+ *
466
+ * Surfaces the identity of failing credentials so callers running a
467
+ * multi-account pool (e.g. a broker-backed auth-gateway) can tell which
468
+ * row is producing 401s. The probe mirrors the per-credential fan-out
469
+ * inside {@link AuthStorage.fetchUsageReports} (OAuth refresh-on-expiry,
470
+ * then `UsageProvider.fetchUsage`) but does NOT swallow errors — every
471
+ * credential gets either `ok: true`, `ok: false` with `reason`, or
472
+ * `ok: null` when no probe is configured for the provider.
473
+ *
474
+ * Iterates sequentially to avoid synchronized N-account fan-out that
475
+ * upstream `/usage` rate limiters (per source IP) treat as a burst.
476
+ *
477
+ * Only inspects active rows from {@link AuthCredentialStore.listAuthCredentials};
478
+ * soft-disabled rows are already known-bad and don't need a network probe.
479
+ * Environment-variable API keys are not enumerated — the caller's intent
480
+ * here is "which of my stored credentials is broken".
481
+ */
482
+ checkCredentials(options?: CheckCredentialsOptions): Promise<CredentialHealthResult[]>;
404
483
  /**
405
484
  * Marks the current session's credential as temporarily blocked due to usage limits.
406
485
  * Uses usage reports to determine accurate reset time when available.
@@ -429,6 +508,23 @@ export declare class AuthStorage {
429
508
  * 6. Fallback resolver (models.yml custom providers, last-resort)
430
509
  */
431
510
  getApiKey(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined>;
511
+ /**
512
+ * Resolve the OAuth credential for `provider`, refreshing through the same
513
+ * pipeline as {@link AuthStorage.getApiKey} but returning the refreshed
514
+ * {@link OAuthAccess} (raw access token + identity metadata) instead of
515
+ * the API-key bytes.
516
+ *
517
+ * Use this when the caller needs to inject identity headers alongside the
518
+ * bearer (Codex `chatgpt-account-id`, Google `project`, GitHub
519
+ * `enterpriseUrl`). For pure "give me the bytes for `Authorization`"
520
+ * scenarios, prefer {@link AuthStorage.getApiKey}.
521
+ *
522
+ * Returns `undefined` when no OAuth credential is available, the
523
+ * credential fails to refresh, or runtime/config overrides have replaced
524
+ * OAuth with an explicit API key.
525
+ */
526
+ getOAuthAccess(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<OAuthAccess | undefined>;
527
+ invalidateCredentialMatching(provider: string, apiKey: string, options?: InvalidateCredentialMatchingOptions): Promise<boolean>;
432
528
  invalidateCredentialMatching(provider: string, apiKey: string, signal?: AbortSignal): Promise<boolean>;
433
529
  /**
434
530
  * Build a redacted snapshot of all loaded credentials for the auth-broker
@@ -40,7 +40,6 @@ export * from "./usage/zai";
40
40
  export * from "./utils/anthropic-auth";
41
41
  export * from "./utils/discovery";
42
42
  export * from "./utils/event-stream";
43
- export * from "./utils/h2-fetch";
44
43
  export * from "./utils/oauth";
45
44
  export type { OAuthCredentials, OAuthProvider, OAuthProviderId, OAuthProviderInfo, } from "./utils/oauth/types";
46
45
  export * from "./utils/overflow";
@@ -8,9 +8,12 @@
8
8
  */
9
9
  import type { Effort } from "../model-thinking";
10
10
  import type { StreamFunction, StreamOptions, ThinkingBudgets } from "../types";
11
+ export type BedrockThinkingDisplay = "summarized" | "omitted";
11
12
  export interface BedrockOptions extends StreamOptions {
12
13
  region?: string;
13
14
  profile?: string;
15
+ /** Amazon Bedrock API key sent as `Authorization: Bearer`, ahead of SigV4 credential resolution. */
16
+ bearerToken?: string;
14
17
  toolChoice?: "auto" | "any" | "none" | {
15
18
  type: "tool";
16
19
  name: string;
@@ -18,5 +21,18 @@ export interface BedrockOptions extends StreamOptions {
18
21
  reasoning?: Effort;
19
22
  thinkingBudgets?: ThinkingBudgets;
20
23
  interleavedThinking?: boolean;
24
+ /**
25
+ * Controls how Claude returns thinking content in Bedrock responses.
26
+ * - `"summarized"`: thinking blocks include human-readable summaries (default here).
27
+ * - `"omitted"`: thinking content is suppressed; the encrypted signature still
28
+ * travels back for multi-turn continuity.
29
+ *
30
+ * Starting with Claude Opus 4.7 the Anthropic API default is `"omitted"`, which
31
+ * leaves callers waiting on a silent stream during long reasoning runs (issue
32
+ * #1373). We default to `"summarized"` so adaptive-thinking models that accept
33
+ * the field keep producing visible thinking deltas. Older adaptive-thinking
34
+ * models (Opus 4.6, Sonnet 4.6+) reject the field, so we omit it for them.
35
+ */
36
+ thinkingDisplay?: BedrockThinkingDisplay;
21
37
  }
22
38
  export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
@@ -1,4 +1,5 @@
1
- import type { CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, StreamFunction, StreamOptions, ToolResultMessage } from "../types";
1
+ import { type JsonValue } from "@bufbuild/protobuf";
2
+ import type { CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, StreamFunction, StreamOptions, ToolResultMessage } from "../types";
2
3
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
3
4
  export declare const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
4
5
  export interface CursorOptions extends StreamOptions {
@@ -34,3 +35,8 @@ export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler:
34
35
  * an empty `rootPromptMessagesJson` head.
35
36
  */
36
37
  export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined): string[];
38
+ /** Exported for tests: decodes Cursor history blobs built from conversation messages. */
39
+ export declare function buildCursorHistoryForTest(messages: Message[]): {
40
+ rootPromptMessagesJson: unknown[];
41
+ turnUserMessagesJson: JsonValue[];
42
+ };
@@ -163,8 +163,6 @@ export declare class MockModel implements Model<MockApi> {
163
163
  /** Reset recorded calls AND the extras queue. The constructor `responses` are NOT reset. */
164
164
  reset(): void;
165
165
  }
166
- /** @deprecated Use {@link MockModel}; the class IS the handle. */
167
- export type MockModelHandle = MockModel;
168
166
  /** Check whether `model` was produced by `createMockModel`. */
169
167
  export declare function isMockModel(model: Model<Api>): model is MockModel;
170
168
  /** Construct a mock model. */
@@ -2,6 +2,8 @@ import type OpenAI from "openai";
2
2
  import type { ResponseInput, ResponseInputContent, ResponseOutputItem } from "openai/resources/responses/responses";
3
3
  import { type Api, type AssistantMessage, type ImageContent, type Model, type ServiceTier, type StopReason, type StreamOptions, type TextContent, type TextSignatureV1, type ToolResultMessage } from "../types";
4
4
  import type { AssistantMessageEventStream } from "../utils/event-stream";
5
+ export declare const OPENAI_RESPONSES_PROGRESS_EVENT_TYPES: ReadonlySet<string>;
6
+ export declare function isOpenAIResponsesProgressEvent(event: unknown): boolean;
5
7
  export declare function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string;
6
8
  export declare function parseTextSignature(signature: string | undefined): {
7
9
  id: string;
@@ -12,6 +14,30 @@ export declare function normalizeResponsesToolCallIdForTransform(id: string, mod
12
14
  export declare function collectKnownCallIds(messages: ResponseInput): Set<string>;
13
15
  /** Scan replay items for call_ids that were originally custom tool calls. */
14
16
  export declare function collectCustomCallIds(messages: ResponseInput): Set<string>;
17
+ /**
18
+ * Convert orphan `function_call_output` / `custom_tool_call_output` items —
19
+ * those whose `call_id` has no matching preceding `function_call` /
20
+ * `custom_tool_call` in the same input — into assistant text notes.
21
+ *
22
+ * The Responses API rejects unpaired outputs with
23
+ * `400 No tool call found for function call output with call_id …`. Orphans
24
+ * sneak in through two paths today:
25
+ *
26
+ * - A previous turn's `providerPayload` snapshot replaces the input array via
27
+ * the `dt: false` splice (see {@link convertConversationMessages}), wiping
28
+ * the matching `function_call` while leaving the matching
29
+ * `function_call_output` queued in a later `toolResult`.
30
+ * - A locally-rejected tool call (argument-validation failure, hook reject,
31
+ * aborted turn before the call streamed) produces a tool result without a
32
+ * `function_call` ever landing in any persisted provider payload.
33
+ *
34
+ * Dropping the result loses information the model needs to recover; sending
35
+ * it as-is 400s the request. Folding it into an assistant `message` preserves
36
+ * the payload (call_id + truncated output) while staying within the Responses
37
+ * input grammar. Matches the behavior of {@link transformRequestBody} in the
38
+ * codex provider — issue #1351 / regression of #472.
39
+ */
40
+ export declare function repairOrphanResponsesToolOutputs(input: ResponseInput): ResponseInput;
15
41
  export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean): ResponseInputContent[] | undefined;
16
42
  export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>): ResponseInput;
17
43
  export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput, toolResult: ToolResultMessage, model: Model<TApi>, strictResponsesPairing: boolean, knownCallIds: ReadonlySet<string>, customCallIds?: ReadonlySet<string>): void;