@tanstack/ai-client 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/byok/client.d.ts +52 -0
- package/dist/esm/byok/client.js +300 -0
- package/dist/esm/byok/client.js.map +1 -0
- package/dist/esm/byok/passkey.d.ts +43 -0
- package/dist/esm/byok/passkey.js +251 -0
- package/dist/esm/byok/passkey.js.map +1 -0
- package/dist/esm/byok/resolve.d.ts +8 -0
- package/dist/esm/byok/resolve.js +20 -0
- package/dist/esm/byok/resolve.js.map +1 -0
- package/dist/esm/byok/storage.d.ts +15 -0
- package/dist/esm/byok/storage.js +16 -0
- package/dist/esm/byok/storage.js.map +1 -0
- package/dist/esm/byok.d.ts +8 -0
- package/dist/esm/byok.js +5 -0
- package/dist/esm/chat-client.d.ts +16 -3
- package/dist/esm/chat-client.js +49 -31
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +2 -0
- package/dist/esm/connection-adapters.js +33 -9
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/generation-client.d.ts +3 -1
- package/dist/esm/generation-client.js +23 -4
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +14 -0
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/interrupt-manager.d.ts +1 -0
- package/dist/esm/interrupt-manager.js +15 -6
- package/dist/esm/interrupt-manager.js.map +1 -1
- package/dist/esm/sse-parser.js +8 -1
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/types.d.ts +29 -3
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +3 -1
- package/dist/esm/video-generation-client.js +25 -6
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +6 -2
- package/src/byok/client.ts +360 -0
- package/src/byok/passkey.ts +399 -0
- package/src/byok/resolve.ts +30 -0
- package/src/byok/storage.ts +29 -0
- package/src/byok.ts +20 -0
- package/src/chat-client.ts +95 -65
- package/src/connection-adapters.ts +41 -12
- package/src/generation-client.ts +48 -4
- package/src/generation-types.ts +16 -0
- package/src/interrupt-manager.ts +20 -6
- package/src/sse-parser.ts +10 -0
- package/src/types.ts +31 -3
- package/src/video-generation-client.ts +45 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passkey.js","names":[],"sources":["../../../src/byok/passkey.ts"],"sourcesContent":["import { isProviderId } from '@tanstack/ai/byok'\nimport { memoryStorage } from './storage'\nimport type { KeyPreview, Keyring, KeyringStorage } from './storage'\n\n/**\n * Passkey-encrypted keyring storage (WebAuthn PRF → HKDF → AES-256-GCM).\n *\n * The keyring is encrypted at rest in IndexedDB with an AES-256-GCM key derived\n * from a passkey's PRF output, unwrapped on demand with a biometric/PIN tap.\n * Decryption happens entirely client-side with the user present — no server,\n * no custodian.\n *\n * Honest scope: this protects against at-rest theft (stolen device,\n * storage-dumping extension, backups). It does NOT defeat live in-page XSS —\n * an attacker running JS in the origin after the user unlocks can read the\n * decrypted keys from memory.\n */\n\nconst STORE_NAME = 'keyring'\nconst RECORD_ID = 'default'\nconst HKDF_INFO = 'byok:keyring:v1'\nconst DEFAULT_DB = 'byok'\n\ninterface StoredRecord {\n id: string\n /** The passkey's raw credential id, replayed in the unlock ceremony. */\n credentialId: ArrayBuffer\n /** Fixed per-install PRF evaluation input (not secret). */\n salt: ArrayBuffer\n /** AES-GCM initialization vector for this ciphertext. */\n iv: ArrayBuffer\n /** Encrypted keyring JSON. */\n ciphertext: ArrayBuffer\n /**\n * Unencrypted presence metadata (`provider → last 4`). Non-sensitive, so it\n * can be read via {@link KeyringStorage.peek} without an unlock ceremony to\n * show saved keys as \"locked\" after a refresh.\n */\n preview: KeyPreview\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\n/** Build the non-sensitive `provider → last 4` preview from a keyring. */\nfunction previewOf(keys: Keyring): KeyPreview {\n const preview: KeyPreview = {}\n for (const [provider, key] of Object.entries(keys)) {\n if (!key || !isProviderId(provider)) continue\n // Keys of length ≤ 4 would make last-4 the whole secret — store presence only.\n preview[provider] = key.length > 4 ? key.slice(-4) : ''\n }\n return preview\n}\n\n/**\n * Whether the current environment exposes WebAuthn. Actual PRF support can\n * only be confirmed during registration; `passkeyStorage` throws if the\n * chosen authenticator does not support PRF.\n */\nexport function isPasskeyStorageSupported(): boolean {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.PublicKeyCredential !== 'undefined' &&\n typeof globalThis.navigator !== 'undefined' &&\n typeof globalThis.navigator.credentials.create === 'function'\n )\n}\n\n// ---------------------------------------------------------------------------\n// Crypto (exported for testing; the WebAuthn ceremony below feeds `deriveAesKey`)\n// ---------------------------------------------------------------------------\n\n/** Derive a non-extractable AES-256-GCM key from a 32-byte PRF output. */\nexport async function deriveAesKey(\n prfOutput: BufferSource,\n): Promise<CryptoKey> {\n const base = await crypto.subtle.importKey('raw', prfOutput, 'HKDF', false, [\n 'deriveKey',\n ])\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: new Uint8Array(0),\n info: new TextEncoder().encode(HKDF_INFO),\n },\n base,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\nexport async function encryptKeyring(\n key: CryptoKey,\n keys: Keyring,\n): Promise<{ iv: ArrayBuffer; ciphertext: ArrayBuffer }> {\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = new TextEncoder().encode(JSON.stringify(keys))\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n key,\n plaintext,\n )\n return { iv: iv.buffer, ciphertext }\n}\n\nexport async function decryptKeyring(\n key: CryptoKey,\n iv: BufferSource,\n ciphertext: BufferSource,\n): Promise<Keyring> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv },\n key,\n ciphertext,\n )\n const parsed: unknown = JSON.parse(new TextDecoder().decode(plaintext))\n return sanitizeKeyring(parsed)\n}\n\n// ---------------------------------------------------------------------------\n// IndexedDB\n// ---------------------------------------------------------------------------\n\nfunction openDb(dbName: string): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(dbName, 1)\n request.onupgradeneeded = () => {\n request.result.createObjectStore(STORE_NAME, { keyPath: 'id' })\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbGet(dbName: string): Promise<StoredRecord | null> {\n return openDb(dbName).then(\n (db) =>\n new Promise((resolve, reject) => {\n const request = db\n .transaction(STORE_NAME, 'readonly')\n .objectStore(STORE_NAME)\n .get(RECORD_ID)\n request.onsuccess = () => resolve(request.result ?? null)\n request.onerror = () => reject(request.error)\n }),\n )\n}\n\nfunction idbPut(dbName: string, record: StoredRecord): Promise<void> {\n return openDb(dbName).then(\n (db) =>\n new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite')\n tx.objectStore(STORE_NAME).put(record)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n }),\n )\n}\n\nfunction idbClear(dbName: string): Promise<void> {\n return openDb(dbName).then(\n (db) =>\n new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite')\n tx.objectStore(STORE_NAME).delete(RECORD_ID)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// WebAuthn ceremonies\n// ---------------------------------------------------------------------------\n\nfunction requirePublicKeyCredential(\n credential: Credential | null,\n action: string,\n): PublicKeyCredential {\n if (!credential) throw new Error(`Passkey ${action} was cancelled`)\n if (!(credential instanceof PublicKeyCredential)) {\n throw new Error(`Unexpected credential type during ${action}`)\n }\n return credential\n}\n\nasync function registerPasskey(\n rpName: string,\n userName: string,\n rpId?: string,\n): Promise<{\n credentialId: ArrayBuffer\n salt: Uint8Array<ArrayBuffer>\n prf?: BufferSource\n}> {\n const salt = crypto.getRandomValues(new Uint8Array(32))\n const credential = requirePublicKeyCredential(\n await navigator.credentials.create({\n publicKey: {\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n // Omit `id` to let the browser bind the passkey to the current origin's\n // effective domain; set it to scope across subdomains of a self-host.\n rp: rpId ? { name: rpName, id: rpId } : { name: rpName },\n user: {\n id: crypto.getRandomValues(new Uint8Array(16)),\n name: userName,\n displayName: userName,\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 },\n { type: 'public-key', alg: -257 },\n ],\n authenticatorSelection: {\n residentKey: 'required',\n userVerification: 'required',\n },\n extensions: { prf: { eval: { first: salt } } },\n },\n }),\n 'registration',\n )\n\n const prf = credential.getClientExtensionResults().prf\n if (!prf?.enabled) {\n throw new Error(\n 'This authenticator does not support the WebAuthn PRF extension',\n )\n }\n return { credentialId: credential.rawId, salt, prf: prf.results?.first }\n}\n\nasync function evaluatePrf(\n credentialId: BufferSource,\n salt: BufferSource,\n): Promise<BufferSource> {\n const credential = requirePublicKeyCredential(\n await navigator.credentials.get({\n publicKey: {\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n allowCredentials: [{ type: 'public-key', id: credentialId }],\n userVerification: 'required',\n extensions: { prf: { eval: { first: salt } } },\n },\n }),\n 'unlock',\n )\n const result = credential.getClientExtensionResults().prf?.results?.first\n if (!result) {\n throw new Error('Authenticator did not return a PRF result')\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Storage strategy\n// ---------------------------------------------------------------------------\n\nexport interface PasskeyStorageOptions {\n /** Relying-party name shown in the passkey prompt. */\n rpName?: string\n /** Username label attached to the created passkey. */\n userName?: string\n /**\n * WebAuthn Relying Party ID. Omit to bind the passkey to the current origin's\n * effective domain (the default — no central/hardcoded domain). Set it to a\n * registrable parent domain to share the credential across subdomains of your\n * own deployment. The encrypted keyring is never portable across unrelated\n * domains.\n */\n rpId?: string\n /** IndexedDB database name. Defaults to `byok`. */\n dbName?: string\n}\n\n/**\n * Passkey-encrypted persistence. `ByokClient` treats this as `unlockable`, so\n * nothing is decrypted until the user calls `unlock()` (or saves a key, which\n * registers a passkey on first use). The derived key is cached in memory for\n * the session so repeated saves don't re-prompt.\n */\nexport function passkeyStorage(\n options: PasskeyStorageOptions = {},\n): KeyringStorage {\n const rpName = options.rpName ?? 'BYOK'\n const userName = options.userName ?? 'byok-keyring'\n const { rpId } = options\n const dbName = options.dbName ?? DEFAULT_DB\n\n let cachedKey: CryptoKey | null = null\n let cachedMeta: {\n credentialId: ArrayBuffer\n salt: Uint8Array<ArrayBuffer>\n } | null = null\n\n // Obtain the AES key, running exactly one WebAuthn ceremony if it isn't\n // already cached for this session (unlock if a passkey exists, else register).\n async function ensureKey(): Promise<{\n key: CryptoKey\n credentialId: ArrayBuffer\n salt: Uint8Array<ArrayBuffer>\n }> {\n if (cachedKey && cachedMeta) {\n return { key: cachedKey, ...cachedMeta }\n }\n const existing = await idbGet(dbName)\n if (existing) {\n const prf = await evaluatePrf(existing.credentialId, existing.salt)\n cachedKey = await deriveAesKey(prf)\n cachedMeta = {\n credentialId: existing.credentialId,\n salt: new Uint8Array(existing.salt),\n }\n } else {\n const reg = await registerPasskey(rpName, userName, rpId)\n const prf = reg.prf ?? (await evaluatePrf(reg.credentialId, reg.salt))\n cachedKey = await deriveAesKey(prf)\n cachedMeta = { credentialId: reg.credentialId, salt: reg.salt }\n }\n return { key: cachedKey, ...cachedMeta }\n }\n\n return {\n id: 'passkey',\n label: 'Passkey-encrypted (this device)',\n persistent: true,\n unlockable: true,\n warning:\n 'Keys are encrypted with your passkey and unlocked with biometrics. ' +\n 'This protects saved keys if your device is stolen, but not against code ' +\n 'running on this page after you unlock.',\n peek: async () => {\n // Unencrypted last-4 sidecar — no key material, no unlock ceremony.\n const existing = await idbGet(dbName)\n return existing?.preview ?? {}\n },\n load: async () => {\n const existing = await idbGet(dbName)\n if (!existing) return {}\n const { key } = await ensureKey()\n return decryptKeyring(key, existing.iv, existing.ciphertext)\n },\n save: async (keys) => {\n const existing = await idbGet(dbName)\n const hasKeys = Object.values(keys).some(Boolean)\n // First save with an empty keyring is a no-op — avoids a passkey ceremony\n // when another storage tier writes an empty ring.\n if (!hasKeys && !existing) return\n\n const { key, credentialId, salt } = await ensureKey()\n const { iv, ciphertext } = await encryptKeyring(key, keys)\n await idbPut(dbName, {\n id: RECORD_ID,\n credentialId,\n salt: salt.buffer,\n iv,\n ciphertext,\n preview: previewOf(keys),\n })\n },\n clear: async () => {\n cachedKey = null\n cachedMeta = null\n await idbClear(dbName)\n },\n }\n}\n\n/**\n * Passkey-encrypted storage when WebAuthn is available in a secure context.\n * Otherwise session memory, with a warning — this is not an automatic PRF\n * fallback. First save still throws if the authenticator lacks PRF.\n */\nexport function defaultByokStorage(\n options?: PasskeyStorageOptions,\n): KeyringStorage {\n const secure =\n typeof globalThis.isSecureContext !== 'boolean' ||\n globalThis.isSecureContext\n if (!isPasskeyStorageSupported() || !secure) {\n return {\n ...memoryStorage(),\n warning:\n 'Passkeys are unavailable in this context. Keys stay in memory for this tab only.',\n }\n }\n return passkeyStorage(options)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ProviderId } from '@tanstack/ai/byok';
|
|
2
|
+
import { ByokClient } from './client.js';
|
|
3
|
+
export declare function resolveByokProviderId(byokProvider: (() => string | undefined) | undefined, ...candidates: Array<unknown>): ProviderId | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Prepare and stamp headers for one resolved slug. Throws instead of
|
|
6
|
+
* attaching every stored key when the slug is missing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function prepareResolvedByokHeaders(byok: ByokClient, provider: ProviderId | undefined): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ByokUnresolvedProviderError, isProviderId } from "@tanstack/ai/byok";
|
|
2
|
+
//#region src/byok/resolve.ts
|
|
3
|
+
function resolveByokProviderId(byokProvider, ...candidates) {
|
|
4
|
+
const fromFn = byokProvider?.();
|
|
5
|
+
if (isProviderId(fromFn)) return fromFn;
|
|
6
|
+
for (const candidate of candidates) if (isProviderId(candidate)) return candidate;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Prepare and stamp headers for one resolved slug. Throws instead of
|
|
10
|
+
* attaching every stored key when the slug is missing.
|
|
11
|
+
*/
|
|
12
|
+
async function prepareResolvedByokHeaders(byok, provider) {
|
|
13
|
+
if (!provider) throw new ByokUnresolvedProviderError();
|
|
14
|
+
await byok.prepare(provider);
|
|
15
|
+
return byok.headers(provider);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { prepareResolvedByokHeaders, resolveByokProviderId };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","names":[],"sources":["../../../src/byok/resolve.ts"],"sourcesContent":["import { ByokUnresolvedProviderError, isProviderId } from '@tanstack/ai/byok'\nimport type { ProviderId } from '@tanstack/ai/byok'\nimport type { ByokClient } from './client'\n\nexport function resolveByokProviderId(\n byokProvider: (() => string | undefined) | undefined,\n ...candidates: Array<unknown>\n): ProviderId | undefined {\n const fromFn = byokProvider?.()\n if (isProviderId(fromFn)) return fromFn\n for (const candidate of candidates) {\n if (isProviderId(candidate)) return candidate\n }\n return undefined\n}\n\n/**\n * Prepare and stamp headers for one resolved slug. Throws instead of\n * attaching every stored key when the slug is missing.\n */\nexport async function prepareResolvedByokHeaders(\n byok: ByokClient,\n provider: ProviderId | undefined,\n): Promise<Record<string, string>> {\n if (!provider) {\n throw new ByokUnresolvedProviderError()\n }\n await byok.prepare(provider)\n return byok.headers(provider)\n}\n"],"mappings":";;AAIA,SAAgB,sBACd,cACA,GAAG,YACqB;CACxB,MAAM,SAAS,eAAe;CAC9B,IAAI,aAAa,MAAM,GAAG,OAAO;CACjC,KAAK,MAAM,aAAa,YACtB,IAAI,aAAa,SAAS,GAAG,OAAO;AAGxC;;;;;AAMA,eAAsB,2BACpB,MACA,UACiC;CACjC,IAAI,CAAC,UACH,MAAM,IAAI,4BAA4B;CAExC,MAAM,KAAK,QAAQ,QAAQ;CAC3B,OAAO,KAAK,QAAQ,QAAQ;AAC9B"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ProviderId } from '@tanstack/ai/byok';
|
|
2
|
+
export type Keyring = Partial<Record<ProviderId, string>>;
|
|
3
|
+
export type KeyPreview = Partial<Record<ProviderId, string>>;
|
|
4
|
+
export interface KeyringStorage {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly persistent: boolean;
|
|
8
|
+
readonly unlockable?: boolean;
|
|
9
|
+
readonly warning?: string;
|
|
10
|
+
peek?: () => KeyPreview | Promise<KeyPreview>;
|
|
11
|
+
load: () => Keyring | Promise<Keyring>;
|
|
12
|
+
save: (keys: Keyring) => void | Promise<void>;
|
|
13
|
+
clear: () => void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare function memoryStorage(): KeyringStorage;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/byok/storage.ts
|
|
2
|
+
function memoryStorage() {
|
|
3
|
+
return {
|
|
4
|
+
id: "memory",
|
|
5
|
+
label: "Session only (not saved)",
|
|
6
|
+
persistent: false,
|
|
7
|
+
warning: "Keys stay in memory for this tab only. They are not saved across reloads.",
|
|
8
|
+
load: () => ({}),
|
|
9
|
+
save: () => {},
|
|
10
|
+
clear: () => {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { memoryStorage };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","names":[],"sources":["../../../src/byok/storage.ts"],"sourcesContent":["import type { ProviderId } from '@tanstack/ai/byok'\n\nexport type Keyring = Partial<Record<ProviderId, string>>\nexport type KeyPreview = Partial<Record<ProviderId, string>>\n\nexport interface KeyringStorage {\n readonly id: string\n readonly label: string\n readonly persistent: boolean\n readonly unlockable?: boolean\n readonly warning?: string\n peek?: () => KeyPreview | Promise<KeyPreview>\n load: () => Keyring | Promise<Keyring>\n save: (keys: Keyring) => void | Promise<void>\n clear: () => void | Promise<void>\n}\n\nexport function memoryStorage(): KeyringStorage {\n return {\n id: 'memory',\n label: 'Session only (not saved)',\n persistent: false,\n warning:\n 'Keys stay in memory for this tab only. They are not saved across reloads.',\n load: () => ({}),\n save: () => {},\n clear: () => {},\n }\n}\n"],"mappings":";AAiBA,SAAgB,gBAAgC;CAC9C,OAAO;EACL,IAAI;EACJ,OAAO;EACP,YAAY;EACZ,SACE;EACF,aAAa,CAAC;EACd,YAAY,CAAC;EACb,aAAa,CAAC;CAChB;AACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { defineByok, ByokClient, EMPTY_BYOK_SNAPSHOT } from './byok/client.js';
|
|
2
|
+
export type { ByokPrompt, ByokSnapshot, DefineByokOptions, KeyStatus, } from './byok/client.js';
|
|
3
|
+
export { defineByokProvider } from '@tanstack/ai/byok';
|
|
4
|
+
export type { ByokProvider, ByokProviderInit } from '@tanstack/ai/byok';
|
|
5
|
+
export { memoryStorage } from './byok/storage.js';
|
|
6
|
+
export type { KeyPreview, Keyring, KeyringStorage } from './byok/storage.js';
|
|
7
|
+
export { defaultByokStorage, decryptKeyring, deriveAesKey, encryptKeyring, isPasskeyStorageSupported, passkeyStorage, } from './byok/passkey.js';
|
|
8
|
+
export type { PasskeyStorageOptions } from './byok/passkey.js';
|
package/dist/esm/byok.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { memoryStorage } from "./byok/storage.js";
|
|
2
|
+
import { ByokClient, EMPTY_BYOK_SNAPSHOT, defineByok } from "./byok/client.js";
|
|
3
|
+
import { decryptKeyring, defaultByokStorage, deriveAesKey, encryptKeyring, isPasskeyStorageSupported, passkeyStorage } from "./byok/passkey.js";
|
|
4
|
+
import { defineByokProvider } from "@tanstack/ai/byok";
|
|
5
|
+
export { ByokClient, EMPTY_BYOK_SNAPSHOT, decryptKeyring, defaultByokStorage, defineByok, defineByokProvider, deriveAesKey, encryptKeyring, isPasskeyStorageSupported, memoryStorage, passkeyStorage };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AnyClientTool, InterruptDefinition, ModelMessage, RunAgentResumeItem, StreamChunk } from '@tanstack/ai/client';
|
|
2
|
+
import { ByokClient } from './byok.js';
|
|
2
3
|
import { ConnectionAdapter } from './connection-adapters.js';
|
|
3
4
|
import { BoundInterrupts, ChatClientOptions, ChatClientState, ChatFetcher, ChatInterruptState, ResolvableChatInterrupt, ChatResumeState, ConnectionStatus, MultimodalContent, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from './types.js';
|
|
4
5
|
type ChatClientUpdateOptionsWithoutContext<TTools extends ReadonlyArray<AnyClientTool>, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []> = {
|
|
@@ -7,6 +8,8 @@ type ChatClientUpdateOptionsWithoutContext<TTools extends ReadonlyArray<AnyClien
|
|
|
7
8
|
/** @deprecated Use `forwardedProps` instead. */
|
|
8
9
|
body?: Record<string, any>;
|
|
9
10
|
forwardedProps?: Record<string, any>;
|
|
11
|
+
byok?: ByokClient;
|
|
12
|
+
byokProvider?: () => string | undefined;
|
|
10
13
|
tools?: TTools;
|
|
11
14
|
interrupts?: TInterrupts;
|
|
12
15
|
queue?: QueueOption;
|
|
@@ -67,6 +70,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
67
70
|
private activeResumeRunId;
|
|
68
71
|
private bodyOption;
|
|
69
72
|
private forwardedPropsOption;
|
|
73
|
+
private byok;
|
|
74
|
+
private byokProvider;
|
|
70
75
|
private context;
|
|
71
76
|
private pendingMessageBody;
|
|
72
77
|
private queueConfig;
|
|
@@ -320,8 +325,12 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
320
325
|
* - A MultimodalContent object with content array and optional custom ID
|
|
321
326
|
* @param body - Optional body parameters to merge with the client's base body for this request.
|
|
322
327
|
* Uses shallow merge with per-message body taking priority.
|
|
323
|
-
* @param sendOptions - Per-call overrides
|
|
324
|
-
*
|
|
328
|
+
* @param sendOptions - Per-call overrides. `{ whenBusy }` overrides the
|
|
329
|
+
* queue policy for this one send. `{ body }`
|
|
330
|
+
* shallow-merges with `body` and with the chat-level
|
|
331
|
+
* `body` / `forwardedProps`. `sendOptions.body` wins
|
|
332
|
+
* on key collisions. Framework hooks forward this
|
|
333
|
+
* object as their second argument.
|
|
325
334
|
*
|
|
326
335
|
* @example
|
|
327
336
|
* ```ts
|
|
@@ -331,9 +340,13 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
331
340
|
* // Text message with custom body params
|
|
332
341
|
* await client.sendMessage('Hello!', { temperature: 0.7 })
|
|
333
342
|
*
|
|
334
|
-
* // Per-call whenBusy override
|
|
343
|
+
* // Per-call whenBusy override
|
|
335
344
|
* await client.sendMessage('Urgent', undefined, { whenBusy: 'interrupt' })
|
|
336
345
|
*
|
|
346
|
+
* // Per-call body via options. Same effect as the positional arg.
|
|
347
|
+
* // This is the shape the framework hooks (`useChat`, `injectChat`) forward.
|
|
348
|
+
* await client.sendMessage('Hello!', undefined, { body: { temperature: 0.7 } })
|
|
349
|
+
*
|
|
337
350
|
* // Multimodal message with image
|
|
338
351
|
* await client.sendMessage({
|
|
339
352
|
* content: [
|
package/dist/esm/chat-client.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { prepareResolvedByokHeaders, resolveByokProviderId } from "./byok/resolve.js";
|
|
1
2
|
import { createNoOpChatDevtoolsBridge } from "./devtools-noop.js";
|
|
2
3
|
import { fetcherToConnectionAdapter, getChunkRunId as getChunkRunId$1, normalizeConnectionAdapter } from "./connection-adapters.js";
|
|
3
4
|
import { ChatPersistor } from "./client-persistor.js";
|
|
4
5
|
import { ClearedStreamTracker } from "./cleared-stream-tracker.js";
|
|
5
6
|
import { InterruptManager } from "./interrupt-manager.js";
|
|
6
7
|
import { StreamProcessor, convertSchemaToJsonSchema, generateMessageId, isStandardSchema, mergeMetadata, normalizeToUIMessage, parseWithStandardSchema, restoreInboundChunk, tanstackMetadata } from "@tanstack/ai/client";
|
|
8
|
+
import { ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError } from "@tanstack/ai/byok";
|
|
7
9
|
//#region src/chat-client.ts
|
|
8
10
|
function assertUniqueInterruptDefinitions(interrupts) {
|
|
9
11
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -148,6 +150,8 @@ var ChatClient = class {
|
|
|
148
150
|
activeResumeRunId = null;
|
|
149
151
|
bodyOption = {};
|
|
150
152
|
forwardedPropsOption = {};
|
|
153
|
+
byok;
|
|
154
|
+
byokProvider;
|
|
151
155
|
context = void 0;
|
|
152
156
|
pendingMessageBody = void 0;
|
|
153
157
|
queueConfig;
|
|
@@ -228,6 +232,8 @@ var ChatClient = class {
|
|
|
228
232
|
}
|
|
229
233
|
this.bodyOption = options.body || {};
|
|
230
234
|
this.forwardedPropsOption = options.forwardedProps || {};
|
|
235
|
+
this.byok = options.byok;
|
|
236
|
+
this.byokProvider = options.byokProvider;
|
|
231
237
|
this.context = options.context;
|
|
232
238
|
this.queueConfig = normalizeQueueOption(options.queue);
|
|
233
239
|
this.connection = normalizeConnectionAdapter(resolveTransport(options));
|
|
@@ -1027,8 +1033,12 @@ var ChatClient = class {
|
|
|
1027
1033
|
* - A MultimodalContent object with content array and optional custom ID
|
|
1028
1034
|
* @param body - Optional body parameters to merge with the client's base body for this request.
|
|
1029
1035
|
* Uses shallow merge with per-message body taking priority.
|
|
1030
|
-
* @param sendOptions - Per-call overrides
|
|
1031
|
-
*
|
|
1036
|
+
* @param sendOptions - Per-call overrides. `{ whenBusy }` overrides the
|
|
1037
|
+
* queue policy for this one send. `{ body }`
|
|
1038
|
+
* shallow-merges with `body` and with the chat-level
|
|
1039
|
+
* `body` / `forwardedProps`. `sendOptions.body` wins
|
|
1040
|
+
* on key collisions. Framework hooks forward this
|
|
1041
|
+
* object as their second argument.
|
|
1032
1042
|
*
|
|
1033
1043
|
* @example
|
|
1034
1044
|
* ```ts
|
|
@@ -1038,9 +1048,13 @@ var ChatClient = class {
|
|
|
1038
1048
|
* // Text message with custom body params
|
|
1039
1049
|
* await client.sendMessage('Hello!', { temperature: 0.7 })
|
|
1040
1050
|
*
|
|
1041
|
-
* // Per-call whenBusy override
|
|
1051
|
+
* // Per-call whenBusy override
|
|
1042
1052
|
* await client.sendMessage('Urgent', undefined, { whenBusy: 'interrupt' })
|
|
1043
1053
|
*
|
|
1054
|
+
* // Per-call body via options. Same effect as the positional arg.
|
|
1055
|
+
* // This is the shape the framework hooks (`useChat`, `injectChat`) forward.
|
|
1056
|
+
* await client.sendMessage('Hello!', undefined, { body: { temperature: 0.7 } })
|
|
1057
|
+
*
|
|
1044
1058
|
* // Multimodal message with image
|
|
1045
1059
|
* await client.sendMessage({
|
|
1046
1060
|
* content: [
|
|
@@ -1066,11 +1080,15 @@ var ChatClient = class {
|
|
|
1066
1080
|
this.mountDevtools();
|
|
1067
1081
|
if (typeof content === "string" && !content.trim()) return;
|
|
1068
1082
|
if (this.hasBlockingInterrupts()) throw new Error("ChatClient: cannot send normal input while pending interrupts exist. Use resumeInterrupts() instead.");
|
|
1083
|
+
const resolvedBody = {
|
|
1084
|
+
...body,
|
|
1085
|
+
...sendOptions?.body
|
|
1086
|
+
};
|
|
1069
1087
|
if (this.isSendBusy()) {
|
|
1070
1088
|
const { action, id } = this.decideWhenBusy(content, sendOptions);
|
|
1071
1089
|
if (action === "drop") return;
|
|
1072
1090
|
if (action === "queue") {
|
|
1073
|
-
this.enqueueMessage(content,
|
|
1091
|
+
this.enqueueMessage(content, resolvedBody, id);
|
|
1074
1092
|
return;
|
|
1075
1093
|
}
|
|
1076
1094
|
this.stopMessageQueueDrain = true;
|
|
@@ -1079,7 +1097,7 @@ var ChatClient = class {
|
|
|
1079
1097
|
this.resetSessionGenerating();
|
|
1080
1098
|
} else this.sendInFlight = true;
|
|
1081
1099
|
try {
|
|
1082
|
-
await this.deliverMessage(content,
|
|
1100
|
+
await this.deliverMessage(content, resolvedBody);
|
|
1083
1101
|
} finally {
|
|
1084
1102
|
this.sendInFlight = false;
|
|
1085
1103
|
}
|
|
@@ -1247,6 +1265,11 @@ var ChatClient = class {
|
|
|
1247
1265
|
this.processor.prepareAssistantMessage();
|
|
1248
1266
|
this.ensureSubscription();
|
|
1249
1267
|
const processingComplete = this.waitForProcessing();
|
|
1268
|
+
let byokHeaders;
|
|
1269
|
+
if (this.byok) {
|
|
1270
|
+
const provider = resolveByokProviderId(this.byokProvider, mergedBody.provider);
|
|
1271
|
+
byokHeaders = await prepareResolvedByokHeaders(this.byok, provider);
|
|
1272
|
+
}
|
|
1250
1273
|
const runContext = {
|
|
1251
1274
|
threadId: resumeThreadId ?? this.threadId,
|
|
1252
1275
|
runId,
|
|
@@ -1257,7 +1280,8 @@ var ChatClient = class {
|
|
|
1257
1280
|
parameters: t.inputSchema ? convertSchemaToJsonSchema(t.inputSchema) : { type: "object" }
|
|
1258
1281
|
})),
|
|
1259
1282
|
forwardedProps: { ...mergedBody },
|
|
1260
|
-
...resumeItems ? { resume: resumeItems } : {}
|
|
1283
|
+
...resumeItems ? { resume: resumeItems } : {},
|
|
1284
|
+
...byokHeaders ? { headers: byokHeaders } : {}
|
|
1261
1285
|
};
|
|
1262
1286
|
this.devtoolsBridge.beginRun(runContext.runId, runContext.threadId);
|
|
1263
1287
|
activeDevtoolsRunId = runContext.runId;
|
|
@@ -1279,22 +1303,24 @@ var ChatClient = class {
|
|
|
1279
1303
|
this.processor.finalizeStream();
|
|
1280
1304
|
streamCompletedSuccessfully = true;
|
|
1281
1305
|
} catch (err) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
return false;
|
|
1306
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1307
|
+
if (error.name === "AbortError") {
|
|
1308
|
+
if (activeDevtoolsRunId) {
|
|
1309
|
+
this.devtoolsBridge.emitRunLifecycle("run:cancelled", activeDevtoolsRunId, "cancelled");
|
|
1310
|
+
runTerminalEventEmitted = true;
|
|
1289
1311
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
if (error instanceof ByokMissingError) this.byok?.request(error.provider, "missing");
|
|
1315
|
+
if (error instanceof ByokBlockedError && error.reason === "locked") this.byok?.request(error.provider, "locked");
|
|
1316
|
+
if (generation === this.streamGeneration) {
|
|
1317
|
+
this.reportStreamError(error);
|
|
1318
|
+
if (activeDevtoolsRunId) {
|
|
1319
|
+
this.devtoolsBridge.emitRunLifecycle("run:errored", activeDevtoolsRunId, "errored", { error: error.message });
|
|
1320
|
+
runTerminalEventEmitted = true;
|
|
1296
1321
|
}
|
|
1297
1322
|
}
|
|
1323
|
+
if (generation === this.streamGeneration && (error instanceof ByokMissingError || error instanceof ByokBlockedError || error instanceof ByokUnresolvedProviderError)) throw error;
|
|
1298
1324
|
} finally {
|
|
1299
1325
|
if (generation === this.streamGeneration) {
|
|
1300
1326
|
this.currentStreamId = null;
|
|
@@ -1312,18 +1338,8 @@ var ChatClient = class {
|
|
|
1312
1338
|
}
|
|
1313
1339
|
await this.drainPostStreamActions();
|
|
1314
1340
|
if (streamCompletedSuccessfully) {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
if (lastPart?.type === "tool-result" && finishReason !== "stop" && this.shouldAutoSend()) try {
|
|
1318
|
-
await this.checkForContinuation();
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
console.error("Failed to continue flow after tool result:", error);
|
|
1321
|
-
await this.drainQueue();
|
|
1322
|
-
}
|
|
1323
|
-
else {
|
|
1324
|
-
if (this.status !== "ready") this.setStatus("ready");
|
|
1325
|
-
if (!this.messageQueueDraining) await this.drainQueue();
|
|
1326
|
-
}
|
|
1341
|
+
if (this.status !== "ready") this.setStatus("ready");
|
|
1342
|
+
if (!this.messageQueueDraining) await this.drainQueue();
|
|
1327
1343
|
} else this.flushQueue();
|
|
1328
1344
|
}
|
|
1329
1345
|
}
|
|
@@ -1680,6 +1696,8 @@ var ChatClient = class {
|
|
|
1680
1696
|
}
|
|
1681
1697
|
if (options.body !== void 0) this.bodyOption = options.body;
|
|
1682
1698
|
if (options.forwardedProps !== void 0) this.forwardedPropsOption = options.forwardedProps;
|
|
1699
|
+
if (options.byok !== void 0) this.byok = options.byok;
|
|
1700
|
+
if (options.byokProvider !== void 0) this.byokProvider = options.byokProvider;
|
|
1683
1701
|
if ("context" in options) this.context = options.context;
|
|
1684
1702
|
if (options.tools !== void 0) {
|
|
1685
1703
|
this.interruptManager.updateTools(options.tools);
|