@tanstack/ai-client 0.13.0 → 0.14.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/chat-client.d.ts +1 -1
- package/dist/esm/chat-client.js +1 -1
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +21 -1
- package/dist/esm/connection-adapters.js +262 -68
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +1 -1
- package/dist/esm/devtools-noop.js.map +1 -1
- package/dist/esm/devtools.d.ts +1 -1
- package/dist/esm/devtools.js +1 -1
- package/dist/esm/devtools.js.map +1 -1
- package/dist/esm/events.d.ts +1 -1
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +1 -1
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +5 -4
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/realtime-client.d.ts +1 -1
- package/dist/esm/realtime-client.js +1 -1
- package/dist/esm/realtime-client.js.map +1 -1
- package/dist/esm/realtime-types.d.ts +1 -1
- package/dist/esm/response-stream.d.ts +7 -0
- package/dist/esm/response-stream.js +32 -0
- package/dist/esm/response-stream.js.map +1 -0
- package/dist/esm/sse-parser.d.ts +1 -1
- package/dist/esm/sse-parser.js +5 -6
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/sse-utils.d.ts +1 -0
- package/dist/esm/sse-utils.js +11 -0
- package/dist/esm/sse-utils.js.map +1 -0
- package/dist/esm/tool-types.d.ts +1 -1
- package/dist/esm/types.d.ts +2 -2
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +3 -3
- package/src/chat-client.ts +2 -2
- package/src/connection-adapters.ts +360 -68
- package/src/devtools-noop.ts +1 -1
- package/src/devtools.ts +2 -2
- package/src/events.ts +1 -1
- package/src/generation-client.ts +1 -1
- package/src/generation-types.ts +1 -1
- package/src/index.ts +7 -3
- package/src/realtime-client.ts +2 -2
- package/src/realtime-types.ts +1 -1
- package/src/response-stream.ts +42 -0
- package/src/sse-parser.ts +9 -7
- package/src/sse-utils.ts +8 -0
- package/src/tool-types.ts +1 -1
- package/src/types.ts +2 -2
- package/src/video-generation-client.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-generation-client.js","sources":["../../src/video-generation-client.ts"],"sourcesContent":["import { GENERATION_EVENTS } from './generation-types'\nimport { createNoOpVideoDevtoolsBridge } from './devtools-noop'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n RunAgentInputContext,\n} from './connection-adapters'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsGenerationProgress,\n VideoDevtoolsBridge,\n VideoDevtoolsBridgeOptions,\n} from './devtools'\nimport type {\n GenerationClientState,\n GenerationFetcher,\n VideoGenerateInput,\n VideoGenerateResult,\n VideoGenerationClientOptions,\n VideoStatusInfo,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface VideoCallbacks<TOutput> {\n onResult?:\n | ((result: VideoGenerateResult) => TOutput | null | void)\n | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onJobCreated?: ((jobId: string) => void) | undefined\n onStatusUpdate?: ((status: VideoStatusInfo) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n onJobIdChange?: ((jobId: string | null) => void) | undefined\n onVideoStatusChange?: ((status: VideoStatusInfo | null) => void) | undefined\n}\n\n/**\n * A specialized client for job-based video generation.\n *\n * Video generation is asynchronous: a job is created, then polled for status\n * until completion. This client handles the full lifecycle.\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Server handles the polling loop internally and\n * streams status updates via CUSTOM events.\n * - **Fetcher** — Direct async function that returns a completed\n * `VideoGenerateResult`.\n *\n * @example\n * ```typescript\n * // With streaming connection adapter (server-driven polling)\n * const client = new VideoGenerationClient({\n * connection: fetchServerSentEvents('/api/generate/video'),\n * onResultChange: setResult,\n * onVideoStatusChange: setVideoStatus,\n * })\n *\n * // With fetcher (direct result)\n * const client = new VideoGenerationClient({\n * fetcher: async (input) => {\n * const res = await fetch('/api/video/generate', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json() // { jobId, status: 'completed', url, expiresAt }\n * },\n * })\n *\n * await client.generate({ prompt: 'A flying car over a city' })\n * ```\n */\nexport class VideoGenerationClient<TOutput = VideoGenerateResult> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher:\n | GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n | undefined\n private readonly uniqueId: string\n private readonly devtoolsMetadata: AIDevtoolsClientMetadata\n private readonly devtoolsBridge: VideoDevtoolsBridge<TOutput>\n private readonly threadId: string\n private body: Record<string, any>\n\n private result: TOutput | null = null\n private input: VideoGenerateInput | null = null\n private progress: AIDevtoolsGenerationProgress | null = null\n private jobId: string | null = null\n private videoStatus: VideoStatusInfo | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private abortController: AbortController | null = null\n private readonly callbacksRef: VideoCallbacks<TOutput>\n private devtoolsMounted = false\n\n constructor(\n options: VideoGenerationClientOptions<TOutput> &\n (\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | {\n fetcher: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n connection?: never\n }\n ),\n ) {\n this.uniqueId = options.id ?? this.generateUniqueId('video')\n this.threadId = this.uniqueId\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.body = options.body ?? {}\n\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onJobCreated: options.onJobCreated,\n onStatusUpdate: options.onStatusUpdate,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n onJobIdChange: options.onJobIdChange,\n onVideoStatusChange: options.onVideoStatusChange,\n }\n\n this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)\n this.devtoolsBridge = (\n options.devtoolsBridgeFactory ?? createNoOpVideoDevtoolsBridge\n )<TOutput>(this.buildDevtoolsBridgeOptions())\n }\n\n private buildDevtoolsBridgeOptions(): VideoDevtoolsBridgeOptions<TOutput> {\n return {\n hookId: this.uniqueId,\n clientId: this.uniqueId,\n threadId: this.threadId,\n metadata: this.devtoolsMetadata,\n getCoreState: () => ({\n input: this.input,\n result: this.result,\n progress: this.progress,\n status: this.status,\n isLoading: this.isLoading,\n jobId: this.jobId,\n videoStatus: this.videoStatus,\n ...(this.error ? { error: this.error.message } : {}),\n }),\n }\n }\n\n mountDevtools(): void {\n if (this.devtoolsMounted) {\n return\n }\n\n this.devtoolsMounted = true\n this.devtoolsBridge.emitRegistered()\n this.devtoolsBridge.emitSnapshot()\n }\n\n /**\n * Trigger video generation.\n * Only one generation can be in-flight at a time.\n */\n async generate(input: VideoGenerateInput): Promise<void> {\n this.mountDevtools()\n if (this.isLoading) return\n\n this.input = input\n this.progress = null\n const runId = this.devtoolsBridge.beginRun(input)\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n this.setJobId(null)\n this.setVideoStatus(null)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n if (this.fetcher) {\n await this.generateWithFetcher(input, signal, runId)\n } else if (this.connection) {\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect(\n [],\n mergedData,\n signal,\n this.createRunContext(runId),\n )\n await this.processStream(stream, runId)\n } else {\n throw new Error(\n 'VideoGenerationClient requires either a connection or fetcher option',\n )\n }\n if (!signal.aborted && this.status === 'success') {\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:completed',\n 'completed',\n )\n }\n } catch (err: unknown) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n this.setError(error)\n this.setStatus('error')\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:errored',\n 'errored',\n error.message,\n )\n this.callbacksRef.onError?.(error)\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n\n /**\n * Direct fetcher mode: call fetcher and set result.\n */\n private async generateWithFetcher(\n input: VideoGenerateInput,\n signal: AbortSignal,\n runId: string,\n ): Promise<void> {\n if (!this.fetcher) return\n\n // Fetcher returns a completed result directly, or a Response with SSE body\n const result = await this.fetcher(input, { signal })\n if (signal.aborted) return\n\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(parseSSEResponse(result, signal), runId)\n } else {\n this.devtoolsBridge.ensureRunStarted(runId)\n this.setResult(result)\n this.setStatus('success')\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n * The server handles the polling loop and streams status updates.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n fallbackRunId: string,\n ): Promise<void> {\n let streamRunId: string | undefined\n\n for await (const chunk of source) {\n if (this.abortController?.signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\n const chunkRunId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to video generation lifecycle.\n switch (chunk.type) {\n case 'RUN_STARTED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n break\n }\n case 'CUSTOM': {\n this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)\n if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {\n const { jobId } = chunk.value as { jobId: string }\n this.setJobId(jobId)\n this.callbacksRef.onJobCreated?.(jobId)\n } else if (chunk.name === GENERATION_EVENTS.VIDEO_STATUS) {\n const statusInfo = chunk.value as VideoStatusInfo\n this.setVideoStatus(statusInfo)\n this.callbacksRef.onStatusUpdate?.(statusInfo)\n if (statusInfo.progress !== undefined) {\n this.setProgress(statusInfo.progress)\n }\n } else if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as VideoGenerateResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.setProgress(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n this.devtoolsBridge.ensureRunStarted(\n chunkRunId ?? streamRunId ?? fallbackRunId,\n )\n // Prefer spec `message`; fall back to deprecated `error.message`\n const msg =\n (chunk.message as string | undefined) ||\n chunk.error?.message ||\n 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n }\n\n /**\n * Abort any in-flight generation or polling.\n */\n stop(): void {\n const runId = this.devtoolsBridge.getActiveRunId()\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n if (runId) {\n this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')\n }\n }\n }\n\n /**\n * Clear all state and return to idle.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.input = null\n this.progress = null\n this.devtoolsBridge.resetRuns()\n this.setJobId(null)\n this.setVideoStatus(null)\n this.setError(undefined)\n this.setStatus('idle')\n this.devtoolsBridge.emitState()\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n VideoGenerationClientOptions<TOutput>,\n | 'body'\n | 'onResult'\n | 'onError'\n | 'onProgress'\n | 'onChunk'\n | 'onJobCreated'\n | 'onStatusUpdate'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n if (options.onJobCreated !== undefined) {\n this.callbacksRef.onJobCreated = options.onJobCreated\n }\n if (options.onStatusUpdate !== undefined) {\n this.callbacksRef.onStatusUpdate = options.onStatusUpdate\n }\n }\n\n dispose(): void {\n this.stop()\n this.devtoolsBridge.dispose()\n this.devtoolsMounted = false\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getJobId(): string | null {\n return this.jobId\n }\n\n getVideoStatus(): VideoStatusInfo | null {\n return this.videoStatus\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: VideoGenerateResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n this.devtoolsBridge.recordResultChange()\n return\n }\n\n const completedStatus = this.createCompletedVideoStatus(rawResult)\n if (this.progress?.value !== 100) {\n this.setProgress(100, this.progress?.message)\n }\n this.setJobId(rawResult.jobId)\n this.setVideoStatus(completedStatus)\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged, just re-emit\n this.devtoolsBridge.emitState()\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value as\n // TOutput. When the caller did not supply an onResult transform,\n // `TOutput` defaults to `VideoGenerateResult`, so the runtime cast is\n // sound.\n // eslint-disable-next-line no-restricted-syntax -- TOutput defaults to VideoGenerateResult when no onResult transform is supplied\n this.result = rawResult as unknown as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n }\n\n private setJobId(jobId: string | null): void {\n this.jobId = jobId\n this.callbacksRef.onJobIdChange?.(jobId)\n this.devtoolsBridge.recordJobIdChange()\n }\n\n private setVideoStatus(status: VideoStatusInfo | null): void {\n this.videoStatus = status\n this.callbacksRef.onVideoStatusChange?.(status)\n this.devtoolsBridge.recordVideoStatusChange()\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n this.devtoolsBridge.recordLoadingChange()\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n this.devtoolsBridge.recordErrorChange(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n this.devtoolsBridge.recordStatusChange(status)\n }\n\n private setProgress(value: number, message?: string): void {\n this.progress = {\n value,\n ...(message ? { message } : {}),\n }\n if (message === undefined) {\n this.callbacksRef.onProgress?.(value)\n } else {\n this.callbacksRef.onProgress?.(value, message)\n }\n this.devtoolsBridge.recordProgressChange()\n }\n\n private createCompletedVideoStatus(\n result: VideoGenerateResult,\n ): VideoStatusInfo {\n return {\n jobId: result.jobId,\n status: result.status,\n progress: 100,\n url: result.url,\n }\n }\n\n private createDevtoolsMetadata(\n metadata?: Partial<AIDevtoolsClientMetadata>,\n ): AIDevtoolsClientMetadata {\n return {\n hookName: metadata?.hookName ?? 'useGenerateVideo',\n outputKind: metadata?.outputKind ?? 'video',\n ...(metadata?.framework ? { framework: metadata.framework } : {}),\n ...(metadata?.name ? { name: metadata.name } : {}),\n }\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private createRunContext(runId: string): RunAgentInputContext {\n return {\n threadId: this.threadId,\n runId,\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"video-generation-client.js","sources":["../../src/video-generation-client.ts"],"sourcesContent":["import { GENERATION_EVENTS } from './generation-types'\nimport { createNoOpVideoDevtoolsBridge } from './devtools-noop'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai/client'\nimport type {\n ConnectConnectionAdapter,\n RunAgentInputContext,\n} from './connection-adapters'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsGenerationProgress,\n VideoDevtoolsBridge,\n VideoDevtoolsBridgeOptions,\n} from './devtools'\nimport type {\n GenerationClientState,\n GenerationFetcher,\n VideoGenerateInput,\n VideoGenerateResult,\n VideoGenerationClientOptions,\n VideoStatusInfo,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface VideoCallbacks<TOutput> {\n onResult?:\n | ((result: VideoGenerateResult) => TOutput | null | void)\n | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onJobCreated?: ((jobId: string) => void) | undefined\n onStatusUpdate?: ((status: VideoStatusInfo) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n onJobIdChange?: ((jobId: string | null) => void) | undefined\n onVideoStatusChange?: ((status: VideoStatusInfo | null) => void) | undefined\n}\n\n/**\n * A specialized client for job-based video generation.\n *\n * Video generation is asynchronous: a job is created, then polled for status\n * until completion. This client handles the full lifecycle.\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Server handles the polling loop internally and\n * streams status updates via CUSTOM events.\n * - **Fetcher** — Direct async function that returns a completed\n * `VideoGenerateResult`.\n *\n * @example\n * ```typescript\n * // With streaming connection adapter (server-driven polling)\n * const client = new VideoGenerationClient({\n * connection: fetchServerSentEvents('/api/generate/video'),\n * onResultChange: setResult,\n * onVideoStatusChange: setVideoStatus,\n * })\n *\n * // With fetcher (direct result)\n * const client = new VideoGenerationClient({\n * fetcher: async (input) => {\n * const res = await fetch('/api/video/generate', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json() // { jobId, status: 'completed', url, expiresAt }\n * },\n * })\n *\n * await client.generate({ prompt: 'A flying car over a city' })\n * ```\n */\nexport class VideoGenerationClient<TOutput = VideoGenerateResult> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher:\n | GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n | undefined\n private readonly uniqueId: string\n private readonly devtoolsMetadata: AIDevtoolsClientMetadata\n private readonly devtoolsBridge: VideoDevtoolsBridge<TOutput>\n private readonly threadId: string\n private body: Record<string, any>\n\n private result: TOutput | null = null\n private input: VideoGenerateInput | null = null\n private progress: AIDevtoolsGenerationProgress | null = null\n private jobId: string | null = null\n private videoStatus: VideoStatusInfo | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private abortController: AbortController | null = null\n private readonly callbacksRef: VideoCallbacks<TOutput>\n private devtoolsMounted = false\n\n constructor(\n options: VideoGenerationClientOptions<TOutput> &\n (\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | {\n fetcher: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n connection?: never\n }\n ),\n ) {\n this.uniqueId = options.id ?? this.generateUniqueId('video')\n this.threadId = this.uniqueId\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.body = options.body ?? {}\n\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onJobCreated: options.onJobCreated,\n onStatusUpdate: options.onStatusUpdate,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n onJobIdChange: options.onJobIdChange,\n onVideoStatusChange: options.onVideoStatusChange,\n }\n\n this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)\n this.devtoolsBridge = (\n options.devtoolsBridgeFactory ?? createNoOpVideoDevtoolsBridge\n )<TOutput>(this.buildDevtoolsBridgeOptions())\n }\n\n private buildDevtoolsBridgeOptions(): VideoDevtoolsBridgeOptions<TOutput> {\n return {\n hookId: this.uniqueId,\n clientId: this.uniqueId,\n threadId: this.threadId,\n metadata: this.devtoolsMetadata,\n getCoreState: () => ({\n input: this.input,\n result: this.result,\n progress: this.progress,\n status: this.status,\n isLoading: this.isLoading,\n jobId: this.jobId,\n videoStatus: this.videoStatus,\n ...(this.error ? { error: this.error.message } : {}),\n }),\n }\n }\n\n mountDevtools(): void {\n if (this.devtoolsMounted) {\n return\n }\n\n this.devtoolsMounted = true\n this.devtoolsBridge.emitRegistered()\n this.devtoolsBridge.emitSnapshot()\n }\n\n /**\n * Trigger video generation.\n * Only one generation can be in-flight at a time.\n */\n async generate(input: VideoGenerateInput): Promise<void> {\n this.mountDevtools()\n if (this.isLoading) return\n\n this.input = input\n this.progress = null\n const runId = this.devtoolsBridge.beginRun(input)\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n this.setJobId(null)\n this.setVideoStatus(null)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n if (this.fetcher) {\n await this.generateWithFetcher(input, signal, runId)\n } else if (this.connection) {\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect(\n [],\n mergedData,\n signal,\n this.createRunContext(runId),\n )\n await this.processStream(stream, runId)\n } else {\n throw new Error(\n 'VideoGenerationClient requires either a connection or fetcher option',\n )\n }\n if (!signal.aborted && this.status === 'success') {\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:completed',\n 'completed',\n )\n }\n } catch (err: unknown) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n this.setError(error)\n this.setStatus('error')\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:errored',\n 'errored',\n error.message,\n )\n this.callbacksRef.onError?.(error)\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n\n /**\n * Direct fetcher mode: call fetcher and set result.\n */\n private async generateWithFetcher(\n input: VideoGenerateInput,\n signal: AbortSignal,\n runId: string,\n ): Promise<void> {\n if (!this.fetcher) return\n\n // Fetcher returns a completed result directly, or a Response with SSE body\n const result = await this.fetcher(input, { signal })\n if (signal.aborted) return\n\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(parseSSEResponse(result, signal), runId)\n } else {\n this.devtoolsBridge.ensureRunStarted(runId)\n this.setResult(result)\n this.setStatus('success')\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n * The server handles the polling loop and streams status updates.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n fallbackRunId: string,\n ): Promise<void> {\n let streamRunId: string | undefined\n\n for await (const chunk of source) {\n if (this.abortController?.signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\n const chunkRunId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to video generation lifecycle.\n switch (chunk.type) {\n case 'RUN_STARTED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n break\n }\n case 'CUSTOM': {\n this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)\n if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {\n const { jobId } = chunk.value as { jobId: string }\n this.setJobId(jobId)\n this.callbacksRef.onJobCreated?.(jobId)\n } else if (chunk.name === GENERATION_EVENTS.VIDEO_STATUS) {\n const statusInfo = chunk.value as VideoStatusInfo\n this.setVideoStatus(statusInfo)\n this.callbacksRef.onStatusUpdate?.(statusInfo)\n if (statusInfo.progress !== undefined) {\n this.setProgress(statusInfo.progress)\n }\n } else if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as VideoGenerateResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.setProgress(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n this.devtoolsBridge.ensureRunStarted(\n chunkRunId ?? streamRunId ?? fallbackRunId,\n )\n // Prefer spec `message`; fall back to deprecated `error.message`\n const msg =\n (chunk.message as string | undefined) ||\n chunk.error?.message ||\n 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n }\n\n /**\n * Abort any in-flight generation or polling.\n */\n stop(): void {\n const runId = this.devtoolsBridge.getActiveRunId()\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n if (runId) {\n this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')\n }\n }\n }\n\n /**\n * Clear all state and return to idle.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.input = null\n this.progress = null\n this.devtoolsBridge.resetRuns()\n this.setJobId(null)\n this.setVideoStatus(null)\n this.setError(undefined)\n this.setStatus('idle')\n this.devtoolsBridge.emitState()\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n VideoGenerationClientOptions<TOutput>,\n | 'body'\n | 'onResult'\n | 'onError'\n | 'onProgress'\n | 'onChunk'\n | 'onJobCreated'\n | 'onStatusUpdate'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n if (options.onJobCreated !== undefined) {\n this.callbacksRef.onJobCreated = options.onJobCreated\n }\n if (options.onStatusUpdate !== undefined) {\n this.callbacksRef.onStatusUpdate = options.onStatusUpdate\n }\n }\n\n dispose(): void {\n this.stop()\n this.devtoolsBridge.dispose()\n this.devtoolsMounted = false\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getJobId(): string | null {\n return this.jobId\n }\n\n getVideoStatus(): VideoStatusInfo | null {\n return this.videoStatus\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: VideoGenerateResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n this.devtoolsBridge.recordResultChange()\n return\n }\n\n const completedStatus = this.createCompletedVideoStatus(rawResult)\n if (this.progress?.value !== 100) {\n this.setProgress(100, this.progress?.message)\n }\n this.setJobId(rawResult.jobId)\n this.setVideoStatus(completedStatus)\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged, just re-emit\n this.devtoolsBridge.emitState()\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value as\n // TOutput. When the caller did not supply an onResult transform,\n // `TOutput` defaults to `VideoGenerateResult`, so the runtime cast is\n // sound.\n // eslint-disable-next-line no-restricted-syntax -- TOutput defaults to VideoGenerateResult when no onResult transform is supplied\n this.result = rawResult as unknown as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n }\n\n private setJobId(jobId: string | null): void {\n this.jobId = jobId\n this.callbacksRef.onJobIdChange?.(jobId)\n this.devtoolsBridge.recordJobIdChange()\n }\n\n private setVideoStatus(status: VideoStatusInfo | null): void {\n this.videoStatus = status\n this.callbacksRef.onVideoStatusChange?.(status)\n this.devtoolsBridge.recordVideoStatusChange()\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n this.devtoolsBridge.recordLoadingChange()\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n this.devtoolsBridge.recordErrorChange(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n this.devtoolsBridge.recordStatusChange(status)\n }\n\n private setProgress(value: number, message?: string): void {\n this.progress = {\n value,\n ...(message ? { message } : {}),\n }\n if (message === undefined) {\n this.callbacksRef.onProgress?.(value)\n } else {\n this.callbacksRef.onProgress?.(value, message)\n }\n this.devtoolsBridge.recordProgressChange()\n }\n\n private createCompletedVideoStatus(\n result: VideoGenerateResult,\n ): VideoStatusInfo {\n return {\n jobId: result.jobId,\n status: result.status,\n progress: 100,\n url: result.url,\n }\n }\n\n private createDevtoolsMetadata(\n metadata?: Partial<AIDevtoolsClientMetadata>,\n ): AIDevtoolsClientMetadata {\n return {\n hookName: metadata?.hookName ?? 'useGenerateVideo',\n outputKind: metadata?.outputKind ?? 'video',\n ...(metadata?.framework ? { framework: metadata.framework } : {}),\n ...(metadata?.name ? { name: metadata.name } : {}),\n }\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private createRunContext(runId: string): RunAgentInputContext {\n return {\n threadId: this.threadId,\n runId,\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Framework-agnostic headless client for TanStack AI chat, realtime sessions, streaming transports, and media generations.",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"src"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@tanstack/ai": "0.
|
|
45
|
-
"@tanstack/ai-event-client": "0.4.
|
|
44
|
+
"@tanstack/ai": "0.23.0",
|
|
45
|
+
"@tanstack/ai-event-client": "0.4.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@standard-schema/spec": "^1.1.0",
|
package/src/chat-client.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
convertSchemaToJsonSchema,
|
|
4
4
|
generateMessageId,
|
|
5
5
|
normalizeToUIMessage,
|
|
6
|
-
} from '@tanstack/ai'
|
|
6
|
+
} from '@tanstack/ai/client'
|
|
7
7
|
import { createNoOpChatDevtoolsBridge } from './devtools-noop'
|
|
8
8
|
import {
|
|
9
9
|
fetcherToConnectionAdapter,
|
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
ContentPart,
|
|
15
15
|
ModelMessage,
|
|
16
16
|
StreamChunk,
|
|
17
|
-
} from '@tanstack/ai'
|
|
17
|
+
} from '@tanstack/ai/client'
|
|
18
18
|
import type {
|
|
19
19
|
ConnectionAdapter,
|
|
20
20
|
SubscribeConnectionAdapter,
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { EventType, uiMessagesToWire } from '@tanstack/ai'
|
|
1
|
+
import { EventType, uiMessagesToWire } from '@tanstack/ai/client'
|
|
2
|
+
import {
|
|
3
|
+
createResponseStreamTextDecoder,
|
|
4
|
+
getResponseStreamReader,
|
|
5
|
+
} from './response-stream'
|
|
6
|
+
import { parseSseDataLine } from './sse-utils'
|
|
2
7
|
import type {
|
|
3
8
|
ModelMessage,
|
|
4
9
|
RunErrorEvent,
|
|
5
10
|
RunFinishedEvent,
|
|
6
11
|
StreamChunk,
|
|
7
12
|
UIMessage,
|
|
8
|
-
} from '@tanstack/ai'
|
|
13
|
+
} from '@tanstack/ai/client'
|
|
9
14
|
import type { ChatFetcher } from './types'
|
|
10
15
|
|
|
11
16
|
/**
|
|
@@ -71,7 +76,7 @@ async function* readStreamLines(
|
|
|
71
76
|
abortSignal?: AbortSignal,
|
|
72
77
|
): AsyncGenerator<string> {
|
|
73
78
|
try {
|
|
74
|
-
const decoder =
|
|
79
|
+
const decoder = createResponseStreamTextDecoder()
|
|
75
80
|
let buffer = ''
|
|
76
81
|
|
|
77
82
|
while (!abortSignal?.aborted) {
|
|
@@ -125,10 +130,7 @@ async function* responseToSSEChunks(
|
|
|
125
130
|
`HTTP error! status: ${response.status} ${response.statusText}`,
|
|
126
131
|
)
|
|
127
132
|
}
|
|
128
|
-
const reader = response
|
|
129
|
-
if (!reader) {
|
|
130
|
-
throw new Error('Response body is not readable')
|
|
131
|
-
}
|
|
133
|
+
const reader = getResponseStreamReader(response)
|
|
132
134
|
let lastThreadId: string | undefined
|
|
133
135
|
let lastRunId: string | undefined
|
|
134
136
|
let lastModel: string | undefined
|
|
@@ -141,7 +143,7 @@ async function* responseToSSEChunks(
|
|
|
141
143
|
) {
|
|
142
144
|
continue
|
|
143
145
|
}
|
|
144
|
-
const data =
|
|
146
|
+
const data = parseSseDataLine(line)
|
|
145
147
|
if (data === '[DONE]') {
|
|
146
148
|
const synthetic: RunFinishedEvent = {
|
|
147
149
|
type: EventType.RUN_FINISHED,
|
|
@@ -381,6 +383,53 @@ export interface FetchConnectionOptions {
|
|
|
381
383
|
fetchClient?: typeof globalThis.fetch
|
|
382
384
|
}
|
|
383
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Options for XHR-based connection adapters.
|
|
388
|
+
*/
|
|
389
|
+
export interface XhrConnectionOptions {
|
|
390
|
+
headers?: Record<string, string> | Headers
|
|
391
|
+
withCredentials?: boolean
|
|
392
|
+
signal?: AbortSignal
|
|
393
|
+
body?: Record<string, any>
|
|
394
|
+
xhrFactory?: () => XMLHttpRequest
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
type ResolvedConnectionOptions = Pick<
|
|
398
|
+
FetchConnectionOptions,
|
|
399
|
+
'body' | 'headers'
|
|
400
|
+
>
|
|
401
|
+
|
|
402
|
+
function buildRunAgentInputBody(
|
|
403
|
+
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
404
|
+
data: Record<string, any> | undefined,
|
|
405
|
+
runContext: RunAgentInputContext | undefined,
|
|
406
|
+
options: ResolvedConnectionOptions,
|
|
407
|
+
): Record<string, unknown> {
|
|
408
|
+
// Precedence (later spreads win): static adapter `body` is the base,
|
|
409
|
+
// overridden by `runContext.forwardedProps`, overridden by per-message `data`.
|
|
410
|
+
const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)
|
|
411
|
+
const forwardedProps = {
|
|
412
|
+
...options.body,
|
|
413
|
+
...(runContext?.forwardedProps ?? {}),
|
|
414
|
+
...data,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
threadId: runContext?.threadId ?? generateRunId('thread'),
|
|
419
|
+
runId: runContext?.runId ?? generateRunId('run'),
|
|
420
|
+
...(runContext?.parentRunId !== undefined && {
|
|
421
|
+
parentRunId: runContext.parentRunId,
|
|
422
|
+
}),
|
|
423
|
+
state: {},
|
|
424
|
+
messages: wireMessages,
|
|
425
|
+
tools: runContext?.clientTools ?? [],
|
|
426
|
+
context: [],
|
|
427
|
+
forwardedProps,
|
|
428
|
+
// Backward-compat mirror of `forwardedProps` under the legacy field name.
|
|
429
|
+
data: { ...forwardedProps },
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
384
433
|
/**
|
|
385
434
|
* Create a Server-Sent Events connection adapter
|
|
386
435
|
*
|
|
@@ -440,34 +489,12 @@ export function fetchServerSentEvents(
|
|
|
440
489
|
// forwardedProps options), overridden by per-message `data` passed
|
|
441
490
|
// to `connection.send`. Runtime values win over static config —
|
|
442
491
|
// this matches the documented "forwardedProps wins" semantic.
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const requestBody = {
|
|
450
|
-
threadId: runContext?.threadId ?? generateRunId('thread'),
|
|
451
|
-
runId: runContext?.runId ?? generateRunId('run'),
|
|
452
|
-
...(runContext?.parentRunId !== undefined && {
|
|
453
|
-
parentRunId: runContext.parentRunId,
|
|
454
|
-
}),
|
|
455
|
-
state: {},
|
|
456
|
-
messages: wireMessages,
|
|
457
|
-
tools: runContext?.clientTools ?? [],
|
|
458
|
-
context: [],
|
|
459
|
-
forwardedProps,
|
|
460
|
-
// Backward-compat mirror of `forwardedProps` under the legacy
|
|
461
|
-
// field name `data`. Server endpoints that have not migrated
|
|
462
|
-
// off the pre-AG-UI shape (`{ messages, data }`) keep working.
|
|
463
|
-
// AG-UI strict consumers strip this via `RunAgentInputSchema`
|
|
464
|
-
// (see `chatParamsFromRequestBody`). Will be removed when the
|
|
465
|
-
// legacy `body` client option is dropped.
|
|
466
|
-
// Shallow-cloned so that downstream mutation of `data` (e.g.
|
|
467
|
-
// by a logging interceptor or fetch wrapper) cannot corrupt
|
|
468
|
-
// `forwardedProps` and vice versa.
|
|
469
|
-
data: { ...forwardedProps },
|
|
470
|
-
}
|
|
492
|
+
const requestBody = buildRunAgentInputBody(
|
|
493
|
+
messages,
|
|
494
|
+
data,
|
|
495
|
+
runContext,
|
|
496
|
+
resolvedOptions,
|
|
497
|
+
)
|
|
471
498
|
|
|
472
499
|
const fetchClient = resolvedOptions.fetchClient ?? fetch
|
|
473
500
|
// `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`
|
|
@@ -546,34 +573,12 @@ export function fetchHttpStream(
|
|
|
546
573
|
// forwardedProps options), overridden by per-message `data` passed
|
|
547
574
|
// to `connection.send`. Runtime values win over static config —
|
|
548
575
|
// this matches the documented "forwardedProps wins" semantic.
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const requestBody = {
|
|
556
|
-
threadId: runContext?.threadId ?? generateRunId('thread'),
|
|
557
|
-
runId: runContext?.runId ?? generateRunId('run'),
|
|
558
|
-
...(runContext?.parentRunId !== undefined && {
|
|
559
|
-
parentRunId: runContext.parentRunId,
|
|
560
|
-
}),
|
|
561
|
-
state: {},
|
|
562
|
-
messages: wireMessages,
|
|
563
|
-
tools: runContext?.clientTools ?? [],
|
|
564
|
-
context: [],
|
|
565
|
-
forwardedProps,
|
|
566
|
-
// Backward-compat mirror of `forwardedProps` under the legacy
|
|
567
|
-
// field name `data`. Server endpoints that have not migrated
|
|
568
|
-
// off the pre-AG-UI shape (`{ messages, data }`) keep working.
|
|
569
|
-
// AG-UI strict consumers strip this via `RunAgentInputSchema`
|
|
570
|
-
// (see `chatParamsFromRequestBody`). Will be removed when the
|
|
571
|
-
// legacy `body` client option is dropped.
|
|
572
|
-
// Shallow-cloned so that downstream mutation of `data` (e.g.
|
|
573
|
-
// by a logging interceptor or fetch wrapper) cannot corrupt
|
|
574
|
-
// `forwardedProps` and vice versa.
|
|
575
|
-
data: { ...forwardedProps },
|
|
576
|
-
}
|
|
576
|
+
const requestBody = buildRunAgentInputBody(
|
|
577
|
+
messages,
|
|
578
|
+
data,
|
|
579
|
+
runContext,
|
|
580
|
+
resolvedOptions,
|
|
581
|
+
)
|
|
577
582
|
|
|
578
583
|
const fetchClient = resolvedOptions.fetchClient ?? fetch
|
|
579
584
|
// `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`
|
|
@@ -595,10 +600,7 @@ export function fetchHttpStream(
|
|
|
595
600
|
}
|
|
596
601
|
|
|
597
602
|
// Parse raw HTTP stream (newline-delimited JSON)
|
|
598
|
-
const reader = response
|
|
599
|
-
if (!reader) {
|
|
600
|
-
throw new Error('Response body is not readable')
|
|
601
|
-
}
|
|
603
|
+
const reader = getResponseStreamReader(response)
|
|
602
604
|
|
|
603
605
|
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
604
606
|
yield JSON.parse(line) as StreamChunk
|
|
@@ -607,6 +609,296 @@ export function fetchHttpStream(
|
|
|
607
609
|
}
|
|
608
610
|
}
|
|
609
611
|
|
|
612
|
+
type XhrConnectionOptionsResolver =
|
|
613
|
+
| XhrConnectionOptions
|
|
614
|
+
| (() => XhrConnectionOptions | Promise<XhrConnectionOptions>)
|
|
615
|
+
|
|
616
|
+
function createDefaultXMLHttpRequest(): XMLHttpRequest {
|
|
617
|
+
if (typeof globalThis.XMLHttpRequest !== 'function') {
|
|
618
|
+
throw new Error('XMLHttpRequest is not available in this runtime')
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return new globalThis.XMLHttpRequest()
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function cleanupXhr(
|
|
625
|
+
xhr: XMLHttpRequest,
|
|
626
|
+
abortSignal: AbortSignal | undefined,
|
|
627
|
+
onAbort: (() => void) | undefined,
|
|
628
|
+
): void {
|
|
629
|
+
xhr.onprogress = null
|
|
630
|
+
xhr.onload = null
|
|
631
|
+
xhr.onerror = null
|
|
632
|
+
xhr.onabort = null
|
|
633
|
+
xhr.onloadend = null
|
|
634
|
+
|
|
635
|
+
if (abortSignal && onAbort) {
|
|
636
|
+
abortSignal.removeEventListener('abort', onAbort)
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function readXhrLines(
|
|
641
|
+
xhr: XMLHttpRequest,
|
|
642
|
+
abortSignal?: AbortSignal,
|
|
643
|
+
): AsyncGenerator<string> {
|
|
644
|
+
let offset = 0
|
|
645
|
+
let buffer = ''
|
|
646
|
+
const lines: Array<string> = []
|
|
647
|
+
const waiters: Array<() => void> = []
|
|
648
|
+
let done = false
|
|
649
|
+
let aborted = false
|
|
650
|
+
let error: unknown
|
|
651
|
+
let onAbort: (() => void) | undefined
|
|
652
|
+
|
|
653
|
+
const wake = () => {
|
|
654
|
+
const waiter = waiters.shift()
|
|
655
|
+
waiter?.()
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const enqueueDelta = () => {
|
|
659
|
+
if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {
|
|
660
|
+
error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)
|
|
661
|
+
done = true
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const responseText = xhr.responseText
|
|
666
|
+
if (responseText.length <= offset) {
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
buffer += responseText.slice(offset)
|
|
671
|
+
offset = responseText.length
|
|
672
|
+
const splitLines = buffer.split('\n')
|
|
673
|
+
buffer = splitLines.pop() ?? ''
|
|
674
|
+
|
|
675
|
+
for (const line of splitLines) {
|
|
676
|
+
const normalized = line.endsWith('\r') ? line.slice(0, -1) : line
|
|
677
|
+
if (normalized.trim()) {
|
|
678
|
+
lines.push(normalized)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const finish = () => {
|
|
684
|
+
enqueueDelta()
|
|
685
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
686
|
+
error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)
|
|
687
|
+
} else if (buffer.trim() && !aborted) {
|
|
688
|
+
error = new StreamTruncatedError()
|
|
689
|
+
}
|
|
690
|
+
done = true
|
|
691
|
+
wake()
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
xhr.onprogress = () => {
|
|
695
|
+
enqueueDelta()
|
|
696
|
+
wake()
|
|
697
|
+
}
|
|
698
|
+
xhr.onload = finish
|
|
699
|
+
xhr.onerror = () => {
|
|
700
|
+
error = new Error('XHR request failed')
|
|
701
|
+
done = true
|
|
702
|
+
wake()
|
|
703
|
+
}
|
|
704
|
+
xhr.onabort = () => {
|
|
705
|
+
aborted = true
|
|
706
|
+
done = true
|
|
707
|
+
wake()
|
|
708
|
+
}
|
|
709
|
+
xhr.onloadend = () => {
|
|
710
|
+
if (!done) {
|
|
711
|
+
finish()
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (abortSignal) {
|
|
716
|
+
onAbort = () => {
|
|
717
|
+
aborted = true
|
|
718
|
+
xhr.abort()
|
|
719
|
+
}
|
|
720
|
+
if (abortSignal.aborted) {
|
|
721
|
+
onAbort()
|
|
722
|
+
} else {
|
|
723
|
+
abortSignal.addEventListener('abort', onAbort, { once: true })
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return (async function* () {
|
|
728
|
+
try {
|
|
729
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
730
|
+
while (true) {
|
|
731
|
+
const line = lines.shift()
|
|
732
|
+
if (line !== undefined) {
|
|
733
|
+
yield line
|
|
734
|
+
continue
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (error) {
|
|
738
|
+
throw error
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
742
|
+
if (done || abortSignal?.aborted) {
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
await new Promise<void>((resolve) => {
|
|
747
|
+
waiters.push(resolve)
|
|
748
|
+
})
|
|
749
|
+
}
|
|
750
|
+
} finally {
|
|
751
|
+
cleanupXhr(xhr, abortSignal, onAbort)
|
|
752
|
+
}
|
|
753
|
+
})()
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
interface ConfiguredXhrRequest {
|
|
757
|
+
xhr: XMLHttpRequest
|
|
758
|
+
body: string
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function createConfiguredXhrRequest(
|
|
762
|
+
url: string,
|
|
763
|
+
options: XhrConnectionOptions,
|
|
764
|
+
messages: Array<UIMessage> | Array<ModelMessage>,
|
|
765
|
+
data: Record<string, any> | undefined,
|
|
766
|
+
runContext: RunAgentInputContext | undefined,
|
|
767
|
+
): ConfiguredXhrRequest {
|
|
768
|
+
const xhr = options.xhrFactory?.() ?? createDefaultXMLHttpRequest()
|
|
769
|
+
xhr.open('POST', url)
|
|
770
|
+
if (options.withCredentials !== undefined) {
|
|
771
|
+
xhr.withCredentials = options.withCredentials
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const requestHeaders: Record<string, string> = {
|
|
775
|
+
'Content-Type': 'application/json',
|
|
776
|
+
...mergeHeaders(options.headers),
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
780
|
+
xhr.setRequestHeader(name, value)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const requestBody = buildRunAgentInputBody(
|
|
784
|
+
messages,
|
|
785
|
+
data,
|
|
786
|
+
runContext,
|
|
787
|
+
options,
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
return { xhr, body: JSON.stringify(requestBody) }
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async function resolveXhrConnectionOptions(
|
|
794
|
+
options: XhrConnectionOptionsResolver,
|
|
795
|
+
): Promise<XhrConnectionOptions> {
|
|
796
|
+
return typeof options === 'function' ? await options() : options
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Create an XMLHttpRequest-backed Server-Sent Events connection adapter.
|
|
801
|
+
*/
|
|
802
|
+
export function xhrServerSentEvents(
|
|
803
|
+
url: string | (() => string),
|
|
804
|
+
options: XhrConnectionOptionsResolver = {},
|
|
805
|
+
): ConnectConnectionAdapter {
|
|
806
|
+
return {
|
|
807
|
+
async *connect(messages, data, abortSignal, runContext) {
|
|
808
|
+
const resolvedUrl = typeof url === 'function' ? url() : url
|
|
809
|
+
const resolvedOptions = await resolveXhrConnectionOptions(options)
|
|
810
|
+
const signal = abortSignal || resolvedOptions.signal
|
|
811
|
+
const request = createConfiguredXhrRequest(
|
|
812
|
+
resolvedUrl,
|
|
813
|
+
resolvedOptions,
|
|
814
|
+
messages,
|
|
815
|
+
data,
|
|
816
|
+
runContext,
|
|
817
|
+
)
|
|
818
|
+
const lines = readXhrLines(request.xhr, signal)
|
|
819
|
+
if (signal?.aborted) {
|
|
820
|
+
await lines.next()
|
|
821
|
+
return
|
|
822
|
+
}
|
|
823
|
+
request.xhr.send(request.body)
|
|
824
|
+
let lastThreadId: string | undefined
|
|
825
|
+
let lastRunId: string | undefined
|
|
826
|
+
let lastModel: string | undefined
|
|
827
|
+
|
|
828
|
+
for await (const line of lines) {
|
|
829
|
+
if (
|
|
830
|
+
line.startsWith(':') ||
|
|
831
|
+
line.startsWith('event:') ||
|
|
832
|
+
line.startsWith('id:') ||
|
|
833
|
+
line.startsWith('retry:')
|
|
834
|
+
) {
|
|
835
|
+
continue
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const chunkData = parseSseDataLine(line)
|
|
839
|
+
if (chunkData === '[DONE]') {
|
|
840
|
+
const synthetic: RunFinishedEvent = {
|
|
841
|
+
type: EventType.RUN_FINISHED,
|
|
842
|
+
threadId: lastThreadId ?? runContext?.threadId ?? '',
|
|
843
|
+
runId: lastRunId ?? runContext?.runId ?? '',
|
|
844
|
+
model: lastModel ?? '',
|
|
845
|
+
timestamp: Date.now(),
|
|
846
|
+
finishReason: 'stop',
|
|
847
|
+
}
|
|
848
|
+
request.xhr.abort()
|
|
849
|
+
yield synthetic
|
|
850
|
+
return
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const chunk = JSON.parse(chunkData) as StreamChunk
|
|
854
|
+
if ('threadId' in chunk && typeof chunk.threadId === 'string') {
|
|
855
|
+
lastThreadId = chunk.threadId
|
|
856
|
+
}
|
|
857
|
+
if ('runId' in chunk && typeof chunk.runId === 'string') {
|
|
858
|
+
lastRunId = chunk.runId
|
|
859
|
+
}
|
|
860
|
+
if ('model' in chunk && typeof chunk.model === 'string') {
|
|
861
|
+
lastModel = chunk.model
|
|
862
|
+
}
|
|
863
|
+
yield chunk
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Create an XMLHttpRequest-backed newline-delimited JSON stream adapter.
|
|
871
|
+
*/
|
|
872
|
+
export function xhrHttpStream(
|
|
873
|
+
url: string | (() => string),
|
|
874
|
+
options: XhrConnectionOptionsResolver = {},
|
|
875
|
+
): ConnectConnectionAdapter {
|
|
876
|
+
return {
|
|
877
|
+
async *connect(messages, data, abortSignal, runContext) {
|
|
878
|
+
const resolvedUrl = typeof url === 'function' ? url() : url
|
|
879
|
+
const resolvedOptions = await resolveXhrConnectionOptions(options)
|
|
880
|
+
const signal = abortSignal || resolvedOptions.signal
|
|
881
|
+
const request = createConfiguredXhrRequest(
|
|
882
|
+
resolvedUrl,
|
|
883
|
+
resolvedOptions,
|
|
884
|
+
messages,
|
|
885
|
+
data,
|
|
886
|
+
runContext,
|
|
887
|
+
)
|
|
888
|
+
const lines = readXhrLines(request.xhr, signal)
|
|
889
|
+
if (signal?.aborted) {
|
|
890
|
+
await lines.next()
|
|
891
|
+
return
|
|
892
|
+
}
|
|
893
|
+
request.xhr.send(request.body)
|
|
894
|
+
|
|
895
|
+
for await (const line of lines) {
|
|
896
|
+
yield JSON.parse(line) as StreamChunk
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
610
902
|
/**
|
|
611
903
|
* Create a direct stream connection adapter (for server functions or direct streams)
|
|
612
904
|
*
|
package/src/devtools-noop.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
VideoDevtoolsBridge,
|
|
16
16
|
VideoDevtoolsBridgeOptions,
|
|
17
17
|
} from './devtools'
|
|
18
|
-
import type { StreamChunk } from '@tanstack/ai'
|
|
18
|
+
import type { StreamChunk } from '@tanstack/ai/client'
|
|
19
19
|
import type {
|
|
20
20
|
ChatClientEventContext,
|
|
21
21
|
ChatClientRunEventContext,
|
package/src/devtools.ts
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
createAIDevtoolsEventEnvelope,
|
|
4
4
|
emitAIDevtoolsEvent,
|
|
5
5
|
} from '@tanstack/ai-event-client'
|
|
6
|
-
import { convertSchemaToJsonSchema } from '@tanstack/ai'
|
|
6
|
+
import { convertSchemaToJsonSchema } from '@tanstack/ai/client'
|
|
7
7
|
import { DefaultChatClientEventEmitter } from './events'
|
|
8
|
-
import type { AnyClientTool, StreamChunk } from '@tanstack/ai'
|
|
8
|
+
import type { AnyClientTool, StreamChunk } from '@tanstack/ai/client'
|
|
9
9
|
import type { AIDevtoolsEventVisibility } from '@tanstack/ai-event-client'
|
|
10
10
|
import type {
|
|
11
11
|
ChatClientEventContext,
|