@tanstack/ai-client 0.31.1 → 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 +42 -16
- package/dist/esm/byok/client.d.ts +5 -0
- package/dist/esm/byok/client.js +19 -2
- package/dist/esm/byok/client.js.map +1 -1
- package/dist/esm/chat-client.d.ts +31 -0
- package/dist/esm/chat-client.js +137 -10
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +25 -4
- package/dist/esm/connection-adapters.js +31 -12
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/generation-client.js +2 -1
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/types.d.ts +9 -0
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.js +2 -1
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +2 -2
- package/src/byok/client.ts +23 -2
- package/src/chat-client.ts +209 -8
- package/src/connection-adapters.ts +92 -14
- package/src/generation-client.ts +2 -0
- package/src/types.ts +9 -0
- package/src/video-generation-client.ts +2 -0
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
|
|
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-
|
|
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
|
|
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;
|
package/dist/esm/byok/client.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -144,6 +146,16 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
144
146
|
/** Constructor inputs `attach()` needs on every re-attach, not just the first. */
|
|
145
147
|
private readonly rejoinRunId;
|
|
146
148
|
private readonly cachesMessages;
|
|
149
|
+
/**
|
|
150
|
+
* Newest-window size from `history.pageSize`. Only set when
|
|
151
|
+
* `persistence === true`.
|
|
152
|
+
*/
|
|
153
|
+
private readonly historyPageSize;
|
|
154
|
+
private hasOlderMessages;
|
|
155
|
+
private olderMessagesCursor;
|
|
156
|
+
private readonly knownServerMessageIds;
|
|
157
|
+
private loadOlderMessagesInFlight;
|
|
158
|
+
private historyGeneration;
|
|
147
159
|
private devtoolsMounted;
|
|
148
160
|
private readonly callbacksRef;
|
|
149
161
|
constructor(options: ChatClientOptions<TTools, TContext, TInterrupts>);
|
|
@@ -222,6 +234,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
222
234
|
* state. This is interrupt (state) resume — there is no delivery cursor.
|
|
223
235
|
*/
|
|
224
236
|
private observeInterruptState;
|
|
237
|
+
/** Walk parentRunId links and mark this run plus its ancestors as answered. */
|
|
238
|
+
private markLineageAnswered;
|
|
225
239
|
/**
|
|
226
240
|
* The interrupt-resume state for the active/interrupted run (its run/thread
|
|
227
241
|
* ids), or null when there is nothing to resume. Apps can persist this to
|
|
@@ -470,6 +484,23 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
470
484
|
* Get current messages
|
|
471
485
|
*/
|
|
472
486
|
getMessages(): Array<UIMessage<TTools>>;
|
|
487
|
+
/**
|
|
488
|
+
* True when the last hydrate or older-page response said more messages exist.
|
|
489
|
+
*/
|
|
490
|
+
getHasOlderMessages(): boolean;
|
|
491
|
+
/**
|
|
492
|
+
* Fetch the next older window and put it in front of the painted messages.
|
|
493
|
+
*
|
|
494
|
+
* No-op when there is no older page, no cursor, or a page load is already
|
|
495
|
+
* running. A network failure rejects and leaves the painted messages as they
|
|
496
|
+
* are; {@link getHasOlderMessages} stays true.
|
|
497
|
+
*/
|
|
498
|
+
loadOlderMessages(): Promise<void>;
|
|
499
|
+
private resetHistoryPaging;
|
|
500
|
+
private applyHydrationPage;
|
|
501
|
+
private rememberServerMessageIds;
|
|
502
|
+
private rememberProcessorMessageIds;
|
|
503
|
+
private messagesForSend;
|
|
473
504
|
/**
|
|
474
505
|
* True when an interrupt (or another direct send) claimed the client during
|
|
475
506
|
* a drain. Read via a method so cross-await mutations are not constant-folded
|
package/dist/esm/chat-client.js
CHANGED
|
@@ -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;
|
|
@@ -235,6 +239,16 @@ var ChatClient = class {
|
|
|
235
239
|
/** Constructor inputs `attach()` needs on every re-attach, not just the first. */
|
|
236
240
|
rejoinRunId;
|
|
237
241
|
cachesMessages;
|
|
242
|
+
/**
|
|
243
|
+
* Newest-window size from `history.pageSize`. Only set when
|
|
244
|
+
* `persistence === true`.
|
|
245
|
+
*/
|
|
246
|
+
historyPageSize;
|
|
247
|
+
hasOlderMessages = false;
|
|
248
|
+
olderMessagesCursor;
|
|
249
|
+
knownServerMessageIds = /* @__PURE__ */ new Set();
|
|
250
|
+
loadOlderMessagesInFlight = false;
|
|
251
|
+
historyGeneration = 0;
|
|
238
252
|
devtoolsMounted = false;
|
|
239
253
|
callbacksRef;
|
|
240
254
|
constructor(options) {
|
|
@@ -245,10 +259,12 @@ var ChatClient = class {
|
|
|
245
259
|
if (options.persistence === true) {
|
|
246
260
|
if (!options.threadId) throw new Error("[TanStack AI] persistence needs a stable `threadId` to key on. Pass a threadId from your app (for example support-42).");
|
|
247
261
|
cachesMessages = false;
|
|
262
|
+
this.historyPageSize = options.history?.pageSize;
|
|
248
263
|
} else if (options.persistence) {
|
|
249
264
|
if (!options.threadId) throw new Error("[TanStack AI] persistence needs a stable `threadId` to key on. Pass a threadId from your app (for example support-42).");
|
|
250
265
|
this.persistor = new ChatPersistor(options.persistence, options.threadId, (messages) => this.processor.setMessages(messages), (snapshot) => this.applyPersistedResume(snapshot));
|
|
251
|
-
|
|
266
|
+
this.historyPageSize = void 0;
|
|
267
|
+
} else this.historyPageSize = void 0;
|
|
252
268
|
this.bodyOption = options.body || {};
|
|
253
269
|
this.forwardedPropsOption = options.forwardedProps || {};
|
|
254
270
|
this.byok = options.byok;
|
|
@@ -506,16 +522,25 @@ var ChatClient = class {
|
|
|
506
522
|
if (!hydrate) return;
|
|
507
523
|
if (this.isLoading || this.abortController) return;
|
|
508
524
|
if (this.disposed) return;
|
|
525
|
+
const pageSize = this.historyPageSize;
|
|
526
|
+
const hydrateOptions = pageSize === void 0 ? void 0 : { limit: pageSize };
|
|
509
527
|
(async () => {
|
|
510
528
|
let result;
|
|
529
|
+
const generation = this.historyGeneration;
|
|
511
530
|
try {
|
|
512
|
-
result = await hydrate(this.threadId);
|
|
531
|
+
result = await hydrate(this.threadId, hydrateOptions);
|
|
513
532
|
} catch {
|
|
514
533
|
return;
|
|
515
534
|
}
|
|
535
|
+
if (generation !== this.historyGeneration) return;
|
|
516
536
|
if (this.disposed || !this.tailing) return;
|
|
517
537
|
if (this.isLoading || this.abortController) return;
|
|
518
|
-
|
|
538
|
+
this.applyHydrationPage(result.page);
|
|
539
|
+
if (result.messages.length > 0) {
|
|
540
|
+
const windowMessages = normalizeMessagesDates(result.messages);
|
|
541
|
+
this.processor.setMessages(windowMessages);
|
|
542
|
+
this.rememberServerMessageIds(windowMessages);
|
|
543
|
+
}
|
|
519
544
|
if (result.interrupts && result.interrupts.pending.length > 0) this.applyResumeSnapshot({
|
|
520
545
|
resumeState: {
|
|
521
546
|
threadId: this.threadId,
|
|
@@ -561,15 +586,28 @@ var ChatClient = class {
|
|
|
561
586
|
updateRunLifecycle(chunk, options) {
|
|
562
587
|
if (chunk.type === "RUN_STARTED") {
|
|
563
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
|
+
}
|
|
564
594
|
this.activeResumeThreadId = "threadId" in chunk && typeof chunk.threadId === "string" ? chunk.threadId : this.activeResumeThreadId;
|
|
565
595
|
this.activeResumeRunId = chunkRunId;
|
|
566
|
-
this.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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({
|
|
570
603
|
threadId: this.activeResumeThreadId ?? this.threadId,
|
|
571
604
|
runId: chunkRunId
|
|
572
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
|
+
}
|
|
573
611
|
return;
|
|
574
612
|
}
|
|
575
613
|
if (chunk.type !== "RUN_FINISHED" && chunk.type !== "RUN_ERROR") return;
|
|
@@ -596,8 +634,10 @@ var ChatClient = class {
|
|
|
596
634
|
if (this.activeInterruptSubmission && chunk.type === "RUN_ERROR") return;
|
|
597
635
|
const runId = getChunkRunId$1(chunk);
|
|
598
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);
|
|
599
638
|
if (chunk.type === "RUN_FINISHED" && chunk.outcome?.type === "interrupt") {
|
|
600
639
|
const interruptedRunId = this.currentRunId ?? runId ?? this.activeResumeRunId ?? "";
|
|
640
|
+
if (interruptedRunId && this.supersededInterruptRunIds.has(interruptedRunId)) return;
|
|
601
641
|
this.lastResume = {
|
|
602
642
|
threadId: threadId ?? this.threadId,
|
|
603
643
|
runId: interruptedRunId
|
|
@@ -616,7 +656,8 @@ var ChatClient = class {
|
|
|
616
656
|
const isActiveStreamRunTerminal = Boolean(this.isLoading && runId && (runId === this.activeResumeRunId || runId === this.currentRunId));
|
|
617
657
|
const isCurrentStreamTerminal = this.isLoading && chunk.type === "RUN_FINISHED" && !runId;
|
|
618
658
|
const isActiveInterruptSubmissionTerminal = Boolean(this.activeInterruptSubmission && this.isLoading && chunk.type === "RUN_FINISHED" && chunk.outcome?.type !== "interrupt");
|
|
619
|
-
|
|
659
|
+
const isLineageDescendantTerminal = Boolean(this.lastResume && this.supersededInterruptRunIds.has(this.lastResume.runId));
|
|
660
|
+
if (isRunlessSessionError || isTrackedRunTerminal || isCurrentRunTerminal || isActiveStreamRunTerminal || isCurrentStreamTerminal || isActiveInterruptSubmissionTerminal || isLineageDescendantTerminal) {
|
|
620
661
|
this.lastResume = null;
|
|
621
662
|
this.persistor?.persistResumeSnapshot(null);
|
|
622
663
|
this.interruptManager.reset();
|
|
@@ -624,6 +665,17 @@ var ChatClient = class {
|
|
|
624
665
|
}
|
|
625
666
|
this.notifyResumeStateChange("live");
|
|
626
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
|
+
}
|
|
627
679
|
/**
|
|
628
680
|
* The interrupt-resume state for the active/interrupted run (its run/thread
|
|
629
681
|
* ids), or null when there is nothing to resume. Apps can persist this to
|
|
@@ -1275,7 +1327,7 @@ var ChatClient = class {
|
|
|
1275
1327
|
let activeDevtoolsRunId = null;
|
|
1276
1328
|
let runTerminalEventEmitted = false;
|
|
1277
1329
|
try {
|
|
1278
|
-
const messages = this.processor.getMessages();
|
|
1330
|
+
const messages = this.messagesForSend(this.processor.getMessages());
|
|
1279
1331
|
const clientTools = new Map(this.clientToolsRef.current);
|
|
1280
1332
|
const runtimeContext = this.context;
|
|
1281
1333
|
await this.callbacksRef.current.onResponse();
|
|
@@ -1296,7 +1348,7 @@ var ChatClient = class {
|
|
|
1296
1348
|
const processingComplete = this.waitForProcessing();
|
|
1297
1349
|
let byokHeaders;
|
|
1298
1350
|
if (this.byok) {
|
|
1299
|
-
const provider = resolveByokProviderId(this.byokProvider, mergedBody.provider);
|
|
1351
|
+
const provider = resolveByokProviderId(this.byokProvider, mergedBody.provider, byokFallbackProviderId(this.byok));
|
|
1300
1352
|
byokHeaders = await prepareResolvedByokHeaders(this.byok, provider);
|
|
1301
1353
|
}
|
|
1302
1354
|
const runContext = {
|
|
@@ -1368,6 +1420,7 @@ var ChatClient = class {
|
|
|
1368
1420
|
}
|
|
1369
1421
|
await this.drainPostStreamActions();
|
|
1370
1422
|
if (streamCompletedSuccessfully) {
|
|
1423
|
+
this.rememberProcessorMessageIds();
|
|
1371
1424
|
if (this.status !== "ready") this.setStatus("ready");
|
|
1372
1425
|
if (!this.messageQueueDraining) await this.drainQueue();
|
|
1373
1426
|
} else this.flushQueue();
|
|
@@ -1446,6 +1499,7 @@ var ChatClient = class {
|
|
|
1446
1499
|
} else if (this.activeRunIds.size > 0) this.resetSessionGenerating({ preserveClearedStreamTracking: true });
|
|
1447
1500
|
this.persistor?.beginClear();
|
|
1448
1501
|
this.processor.clearMessages();
|
|
1502
|
+
this.resetHistoryPaging();
|
|
1449
1503
|
this.discardPendingSends();
|
|
1450
1504
|
this.persistor?.remove();
|
|
1451
1505
|
this.lastResume = null;
|
|
@@ -1586,6 +1640,79 @@ var ChatClient = class {
|
|
|
1586
1640
|
return this.processor.getMessages();
|
|
1587
1641
|
}
|
|
1588
1642
|
/**
|
|
1643
|
+
* True when the last hydrate or older-page response said more messages exist.
|
|
1644
|
+
*/
|
|
1645
|
+
getHasOlderMessages() {
|
|
1646
|
+
return this.hasOlderMessages;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Fetch the next older window and put it in front of the painted messages.
|
|
1650
|
+
*
|
|
1651
|
+
* No-op when there is no older page, no cursor, or a page load is already
|
|
1652
|
+
* running. A network failure rejects and leaves the painted messages as they
|
|
1653
|
+
* are; {@link getHasOlderMessages} stays true.
|
|
1654
|
+
*/
|
|
1655
|
+
async loadOlderMessages() {
|
|
1656
|
+
const hydrate = this.connection.hydrate;
|
|
1657
|
+
if (hydrate === void 0) return;
|
|
1658
|
+
if (this.disposed) return;
|
|
1659
|
+
if (!this.hasOlderMessages) return;
|
|
1660
|
+
const cursor = this.olderMessagesCursor;
|
|
1661
|
+
if (cursor === void 0) return;
|
|
1662
|
+
if (this.loadOlderMessagesInFlight) return;
|
|
1663
|
+
const hydrateOptions = { before: cursor };
|
|
1664
|
+
if (this.historyPageSize !== void 0) hydrateOptions.limit = this.historyPageSize;
|
|
1665
|
+
this.loadOlderMessagesInFlight = true;
|
|
1666
|
+
const generation = this.historyGeneration;
|
|
1667
|
+
try {
|
|
1668
|
+
const result = await hydrate(this.threadId, hydrateOptions);
|
|
1669
|
+
if (this.disposed) return;
|
|
1670
|
+
if (generation !== this.historyGeneration) return;
|
|
1671
|
+
const olderMessages = normalizeMessagesDates(result.messages);
|
|
1672
|
+
if (olderMessages.length === 0) throw new Error("Older page was empty");
|
|
1673
|
+
this.applyHydrationPage(result.page);
|
|
1674
|
+
this.processor.prependMessages(olderMessages);
|
|
1675
|
+
this.rememberServerMessageIds(olderMessages);
|
|
1676
|
+
} finally {
|
|
1677
|
+
this.loadOlderMessagesInFlight = false;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
resetHistoryPaging() {
|
|
1681
|
+
this.hasOlderMessages = false;
|
|
1682
|
+
this.olderMessagesCursor = void 0;
|
|
1683
|
+
this.knownServerMessageIds.clear();
|
|
1684
|
+
this.historyGeneration++;
|
|
1685
|
+
}
|
|
1686
|
+
applyHydrationPage(page) {
|
|
1687
|
+
if (page?.truncated === true) {
|
|
1688
|
+
this.hasOlderMessages = true;
|
|
1689
|
+
this.olderMessagesCursor = page.cursor;
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
this.hasOlderMessages = false;
|
|
1693
|
+
this.olderMessagesCursor = void 0;
|
|
1694
|
+
}
|
|
1695
|
+
rememberServerMessageIds(messages) {
|
|
1696
|
+
const ids = messages.map((message) => message.id);
|
|
1697
|
+
for (const id of ids) this.knownServerMessageIds.add(id);
|
|
1698
|
+
}
|
|
1699
|
+
rememberProcessorMessageIds() {
|
|
1700
|
+
if (this.historyPageSize === void 0) return;
|
|
1701
|
+
this.rememberServerMessageIds(this.processor.getMessages());
|
|
1702
|
+
}
|
|
1703
|
+
messagesForSend(messages) {
|
|
1704
|
+
if (this.historyPageSize === void 0) return messages;
|
|
1705
|
+
const unknownMessages = [];
|
|
1706
|
+
for (const message of messages) {
|
|
1707
|
+
if (this.knownServerMessageIds.has(message.id)) continue;
|
|
1708
|
+
unknownMessages.push(message);
|
|
1709
|
+
}
|
|
1710
|
+
if (unknownMessages.length > 0) return unknownMessages;
|
|
1711
|
+
const lastUserIndex = messages.findLastIndex((message) => message.role === "user");
|
|
1712
|
+
if (lastUserIndex === -1) return [];
|
|
1713
|
+
return messages.slice(lastUserIndex);
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1589
1716
|
* True when an interrupt (or another direct send) claimed the client during
|
|
1590
1717
|
* a drain. Read via a method so cross-await mutations are not constant-folded
|
|
1591
1718
|
* by control-flow analysis.
|