| 23 | } from './types' |
| 24 | |
| 25 | export function useChat< |
| 26 | TTools extends ReadonlyArray<AnyClientTool> = any, |
| 27 | TContext = InferredClientContext<TTools>, |
| 28 | >(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools> { |
| 29 | const hookId = useId() |
| 30 | const clientId = options.id || hookId |
| 31 | |
| 32 | const [messages, setMessages] = useState<Array<UIMessage<TTools>>>( |
| 33 | options.initialMessages || [], |
| 34 | ) |
| 35 | const [isLoading, setIsLoading] = useState(false) |
| 36 | const [error, setError] = useState<Error | undefined>(undefined) |
| 37 | const [status, setStatus] = useState<ChatClientState>('ready') |
| 38 | const [isSubscribed, setIsSubscribed] = useState(false) |
| 39 | const [connectionStatus, setConnectionStatus] = |
| 40 | useState<ConnectionStatus>('disconnected') |
| 41 | const [sessionGenerating, setSessionGenerating] = useState(false) |
| 42 | |
| 43 | // Track current messages in a ref to preserve them when client is recreated |
| 44 | const messagesRef = useRef<Array<UIMessage<TTools>>>( |
| 45 | options.initialMessages || [], |
| 46 | ) |
| 47 | const isFirstMountRef = useRef(true) |
| 48 | const activeClientRef = useRef<ChatClient | null>(null) |
| 49 | const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>( |
| 50 | null, |
| 51 | ) |
| 52 | const optionsRef = useRef<UseChatOptions<TTools, TContext>>(options) |
| 53 | |
| 54 | optionsRef.current = options |
| 55 | |
| 56 | useEffect(() => { |
| 57 | messagesRef.current = messages |
| 58 | }, [messages]) |
| 59 | |
| 60 | const client = useMemo(() => { |
| 61 | const messagesToUse = options.initialMessages || [] |
| 62 | isFirstMountRef.current = false |
| 63 | |
| 64 | // Build options with conditional spreads for fields whose source |
| 65 | // type is `T | undefined` but the ChatClient target uses a strict |
| 66 | // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects |
| 67 | // assigning `undefined` to those, so we omit the key when absent. |
| 68 | const initialOptions = optionsRef.current |
| 69 | const transport = initialOptions.connection |
| 70 | ? { connection: initialOptions.connection } |
| 71 | : { fetcher: initialOptions.fetcher } |
| 72 | |
| 73 | const instance = new ChatClient<TTools, TContext>({ |
| 74 | devtoolsBridgeFactory: createChatDevtoolsBridge, |
| 75 | ...transport, |
| 76 | id: clientId, |
| 77 | initialMessages: messagesToUse, |
| 78 | ...(initialOptions.body !== undefined && { body: initialOptions.body }), |
| 79 | ...(initialOptions.threadId !== undefined && { |
| 80 | threadId: initialOptions.threadId, |
| 81 | }), |
| 82 | ...(initialOptions.forwardedProps !== undefined && { |