@tanstack/ai-client 0.11.8 → 0.13.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.
Files changed (39) hide show
  1. package/dist/esm/chat-client.d.ts +17 -2
  2. package/dist/esm/chat-client.js +223 -37
  3. package/dist/esm/chat-client.js.map +1 -1
  4. package/dist/esm/connection-adapters.d.ts +20 -2
  5. package/dist/esm/connection-adapters.js +154 -48
  6. package/dist/esm/connection-adapters.js.map +1 -1
  7. package/dist/esm/devtools-noop.d.ts +60 -0
  8. package/dist/esm/devtools-noop.js +135 -0
  9. package/dist/esm/devtools-noop.js.map +1 -0
  10. package/dist/esm/devtools.d.ts +276 -0
  11. package/dist/esm/devtools.js +1178 -0
  12. package/dist/esm/devtools.js.map +1 -0
  13. package/dist/esm/events.d.ts +50 -7
  14. package/dist/esm/events.js +65 -20
  15. package/dist/esm/events.js.map +1 -1
  16. package/dist/esm/generation-client.d.ts +14 -0
  17. package/dist/esm/generation-client.js +135 -5
  18. package/dist/esm/generation-client.js.map +1 -1
  19. package/dist/esm/generation-types.d.ts +15 -1
  20. package/dist/esm/generation-types.js.map +1 -1
  21. package/dist/esm/index.d.ts +3 -2
  22. package/dist/esm/index.js +4 -1
  23. package/dist/esm/index.js.map +1 -1
  24. package/dist/esm/types.d.ts +78 -10
  25. package/dist/esm/types.js.map +1 -1
  26. package/dist/esm/video-generation-client.d.ts +15 -0
  27. package/dist/esm/video-generation-client.js +147 -8
  28. package/dist/esm/video-generation-client.js.map +1 -1
  29. package/package.json +8 -4
  30. package/src/chat-client.ts +285 -39
  31. package/src/connection-adapters.ts +226 -61
  32. package/src/devtools-noop.ts +205 -0
  33. package/src/devtools.ts +1866 -0
  34. package/src/events.ts +142 -11
  35. package/src/generation-client.ts +176 -9
  36. package/src/generation-types.ts +23 -4
  37. package/src/index.ts +15 -1
  38. package/src/types.ts +98 -12
  39. package/src/video-generation-client.ts +186 -11
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n AnyClientTool,\n AudioPart,\n ChunkStrategy,\n ContentPart,\n DocumentPart,\n ImagePart,\n InferToolInput,\n InferToolOutput,\n ModelMessage,\n StreamChunk,\n StructuredOutputPart,\n VideoPart,\n} from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\n\nexport type { StructuredOutputPart } from '@tanstack/ai'\n\n/**\n * Tool call states - track the lifecycle of a tool call\n */\nexport type ToolCallState =\n | 'awaiting-input' // Received start but no arguments yet\n | 'input-streaming' // Partial arguments received\n | 'input-complete' // All arguments received\n | 'approval-requested' // Waiting for user approval\n | 'approval-responded' // User has approved/denied\n | 'complete' // Result is complete\n\n/**\n * Tool result states - track the lifecycle of a tool result\n */\nexport type ToolResultState =\n | 'streaming' // Placeholder for future streamed output\n | 'complete' // Result is complete\n | 'error' // Error occurred\n\n/**\n * ChatClient state - track the lifecycle of a chat\n */\nexport type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error'\n\n/**\n * Connection lifecycle state for the subscription loop.\n */\nexport type ConnectionStatus =\n | 'disconnected'\n | 'connecting'\n | 'connected'\n | 'error'\n\n/**\n * Multimodal content input for sending messages with rich media.\n * Allows sending text, images, audio, video, and documents to the LLM.\n *\n * @example\n * ```ts\n * // Send an image with a question\n * client.sendMessage({\n * content: [\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ],\n * id: 'custom-message-id' // optional\n * })\n * ```\n */\nexport interface MultimodalContent {\n /**\n * The content of the message.\n * Can be a simple string or an array of content parts for multimodal messages.\n */\n content: string | Array<ContentPart>\n /**\n * Optional custom ID for the message.\n * If not provided, a unique ID will be generated.\n */\n id?: string\n}\n\n/**\n * Message parts - building blocks of UIMessage\n */\nexport interface TextPart {\n type: 'text'\n content: string\n}\n\n/**\n * Helper type that creates a tool-call part for a specific tool.\n * This is a conditional type to enable proper distribution over union types,\n * creating a discriminated union where `name` is the discriminant.\n */\ntype ToolCallPartForTool<T> = T extends AnyClientTool\n ? {\n type: 'tool-call'\n id: string\n name: T['name']\n arguments: string // JSON string (may be incomplete)\n /** Parsed tool input (typed from inputSchema) */\n input?: InferToolInput<T>\n state: ToolCallState\n /** Approval metadata if tool requires user approval */\n approval?: {\n id: string // Unique approval ID\n needsApproval: boolean // Always true if present\n approved?: boolean // User's decision (undefined until responded)\n }\n /** Tool execution output (for client tools or after approval) */\n output?: InferToolOutput<T>\n }\n : never\n\n/**\n * Fallback tool-call part type when tools are not typed\n */\ntype UntypedToolCallPart = {\n type: 'tool-call'\n id: string\n name: string\n arguments: string\n input?: any\n state: ToolCallState\n approval?: {\n id: string\n needsApproval: boolean\n approved?: boolean\n }\n output?: any\n}\n\n/**\n * Tool call part that creates a proper discriminated union.\n * When TTools is typed, checking `part.name === 'toolName'` will narrow\n * `part.output` to the correct type for that tool.\n *\n * The discriminant is `name`, so code like:\n * ```ts\n * if (part.name === 'recommendGuitar') {\n * // part.output is now typed to the recommendGuitar tool's output\n * }\n * ```\n */\nexport type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n // Check if we have a concrete tools array (not 'any' or 'never')\n [TTools] extends [never]\n ? UntypedToolCallPart\n : unknown extends TTools\n ? UntypedToolCallPart\n : TTools extends ReadonlyArray<infer Tool>\n ? Tool extends AnyClientTool\n ? ToolCallPartForTool<Tool>\n : UntypedToolCallPart\n : UntypedToolCallPart\n\nexport interface ToolResultPart {\n type: 'tool-result'\n toolCallId: string\n content: string\n state: ToolResultState\n error?: string // Error message if state is \"error\"\n}\n\nexport interface ThinkingPart {\n type: 'thinking'\n content: string\n}\n\nexport type MessagePart<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> =\n | TextPart\n | ImagePart\n | AudioPart\n | VideoPart\n | DocumentPart\n | ToolCallPart<TTools>\n | ToolResultPart\n | ThinkingPart\n | StructuredOutputPart<TData>\n\n/**\n * UIMessage - Domain-specific message format optimized for building chat UIs\n * Contains parts that can be text, tool calls, or tool results.\n *\n * `TTools` narrows the tool-call/result part types based on the registered\n * tools. `TData` is the schema-inferred type for any `structured-output` part\n * on the message — defaulted to `unknown` so untyped consumers (the core\n * stream processor, the wire converter) don't need to thread a schema generic\n * everywhere; the hook layer (`useChat({ outputSchema })`) substitutes it on\n * the public return so `m.parts.find(p => p.type === 'structured-output').data`\n * is typed without manual casts.\n */\nexport interface UIMessage<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> {\n id: string\n role: 'system' | 'user' | 'assistant'\n parts: Array<MessagePart<TTools, TData>>\n createdAt?: Date\n}\n\nexport interface ChatClientOptions<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n> {\n /**\n * Connection adapter for streaming.\n * Supports mutually exclusive modes: request-response via `connect()`, or\n * subscribe/send mode via `subscribe()` + `send()`.\n */\n connection: ConnectionAdapter\n\n /**\n * Initial messages to populate the chat\n */\n initialMessages?: Array<UIMessage<TTools>>\n\n /**\n * Unique identifier for this chat instance\n * Used for managing multiple chats\n */\n id?: string\n\n /**\n * Thread ID to use for this chat session. Persists across sends within\n * the session. If omitted, a unique thread ID is generated.\n */\n threadId?: string\n\n /**\n * Arbitrary client-controlled JSON forwarded to the server in the\n * AG-UI `RunAgentInput.forwardedProps` field. Use this for per-session\n * options like provider/model selection or feature flags that the\n * server endpoint should read.\n *\n * Replaces the legacy `body` option. If both are provided,\n * `forwardedProps` wins on key collision.\n */\n forwardedProps?: Record<string, any>\n\n /**\n * @deprecated Use `forwardedProps` instead. `body` continues to work\n * unchanged — its values are merged into the AG-UI\n * `RunAgentInput.forwardedProps` field on the wire and are also\n * mirrored under the legacy `data` field for servers that have not\n * migrated yet. Will be removed in a future major release.\n */\n body?: Record<string, any>\n\n /**\n * Callback when a response is received\n */\n onResponse?: (response?: Response) => void | Promise<void>\n\n /**\n * Callback when a stream chunk is received\n */\n onChunk?: (chunk: StreamChunk) => void\n\n /**\n * Callback when the response is finished\n */\n onFinish?: (message: UIMessage<TTools>) => void\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void\n\n /**\n * Callback when messages change\n */\n onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void\n\n /**\n * Callback when loading state changes\n */\n onLoadingChange?: (isLoading: boolean) => void\n\n /**\n * Callback when error state changes\n */\n onErrorChange?: (error: Error | undefined) => void\n\n /**\n * Callback when chat status changes\n */\n onStatusChange?: (status: ChatClientState) => void\n\n /**\n * Callback when subscription lifecycle changes.\n * This is independent from request lifecycle (`isLoading`, `status`).\n */\n onSubscriptionChange?: (isSubscribed: boolean) => void\n\n /**\n * Callback when connection lifecycle changes.\n */\n onConnectionStatusChange?: (status: ConnectionStatus) => void\n\n /**\n * Callback when session generation activity changes.\n * Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).\n * Unlike `onLoadingChange` (request-local), this reflects shared generation\n * activity visible to all subscribers (e.g. across tabs/devices).\n */\n onSessionGeneratingChange?: (isGenerating: boolean) => void\n\n /**\n * Callback when a custom event is received from a server-side tool.\n * Custom events are emitted by tools using `context.emitCustomEvent()` during execution.\n *\n * @param eventType - The name of the custom event\n * @param data - The event payload data\n * @param context - Additional context including the toolCallId that emitted the event\n */\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n /**\n * Client-side tools with execution logic\n * When provided, tools with execute functions will be called automatically\n */\n tools?: TTools\n\n /**\n * Stream processing options (optional)\n * Configure chunking strategy\n */\n streamProcessor?: {\n /**\n * Strategy for when to emit text updates\n * Defaults to ImmediateStrategy (every chunk)\n */\n chunkStrategy?: ChunkStrategy\n }\n}\n\nexport interface ChatRequestBody {\n messages: Array<ModelMessage>\n data?: Record<string, any>\n}\n\n/**\n * Create a typed array of client tools with proper type inference.\n * This eliminates the need for `as const` when defining tool arrays.\n *\n * @example\n * ```ts\n * const tools = clientTools(\n * myTool1.client(() => result1),\n * myTool2.client(() => result2),\n * )\n *\n * // tools is now properly typed as a tuple with literal tool names\n * // This enables type narrowing when checking part.name === 'toolName'\n * ```\n */\nexport function clientTools<const T extends Array<AnyClientTool>>(\n ...tools: T\n): T {\n return tools\n}\n\n/**\n * Helper to create typed chat client options\n * Use this to get proper type inference for messages\n *\n * @example\n * ```ts\n * const tools = clientTools(myTool1, myTool2)\n *\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools,\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * ```\n */\nexport function createChatClientOptions<\n const TTools extends ReadonlyArray<AnyClientTool>,\n>(options: ChatClientOptions<TTools>): ChatClientOptions<TTools> {\n return options\n}\n\n/**\n * Extract the message type from chat options\n *\n * @example\n * ```ts\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools: [myTool1, myTool2],\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>\n * ```\n */\nexport type InferChatMessages<T> =\n T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never\n"],"names":[],"mappings":"AA2WO,SAAS,eACX,OACA;AACH,SAAO;AACT;AAkBO,SAAS,wBAEd,SAA+D;AAC/D,SAAO;AACT;"}
1
+ {"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n AnyClientTool,\n AudioPart,\n ChunkStrategy,\n ContentPart,\n DocumentPart,\n ImagePart,\n InferToolInput,\n InferToolOutput,\n ModelMessage,\n StreamChunk,\n StructuredOutputPart,\n VideoPart,\n} from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\nimport type { AIDevtoolsClientMetadata } from './devtools'\nimport type { ChatDevtoolsBridgeFactory } from './devtools-noop'\n\nexport type { StructuredOutputPart } from '@tanstack/ai'\n\n/**\n * `messages` is the full UIMessage history (not a delta). `data` is the\n * merged body — `ChatClientOptions.body` plus any per-call data passed to\n * `sendMessage(...)`. `threadId` / `runId` are the AG-UI correlation ids\n * the chat client uses to track this turn — forward them to your server\n * if it needs to correlate requests.\n */\nexport interface ChatFetcherInput {\n messages: Array<UIMessage>\n data?: Record<string, unknown>\n threadId: string\n runId: string\n}\n\nexport interface ChatFetcherOptions {\n /** Fires when `stop()` is called or the request is superseded. */\n signal: AbortSignal\n}\n\n/**\n * Direct function that performs a chat request. Mirrors\n * `GenerationFetcher`. Returns either a `Response` (SSE body parsed by the\n * chat client) or an `AsyncIterable<StreamChunk>` (yielded directly). May\n * return the value synchronously, as a `Promise`, or as an async generator\n * (`async function*`) — the chat client awaits whichever shape is returned.\n *\n * @example\n * ```ts\n * useChat({\n * fetcher: ({ messages }, { signal }) =>\n * chatFn({ data: { messages }, signal }),\n * })\n * ```\n */\nexport type ChatFetcher = (\n input: ChatFetcherInput,\n options: ChatFetcherOptions,\n) =>\n | Response\n | AsyncIterable<StreamChunk>\n | Promise<Response | AsyncIterable<StreamChunk>>\n\n/**\n * Distributive `Omit` — applies `Omit<O, K>` per branch of a union so\n * discriminated unions survive omission. Plain `Omit` collapses unions\n * into a single object shape, which would erase the `ChatTransport` XOR\n * when framework hooks omit React-managed callbacks from\n * `ChatClientOptions`.\n */\nexport type DistributedOmit<\n TObject,\n TKeys extends keyof any,\n> = TObject extends unknown ? Omit<TObject, TKeys> : never\n\n/**\n * Discriminated union enforcing that exactly one of `connection` or\n * `fetcher` is provided. Mirrors `GenerationTransport`.\n */\nexport type ChatTransport =\n | { connection: ConnectionAdapter; fetcher?: never }\n | { fetcher: ChatFetcher; connection?: never }\n\n/**\n * Tool call states - track the lifecycle of a tool call\n */\nexport type ToolCallState =\n | 'awaiting-input' // Received start but no arguments yet\n | 'input-streaming' // Partial arguments received\n | 'input-complete' // All arguments received\n | 'approval-requested' // Waiting for user approval\n | 'approval-responded' // User has approved/denied\n | 'complete' // Result is complete\n\n/**\n * Tool result states - track the lifecycle of a tool result\n */\nexport type ToolResultState =\n | 'streaming' // Placeholder for future streamed output\n | 'complete' // Result is complete\n | 'error' // Error occurred\n\n/**\n * ChatClient state - track the lifecycle of a chat\n */\nexport type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error'\n\n/**\n * Connection lifecycle state for the subscription loop.\n */\nexport type ConnectionStatus =\n | 'disconnected'\n | 'connecting'\n | 'connected'\n | 'error'\n\n/**\n * Multimodal content input for sending messages with rich media.\n * Allows sending text, images, audio, video, and documents to the LLM.\n *\n * @example\n * ```ts\n * // Send an image with a question\n * client.sendMessage({\n * content: [\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ],\n * id: 'custom-message-id' // optional\n * })\n * ```\n */\nexport interface MultimodalContent {\n /**\n * The content of the message.\n * Can be a simple string or an array of content parts for multimodal messages.\n */\n content: string | Array<ContentPart>\n /**\n * Optional custom ID for the message.\n * If not provided, a unique ID will be generated.\n */\n id?: string\n}\n\n/**\n * Message parts - building blocks of UIMessage\n */\nexport interface TextPart {\n type: 'text'\n content: string\n}\n\n/**\n * Helper type that creates a tool-call part for a specific tool.\n * This is a conditional type to enable proper distribution over union types,\n * creating a discriminated union where `name` is the discriminant.\n */\ntype ToolCallPartForTool<T> = T extends AnyClientTool\n ? {\n type: 'tool-call'\n id: string\n name: T['name']\n arguments: string // JSON string (may be incomplete)\n /** Parsed tool input (typed from inputSchema) */\n input?: InferToolInput<T>\n state: ToolCallState\n /** Approval metadata if tool requires user approval */\n approval?: {\n id: string // Unique approval ID\n needsApproval: boolean // Always true if present\n approved?: boolean // User's decision (undefined until responded)\n }\n /** Tool execution output (for client tools or after approval) */\n output?: InferToolOutput<T>\n }\n : never\n\n/**\n * Fallback tool-call part type when tools are not typed\n */\ntype UntypedToolCallPart = {\n type: 'tool-call'\n id: string\n name: string\n arguments: string\n input?: any\n state: ToolCallState\n approval?: {\n id: string\n needsApproval: boolean\n approved?: boolean\n }\n output?: any\n}\n\n/**\n * Tool call part that creates a proper discriminated union.\n * When TTools is typed, checking `part.name === 'toolName'` will narrow\n * `part.output` to the correct type for that tool.\n *\n * The discriminant is `name`, so code like:\n * ```ts\n * if (part.name === 'recommendGuitar') {\n * // part.output is now typed to the recommendGuitar tool's output\n * }\n * ```\n */\nexport type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n // Check if we have a concrete tools array (not 'any' or 'never')\n [TTools] extends [never]\n ? UntypedToolCallPart\n : unknown extends TTools\n ? UntypedToolCallPart\n : TTools extends ReadonlyArray<infer Tool>\n ? Tool extends AnyClientTool\n ? ToolCallPartForTool<Tool>\n : UntypedToolCallPart\n : UntypedToolCallPart\n\nexport interface ToolResultPart {\n type: 'tool-result'\n toolCallId: string\n content: string\n state: ToolResultState\n error?: string // Error message if state is \"error\"\n}\n\nexport interface ThinkingPart {\n type: 'thinking'\n content: string\n}\n\nexport type MessagePart<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> =\n | TextPart\n | ImagePart\n | AudioPart\n | VideoPart\n | DocumentPart\n | ToolCallPart<TTools>\n | ToolResultPart\n | ThinkingPart\n | StructuredOutputPart<TData>\n\n/**\n * UIMessage - Domain-specific message format optimized for building chat UIs\n * Contains parts that can be text, tool calls, or tool results.\n *\n * `TTools` narrows the tool-call/result part types based on the registered\n * tools. `TData` is the schema-inferred type for any `structured-output` part\n * on the message — defaulted to `unknown` so untyped consumers (the core\n * stream processor, the wire converter) don't need to thread a schema generic\n * everywhere; the hook layer (`useChat({ outputSchema })`) substitutes it on\n * the public return so `m.parts.find(p => p.type === 'structured-output').data`\n * is typed without manual casts.\n */\nexport interface UIMessage<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> {\n id: string\n role: 'system' | 'user' | 'assistant'\n parts: Array<MessagePart<TTools, TData>>\n createdAt?: Date\n}\n\n/**\n * Options for `ChatClient`. Exactly one of `connection` or `fetcher` must be\n * provided — the type-level XOR is enforced via `ChatTransport`.\n */\nexport type ChatClientOptions<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TContext = unknown,\n> = {\n /**\n * Initial messages to populate the chat\n */\n initialMessages?: Array<UIMessage<TTools>>\n\n /**\n * Unique identifier for this chat instance\n * Used for managing multiple chats\n */\n id?: string\n\n /**\n * Thread ID to use for this chat session. Persists across sends within\n * the session. If omitted, a unique thread ID is generated.\n */\n threadId?: string\n\n /**\n * Arbitrary client-controlled JSON forwarded to the server in the\n * AG-UI `RunAgentInput.forwardedProps` field. Use this for per-session\n * options like provider/model selection or feature flags that the\n * server endpoint should read.\n *\n * Replaces the legacy `body` option. If both are provided,\n * `forwardedProps` wins on key collision.\n */\n forwardedProps?: Record<string, any>\n\n /**\n * @deprecated Use `forwardedProps` instead. `body` continues to work\n * unchanged — its values are merged into the AG-UI\n * `RunAgentInput.forwardedProps` field on the wire and are also\n * mirrored under the legacy `data` field for servers that have not\n * migrated yet. Will be removed in a future major release.\n */\n body?: Record<string, any>\n\n /**\n * Callback when a response is received\n */\n onResponse?: (response?: Response) => void | Promise<void>\n\n /**\n * Callback when a stream chunk is received\n */\n onChunk?: (chunk: StreamChunk) => void\n\n /**\n * Callback when the response is finished\n */\n onFinish?: (message: UIMessage<TTools>) => void\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void\n\n /**\n * Callback when messages change\n */\n onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void\n\n /**\n * Callback when loading state changes\n */\n onLoadingChange?: (isLoading: boolean) => void\n\n /**\n * Callback when error state changes\n */\n onErrorChange?: (error: Error | undefined) => void\n\n /**\n * Callback when chat status changes\n */\n onStatusChange?: (status: ChatClientState) => void\n\n /**\n * Callback when subscription lifecycle changes.\n * This is independent from request lifecycle (`isLoading`, `status`).\n */\n onSubscriptionChange?: (isSubscribed: boolean) => void\n\n /**\n * Callback when connection lifecycle changes.\n */\n onConnectionStatusChange?: (status: ConnectionStatus) => void\n\n /**\n * Callback when session generation activity changes.\n * Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).\n * Unlike `onLoadingChange` (request-local), this reflects shared generation\n * activity visible to all subscribers (e.g. across tabs/devices).\n */\n onSessionGeneratingChange?: (isGenerating: boolean) => void\n\n /**\n * Callback when a custom event is received from a server-side tool.\n * Custom events are emitted by tools using `context.emitCustomEvent()` during execution.\n *\n * @param eventType - The name of the custom event\n * @param data - The event payload data\n * @param context - Additional context including the toolCallId that emitted the event\n */\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n /**\n * Client-side tools with execution logic\n * When provided, tools with execute functions will be called automatically\n */\n tools?: TTools\n\n /**\n * Client-local context passed to client-side tool execution.\n */\n context?: TContext\n\n /**\n * Devtools hook metadata for this client instance.\n */\n devtools?: Partial<AIDevtoolsClientMetadata>\n\n /**\n * Factory that constructs the devtools bridge. Default is a no-op\n * factory, which keeps `@tanstack/ai-client/devtools` (the heavy\n * bridge implementation) out of the main entry's bundle. Frameworks\n * that need live devtools should pass the real factory from\n * `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: ChatDevtoolsBridgeFactory\n\n /**\n * Stream processing options (optional)\n * Configure chunking strategy\n */\n streamProcessor?: {\n /**\n * Strategy for when to emit text updates\n * Defaults to ImmediateStrategy (every chunk)\n */\n chunkStrategy?: ChunkStrategy\n }\n} & ChatTransport\n\nexport interface ChatRequestBody {\n messages: Array<ModelMessage>\n data?: Record<string, any>\n}\n\n/**\n * Create a typed array of client tools with proper type inference.\n * This eliminates the need for `as const` when defining tool arrays.\n *\n * @example\n * ```ts\n * const tools = clientTools(\n * myTool1.client(() => result1),\n * myTool2.client(() => result2),\n * )\n *\n * // tools is now properly typed as a tuple with literal tool names\n * // This enables type narrowing when checking part.name === 'toolName'\n * ```\n */\nexport function clientTools<const T extends Array<AnyClientTool>>(\n ...tools: T\n): T {\n return tools\n}\n\n/**\n * Helper to create typed chat client options\n * Use this to get proper type inference for messages\n *\n * @example\n * ```ts\n * const tools = clientTools(myTool1, myTool2)\n *\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools,\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * ```\n */\nexport function createChatClientOptions<\n const TTools extends ReadonlyArray<AnyClientTool>,\n TContext = unknown,\n>(\n options: ChatClientOptions<TTools, TContext>,\n): ChatClientOptions<TTools, TContext> {\n return options\n}\n\n/**\n * Extract the message type from chat options\n *\n * @example\n * ```ts\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools: [myTool1, myTool2],\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>\n * ```\n */\nexport type InferChatMessages<T> =\n T extends ChatClientOptions<infer TTools, any>\n ? Array<UIMessage<TTools>>\n : never\n"],"names":[],"mappings":"AA4bO,SAAS,eACX,OACA;AACH,SAAO;AACT;AAkBO,SAAS,wBAId,SACqC;AACrC,SAAO;AACT;"}
@@ -38,8 +38,14 @@ import { GenerationClientState, GenerationFetcher, VideoGenerateInput, VideoGene
38
38
  export declare class VideoGenerationClient<TOutput = VideoGenerateResult> {
39
39
  private readonly connection;
40
40
  private readonly fetcher;
41
+ private readonly uniqueId;
42
+ private readonly devtoolsMetadata;
43
+ private readonly devtoolsBridge;
44
+ private readonly threadId;
41
45
  private body;
42
46
  private result;
47
+ private input;
48
+ private progress;
43
49
  private jobId;
44
50
  private videoStatus;
45
51
  private isLoading;
@@ -47,6 +53,7 @@ export declare class VideoGenerationClient<TOutput = VideoGenerateResult> {
47
53
  private status;
48
54
  private abortController;
49
55
  private readonly callbacksRef;
56
+ private devtoolsMounted;
50
57
  constructor(options: VideoGenerationClientOptions<TOutput> & ({
51
58
  connection: ConnectConnectionAdapter;
52
59
  fetcher?: never;
@@ -54,6 +61,8 @@ export declare class VideoGenerationClient<TOutput = VideoGenerateResult> {
54
61
  fetcher: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>;
55
62
  connection?: never;
56
63
  }));
64
+ private buildDevtoolsBridgeOptions;
65
+ mountDevtools(): void;
57
66
  /**
58
67
  * Trigger video generation.
59
68
  * Only one generation can be in-flight at a time.
@@ -80,6 +89,7 @@ export declare class VideoGenerationClient<TOutput = VideoGenerateResult> {
80
89
  * Update options without recreating the client.
81
90
  */
82
91
  updateOptions(options: Partial<Pick<VideoGenerationClientOptions<TOutput>, 'body' | 'onResult' | 'onError' | 'onProgress' | 'onChunk' | 'onJobCreated' | 'onStatusUpdate'>>): void;
92
+ dispose(): void;
83
93
  getResult(): TOutput | null;
84
94
  getJobId(): string | null;
85
95
  getVideoStatus(): VideoStatusInfo | null;
@@ -92,4 +102,9 @@ export declare class VideoGenerationClient<TOutput = VideoGenerateResult> {
92
102
  private setIsLoading;
93
103
  private setError;
94
104
  private setStatus;
105
+ private setProgress;
106
+ private createCompletedVideoStatus;
107
+ private createDevtoolsMetadata;
108
+ private generateUniqueId;
109
+ private createRunContext;
95
110
  }
@@ -1,10 +1,17 @@
1
1
  import { GENERATION_EVENTS } from "./generation-types.js";
2
+ import { createNoOpVideoDevtoolsBridge } from "./devtools-noop.js";
2
3
  import { parseSSEResponse } from "./sse-parser.js";
3
4
  class VideoGenerationClient {
4
5
  connection;
5
6
  fetcher;
7
+ uniqueId;
8
+ devtoolsMetadata;
9
+ devtoolsBridge;
10
+ threadId;
6
11
  body;
7
12
  result = null;
13
+ input = null;
14
+ progress = null;
8
15
  jobId = null;
9
16
  videoStatus = null;
10
17
  isLoading = false;
@@ -12,7 +19,10 @@ class VideoGenerationClient {
12
19
  status = "idle";
13
20
  abortController = null;
14
21
  callbacksRef;
22
+ devtoolsMounted = false;
15
23
  constructor(options) {
24
+ this.uniqueId = options.id ?? this.generateUniqueId("video");
25
+ this.threadId = this.uniqueId;
16
26
  this.connection = options.connection;
17
27
  this.fetcher = options.fetcher;
18
28
  this.body = options.body ?? {};
@@ -30,13 +40,45 @@ class VideoGenerationClient {
30
40
  onJobIdChange: options.onJobIdChange,
31
41
  onVideoStatusChange: options.onVideoStatusChange
32
42
  };
43
+ this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools);
44
+ this.devtoolsBridge = (options.devtoolsBridgeFactory ?? createNoOpVideoDevtoolsBridge)(this.buildDevtoolsBridgeOptions());
45
+ }
46
+ buildDevtoolsBridgeOptions() {
47
+ return {
48
+ hookId: this.uniqueId,
49
+ clientId: this.uniqueId,
50
+ threadId: this.threadId,
51
+ metadata: this.devtoolsMetadata,
52
+ getCoreState: () => ({
53
+ input: this.input,
54
+ result: this.result,
55
+ progress: this.progress,
56
+ status: this.status,
57
+ isLoading: this.isLoading,
58
+ jobId: this.jobId,
59
+ videoStatus: this.videoStatus,
60
+ ...this.error ? { error: this.error.message } : {}
61
+ })
62
+ };
63
+ }
64
+ mountDevtools() {
65
+ if (this.devtoolsMounted) {
66
+ return;
67
+ }
68
+ this.devtoolsMounted = true;
69
+ this.devtoolsBridge.emitRegistered();
70
+ this.devtoolsBridge.emitSnapshot();
33
71
  }
34
72
  /**
35
73
  * Trigger video generation.
36
74
  * Only one generation can be in-flight at a time.
37
75
  */
38
76
  async generate(input) {
77
+ this.mountDevtools();
39
78
  if (this.isLoading) return;
79
+ this.input = input;
80
+ this.progress = null;
81
+ const runId = this.devtoolsBridge.beginRun(input);
40
82
  this.setIsLoading(true);
41
83
  this.setStatus("generating");
42
84
  this.setError(void 0);
@@ -47,21 +89,39 @@ class VideoGenerationClient {
47
89
  const { signal } = abortController;
48
90
  try {
49
91
  if (this.fetcher) {
50
- await this.generateWithFetcher(input, signal);
92
+ await this.generateWithFetcher(input, signal, runId);
51
93
  } else if (this.connection) {
52
94
  const mergedData = { ...this.body, ...input };
53
- const stream = this.connection.connect([], mergedData, signal);
54
- await this.processStream(stream);
95
+ const stream = this.connection.connect(
96
+ [],
97
+ mergedData,
98
+ signal,
99
+ this.createRunContext(runId)
100
+ );
101
+ await this.processStream(stream, runId);
55
102
  } else {
56
103
  throw new Error(
57
104
  "VideoGenerationClient requires either a connection or fetcher option"
58
105
  );
59
106
  }
107
+ if (!signal.aborted && this.status === "success") {
108
+ this.devtoolsBridge.finishRun(
109
+ this.devtoolsBridge.getActiveRunId() ?? runId,
110
+ "run:completed",
111
+ "completed"
112
+ );
113
+ }
60
114
  } catch (err) {
61
115
  if (signal.aborted) return;
62
116
  const error = err instanceof Error ? err : new Error(String(err));
63
117
  this.setError(error);
64
118
  this.setStatus("error");
119
+ this.devtoolsBridge.finishRun(
120
+ this.devtoolsBridge.getActiveRunId() ?? runId,
121
+ "run:errored",
122
+ "errored",
123
+ error.message
124
+ );
65
125
  this.callbacksRef.onError?.(error);
66
126
  } finally {
67
127
  this.abortController = null;
@@ -71,13 +131,14 @@ class VideoGenerationClient {
71
131
  /**
72
132
  * Direct fetcher mode: call fetcher and set result.
73
133
  */
74
- async generateWithFetcher(input, signal) {
134
+ async generateWithFetcher(input, signal, runId) {
75
135
  if (!this.fetcher) return;
76
136
  const result = await this.fetcher(input, { signal });
77
137
  if (signal.aborted) return;
78
138
  if (result instanceof Response) {
79
- await this.processStream(parseSSEResponse(result, signal));
139
+ await this.processStream(parseSSEResponse(result, signal), runId);
80
140
  } else {
141
+ this.devtoolsBridge.ensureRunStarted(runId);
81
142
  this.setResult(result);
82
143
  this.setStatus("success");
83
144
  }
@@ -86,12 +147,20 @@ class VideoGenerationClient {
86
147
  * Process a stream of AG-UI events from the streaming connection adapter.
87
148
  * The server handles the polling loop and streams status updates.
88
149
  */
89
- async processStream(source) {
150
+ async processStream(source, fallbackRunId) {
151
+ let streamRunId;
90
152
  for await (const chunk of source) {
91
153
  if (this.abortController?.signal.aborted) break;
92
154
  this.callbacksRef.onChunk?.(chunk);
155
+ const chunkRunId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
93
156
  switch (chunk.type) {
157
+ case "RUN_STARTED": {
158
+ streamRunId = chunk.runId;
159
+ this.devtoolsBridge.ensureRunStarted(chunk.runId);
160
+ break;
161
+ }
94
162
  case "CUSTOM": {
163
+ this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId);
95
164
  if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {
96
165
  const { jobId } = chunk.value;
97
166
  this.setJobId(jobId);
@@ -101,21 +170,26 @@ class VideoGenerationClient {
101
170
  this.setVideoStatus(statusInfo);
102
171
  this.callbacksRef.onStatusUpdate?.(statusInfo);
103
172
  if (statusInfo.progress !== void 0) {
104
- this.callbacksRef.onProgress?.(statusInfo.progress);
173
+ this.setProgress(statusInfo.progress);
105
174
  }
106
175
  } else if (chunk.name === GENERATION_EVENTS.RESULT) {
107
176
  this.setResult(chunk.value);
108
177
  } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {
109
178
  const { progress, message } = chunk.value;
110
- this.callbacksRef.onProgress?.(progress, message);
179
+ this.setProgress(progress, message);
111
180
  }
112
181
  break;
113
182
  }
114
183
  case "RUN_FINISHED": {
184
+ streamRunId = chunk.runId;
185
+ this.devtoolsBridge.ensureRunStarted(chunk.runId);
115
186
  this.setStatus("success");
116
187
  break;
117
188
  }
118
189
  case "RUN_ERROR": {
190
+ this.devtoolsBridge.ensureRunStarted(
191
+ chunkRunId ?? streamRunId ?? fallbackRunId
192
+ );
119
193
  const msg = chunk.message || chunk.error?.message || "An error occurred";
120
194
  throw new Error(msg);
121
195
  }
@@ -126,6 +200,7 @@ class VideoGenerationClient {
126
200
  * Abort any in-flight generation or polling.
127
201
  */
128
202
  stop() {
203
+ const runId = this.devtoolsBridge.getActiveRunId();
129
204
  if (this.abortController) {
130
205
  this.abortController.abort();
131
206
  this.abortController = null;
@@ -133,6 +208,9 @@ class VideoGenerationClient {
133
208
  this.setIsLoading(false);
134
209
  if (this.status === "generating") {
135
210
  this.setStatus("idle");
211
+ if (runId) {
212
+ this.devtoolsBridge.finishRun(runId, "run:cancelled", "cancelled");
213
+ }
136
214
  }
137
215
  }
138
216
  /**
@@ -141,10 +219,14 @@ class VideoGenerationClient {
141
219
  reset() {
142
220
  this.stop();
143
221
  this.setResult(null);
222
+ this.input = null;
223
+ this.progress = null;
224
+ this.devtoolsBridge.resetRuns();
144
225
  this.setJobId(null);
145
226
  this.setVideoStatus(null);
146
227
  this.setError(void 0);
147
228
  this.setStatus("idle");
229
+ this.devtoolsBridge.emitState();
148
230
  }
149
231
  /**
150
232
  * Update options without recreating the client.
@@ -172,6 +254,11 @@ class VideoGenerationClient {
172
254
  this.callbacksRef.onStatusUpdate = options.onStatusUpdate;
173
255
  }
174
256
  }
257
+ dispose() {
258
+ this.stop();
259
+ this.devtoolsBridge.dispose();
260
+ this.devtoolsMounted = false;
261
+ }
175
262
  // ===========================
176
263
  // Getters
177
264
  // ===========================
@@ -200,41 +287,93 @@ class VideoGenerationClient {
200
287
  if (rawResult === null) {
201
288
  this.result = null;
202
289
  this.callbacksRef.onResultChange?.(null);
290
+ this.devtoolsBridge.recordResultChange();
203
291
  return;
204
292
  }
293
+ const completedStatus = this.createCompletedVideoStatus(rawResult);
294
+ if (this.progress?.value !== 100) {
295
+ this.setProgress(100, this.progress?.message);
296
+ }
297
+ this.setJobId(rawResult.jobId);
298
+ this.setVideoStatus(completedStatus);
205
299
  if (this.callbacksRef.onResult) {
206
300
  const transformed = this.callbacksRef.onResult(rawResult);
207
301
  if (transformed === null) {
302
+ this.devtoolsBridge.emitState();
208
303
  return;
209
304
  }
210
305
  if (transformed !== void 0) {
211
306
  this.result = transformed;
212
307
  this.callbacksRef.onResultChange?.(this.result);
308
+ this.devtoolsBridge.recordResultChange();
213
309
  return;
214
310
  }
215
311
  }
216
312
  this.result = rawResult;
217
313
  this.callbacksRef.onResultChange?.(this.result);
314
+ this.devtoolsBridge.recordResultChange();
218
315
  }
219
316
  setJobId(jobId) {
220
317
  this.jobId = jobId;
221
318
  this.callbacksRef.onJobIdChange?.(jobId);
319
+ this.devtoolsBridge.recordJobIdChange();
222
320
  }
223
321
  setVideoStatus(status) {
224
322
  this.videoStatus = status;
225
323
  this.callbacksRef.onVideoStatusChange?.(status);
324
+ this.devtoolsBridge.recordVideoStatusChange();
226
325
  }
227
326
  setIsLoading(isLoading) {
228
327
  this.isLoading = isLoading;
229
328
  this.callbacksRef.onLoadingChange?.(isLoading);
329
+ this.devtoolsBridge.recordLoadingChange();
230
330
  }
231
331
  setError(error) {
232
332
  this.error = error;
233
333
  this.callbacksRef.onErrorChange?.(error);
334
+ this.devtoolsBridge.recordErrorChange(error);
234
335
  }
235
336
  setStatus(status) {
236
337
  this.status = status;
237
338
  this.callbacksRef.onStatusChange?.(status);
339
+ this.devtoolsBridge.recordStatusChange(status);
340
+ }
341
+ setProgress(value, message) {
342
+ this.progress = {
343
+ value,
344
+ ...message ? { message } : {}
345
+ };
346
+ if (message === void 0) {
347
+ this.callbacksRef.onProgress?.(value);
348
+ } else {
349
+ this.callbacksRef.onProgress?.(value, message);
350
+ }
351
+ this.devtoolsBridge.recordProgressChange();
352
+ }
353
+ createCompletedVideoStatus(result) {
354
+ return {
355
+ jobId: result.jobId,
356
+ status: result.status,
357
+ progress: 100,
358
+ url: result.url
359
+ };
360
+ }
361
+ createDevtoolsMetadata(metadata) {
362
+ return {
363
+ hookName: metadata?.hookName ?? "useGenerateVideo",
364
+ outputKind: metadata?.outputKind ?? "video",
365
+ ...metadata?.framework ? { framework: metadata.framework } : {},
366
+ ...metadata?.name ? { name: metadata.name } : {}
367
+ };
368
+ }
369
+ generateUniqueId(prefix) {
370
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
371
+ }
372
+ createRunContext(runId) {
373
+ return {
374
+ threadId: this.threadId,
375
+ runId
376
+ };
238
377
  }
239
378
  }
240
379
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"video-generation-client.js","sources":["../../src/video-generation-client.ts"],"sourcesContent":["import { GENERATION_EVENTS } from './generation-types'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\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 body: Record<string, any>\n\n private result: TOutput | 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\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.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\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 if (this.isLoading) return\n\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)\n } else if (this.connection) {\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect([], mergedData, signal)\n await this.processStream(stream)\n } else {\n throw new Error(\n 'VideoGenerationClient requires either a connection or fetcher option',\n )\n }\n } catch (err: any) {\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.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 ): 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))\n } else {\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 ): Promise<void> {\n for await (const chunk of source) {\n if (this.abortController?.signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\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 'CUSTOM': {\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.callbacksRef.onProgress?.(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.callbacksRef.onProgress?.(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\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 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 }\n }\n\n /**\n * Clear all state and return to idle.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.setJobId(null)\n this.setVideoStatus(null)\n this.setError(undefined)\n this.setStatus('idle')\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 // ===========================\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 return\n }\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged\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 return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value\n this.result = rawResult as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n }\n\n private setJobId(jobId: string | null): void {\n this.jobId = jobId\n this.callbacksRef.onJobIdChange?.(jobId)\n }\n\n private setVideoStatus(status: VideoStatusInfo | null): void {\n this.videoStatus = status\n this.callbacksRef.onVideoStatusChange?.(status)\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\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'\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,13 +1,13 @@
1
1
  {
2
2
  "name": "@tanstack/ai-client",
3
- "version": "0.11.8",
3
+ "version": "0.13.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",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/TanStack/ai.git",
10
- "directory": "packages/typescript/ai-client"
10
+ "directory": "packages/ai-client"
11
11
  },
12
12
  "keywords": [
13
13
  "ai",
@@ -30,6 +30,10 @@
30
30
  ".": {
31
31
  "types": "./dist/esm/index.d.ts",
32
32
  "import": "./dist/esm/index.js"
33
+ },
34
+ "./devtools": {
35
+ "types": "./dist/esm/devtools.d.ts",
36
+ "import": "./dist/esm/devtools.js"
33
37
  }
34
38
  },
35
39
  "files": [
@@ -37,8 +41,8 @@
37
41
  "src"
38
42
  ],
39
43
  "dependencies": {
40
- "@tanstack/ai": "0.22.0",
41
- "@tanstack/ai-event-client": "0.3.11"
44
+ "@tanstack/ai": "0.22.1",
45
+ "@tanstack/ai-event-client": "0.4.0"
42
46
  },
43
47
  "devDependencies": {
44
48
  "@standard-schema/spec": "^1.1.0",