@tanstack/ai-solid 0.10.2 → 0.10.4

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/index.d.ts CHANGED
@@ -7,5 +7,5 @@ import { UseGenerateSpeechOptions, UseGenerateSpeechReturn, useGenerateSpeech }
7
7
  import { UseTranscriptionOptions, UseTranscriptionReturn, useTranscription } from "./use-transcription.js";
8
8
  import { UseSummarizeOptions, UseSummarizeReturn, useSummarize } from "./use-summarize.js";
9
9
  import { UseGenerateVideoOptions, UseGenerateVideoReturn, useGenerateVideo } from "./use-generate-video.js";
10
- import { AudioGenerateInput, ConnectionAdapter, FetchConnectionOptions, GenerationClientState, ImageGenerateInput, InferChatMessages, SpeechGenerateInput, SummarizeGenerateInput, TranscriptionGenerateInput, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo, createChatClientOptions, fetchHttpStream, fetchServerSentEvents, stream } from "@tanstack/ai-client";
11
- export { type AudioGenerateInput, type ChatRequestBody, type ConnectionAdapter, type DeepPartial, type FetchConnectionOptions, type GenerationClientState, type ImageGenerateInput, type InferChatMessages, type SpeechGenerateInput, type SummarizeGenerateInput, type TranscriptionGenerateInput, type UIMessage, type UseChatOptions, type UseChatReturn, type UseGenerateAudioOptions, type UseGenerateAudioReturn, type UseGenerateImageOptions, type UseGenerateImageReturn, type UseGenerateSpeechOptions, type UseGenerateSpeechReturn, type UseGenerateVideoOptions, type UseGenerateVideoReturn, type UseGenerationOptions, type UseGenerationReturn, type UseSummarizeOptions, type UseSummarizeReturn, type UseTranscriptionOptions, type UseTranscriptionReturn, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, createChatClientOptions, fetchHttpStream, fetchServerSentEvents, stream, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useSummarize, useTranscription };
10
+ import { AudioGenerateInput, ConnectConnectionAdapter, ConnectionAdapter, FetchConnectionOptions, GenerationClientState, ImageGenerateInput, InferChatMessages, RunAgentInputContext, SpeechGenerateInput, SubscribeConnectionAdapter, SummarizeGenerateInput, TranscriptionGenerateInput, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo, createChatClientOptions, fetchHttpStream, fetchServerSentEvents, rpcStream, stream } from "@tanstack/ai-client";
11
+ export { type AudioGenerateInput, type ChatRequestBody, type ConnectConnectionAdapter, type ConnectionAdapter, type DeepPartial, type FetchConnectionOptions, type GenerationClientState, type ImageGenerateInput, type InferChatMessages, type RunAgentInputContext, type SpeechGenerateInput, type SubscribeConnectionAdapter, type SummarizeGenerateInput, type TranscriptionGenerateInput, type UIMessage, type UseChatOptions, type UseChatReturn, type UseGenerateAudioOptions, type UseGenerateAudioReturn, type UseGenerateImageOptions, type UseGenerateImageReturn, type UseGenerateSpeechOptions, type UseGenerateSpeechReturn, type UseGenerateVideoOptions, type UseGenerateVideoReturn, type UseGenerationOptions, type UseGenerationReturn, type UseSummarizeOptions, type UseSummarizeReturn, type UseTranscriptionOptions, type UseTranscriptionReturn, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, createChatClientOptions, fetchHttpStream, fetchServerSentEvents, rpcStream, stream, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useSummarize, useTranscription };
package/dist/index.js CHANGED
@@ -6,6 +6,6 @@ import { useGenerateSpeech } from "./use-generate-speech.js";
6
6
  import { useTranscription } from "./use-transcription.js";
7
7
  import { useSummarize } from "./use-summarize.js";
8
8
  import { useGenerateVideo } from "./use-generate-video.js";
9
- import { createChatClientOptions, fetchHttpStream, fetchServerSentEvents, stream } from "@tanstack/ai-client";
9
+ import { createChatClientOptions, fetchHttpStream, fetchServerSentEvents, rpcStream, stream } from "@tanstack/ai-client";
10
10
 
11
- export { createChatClientOptions, fetchHttpStream, fetchServerSentEvents, stream, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useSummarize, useTranscription };
11
+ export { createChatClientOptions, fetchHttpStream, fetchServerSentEvents, rpcStream, stream, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useSummarize, useTranscription };
package/dist/use-chat.js CHANGED
@@ -16,9 +16,9 @@ function useChat(options = {}) {
16
16
  return new ChatClient({
17
17
  connection: options.connection,
18
18
  id: clientId,
19
- initialMessages: options.initialMessages,
19
+ ...options.initialMessages !== void 0 && { initialMessages: options.initialMessages },
20
20
  body: options.body,
21
- forwardedProps: options.forwardedProps,
21
+ ...options.forwardedProps !== void 0 && { forwardedProps: options.forwardedProps },
22
22
  onResponse: (response) => options.onResponse?.(response),
23
23
  onChunk: (chunk) => {
24
24
  options.onChunk?.(chunk);
@@ -31,7 +31,7 @@ function useChat(options = {}) {
31
31
  },
32
32
  tools: options.tools,
33
33
  onCustomEvent: (eventType, data, context) => options.onCustomEvent?.(eventType, data, context),
34
- streamProcessor: options.streamProcessor,
34
+ ...options.streamProcessor !== void 0 && { streamProcessor: options.streamProcessor },
35
35
  onMessagesChange: (newMessages) => {
36
36
  setMessages(newMessages);
37
37
  },
@@ -57,8 +57,8 @@ function useChat(options = {}) {
57
57
  }, [clientId]);
58
58
  createEffect(() => {
59
59
  client().updateOptions({
60
- body: options.body,
61
- forwardedProps: options.forwardedProps
60
+ ...options.body !== void 0 && { body: options.body },
61
+ ...options.forwardedProps !== void 0 && { forwardedProps: options.forwardedProps }
62
62
  });
63
63
  });
64
64
  createEffect(() => {
@@ -103,14 +103,14 @@ function useChat(options = {}) {
103
103
  const activeStructuredPart = createMemo(() => {
104
104
  const list = messages();
105
105
  let lastUserIndex = -1;
106
- for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
106
+ for (let i = list.length - 1; i >= 0; i--) if (list[i]?.role === "user") {
107
107
  lastUserIndex = i;
108
108
  break;
109
109
  }
110
110
  if (lastUserIndex === -1) return null;
111
111
  for (let i = list.length - 1; i > lastUserIndex; i--) {
112
112
  const m = list[i];
113
- if (m.role !== "assistant") continue;
113
+ if (m?.role !== "assistant") continue;
114
114
  const part = m.parts.find((p) => p.type === "structured-output");
115
115
  if (part) return part;
116
116
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n onCleanup,\n} from 'solid-js'\n\nimport { ChatClient } from '@tanstack/ai-client'\nimport type {\n ChatClientState,\n ConnectionStatus,\n StructuredOutputPart,\n} from '@tanstack/ai-client'\nimport type {\n AnyClientTool,\n InferSchemaType,\n ModelMessage,\n SchemaInput,\n StreamChunk,\n} from '@tanstack/ai'\nimport type {\n DeepPartial,\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TSchema extends SchemaInput | undefined = undefined,\n>(\n options: UseChatOptions<TTools, TSchema> = {} as UseChatOptions<\n TTools,\n TSchema\n >,\n): UseChatReturn<TTools, TSchema> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = createSignal(false)\n const [connectionStatus, setConnectionStatus] =\n createSignal<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = createSignal(false)\n\n // Structured-output `partial` / `final` are derived from `messages` —\n // specifically from the structured-output part on the latest assistant\n // message (the one after the most recent user message). Per-turn parts\n // keep history coherent without a separate reset signal.\n type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>\n type Final = InferSchemaType<NonNullable<TSchema>>\n\n // Create ChatClient instance with callbacks to sync state.\n // Every user-provided callback is wrapped so the LATEST `options.xxx` value\n // is read at call time. Direct assignment would freeze the callback to the\n // reference we saw at creation; the wrapper lets reactive `options` or\n // in-place mutations propagate. When the user clears a callback (sets it to\n // undefined), `?.` no-ops.\n const client = createMemo(() => {\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n initialMessages: options.initialMessages,\n body: options.body,\n forwardedProps: options.forwardedProps,\n onResponse: (response) => options.onResponse?.(response),\n onChunk: (chunk: StreamChunk) => {\n options.onChunk?.(chunk)\n },\n onFinish: (message) => {\n options.onFinish?.(message)\n },\n onError: (err) => {\n options.onError?.(err)\n },\n tools: options.tools,\n onCustomEvent: (eventType, data, context) =>\n options.onCustomEvent?.(eventType, data, context),\n streamProcessor: options.streamProcessor,\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onStatusChange: (newStatus: ChatClientState) => {\n setStatus(newStatus)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n setSessionGenerating(isGenerating)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync body / forwardedProps changes to the client.\n // Both populate the same wire payload; `forwardedProps` is preferred\n // and `body` is deprecated but still supported.\n createEffect(() => {\n client().updateOptions({\n body: options.body,\n forwardedProps: options.forwardedProps,\n })\n })\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Apply initial live mode immediately on hook creation.\n if (options.live) {\n client().subscribe()\n } else {\n client().unsubscribe()\n }\n\n createEffect(() => {\n if (options.live) {\n client().subscribe()\n } else {\n client().unsubscribe()\n }\n })\n\n // Cleanup on unmount: stop any in-flight requests.\n onCleanup(() => {\n if (options.live) {\n client().unsubscribe()\n } else {\n client().stop()\n }\n })\n\n // Callback options are read through `options.xxx` at call time, so reactive\n // or mutated options propagate without recreating the client.\n\n const sendMessage = async (content: string | MultimodalContent) => {\n await client().sendMessage(content)\n }\n\n const append = async (message: ModelMessage | UIMessage<TTools>) => {\n await client().append(message)\n }\n\n const reload = async () => {\n await client().reload()\n }\n\n const stop = () => {\n client().stop()\n }\n\n const clear = () => {\n client().clear()\n }\n\n const setMessagesManually = (newMessages: Array<UIMessage<TTools>>) => {\n client().setMessagesManually(newMessages)\n }\n\n const addToolResult = async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client().addToolResult(result)\n }\n\n const addToolApprovalResponse = async (response: {\n id: string\n approved: boolean\n }) => {\n await client().addToolApprovalResponse(response)\n }\n\n // The \"active\" structured-output part is on the assistant message after\n // the latest user message. When no user message exists yet, return null\n // rather than scanning history — otherwise a stale `final` from\n // `initialMessages` would leak into the value on first render.\n const activeStructuredPart = createMemo<StructuredOutputPart | null>(() => {\n const list = messages()\n let lastUserIndex = -1\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i]!.role === 'user') {\n lastUserIndex = i\n break\n }\n }\n if (lastUserIndex === -1) return null\n for (let i = list.length - 1; i > lastUserIndex; i--) {\n const m = list[i]!\n if (m.role !== 'assistant') continue\n const part = m.parts.find(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n if (part) return part\n }\n return null\n })\n\n const partial = createMemo<Partial>(() => {\n const part = activeStructuredPart()\n if (!part) return {} as Partial\n const v = part.partial ?? part.data\n return (v ?? {}) as Partial\n })\n\n const final = createMemo<Final | null>(() => {\n const part = activeStructuredPart()\n if (!part || part.status !== 'complete') return null\n return part.data as Final\n })\n\n // partial / final are runtime-tracked unconditionally; the conditional\n // return type hides them when no `outputSchema` is supplied.\n return {\n messages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n partial,\n final,\n } as unknown as UseChatReturn<TTools, TSchema>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n onCleanup,\n} from 'solid-js'\n\nimport { ChatClient } from '@tanstack/ai-client'\nimport type {\n ChatClientState,\n ConnectionStatus,\n StructuredOutputPart,\n} from '@tanstack/ai-client'\nimport type {\n AnyClientTool,\n InferSchemaType,\n ModelMessage,\n SchemaInput,\n StreamChunk,\n} from '@tanstack/ai'\nimport type {\n DeepPartial,\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TSchema extends SchemaInput | undefined = undefined,\n>(\n options: UseChatOptions<TTools, TSchema> = {} as UseChatOptions<\n TTools,\n TSchema\n >,\n): UseChatReturn<TTools, TSchema> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = createSignal(false)\n const [connectionStatus, setConnectionStatus] =\n createSignal<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = createSignal(false)\n\n // Structured-output `partial` / `final` are derived from `messages` —\n // specifically from the structured-output part on the latest assistant\n // message (the one after the most recent user message). Per-turn parts\n // keep history coherent without a separate reset signal.\n type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>\n type Final = InferSchemaType<NonNullable<TSchema>>\n\n // Create ChatClient instance with callbacks to sync state.\n // Every user-provided callback is wrapped so the LATEST `options.xxx` value\n // is read at call time. Direct assignment would freeze the callback to the\n // reference we saw at creation; the wrapper lets reactive `options` or\n // in-place mutations propagate. When the user clears a callback (sets it to\n // undefined), `?.` no-ops.\n const client = createMemo(() => {\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n ...(options.initialMessages !== undefined && {\n initialMessages: options.initialMessages,\n }),\n body: options.body,\n ...(options.forwardedProps !== undefined && {\n forwardedProps: options.forwardedProps,\n }),\n onResponse: (response) => options.onResponse?.(response),\n onChunk: (chunk: StreamChunk) => {\n options.onChunk?.(chunk)\n },\n onFinish: (message) => {\n options.onFinish?.(message)\n },\n onError: (err) => {\n options.onError?.(err)\n },\n tools: options.tools,\n onCustomEvent: (eventType, data, context) =>\n options.onCustomEvent?.(eventType, data, context),\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onStatusChange: (newStatus: ChatClientState) => {\n setStatus(newStatus)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n setSessionGenerating(isGenerating)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync body / forwardedProps changes to the client.\n // Both populate the same wire payload; `forwardedProps` is preferred\n // and `body` is deprecated but still supported.\n createEffect(() => {\n // Conditional spread: `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT.\n client().updateOptions({\n ...(options.body !== undefined && { body: options.body }),\n ...(options.forwardedProps !== undefined && {\n forwardedProps: options.forwardedProps,\n }),\n })\n })\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Apply initial live mode immediately on hook creation.\n if (options.live) {\n client().subscribe()\n } else {\n client().unsubscribe()\n }\n\n createEffect(() => {\n if (options.live) {\n client().subscribe()\n } else {\n client().unsubscribe()\n }\n })\n\n // Cleanup on unmount: stop any in-flight requests.\n onCleanup(() => {\n if (options.live) {\n client().unsubscribe()\n } else {\n client().stop()\n }\n })\n\n // Callback options are read through `options.xxx` at call time, so reactive\n // or mutated options propagate without recreating the client.\n\n const sendMessage = async (content: string | MultimodalContent) => {\n await client().sendMessage(content)\n }\n\n const append = async (message: ModelMessage | UIMessage<TTools>) => {\n await client().append(message)\n }\n\n const reload = async () => {\n await client().reload()\n }\n\n const stop = () => {\n client().stop()\n }\n\n const clear = () => {\n client().clear()\n }\n\n const setMessagesManually = (newMessages: Array<UIMessage<TTools>>) => {\n client().setMessagesManually(newMessages)\n }\n\n const addToolResult = async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client().addToolResult(result)\n }\n\n const addToolApprovalResponse = async (response: {\n id: string\n approved: boolean\n }) => {\n await client().addToolApprovalResponse(response)\n }\n\n // The \"active\" structured-output part is on the assistant message after\n // the latest user message. When no user message exists yet, return null\n // rather than scanning history — otherwise a stale `final` from\n // `initialMessages` would leak into the value on first render.\n const activeStructuredPart = createMemo<StructuredOutputPart | null>(() => {\n const list = messages()\n let lastUserIndex = -1\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i]?.role === 'user') {\n lastUserIndex = i\n break\n }\n }\n if (lastUserIndex === -1) return null\n for (let i = list.length - 1; i > lastUserIndex; i--) {\n const m = list[i]\n if (m?.role !== 'assistant') continue\n const part = m.parts.find(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n if (part) return part\n }\n return null\n })\n\n const partial = createMemo<Partial>(() => {\n const part = activeStructuredPart()\n if (!part) return {} as Partial\n const v = part.partial ?? part.data\n return (v ?? {}) as Partial\n })\n\n const final = createMemo<Final | null>(() => {\n const part = activeStructuredPart()\n if (!part || part.status !== 'complete') return null\n return part.data as Final\n })\n\n // partial / final are runtime-tracked unconditionally; the conditional\n // return type hides them when no `outputSchema` is supplied.\n // eslint-disable-next-line no-restricted-syntax -- primitive return shape diverges from generic UseChatReturn<TTools, TSchema>; TS can't structurally narrow the conditional partial/final fields\n return {\n messages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n partial,\n final,\n } as unknown as UseChatReturn<TTools, TSchema>\n}\n"],"mappings":"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"}
@@ -71,7 +71,7 @@ function useGenerateVideo(options) {
71
71
  }, [clientId]);
72
72
  createEffect(() => {
73
73
  const currentBody = options.body;
74
- client().updateOptions({ body: currentBody });
74
+ client().updateOptions({ ...currentBody !== void 0 && { body: currentBody } });
75
75
  });
76
76
  createEffect(() => {
77
77
  return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-generate-video.js","names":[],"sources":["../src/use-generate-video.ts"],"sourcesContent":["import { VideoGenerationClient } from '@tanstack/ai-client'\nimport {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n InferGenerationOutput,\n VideoGenerateInput,\n VideoGenerateResult,\n VideoStatusInfo,\n} from '@tanstack/ai-client'\nimport type { Accessor } from 'solid-js'\n\n/**\n * Options for the useGenerateVideo hook.\n *\n * @template TOutput - The transformed output type (defaults to VideoGenerateResult)\n */\nexport interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {\n /** Connect-based adapter for streaming transport (server handles polling) */\n connection?: ConnectConnectionAdapter\n /** Direct async function that returns a completed video result */\n fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n /** Unique identifier for this generation instance */\n id?: string\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /**\n * Callback when video generation completes. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: VideoGenerateResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateVideo hook.\n *\n * @template TOutput - The transformed output type (defaults to VideoGenerateResult)\n */\nexport interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {\n /** Trigger video generation */\n generate: (input: VideoGenerateInput) => Promise<void>\n /** The final video result (with URL), or null */\n result: Accessor<TOutput | null>\n /** The current job ID, or null */\n jobId: Accessor<string | null>\n /** Current video generation status info, or null */\n videoStatus: Accessor<VideoStatusInfo | null>\n /** Whether generation/polling is in progress */\n isLoading: Accessor<boolean>\n /** Current error, if any */\n error: Accessor<Error | undefined>\n /** Current state of the generation */\n status: Accessor<GenerationClientState>\n /** Abort the current generation/polling */\n stop: () => void\n /** Clear all state and return to idle */\n reset: () => void\n}\n\n/**\n * Solid hook for generating videos using AI models.\n *\n * Video generation is asynchronous: a job is created, then polled for status\n * until completion. This hook handles the full lifecycle.\n *\n * @example\n * ```tsx\n * import { useGenerateVideo } from '@tanstack/ai-solid'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function VideoGenerator() {\n * const { generate, result, videoStatus, isLoading } = useGenerateVideo({\n * connection: fetchServerSentEvents('/api/generate/video'),\n * onStatusUpdate: (status) => console.log(`Progress: ${status.progress}%`),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ prompt: 'A flying car over a city' })}>\n * Generate Video\n * </button>\n * {isLoading() && videoStatus() && (\n * <p>Status: {videoStatus()!.status} ({videoStatus()!.progress}%)</p>\n * )}\n * {result() && <video src={result()!.url} controls />}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateVideo<\n TOnResult extends ((result: VideoGenerateResult) => any) | undefined =\n undefined,\n>(\n options: Omit<UseGenerateVideoOptions, 'onResult'> & {\n onResult?: TOnResult\n },\n): UseGenerateVideoReturn<\n InferGenerationOutput<VideoGenerateResult, TOnResult>\n> {\n type TOutput = InferGenerationOutput<VideoGenerateResult, TOnResult>\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [result, setResult] = createSignal<TOutput | null>(null)\n const [jobId, setJobId] = createSignal<string | null>(null)\n const [videoStatus, setVideoStatus] = createSignal<VideoStatusInfo | null>(\n null,\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<GenerationClientState>('idle')\n\n const client = createMemo(() => {\n const baseOptions = {\n id: clientId,\n body: options.body,\n onResult: (r: VideoGenerateResult) => options.onResult?.(r),\n onError: (e: Error) => options.onError?.(e),\n onProgress: (p: number, m?: string) => options.onProgress?.(p, m),\n onChunk: (c: StreamChunk) => options.onChunk?.(c),\n onJobCreated: (id: string) => options.onJobCreated?.(id),\n onStatusUpdate: (s: VideoStatusInfo) => options.onStatusUpdate?.(s),\n onResultChange: setResult,\n onLoadingChange: setIsLoading,\n onErrorChange: setError,\n onStatusChange: setStatus,\n onJobIdChange: setJobId,\n onVideoStatusChange: setVideoStatus,\n }\n\n if (options.connection) {\n return new VideoGenerationClient<TOutput>({\n ...baseOptions,\n connection: options.connection,\n })\n }\n\n if (options.fetcher) {\n return new VideoGenerationClient<TOutput>({\n ...baseOptions,\n fetcher: options.fetcher,\n })\n }\n\n throw new Error(\n 'useGenerateVideo requires either a connection or fetcher option',\n )\n }, [clientId])\n\n // Sync body changes without recreating client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({ body: currentBody })\n })\n\n // Cleanup on unmount: stop any in-flight requests\n createEffect(() => {\n return () => {\n client().stop()\n }\n })\n\n const generate = async (input: VideoGenerateInput) => {\n await client().generate(input)\n }\n\n const stop = () => {\n client().stop()\n }\n\n const reset = () => {\n client().reset()\n }\n\n return {\n generate,\n result,\n jobId,\n videoStatus,\n isLoading,\n error,\n status,\n stop,\n reset,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,SAAgB,iBAId,SAKA;CAEA,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,QAAQ,aAAa,aAA6B,KAAK;CAC9D,MAAM,CAAC,OAAO,YAAY,aAA4B,KAAK;CAC3D,MAAM,CAAC,aAAa,kBAAkB,aACpC,KACD;CACD,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CACpE,MAAM,CAAC,QAAQ,aAAa,aAAoC,OAAO;CAEvE,MAAM,SAAS,iBAAiB;EAC9B,MAAM,cAAc;GAClB,IAAI;GACJ,MAAM,QAAQ;GACd,WAAW,MAA2B,QAAQ,WAAW,EAAE;GAC3D,UAAU,MAAa,QAAQ,UAAU,EAAE;GAC3C,aAAa,GAAW,MAAe,QAAQ,aAAa,GAAG,EAAE;GACjE,UAAU,MAAmB,QAAQ,UAAU,EAAE;GACjD,eAAe,OAAe,QAAQ,eAAe,GAAG;GACxD,iBAAiB,MAAuB,QAAQ,iBAAiB,EAAE;GACnE,gBAAgB;GAChB,iBAAiB;GACjB,eAAe;GACf,gBAAgB;GAChB,eAAe;GACf,qBAAqB;GACtB;AAED,MAAI,QAAQ,WACV,QAAO,IAAI,sBAA+B;GACxC,GAAG;GACH,YAAY,QAAQ;GACrB,CAAC;AAGJ,MAAI,QAAQ,QACV,QAAO,IAAI,sBAA+B;GACxC,GAAG;GACH,SAAS,QAAQ;GAClB,CAAC;AAGJ,QAAM,IAAI,MACR,kEACD;IACA,CAAC,SAAS,CAAC;AAGd,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EAAE,MAAM,aAAa,CAAC;GAC7C;AAGF,oBAAmB;AACjB,eAAa;AACX,WAAQ,CAAC,MAAM;;GAEjB;CAEF,MAAM,WAAW,OAAO,UAA8B;AACpD,QAAM,QAAQ,CAAC,SAAS,MAAM;;CAGhC,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;AAGlB,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"use-generate-video.js","names":[],"sources":["../src/use-generate-video.ts"],"sourcesContent":["import { VideoGenerationClient } from '@tanstack/ai-client'\nimport {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n GenerationClientState,\n GenerationFetcher,\n InferGenerationOutput,\n VideoGenerateInput,\n VideoGenerateResult,\n VideoStatusInfo,\n} from '@tanstack/ai-client'\nimport type { Accessor } from 'solid-js'\n\n/**\n * Options for the useGenerateVideo hook.\n *\n * @template TOutput - The transformed output type (defaults to VideoGenerateResult)\n */\nexport interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {\n /** Connect-based adapter for streaming transport (server handles polling) */\n connection?: ConnectConnectionAdapter\n /** Direct async function that returns a completed video result */\n fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>\n /** Unique identifier for this generation instance */\n id?: string\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /**\n * Callback when video generation completes. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: VideoGenerateResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGenerateVideo hook.\n *\n * @template TOutput - The transformed output type (defaults to VideoGenerateResult)\n */\nexport interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {\n /** Trigger video generation */\n generate: (input: VideoGenerateInput) => Promise<void>\n /** The final video result (with URL), or null */\n result: Accessor<TOutput | null>\n /** The current job ID, or null */\n jobId: Accessor<string | null>\n /** Current video generation status info, or null */\n videoStatus: Accessor<VideoStatusInfo | null>\n /** Whether generation/polling is in progress */\n isLoading: Accessor<boolean>\n /** Current error, if any */\n error: Accessor<Error | undefined>\n /** Current state of the generation */\n status: Accessor<GenerationClientState>\n /** Abort the current generation/polling */\n stop: () => void\n /** Clear all state and return to idle */\n reset: () => void\n}\n\n/**\n * Solid hook for generating videos using AI models.\n *\n * Video generation is asynchronous: a job is created, then polled for status\n * until completion. This hook handles the full lifecycle.\n *\n * @example\n * ```tsx\n * import { useGenerateVideo } from '@tanstack/ai-solid'\n * import { fetchServerSentEvents } from '@tanstack/ai-client'\n *\n * function VideoGenerator() {\n * const { generate, result, videoStatus, isLoading } = useGenerateVideo({\n * connection: fetchServerSentEvents('/api/generate/video'),\n * onStatusUpdate: (status) => console.log(`Progress: ${status.progress}%`),\n * })\n *\n * return (\n * <div>\n * <button onClick={() => generate({ prompt: 'A flying car over a city' })}>\n * Generate Video\n * </button>\n * {isLoading() && videoStatus() && (\n * <p>Status: {videoStatus()!.status} ({videoStatus()!.progress}%)</p>\n * )}\n * {result() && <video src={result()!.url} controls />}\n * </div>\n * )\n * }\n * ```\n */\nexport function useGenerateVideo<\n TOnResult extends ((result: VideoGenerateResult) => any) | undefined =\n undefined,\n>(\n options: Omit<UseGenerateVideoOptions, 'onResult'> & {\n onResult?: TOnResult\n },\n): UseGenerateVideoReturn<\n InferGenerationOutput<VideoGenerateResult, TOnResult>\n> {\n type TOutput = InferGenerationOutput<VideoGenerateResult, TOnResult>\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [result, setResult] = createSignal<TOutput | null>(null)\n const [jobId, setJobId] = createSignal<string | null>(null)\n const [videoStatus, setVideoStatus] = createSignal<VideoStatusInfo | null>(\n null,\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<GenerationClientState>('idle')\n\n const client = createMemo(() => {\n // Conditional spread on `body`: VideoGenerationClientOptions.body\n // is a strict optional; EOPT forbids passing `T | undefined`.\n const baseOptions = {\n id: clientId,\n body: options.body,\n onResult: (r: VideoGenerateResult) => options.onResult?.(r),\n onError: (e: Error) => options.onError?.(e),\n onProgress: (p: number, m?: string) => options.onProgress?.(p, m),\n onChunk: (c: StreamChunk) => options.onChunk?.(c),\n onJobCreated: (id: string) => options.onJobCreated?.(id),\n onStatusUpdate: (s: VideoStatusInfo) => options.onStatusUpdate?.(s),\n onResultChange: setResult,\n onLoadingChange: setIsLoading,\n onErrorChange: setError,\n onStatusChange: setStatus,\n onJobIdChange: setJobId,\n onVideoStatusChange: setVideoStatus,\n }\n\n if (options.connection) {\n return new VideoGenerationClient<TOutput>({\n ...baseOptions,\n connection: options.connection,\n })\n }\n\n if (options.fetcher) {\n return new VideoGenerationClient<TOutput>({\n ...baseOptions,\n fetcher: options.fetcher,\n })\n }\n\n throw new Error(\n 'useGenerateVideo requires either a connection or fetcher option',\n )\n }, [clientId])\n\n // Sync body changes without recreating client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({\n ...(currentBody !== undefined && { body: currentBody }),\n })\n })\n\n // Cleanup on unmount: stop any in-flight requests\n createEffect(() => {\n return () => {\n client().stop()\n }\n })\n\n const generate = async (input: VideoGenerateInput) => {\n await client().generate(input)\n }\n\n const stop = () => {\n client().stop()\n }\n\n const reset = () => {\n client().reset()\n }\n\n return {\n generate,\n result,\n jobId,\n videoStatus,\n isLoading,\n error,\n status,\n stop,\n reset,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,SAAgB,iBAId,SAKA;CAEA,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,QAAQ,aAAa,aAA6B,KAAK;CAC9D,MAAM,CAAC,OAAO,YAAY,aAA4B,KAAK;CAC3D,MAAM,CAAC,aAAa,kBAAkB,aACpC,KACD;CACD,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CACpE,MAAM,CAAC,QAAQ,aAAa,aAAoC,OAAO;CAEvE,MAAM,SAAS,iBAAiB;EAG9B,MAAM,cAAc;GAClB,IAAI;GACJ,MAAM,QAAQ;GACd,WAAW,MAA2B,QAAQ,WAAW,EAAE;GAC3D,UAAU,MAAa,QAAQ,UAAU,EAAE;GAC3C,aAAa,GAAW,MAAe,QAAQ,aAAa,GAAG,EAAE;GACjE,UAAU,MAAmB,QAAQ,UAAU,EAAE;GACjD,eAAe,OAAe,QAAQ,eAAe,GAAG;GACxD,iBAAiB,MAAuB,QAAQ,iBAAiB,EAAE;GACnE,gBAAgB;GAChB,iBAAiB;GACjB,eAAe;GACf,gBAAgB;GAChB,eAAe;GACf,qBAAqB;GACtB;AAED,MAAI,QAAQ,WACV,QAAO,IAAI,sBAA+B;GACxC,GAAG;GACH,YAAY,QAAQ;GACrB,CAAC;AAGJ,MAAI,QAAQ,QACV,QAAO,IAAI,sBAA+B;GACxC,GAAG;GACH,SAAS,QAAQ;GAClB,CAAC;AAGJ,QAAM,IAAI,MACR,kEACD;IACA,CAAC,SAAS,CAAC;AAGd,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EACrB,GAAI,gBAAgB,UAAa,EAAE,MAAM,aAAa,EACvD,CAAC;GACF;AAGF,oBAAmB;AACjB,eAAa;AACX,WAAQ,CAAC,MAAM;;GAEjB;CAEF,MAAM,WAAW,OAAO,UAA8B;AACpD,QAAM,QAAQ,CAAC,SAAS,MAAM;;CAGhC,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;AAGlB,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
@@ -54,7 +54,7 @@ function useGeneration(options) {
54
54
  }, [clientId]);
55
55
  createEffect(() => {
56
56
  const currentBody = options.body;
57
- client().updateOptions({ body: currentBody });
57
+ client().updateOptions({ ...currentBody !== void 0 && { body: currentBody } });
58
58
  });
59
59
  createEffect(() => {
60
60
  return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-generation.js","names":["clientOptions: GenerationClientOptions<TInput, TResult, TOutput>"],"sources":["../src/use-generation.ts"],"sourcesContent":["import { GenerationClient } from '@tanstack/ai-client'\nimport {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n InferGenerationOutput,\n} from '@tanstack/ai-client'\nimport type { Accessor } from 'solid-js'\n\n/**\n * Options for the useGeneration hook.\n *\n * Accepts either a `connection` (streaming transport) or a `fetcher` (direct async call).\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The transformed output type (defaults to TResult)\n */\nexport interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for one-shot generation (no streaming protocol needed) */\n fetcher?: GenerationFetcher<TInput, TResult>\n /** Unique identifier for this generation instance */\n id?: string\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /**\n * Callback when a result is received. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGeneration hook.\n *\n * @template TOutput - The output type (possibly transformed from the raw result)\n */\nexport interface UseGenerationReturn<TOutput> {\n /** Trigger a generation request */\n generate: (input: Record<string, any>) => Promise<void>\n /** The generation result, or null if not yet generated */\n result: Accessor<TOutput | null>\n /** Whether a generation is currently in progress */\n isLoading: Accessor<boolean>\n /** Current error, if any */\n error: Accessor<Error | undefined>\n /** Current state of the generation client */\n status: Accessor<GenerationClientState>\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n}\n\n/**\n * Generic Solid hook for one-shot generation tasks.\n *\n * This is the base hook used by `useGenerateImage`, `useGenerateSpeech`,\n * `useTranscription`, and `useSummarize`. You can also use it directly\n * for custom generation types.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The transformed output type (defaults to TResult)\n *\n * @example\n * ```tsx\n * const { generate, result, isLoading } = useGeneration<MyInput, MyResult>({\n * connection: fetchServerSentEvents('/api/generate/custom'),\n * })\n *\n * await generate({ prompt: 'Hello' })\n * ```\n */\nexport function useGeneration<\n TInput extends Record<string, any>,\n TResult,\n TOnResult extends ((result: TResult) => any) | undefined = undefined,\n>(\n options: Omit<UseGenerationOptions<TInput, TResult>, 'onResult'> & {\n onResult?: TOnResult\n },\n): UseGenerationReturn<InferGenerationOutput<TResult, TOnResult>> {\n type TOutput = InferGenerationOutput<TResult, TOnResult>\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [result, setResult] = createSignal<TOutput | null>(null)\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<GenerationClientState>('idle')\n\n const client = createMemo(() => {\n const clientOptions: GenerationClientOptions<TInput, TResult, TOutput> = {\n id: clientId,\n body: options.body,\n onResult: (r: TResult) => options.onResult?.(r),\n onError: (e: Error) => options.onError?.(e),\n onProgress: (p: number, m?: string) => options.onProgress?.(p, m),\n onChunk: (c: StreamChunk) => options.onChunk?.(c),\n onResultChange: setResult,\n onLoadingChange: setIsLoading,\n onErrorChange: setError,\n onStatusChange: setStatus,\n }\n\n if (options.connection) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n connection: options.connection,\n })\n }\n\n if (options.fetcher) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n fetcher: options.fetcher,\n })\n }\n\n throw new Error(\n 'useGeneration requires either a connection or fetcher option',\n )\n }, [clientId])\n\n // Sync body changes without recreating client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({ body: currentBody })\n })\n\n // Cleanup on unmount: stop any in-flight requests\n createEffect(() => {\n return () => {\n client().stop()\n }\n })\n\n const generate = async (input: TInput) => {\n await client().generate(input)\n }\n\n const stop = () => {\n client().stop()\n }\n\n const reset = () => {\n client().reset()\n }\n\n return {\n generate: generate as (input: Record<string, any>) => Promise<void>,\n result,\n isLoading,\n error,\n status,\n stop,\n reset,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6FA,SAAgB,cAKd,SAGgE;CAEhE,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,QAAQ,aAAa,aAA6B,KAAK;CAC9D,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CACpE,MAAM,CAAC,QAAQ,aAAa,aAAoC,OAAO;CAEvE,MAAM,SAAS,iBAAiB;EAC9B,MAAMA,gBAAmE;GACvE,IAAI;GACJ,MAAM,QAAQ;GACd,WAAW,MAAe,QAAQ,WAAW,EAAE;GAC/C,UAAU,MAAa,QAAQ,UAAU,EAAE;GAC3C,aAAa,GAAW,MAAe,QAAQ,aAAa,GAAG,EAAE;GACjE,UAAU,MAAmB,QAAQ,UAAU,EAAE;GACjD,gBAAgB;GAChB,iBAAiB;GACjB,eAAe;GACf,gBAAgB;GACjB;AAED,MAAI,QAAQ,WACV,QAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,YAAY,QAAQ;GACrB,CAAC;AAGJ,MAAI,QAAQ,QACV,QAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,SAAS,QAAQ;GAClB,CAAC;AAGJ,QAAM,IAAI,MACR,+DACD;IACA,CAAC,SAAS,CAAC;AAGd,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EAAE,MAAM,aAAa,CAAC;GAC7C;AAGF,oBAAmB;AACjB,eAAa;AACX,WAAQ,CAAC,MAAM;;GAEjB;CAEF,MAAM,WAAW,OAAO,UAAkB;AACxC,QAAM,QAAQ,CAAC,SAAS,MAAM;;CAGhC,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;AAGlB,QAAO;EACK;EACV;EACA;EACA;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"use-generation.js","names":["clientOptions: GenerationClientOptions<TInput, TResult, TOutput>"],"sources":["../src/use-generation.ts"],"sourcesContent":["import { GenerationClient } from '@tanstack/ai-client'\nimport {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n InferGenerationOutput,\n} from '@tanstack/ai-client'\nimport type { Accessor } from 'solid-js'\n\n/**\n * Options for the useGeneration hook.\n *\n * Accepts either a `connection` (streaming transport) or a `fetcher` (direct async call).\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The transformed output type (defaults to TResult)\n */\nexport interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {\n /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */\n connection?: ConnectConnectionAdapter\n /** Direct async function for one-shot generation (no streaming protocol needed) */\n fetcher?: GenerationFetcher<TInput, TResult>\n /** Unique identifier for this generation instance */\n id?: string\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n /**\n * Callback when a result is received. Can optionally return a transformed value.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n}\n\n/**\n * Return type for the useGeneration hook.\n *\n * @template TOutput - The output type (possibly transformed from the raw result)\n */\nexport interface UseGenerationReturn<TOutput> {\n /** Trigger a generation request */\n generate: (input: Record<string, any>) => Promise<void>\n /** The generation result, or null if not yet generated */\n result: Accessor<TOutput | null>\n /** Whether a generation is currently in progress */\n isLoading: Accessor<boolean>\n /** Current error, if any */\n error: Accessor<Error | undefined>\n /** Current state of the generation client */\n status: Accessor<GenerationClientState>\n /** Abort the current generation */\n stop: () => void\n /** Clear result, error, and return to idle */\n reset: () => void\n}\n\n/**\n * Generic Solid hook for one-shot generation tasks.\n *\n * This is the base hook used by `useGenerateImage`, `useGenerateSpeech`,\n * `useTranscription`, and `useSummarize`. You can also use it directly\n * for custom generation types.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n * @template TOutput - The transformed output type (defaults to TResult)\n *\n * @example\n * ```tsx\n * const { generate, result, isLoading } = useGeneration<MyInput, MyResult>({\n * connection: fetchServerSentEvents('/api/generate/custom'),\n * })\n *\n * await generate({ prompt: 'Hello' })\n * ```\n */\nexport function useGeneration<\n TInput extends Record<string, any>,\n TResult,\n TOnResult extends ((result: TResult) => any) | undefined = undefined,\n>(\n options: Omit<UseGenerationOptions<TInput, TResult>, 'onResult'> & {\n onResult?: TOnResult\n },\n): UseGenerationReturn<InferGenerationOutput<TResult, TOnResult>> {\n type TOutput = InferGenerationOutput<TResult, TOnResult>\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [result, setResult] = createSignal<TOutput | null>(null)\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<GenerationClientState>('idle')\n\n const client = createMemo(() => {\n // Conditional spread on `body`: `GenerationClientOptions.body` is a\n // strict optional (`body?: Record<string, any>`) and EOPT forbids\n // assigning the source `T | undefined` directly.\n const clientOptions: GenerationClientOptions<TInput, TResult, TOutput> = {\n id: clientId,\n body: options.body,\n onResult: (r: TResult) => options.onResult?.(r),\n onError: (e: Error) => options.onError?.(e),\n onProgress: (p: number, m?: string) => options.onProgress?.(p, m),\n onChunk: (c: StreamChunk) => options.onChunk?.(c),\n onResultChange: setResult,\n onLoadingChange: setIsLoading,\n onErrorChange: setError,\n onStatusChange: setStatus,\n }\n\n if (options.connection) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n connection: options.connection,\n })\n }\n\n if (options.fetcher) {\n return new GenerationClient<TInput, TResult, TOutput>({\n ...clientOptions,\n fetcher: options.fetcher,\n })\n }\n\n throw new Error(\n 'useGeneration requires either a connection or fetcher option',\n )\n }, [clientId])\n\n // Sync body changes without recreating client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({\n ...(currentBody !== undefined && { body: currentBody }),\n })\n })\n\n // Cleanup on unmount: stop any in-flight requests\n createEffect(() => {\n return () => {\n client().stop()\n }\n })\n\n const generate = async (input: TInput) => {\n await client().generate(input)\n }\n\n const stop = () => {\n client().stop()\n }\n\n const reset = () => {\n client().reset()\n }\n\n return {\n generate: generate as (input: Record<string, any>) => Promise<void>,\n result,\n isLoading,\n error,\n status,\n stop,\n reset,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6FA,SAAgB,cAKd,SAGgE;CAEhE,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,QAAQ,aAAa,aAA6B,KAAK;CAC9D,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CACpE,MAAM,CAAC,QAAQ,aAAa,aAAoC,OAAO;CAEvE,MAAM,SAAS,iBAAiB;EAI9B,MAAMA,gBAAmE;GACvE,IAAI;GACJ,MAAM,QAAQ;GACd,WAAW,MAAe,QAAQ,WAAW,EAAE;GAC/C,UAAU,MAAa,QAAQ,UAAU,EAAE;GAC3C,aAAa,GAAW,MAAe,QAAQ,aAAa,GAAG,EAAE;GACjE,UAAU,MAAmB,QAAQ,UAAU,EAAE;GACjD,gBAAgB;GAChB,iBAAiB;GACjB,eAAe;GACf,gBAAgB;GACjB;AAED,MAAI,QAAQ,WACV,QAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,YAAY,QAAQ;GACrB,CAAC;AAGJ,MAAI,QAAQ,QACV,QAAO,IAAI,iBAA2C;GACpD,GAAG;GACH,SAAS,QAAQ;GAClB,CAAC;AAGJ,QAAM,IAAI,MACR,+DACD;IACA,CAAC,SAAS,CAAC;AAGd,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EACrB,GAAI,gBAAgB,UAAa,EAAE,MAAM,aAAa,EACvD,CAAC;GACF;AAGF,oBAAmB;AACjB,eAAa;AACX,WAAQ,CAAC,MAAM;;GAEjB;CAEF,MAAM,WAAW,OAAO,UAAkB;AACxC,QAAM,QAAQ,CAAC,SAAS,MAAM;;CAGhC,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;AAGlB,QAAO;EACK;EACV;EACA;EACA;EACA;EACA;EACA;EACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-solid",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Solid hooks for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -31,11 +31,11 @@
31
31
  "streaming"
32
32
  ],
33
33
  "dependencies": {
34
- "@tanstack/ai-client": "0.11.2"
34
+ "@tanstack/ai-client": "0.11.4"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "solid-js": ">=1.9.10",
38
- "@tanstack/ai": "^0.20.0"
38
+ "@tanstack/ai": "^0.21.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@solidjs/testing-library": "^0.8.10",
@@ -47,7 +47,7 @@
47
47
  "tsdown": "^0.17.0-beta.6",
48
48
  "typescript": "5.9.3",
49
49
  "vitest": "^4.0.14",
50
- "@tanstack/ai": "0.20.0"
50
+ "@tanstack/ai": "0.21.0"
51
51
  },
52
52
  "scripts": {
53
53
  "clean": "premove ./build ./dist",
package/src/index.ts CHANGED
@@ -52,8 +52,12 @@ export {
52
52
  fetchServerSentEvents,
53
53
  fetchHttpStream,
54
54
  stream,
55
+ rpcStream,
55
56
  createChatClientOptions,
56
57
  type ConnectionAdapter,
58
+ type ConnectConnectionAdapter,
59
+ type SubscribeConnectionAdapter,
60
+ type RunAgentInputContext,
57
61
  type FetchConnectionOptions,
58
62
  type InferChatMessages,
59
63
  type GenerationClientState,
package/src/use-chat.ts CHANGED
@@ -64,12 +64,20 @@ export function useChat<
64
64
  // in-place mutations propagate. When the user clears a callback (sets it to
65
65
  // undefined), `?.` no-ops.
66
66
  const client = createMemo(() => {
67
+ // Build options with conditional spreads for fields whose source
68
+ // type is `T | undefined` but the ChatClient target uses a strict
69
+ // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects
70
+ // assigning `undefined` to those, so we omit the key when absent.
67
71
  return new ChatClient({
68
72
  connection: options.connection,
69
73
  id: clientId,
70
- initialMessages: options.initialMessages,
74
+ ...(options.initialMessages !== undefined && {
75
+ initialMessages: options.initialMessages,
76
+ }),
71
77
  body: options.body,
72
- forwardedProps: options.forwardedProps,
78
+ ...(options.forwardedProps !== undefined && {
79
+ forwardedProps: options.forwardedProps,
80
+ }),
73
81
  onResponse: (response) => options.onResponse?.(response),
74
82
  onChunk: (chunk: StreamChunk) => {
75
83
  options.onChunk?.(chunk)
@@ -83,7 +91,9 @@ export function useChat<
83
91
  tools: options.tools,
84
92
  onCustomEvent: (eventType, data, context) =>
85
93
  options.onCustomEvent?.(eventType, data, context),
86
- streamProcessor: options.streamProcessor,
94
+ ...(options.streamProcessor !== undefined && {
95
+ streamProcessor: options.streamProcessor,
96
+ }),
87
97
  onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {
88
98
  setMessages(newMessages)
89
99
  },
@@ -114,9 +124,13 @@ export function useChat<
114
124
  // Both populate the same wire payload; `forwardedProps` is preferred
115
125
  // and `body` is deprecated but still supported.
116
126
  createEffect(() => {
127
+ // Conditional spread: `updateOptions` declares strict-optional
128
+ // fields and rejects explicit `undefined` under EOPT.
117
129
  client().updateOptions({
118
- body: options.body,
119
- forwardedProps: options.forwardedProps,
130
+ ...(options.body !== undefined && { body: options.body }),
131
+ ...(options.forwardedProps !== undefined && {
132
+ forwardedProps: options.forwardedProps,
133
+ }),
120
134
  })
121
135
  })
122
136
 
@@ -208,15 +222,15 @@ export function useChat<
208
222
  const list = messages()
209
223
  let lastUserIndex = -1
210
224
  for (let i = list.length - 1; i >= 0; i--) {
211
- if (list[i]!.role === 'user') {
225
+ if (list[i]?.role === 'user') {
212
226
  lastUserIndex = i
213
227
  break
214
228
  }
215
229
  }
216
230
  if (lastUserIndex === -1) return null
217
231
  for (let i = list.length - 1; i > lastUserIndex; i--) {
218
- const m = list[i]!
219
- if (m.role !== 'assistant') continue
232
+ const m = list[i]
233
+ if (m?.role !== 'assistant') continue
220
234
  const part = m.parts.find(
221
235
  (p): p is StructuredOutputPart => p.type === 'structured-output',
222
236
  )
@@ -240,6 +254,7 @@ export function useChat<
240
254
 
241
255
  // partial / final are runtime-tracked unconditionally; the conditional
242
256
  // return type hides them when no `outputSchema` is supplied.
257
+ // eslint-disable-next-line no-restricted-syntax -- primitive return shape diverges from generic UseChatReturn<TTools, TSchema>; TS can't structurally narrow the conditional partial/final fields
243
258
  return {
244
259
  messages,
245
260
  sendMessage,
@@ -132,6 +132,8 @@ export function useGenerateVideo<
132
132
  const [status, setStatus] = createSignal<GenerationClientState>('idle')
133
133
 
134
134
  const client = createMemo(() => {
135
+ // Conditional spread on `body`: VideoGenerationClientOptions.body
136
+ // is a strict optional; EOPT forbids passing `T | undefined`.
135
137
  const baseOptions = {
136
138
  id: clientId,
137
139
  body: options.body,
@@ -171,7 +173,9 @@ export function useGenerateVideo<
171
173
  // Sync body changes without recreating client
172
174
  createEffect(() => {
173
175
  const currentBody = options.body
174
- client().updateOptions({ body: currentBody })
176
+ client().updateOptions({
177
+ ...(currentBody !== undefined && { body: currentBody }),
178
+ })
175
179
  })
176
180
 
177
181
  // Cleanup on unmount: stop any in-flight requests
@@ -110,6 +110,9 @@ export function useGeneration<
110
110
  const [status, setStatus] = createSignal<GenerationClientState>('idle')
111
111
 
112
112
  const client = createMemo(() => {
113
+ // Conditional spread on `body`: `GenerationClientOptions.body` is a
114
+ // strict optional (`body?: Record<string, any>`) and EOPT forbids
115
+ // assigning the source `T | undefined` directly.
113
116
  const clientOptions: GenerationClientOptions<TInput, TResult, TOutput> = {
114
117
  id: clientId,
115
118
  body: options.body,
@@ -145,7 +148,9 @@ export function useGeneration<
145
148
  // Sync body changes without recreating client
146
149
  createEffect(() => {
147
150
  const currentBody = options.body
148
- client().updateOptions({ body: currentBody })
151
+ client().updateOptions({
152
+ ...(currentBody !== undefined && { body: currentBody }),
153
+ })
149
154
  })
150
155
 
151
156
  // Cleanup on unmount: stop any in-flight requests