| 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 = useId() // Available in Vue 3.5+ |
| 45 | const clientId = options.id || hookId |
| 46 | |
| 47 | const messages = shallowRef<Array<UIMessage<TTools>>>( |
| 48 | options.initialMessages || [], |
| 49 | ) |
| 50 | const isLoading = shallowRef(false) |
| 51 | const error = shallowRef<Error | undefined>(undefined) |
| 52 | const status = shallowRef<ChatClientState>('ready') |
| 53 | const isSubscribed = shallowRef(false) |
| 54 | const connectionStatus = shallowRef<ConnectionStatus>('disconnected') |
| 55 | const sessionGenerating = shallowRef(false) |
| 56 | |
| 57 | // Structured-output `partial` / `final` are derived from `messages` — |
| 58 | // specifically from the structured-output part on the latest assistant |
| 59 | // message (the one after the most recent user message). This keeps |
| 60 | // history coherent: every assistant turn carries its own typed |
| 61 | // structured-output part, and the hook-level sugar always reflects the |
| 62 | // freshest turn without a separate reset signal. |
| 63 | type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>> |
| 64 | type Final = InferSchemaType<NonNullable<TSchema>> |
| 65 | |
| 66 | // Create ChatClient instance with callbacks to sync state. |
| 67 | // Every user-provided callback is wrapped so the LATEST `options.xxx` value |
| 68 | // is read at call time. Direct assignment would freeze the callback to the |
| 69 | // reference we saw at setup time; the wrapper lets reactive `options` or |
| 70 | // in-place mutations propagate. When the user clears a callback (sets it to |
| 71 | // undefined), `?.` no-ops — unlike `client.updateOptions`, which silently |
| 72 | // skips undefined and leaves the old callback installed. |
| 73 | // |
| 74 | // Conditional spreads for `initialMessages`, `body`, `forwardedProps`, and |
| 75 | // `tools`: the ChatClient target declares those as strict-optional |
| 76 | // (`field?: T`), so under `exactOptionalPropertyTypes` we omit the key when |
| 77 | // the source value is `undefined` instead of assigning `undefined`. |
| 78 | const transport = options.connection |
| 79 | ? { connection: options.connection } |
| 80 | : { fetcher: options.fetcher } |
| 81 | |
| 82 | const client = new ChatClient<TTools, TContext>({ |
| 83 | devtoolsBridgeFactory: createChatDevtoolsBridge, |
| 84 | ...transport, |
| 85 | id: clientId, |
| 86 | ...(options.initialMessages !== undefined && { |
| 87 | initialMessages: options.initialMessages, |
| 88 | }), |
| 89 | ...(options.persistence !== undefined && { |
| 90 | persistence: options.persistence, |