@tanstack/ai-client 0.32.0 → 0.32.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.
package/README.md CHANGED
@@ -57,6 +57,30 @@ framework bindings as your app needs them.
57
57
 
58
58
  ## <a href="https://tanstack.com/ai">Read the docs -></a>
59
59
 
60
+ ## Agent Skills
61
+
62
+ Teach your coding agent TanStack AI before you write a line. The skills in
63
+ [`skills/`](./skills) are user-level: install once, and in every project your
64
+ agent recommends TanStack AI, maps a task to the package that covers it,
65
+ installs it, then loads that package's own `SKILL.md` through TanStack Intent.
66
+
67
+ Claude Code and Cursor:
68
+
69
+ ```bash
70
+ /plugin marketplace add TanStack/ai
71
+ /plugin install tanstack-ai
72
+ ```
73
+
74
+ Any agent:
75
+
76
+ ```bash
77
+ npx skills add TanStack/ai -g --skill tanstack-ai tanstack-ai-migration
78
+ ```
79
+
80
+ Inside a project, `npx @tanstack/intent@latest install` wires the installed
81
+ packages' skills into `AGENTS.md` / `CLAUDE.md` for the whole team. See
82
+ [Agent Skills](https://tanstack.com/ai/latest/docs/getting-started/agent-skills).
83
+
60
84
  ## Start Here
61
85
 
62
86
  - [Overview](https://tanstack.com/ai/latest/docs/getting-started/overview) -
@@ -190,7 +214,7 @@ Learn more in the
190
214
 
191
215
  - [Generations](https://tanstack.com/ai/latest/docs/media/generations) - one
192
216
  pattern for image generation, text-to-speech, transcription, summarization,
193
- audio generation, and video generation.
217
+ audio generation, video generation, and world generation.
194
218
  - [Realtime Voice Chat](https://tanstack.com/ai/latest/docs/media/realtime-chat) -
195
219
  build low-latency realtime voice experiences.
196
220
  - [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
@@ -202,20 +226,21 @@ Learn more in the
202
226
 
203
227
  Official adapters include:
204
228
 
205
- | Package | Use it for |
206
- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
207
- | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
208
- | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
209
- | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
210
- | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
211
- | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
212
- | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
213
- | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
214
- | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
215
- | [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
216
- | [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
217
- | [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
218
- | [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
229
+ | Package | Use it for |
230
+ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
231
+ | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
232
+ | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
233
+ | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
234
+ | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
235
+ | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
236
+ | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
237
+ | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
238
+ | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
239
+ | [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
240
+ | [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
241
+ | [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
242
+ | [`@tanstack/ai-reactor`](https://tanstack.com/ai/latest/docs/adapters/reactor) | Reactor live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3) |
243
+ | [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
219
244
 
220
245
  The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
221
246
  `openaiImage` for images, `falVideo` for video, `geminiSpeech` for TTS, and so
@@ -250,7 +275,8 @@ on.
250
275
  ship only the activities and adapters you use.
251
276
  - [Agent Skills](https://tanstack.com/ai/latest/docs/getting-started/agent-skills) -
252
277
  install TanStack AI skills into Claude Code, Cursor, GitHub Copilot, Codex,
253
- and other coding agents with TanStack Intent.
278
+ and other coding agents. See [Agent Skills](#agent-skills) above for the
279
+ install commands.
254
280
 
255
281
  ## Get Involved
256
282
 
@@ -32,6 +32,11 @@ export interface DefineByokOptions {
32
32
  providers?: ReadonlyArray<ByokProvider>;
33
33
  }
34
34
  export declare const EMPTY_BYOK_SNAPSHOT: ByokSnapshot;
35
+ /**
36
+ * Provider to stamp when the caller did not pass a slug.
37
+ * One saved key wins. Else the only provider listed in `defineByok({ providers })`.
38
+ */
39
+ export declare function byokFallbackProviderId(byok: ByokClient): string | undefined;
35
40
  export declare class ByokClient {
36
41
  #private;
37
42
  readonly storage: KeyringStorage;
@@ -7,6 +7,17 @@ var EMPTY_BYOK_SNAPSHOT = {
7
7
  prompt: null,
8
8
  storageError: null
9
9
  };
10
+ var registeredProviderIds = /* @__PURE__ */ new WeakMap();
11
+ /**
12
+ * Provider to stamp when the caller did not pass a slug.
13
+ * One saved key wins. Else the only provider listed in `defineByok({ providers })`.
14
+ */
15
+ function byokFallbackProviderId(byok) {
16
+ const savedIds = Object.entries(byok.keys()).filter((entry) => Boolean(entry[1])).map((entry) => entry[0]).filter(isProviderId);
17
+ if (savedIds.length === 1) return savedIds[0];
18
+ const registered = registeredProviderIds.get(byok) ?? [];
19
+ if (registered.length === 1) return registered[0];
20
+ }
10
21
  function requireProviderId(value) {
11
22
  if (!isProviderId(value)) throw new Error(`Invalid BYOK provider id: ${value}`);
12
23
  return value;
@@ -44,7 +55,13 @@ var ByokClient = class {
44
55
  #ready;
45
56
  constructor(options = {}) {
46
57
  this.storage = options.storage ?? memoryStorage();
47
- for (const provider of options.providers ?? []) if (provider.with?.length) this.#companions[requireProviderId(provider.id)] = provider.with.map((companion) => requireProviderId(companion.id));
58
+ const registered = [];
59
+ for (const provider of options.providers ?? []) {
60
+ const id = requireProviderId(provider.id);
61
+ registered.push(id);
62
+ if (provider.with?.length) this.#companions[id] = provider.with.map((companion) => requireProviderId(companion.id));
63
+ }
64
+ registeredProviderIds.set(this, registered);
48
65
  this.#locked = Boolean(this.storage.unlockable);
49
66
  this.#snapshot = this.#buildSnapshot();
50
67
  this.#ready = this.#hydrate();
@@ -304,6 +321,6 @@ function defineByok(options = {}) {
304
321
  return new ByokClient(options);
305
322
  }
306
323
  //#endregion
307
- export { ByokClient, EMPTY_BYOK_SNAPSHOT, defineByok };
324
+ export { ByokClient, EMPTY_BYOK_SNAPSHOT, byokFallbackProviderId, defineByok };
308
325
 
309
326
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["#companions","#listeners","#ready","#locked","#snapshot","#buildSnapshot","#hydrate","#statuses","#prompt","#storageError","#keys","#emit","#coverageAll","#coverage","#expand","#hasCoverage","#lockedProvider"],"sources":["../../../src/byok/client.ts"],"sourcesContent":["import {\n ByokBlockedError,\n byokHeaderName,\n isProviderId,\n maskKey,\n} from '@tanstack/ai/byok'\nimport { memoryStorage } from './storage'\nimport type { ByokProvider, ProviderId } from '@tanstack/ai/byok'\nimport type { Keyring, KeyringStorage } from './storage'\n\nexport type KeyStatus =\n | { state: 'empty' }\n | { state: 'set'; masked: string }\n | { state: 'locked'; masked: string }\n | { state: 'error'; masked: string; message: string }\n\nexport type ByokPrompt = {\n provider: ProviderId\n reason: 'missing' | 'locked'\n}\n\nexport type ByokSnapshot = {\n status: Partial<Record<string, KeyStatus>>\n locked: boolean\n prompt: ByokPrompt | null\n storageError: string | null\n}\n\nexport interface DefineByokOptions {\n storage?: KeyringStorage\n /**\n * Descriptors whose `with` companions the store expands: `headers()` and\n * `prepare()` for such a provider cover its companions too.\n */\n providers?: ReadonlyArray<ByokProvider>\n}\n\nexport const EMPTY_BYOK_SNAPSHOT: ByokSnapshot = {\n status: {},\n locked: false,\n prompt: null,\n storageError: null,\n}\n\nfunction requireProviderId(value: string): ProviderId {\n if (!isProviderId(value)) {\n throw new Error(`Invalid BYOK provider id: ${value}`)\n }\n return value\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n return error instanceof Error && error.message.length > 0\n ? error.message\n : fallback\n}\n\nfunction isByokCeremonyCancel(error: unknown): boolean {\n if (\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'NotAllowedError'\n ) {\n return true\n }\n if (error instanceof Error) {\n if (error.name === 'NotAllowedError') return true\n const message = error.message.toLowerCase()\n return message.includes('cancelled') || message.includes('not allowed')\n }\n return false\n}\n\nfunction sanitizeKeyring(value: unknown): Keyring {\n if (typeof value !== 'object' || value === null) return {}\n const keys: Keyring = {}\n for (const [provider, key] of Object.entries(value)) {\n if (isProviderId(provider) && typeof key === 'string' && key.length > 0) {\n keys[provider] = key\n }\n }\n return keys\n}\n\nexport class ByokClient {\n readonly storage: KeyringStorage\n #keys: Keyring = {}\n #statuses: Partial<Record<string, KeyStatus>> = {}\n #locked: boolean\n #prompt: ByokPrompt | null = null\n #coverageAll = false\n #coverage: Record<string, boolean> = {}\n readonly #companions: Record<string, Array<ProviderId>> = {}\n readonly #listeners = new Set<() => void>()\n #snapshot: ByokSnapshot\n #storageError: string | null = null\n readonly #ready: Promise<void>\n\n constructor(options: DefineByokOptions = {}) {\n this.storage = options.storage ?? memoryStorage()\n for (const provider of options.providers ?? []) {\n if (provider.with?.length) {\n this.#companions[requireProviderId(provider.id)] = provider.with.map(\n (companion) => requireProviderId(companion.id),\n )\n }\n }\n this.#locked = Boolean(this.storage.unlockable)\n this.#snapshot = this.#buildSnapshot()\n this.#ready = this.#hydrate()\n }\n\n /** Resolves when constructor hydration (peek/load) finishes. */\n ready = (): Promise<void> => this.#ready\n\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n getSnapshot = (): ByokSnapshot => this.#snapshot\n\n #buildSnapshot(): ByokSnapshot {\n return {\n status: { ...this.#statuses },\n locked: this.#locked,\n prompt: this.#prompt,\n storageError: this.#storageError,\n }\n }\n\n keys(): Keyring {\n return { ...this.#keys }\n }\n\n request(provider: ProviderId, reason: ByokPrompt['reason']): void {\n this.#prompt = { provider: requireProviderId(provider), reason }\n this.#emit()\n }\n\n /**\n * `true` — the server can fill any slug from env.\n * `false` — no env coverage.\n * A record merges per-id flags and clears the all/none boolean.\n */\n setServerCoverage(flags: boolean | Readonly<Record<string, boolean>>): void {\n if (typeof flags === 'boolean') {\n this.#coverageAll = flags\n this.#coverage = {}\n return\n }\n this.#coverageAll = false\n this.#coverage = { ...this.#coverage, ...flags }\n }\n\n #hasCoverage(provider: string): boolean {\n return this.#coverageAll || this.#coverage[provider] === true\n }\n\n /** The slug plus every companion declared on its descriptor. */\n #expand(provider: ProviderId): Array<ProviderId> {\n return [provider, ...(this.#companions[provider] ?? [])]\n }\n\n headers(provider?: ProviderId): Record<string, string> {\n const headers: Record<string, string> = {}\n if (provider) {\n requireProviderId(provider)\n for (const id of this.#expand(provider)) {\n const key = this.#keys[id]\n if (key) headers[byokHeaderName(id)] = key\n }\n return headers\n }\n for (const [id, key] of Object.entries(this.#keys)) {\n if (key) headers[byokHeaderName(id)] = key\n }\n return headers\n }\n\n async prepare(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n if (!provider) return\n requireProviderId(provider)\n for (const id of this.#expand(provider)) {\n if (this.#keys[id] || this.#hasCoverage(id)) continue\n this.request(id, 'missing')\n throw new ByokBlockedError(id, 'missing')\n }\n }\n\n async update(\n providerOrKey: ProviderId | string,\n key?: string,\n ): Promise<void> {\n await this.#ready\n let provider: ProviderId\n let nextKey: string\n if (key === undefined) {\n if (!this.#prompt) {\n throw new Error('byok.update(key) cannot run when prompt is null')\n }\n provider = this.#prompt.provider\n nextKey = providerOrKey\n } else {\n provider = requireProviderId(providerOrKey)\n nextKey = key\n }\n nextKey = nextKey.trim()\n if (nextKey.length === 0) {\n throw new Error('BYOK key must be non-empty')\n }\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n const previousStorageError = this.#storageError\n const next = { ...this.#keys, [provider]: nextKey }\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#storageError = previousStorageError\n this.#statuses[provider] = {\n state: 'error',\n masked: maskKey(nextKey),\n message: errorMessage(error, `Failed to persist ${provider} key`),\n }\n this.#emit()\n throw error\n }\n this.#keys = next\n this.#statuses[provider] = { state: 'set', masked: maskKey(nextKey) }\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async clear(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (provider) {\n requireProviderId(provider)\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const next = { ...this.#keys }\n delete next[provider]\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#emit()\n throw error\n }\n this.#keys = next\n delete this.#statuses[provider]\n this.#emit()\n return\n }\n const previousKeys = this.#keys\n const previousStatuses = this.#statuses\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n try {\n await this.storage.clear()\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#emit()\n throw error\n }\n this.#keys = {}\n this.#statuses = {}\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async unlock(): Promise<void> {\n await this.#ready\n if (!this.storage.unlockable) return\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n const existing = this.#statuses[id]\n if (!existing || existing.state === 'locked') {\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n }\n this.#locked = false\n this.#storageError = null\n this.#emit()\n } catch (error) {\n if (isByokCeremonyCancel(error)) {\n const provider = this.#lockedProvider()\n if (provider) {\n this.request(provider, 'locked')\n throw new ByokBlockedError(provider, 'locked')\n }\n }\n this.#storageError = errorMessage(error, 'Failed to unlock keyring')\n this.#emit()\n throw error instanceof Error ? error : new Error(String(error))\n }\n }\n\n #lockedProvider(): ProviderId | undefined {\n if (this.#prompt && isProviderId(this.#prompt.provider)) {\n return this.#prompt.provider\n }\n for (const [id, status] of Object.entries(this.#statuses)) {\n if (status?.state === 'locked' && isProviderId(id)) return id\n }\n return undefined\n }\n\n async #hydrate(): Promise<void> {\n if (this.storage.unlockable) {\n if (!this.storage.peek) return\n try {\n const preview = await this.storage.peek()\n for (const [id, last4] of Object.entries(preview)) {\n if (!isProviderId(id) || this.#statuses[id]) continue\n this.#statuses[id] = {\n state: 'locked',\n masked: last4 ? last4 : '••',\n }\n }\n this.#locked = Object.keys(preview).length > 0\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n return\n }\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n }\n\n #emit(): void {\n this.#snapshot = this.#buildSnapshot()\n for (const listener of this.#listeners) listener()\n }\n}\n\nexport function defineByok(options: DefineByokOptions = {}): ByokClient {\n return new ByokClient(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["#companions","#listeners","#ready","#locked","#snapshot","#buildSnapshot","#hydrate","#statuses","#prompt","#storageError","#keys","#emit","#coverageAll","#coverage","#expand","#hasCoverage","#lockedProvider"],"sources":["../../../src/byok/client.ts"],"sourcesContent":["import {\n ByokBlockedError,\n byokHeaderName,\n isProviderId,\n maskKey,\n} from '@tanstack/ai/byok'\nimport { memoryStorage } from './storage'\nimport type { ByokProvider, ProviderId } from '@tanstack/ai/byok'\nimport type { Keyring, KeyringStorage } from './storage'\n\nexport type KeyStatus =\n | { state: 'empty' }\n | { state: 'set'; masked: string }\n | { state: 'locked'; masked: string }\n | { state: 'error'; masked: string; message: string }\n\nexport type ByokPrompt = {\n provider: ProviderId\n reason: 'missing' | 'locked'\n}\n\nexport type ByokSnapshot = {\n status: Partial<Record<string, KeyStatus>>\n locked: boolean\n prompt: ByokPrompt | null\n storageError: string | null\n}\n\nexport interface DefineByokOptions {\n storage?: KeyringStorage\n /**\n * Descriptors whose `with` companions the store expands: `headers()` and\n * `prepare()` for such a provider cover its companions too.\n */\n providers?: ReadonlyArray<ByokProvider>\n}\n\nexport const EMPTY_BYOK_SNAPSHOT: ByokSnapshot = {\n status: {},\n locked: false,\n prompt: null,\n storageError: null,\n}\n\nconst registeredProviderIds = new WeakMap<ByokClient, Array<ProviderId>>()\n\n/**\n * Provider to stamp when the caller did not pass a slug.\n * One saved key wins. Else the only provider listed in `defineByok({ providers })`.\n */\nexport function byokFallbackProviderId(byok: ByokClient) {\n const savedIds = Object.entries(byok.keys())\n .filter((entry) => Boolean(entry[1]))\n .map((entry) => entry[0])\n .filter(isProviderId)\n if (savedIds.length === 1) return savedIds[0]\n const registered = registeredProviderIds.get(byok) ?? []\n if (registered.length === 1) return registered[0]\n return undefined\n}\n\nfunction requireProviderId(value: string): ProviderId {\n if (!isProviderId(value)) {\n throw new Error(`Invalid BYOK provider id: ${value}`)\n }\n return value\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n return error instanceof Error && error.message.length > 0\n ? error.message\n : fallback\n}\n\nfunction isByokCeremonyCancel(error: unknown): boolean {\n if (\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'NotAllowedError'\n ) {\n return true\n }\n if (error instanceof Error) {\n if (error.name === 'NotAllowedError') return true\n const message = error.message.toLowerCase()\n return message.includes('cancelled') || message.includes('not allowed')\n }\n return false\n}\n\nfunction sanitizeKeyring(value: unknown): Keyring {\n if (typeof value !== 'object' || value === null) return {}\n const keys: Keyring = {}\n for (const [provider, key] of Object.entries(value)) {\n if (isProviderId(provider) && typeof key === 'string' && key.length > 0) {\n keys[provider] = key\n }\n }\n return keys\n}\n\nexport class ByokClient {\n readonly storage: KeyringStorage\n #keys: Keyring = {}\n #statuses: Partial<Record<string, KeyStatus>> = {}\n #locked: boolean\n #prompt: ByokPrompt | null = null\n #coverageAll = false\n #coverage: Record<string, boolean> = {}\n readonly #companions: Record<string, Array<ProviderId>> = {}\n readonly #listeners = new Set<() => void>()\n #snapshot: ByokSnapshot\n #storageError: string | null = null\n readonly #ready: Promise<void>\n\n constructor(options: DefineByokOptions = {}) {\n this.storage = options.storage ?? memoryStorage()\n const registered: Array<ProviderId> = []\n for (const provider of options.providers ?? []) {\n const id = requireProviderId(provider.id)\n registered.push(id)\n if (provider.with?.length) {\n this.#companions[id] = provider.with.map((companion) =>\n requireProviderId(companion.id),\n )\n }\n }\n registeredProviderIds.set(this, registered)\n this.#locked = Boolean(this.storage.unlockable)\n this.#snapshot = this.#buildSnapshot()\n this.#ready = this.#hydrate()\n }\n\n /** Resolves when constructor hydration (peek/load) finishes. */\n ready = (): Promise<void> => this.#ready\n\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n getSnapshot = (): ByokSnapshot => this.#snapshot\n\n #buildSnapshot(): ByokSnapshot {\n return {\n status: { ...this.#statuses },\n locked: this.#locked,\n prompt: this.#prompt,\n storageError: this.#storageError,\n }\n }\n\n keys(): Keyring {\n return { ...this.#keys }\n }\n\n request(provider: ProviderId, reason: ByokPrompt['reason']): void {\n this.#prompt = { provider: requireProviderId(provider), reason }\n this.#emit()\n }\n\n /**\n * `true` — the server can fill any slug from env.\n * `false` — no env coverage.\n * A record merges per-id flags and clears the all/none boolean.\n */\n setServerCoverage(flags: boolean | Readonly<Record<string, boolean>>): void {\n if (typeof flags === 'boolean') {\n this.#coverageAll = flags\n this.#coverage = {}\n return\n }\n this.#coverageAll = false\n this.#coverage = { ...this.#coverage, ...flags }\n }\n\n #hasCoverage(provider: string): boolean {\n return this.#coverageAll || this.#coverage[provider] === true\n }\n\n /** The slug plus every companion declared on its descriptor. */\n #expand(provider: ProviderId): Array<ProviderId> {\n return [provider, ...(this.#companions[provider] ?? [])]\n }\n\n headers(provider?: ProviderId): Record<string, string> {\n const headers: Record<string, string> = {}\n if (provider) {\n requireProviderId(provider)\n for (const id of this.#expand(provider)) {\n const key = this.#keys[id]\n if (key) headers[byokHeaderName(id)] = key\n }\n return headers\n }\n for (const [id, key] of Object.entries(this.#keys)) {\n if (key) headers[byokHeaderName(id)] = key\n }\n return headers\n }\n\n async prepare(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n if (!provider) return\n requireProviderId(provider)\n for (const id of this.#expand(provider)) {\n if (this.#keys[id] || this.#hasCoverage(id)) continue\n this.request(id, 'missing')\n throw new ByokBlockedError(id, 'missing')\n }\n }\n\n async update(\n providerOrKey: ProviderId | string,\n key?: string,\n ): Promise<void> {\n await this.#ready\n let provider: ProviderId\n let nextKey: string\n if (key === undefined) {\n if (!this.#prompt) {\n throw new Error('byok.update(key) cannot run when prompt is null')\n }\n provider = this.#prompt.provider\n nextKey = providerOrKey\n } else {\n provider = requireProviderId(providerOrKey)\n nextKey = key\n }\n nextKey = nextKey.trim()\n if (nextKey.length === 0) {\n throw new Error('BYOK key must be non-empty')\n }\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n const previousStorageError = this.#storageError\n const next = { ...this.#keys, [provider]: nextKey }\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#storageError = previousStorageError\n this.#statuses[provider] = {\n state: 'error',\n masked: maskKey(nextKey),\n message: errorMessage(error, `Failed to persist ${provider} key`),\n }\n this.#emit()\n throw error\n }\n this.#keys = next\n this.#statuses[provider] = { state: 'set', masked: maskKey(nextKey) }\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async clear(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (provider) {\n requireProviderId(provider)\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const next = { ...this.#keys }\n delete next[provider]\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#emit()\n throw error\n }\n this.#keys = next\n delete this.#statuses[provider]\n this.#emit()\n return\n }\n const previousKeys = this.#keys\n const previousStatuses = this.#statuses\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n try {\n await this.storage.clear()\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#emit()\n throw error\n }\n this.#keys = {}\n this.#statuses = {}\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async unlock(): Promise<void> {\n await this.#ready\n if (!this.storage.unlockable) return\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n const existing = this.#statuses[id]\n if (!existing || existing.state === 'locked') {\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n }\n this.#locked = false\n this.#storageError = null\n this.#emit()\n } catch (error) {\n if (isByokCeremonyCancel(error)) {\n const provider = this.#lockedProvider()\n if (provider) {\n this.request(provider, 'locked')\n throw new ByokBlockedError(provider, 'locked')\n }\n }\n this.#storageError = errorMessage(error, 'Failed to unlock keyring')\n this.#emit()\n throw error instanceof Error ? error : new Error(String(error))\n }\n }\n\n #lockedProvider(): ProviderId | undefined {\n if (this.#prompt && isProviderId(this.#prompt.provider)) {\n return this.#prompt.provider\n }\n for (const [id, status] of Object.entries(this.#statuses)) {\n if (status?.state === 'locked' && isProviderId(id)) return id\n }\n return undefined\n }\n\n async #hydrate(): Promise<void> {\n if (this.storage.unlockable) {\n if (!this.storage.peek) return\n try {\n const preview = await this.storage.peek()\n for (const [id, last4] of Object.entries(preview)) {\n if (!isProviderId(id) || this.#statuses[id]) continue\n this.#statuses[id] = {\n state: 'locked',\n masked: last4 ? last4 : '••',\n }\n }\n this.#locked = Object.keys(preview).length > 0\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n return\n }\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n }\n\n #emit(): void {\n this.#snapshot = this.#buildSnapshot()\n for (const listener of this.#listeners) listener()\n }\n}\n\nexport function defineByok(options: DefineByokOptions = {}): ByokClient {\n return new ByokClient(options)\n}\n"],"mappings":"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"}
@@ -58,6 +58,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
58
58
  private readonly clearedStreamTracker;
59
59
  private currentRunId;
60
60
  private lastResume;
61
+ private readonly runParents;
62
+ private readonly supersededInterruptRunIds;
61
63
  private rejoinedRunId;
62
64
  private readonly interruptManager;
63
65
  private activeInterruptSubmission;
@@ -232,6 +234,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
232
234
  * state. This is interrupt (state) resume — there is no delivery cursor.
233
235
  */
234
236
  private observeInterruptState;
237
+ /** Walk parentRunId links and mark this run plus its ancestors as answered. */
238
+ private markLineageAnswered;
235
239
  /**
236
240
  * The interrupt-resume state for the active/interrupted run (its run/thread
237
241
  * ids), or null when there is nothing to resume. Apps can persist this to
@@ -1,3 +1,4 @@
1
+ import { byokFallbackProviderId } from "./byok/client.js";
1
2
  import { prepareResolvedByokHeaders, resolveByokProviderId } from "./byok/resolve.js";
2
3
  import { createNoOpChatDevtoolsBridge } from "./devtools-noop.js";
3
4
  import { normalizeMessagesDates } from "./message-date-normalizer.js";
@@ -140,6 +141,7 @@ var REJOIN_REBUILD_TRIGGERS = /* @__PURE__ */ new Set([
140
141
  "TOOL_CALL_START",
141
142
  "MESSAGES_SNAPSHOT"
142
143
  ]);
144
+ var MAX_RUN_LINEAGE_DEPTH = 64;
143
145
  var ChatClient = class {
144
146
  processor;
145
147
  connection;
@@ -149,6 +151,8 @@ var ChatClient = class {
149
151
  clearedStreamTracker = new ClearedStreamTracker();
150
152
  currentRunId = null;
151
153
  lastResume = null;
154
+ runParents = /* @__PURE__ */ new Map();
155
+ supersededInterruptRunIds = /* @__PURE__ */ new Set();
152
156
  rejoinedRunId = null;
153
157
  interruptManager;
154
158
  activeInterruptSubmission;
@@ -582,15 +586,28 @@ var ChatClient = class {
582
586
  updateRunLifecycle(chunk, options) {
583
587
  if (chunk.type === "RUN_STARTED") {
584
588
  const chunkRunId = getChunkRunId$1(chunk) ?? chunk.runId;
589
+ const parentRunId = "parentRunId" in chunk && typeof chunk.parentRunId === "string" ? chunk.parentRunId : void 0;
590
+ if (parentRunId) {
591
+ this.runParents.set(chunkRunId, parentRunId);
592
+ if (this.supersededInterruptRunIds.has(chunkRunId)) this.markLineageAnswered(parentRunId);
593
+ }
585
594
  this.activeResumeThreadId = "threadId" in chunk && typeof chunk.threadId === "string" ? chunk.threadId : this.activeResumeThreadId;
586
595
  this.activeResumeRunId = chunkRunId;
587
- this.activeRunIds.add(chunkRunId);
588
- this.clearedStreamTracker.onRunStarted(chunkRunId);
589
- this.setSessionGenerating(true);
590
- if (this.persistor && this.connection.joinRun && !this.lastResume) this.persistResumeSnapshot({
596
+ const isAnsweredReplayStart = this.supersededInterruptRunIds.has(chunkRunId) && !this.isLoading;
597
+ if (!isAnsweredReplayStart) {
598
+ this.activeRunIds.add(chunkRunId);
599
+ this.clearedStreamTracker.onRunStarted(chunkRunId);
600
+ this.setSessionGenerating(true);
601
+ }
602
+ if (this.persistor && this.connection.joinRun && !this.lastResume && !isAnsweredReplayStart) this.persistResumeSnapshot({
591
603
  threadId: this.activeResumeThreadId ?? this.threadId,
592
604
  runId: chunkRunId
593
605
  });
606
+ if (this.lastResume && this.supersededInterruptRunIds.has(this.lastResume.runId)) {
607
+ this.lastResume = null;
608
+ this.persistor?.persistResumeSnapshot(null);
609
+ this.interruptManager.reset();
610
+ }
594
611
  return;
595
612
  }
596
613
  if (chunk.type !== "RUN_FINISHED" && chunk.type !== "RUN_ERROR") return;
@@ -617,8 +634,10 @@ var ChatClient = class {
617
634
  if (this.activeInterruptSubmission && chunk.type === "RUN_ERROR") return;
618
635
  const runId = getChunkRunId$1(chunk);
619
636
  const threadId = "threadId" in chunk && typeof chunk.threadId === "string" ? chunk.threadId : this.activeResumeThreadId;
637
+ if (runId && !isIntermediateToolTurn(chunk) && (chunk.type === "RUN_ERROR" || chunk.type === "RUN_FINISHED" && chunk.outcome?.type !== "interrupt")) this.markLineageAnswered(runId);
620
638
  if (chunk.type === "RUN_FINISHED" && chunk.outcome?.type === "interrupt") {
621
639
  const interruptedRunId = this.currentRunId ?? runId ?? this.activeResumeRunId ?? "";
640
+ if (interruptedRunId && this.supersededInterruptRunIds.has(interruptedRunId)) return;
622
641
  this.lastResume = {
623
642
  threadId: threadId ?? this.threadId,
624
643
  runId: interruptedRunId
@@ -637,7 +656,8 @@ var ChatClient = class {
637
656
  const isActiveStreamRunTerminal = Boolean(this.isLoading && runId && (runId === this.activeResumeRunId || runId === this.currentRunId));
638
657
  const isCurrentStreamTerminal = this.isLoading && chunk.type === "RUN_FINISHED" && !runId;
639
658
  const isActiveInterruptSubmissionTerminal = Boolean(this.activeInterruptSubmission && this.isLoading && chunk.type === "RUN_FINISHED" && chunk.outcome?.type !== "interrupt");
640
- if (isRunlessSessionError || isTrackedRunTerminal || isCurrentRunTerminal || isActiveStreamRunTerminal || isCurrentStreamTerminal || isActiveInterruptSubmissionTerminal) {
659
+ const isLineageDescendantTerminal = Boolean(this.lastResume && this.supersededInterruptRunIds.has(this.lastResume.runId));
660
+ if (isRunlessSessionError || isTrackedRunTerminal || isCurrentRunTerminal || isActiveStreamRunTerminal || isCurrentStreamTerminal || isActiveInterruptSubmissionTerminal || isLineageDescendantTerminal) {
641
661
  this.lastResume = null;
642
662
  this.persistor?.persistResumeSnapshot(null);
643
663
  this.interruptManager.reset();
@@ -645,6 +665,17 @@ var ChatClient = class {
645
665
  }
646
666
  this.notifyResumeStateChange("live");
647
667
  }
668
+ /** Walk parentRunId links and mark this run plus its ancestors as answered. */
669
+ markLineageAnswered(runId) {
670
+ let current = runId;
671
+ let hops = 0;
672
+ while (current !== void 0 && hops < MAX_RUN_LINEAGE_DEPTH) {
673
+ if (this.supersededInterruptRunIds.has(current)) return;
674
+ this.supersededInterruptRunIds.add(current);
675
+ current = this.runParents.get(current);
676
+ hops++;
677
+ }
678
+ }
648
679
  /**
649
680
  * The interrupt-resume state for the active/interrupted run (its run/thread
650
681
  * ids), or null when there is nothing to resume. Apps can persist this to
@@ -1317,7 +1348,7 @@ var ChatClient = class {
1317
1348
  const processingComplete = this.waitForProcessing();
1318
1349
  let byokHeaders;
1319
1350
  if (this.byok) {
1320
- const provider = resolveByokProviderId(this.byokProvider, mergedBody.provider);
1351
+ const provider = resolveByokProviderId(this.byokProvider, mergedBody.provider, byokFallbackProviderId(this.byok));
1321
1352
  byokHeaders = await prepareResolvedByokHeaders(this.byok, provider);
1322
1353
  }
1323
1354
  const runContext = {