| 31 | } from './types' |
| 32 | |
| 33 | export function useChat< |
| 34 | TTools extends ReadonlyArray<AnyClientTool> = any, |
| 35 | TSchema extends SchemaInput | undefined = undefined, |
| 36 | TContext = InferredClientContext<TTools>, |
| 37 | >( |
| 38 | options: UseChatOptions<TTools, TSchema, TContext> = {} as UseChatOptions< |
| 39 | TTools, |
| 40 | TSchema, |
| 41 | TContext |
| 42 | >, |
| 43 | ): UseChatReturn<TTools, TSchema> { |
| 44 | const hookId = createUniqueId() |
| 45 | const clientId = options.id || hookId |
| 46 | |
| 47 | const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>( |
| 48 | options.initialMessages || [], |
| 49 | ) |
| 50 | const [isLoading, setIsLoading] = createSignal(false) |
| 51 | const [error, setError] = createSignal<Error | undefined>(undefined) |
| 52 | const [status, setStatus] = createSignal<ChatClientState>('ready') |
| 53 | const [isSubscribed, setIsSubscribed] = createSignal(false) |
| 54 | const [connectionStatus, setConnectionStatus] = |
| 55 | createSignal<ConnectionStatus>('disconnected') |
| 56 | const [sessionGenerating, setSessionGenerating] = createSignal(false) |
| 57 | |
| 58 | // Structured-output `partial` / `final` are derived from `messages` — |
| 59 | // specifically from the structured-output part on the latest assistant |
| 60 | // message (the one after the most recent user message). Per-turn parts |
| 61 | // keep history coherent without a separate reset signal. |
| 62 | type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>> |
| 63 | type Final = InferSchemaType<NonNullable<TSchema>> |
| 64 | |
| 65 | // Create ChatClient instance with callbacks to sync state. |
| 66 | // Every user-provided callback is wrapped so the LATEST `options.xxx` value |
| 67 | // is read at call time. Direct assignment would freeze the callback to the |
| 68 | // reference we saw at creation; the wrapper lets reactive `options` or |
| 69 | // in-place mutations propagate. When the user clears a callback (sets it to |
| 70 | // undefined), `?.` no-ops. |
| 71 | const client = createMemo(() => { |
| 72 | // Build options with conditional spreads for fields whose source |
| 73 | // type is `T | undefined` but the ChatClient target uses a strict |
| 74 | // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects |
| 75 | // assigning `undefined` to those, so we omit the key when absent. |
| 76 | const transport = options.connection |
| 77 | ? { connection: options.connection } |
| 78 | : { fetcher: options.fetcher } |
| 79 | return new ChatClient<TTools, TContext>({ |
| 80 | devtoolsBridgeFactory: createChatDevtoolsBridge, |
| 81 | ...transport, |
| 82 | id: clientId, |
| 83 | ...(options.initialMessages !== undefined && { |
| 84 | initialMessages: options.initialMessages, |
| 85 | }), |
| 86 | ...(options.persistence !== undefined && { |
| 87 | persistence: options.persistence, |
| 88 | }), |
| 89 | body: options.body, |
| 90 | ...(options.threadId !== undefined && { threadId: options.threadId }), |