Skip to content

Conversation

@nikas-belogolov
Copy link

@nikas-belogolov nikas-belogolov commented Jan 25, 2026

🎯 Changes

Added a status property to useChat to track the generation lifecycle (ready, submitted, streaming, error).

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Exposes a unified generation status (ready, submitted, streaming, error) across chat hooks and client, with a status observer callback and a status getter.
  • Tests

    • Adds cross-framework tests validating status transitions, error/error-recovery handling, stop/reset behavior, and initial readiness.
  • Chores

    • Bumps minor versions of ai-* packages.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

Adds a ChatClientState lifecycle type ('ready' | 'submitted' | 'streaming' | 'error'); exposes a new status field across useChat/createChat APIs; implements client-side status tracking with onStatusChange and getStatus() on ChatClient; updates tests and test-utils to assert status transitions.

Changes

Cohort / File(s) Summary
Changeset / Release note
\.changeset/shy-ravens-sink.md
Bumps minor package versions and documents the new status property on useChat.
AI Client core
packages/typescript/ai-client/src/chat-client.ts, packages/typescript/ai-client/src/types.ts, packages/typescript/ai-client/src/index.ts
Add exported ChatClientState type; add onStatusChange?: (status: ChatClientState) => void to options; add private status + setStatus(); emit transitions (submitted, streaming, ready, error); expose getStatus().
Framework integrations (shared)
packages/typescript/*/src/*types.ts, packages/typescript/*/src/*use-chat*.ts, packages/typescript/*/src/create-chat.svelte.ts
Import/re-export ChatClientState; omit onStatusChange from public option mappings; wire client onStatusChange to framework state; expose status in UseChat/CreateChat return shapes; wrap onFinish/onError to preserve callbacks.
Preact
packages/typescript/ai-preact/src/types.ts, packages/typescript/ai-preact/src/use-chat.ts, packages/typescript/ai-preact/tests/use-chat.test.ts
Add status: ChatClientState to return type; hook subscribes to onStatusChange; tests assert lifecycle transitions.
React
packages/typescript/ai-react/src/types.ts, packages/typescript/ai-react/src/use-chat.ts, packages/typescript/ai-react/tests/use-chat.test.ts
Add status: ChatClientState to return type; hook subscribes to onStatusChange; tests updated to assert transitions.
Svelte
packages/typescript/ai-svelte/src/types.ts, packages/typescript/ai-svelte/src/create-chat.svelte.ts, packages/typescript/ai-svelte/tests/use-chat.test.ts
Introduce reactive status initialized to 'ready'; expose get status() on returned chat object; types and tests updated.
Solid
packages/typescript/ai-solid/src/types.ts, packages/typescript/ai-solid/src/use-chat.ts, packages/typescript/ai-solid/tests/*, packages/typescript/ai-solid/tests/test-utils.ts
Introduce signal-based status: Accessor<ChatClientState>; update hook, test-utils, and tests to use status().
Vue
packages/typescript/ai-vue/src/types.ts, packages/typescript/ai-vue/src/use-chat.ts, packages/typescript/ai-vue/tests/*, packages/typescript/ai-vue/tests/test-utils.ts
Add shallowRef-based status exposed as readonly/DeepReadonly; wire onStatusChange; update types, test-utils, and tests to assert transitions.
Tests & test-utils
packages/typescript/*/tests/*use-chat.test.ts, packages/typescript/ai-client/tests/chat-client.test.ts, packages/typescript/*/tests/test-utils.ts
Add/extend tests asserting initial 'ready', transitions (submitted/streamingready), error state, and stop/reset behavior; test-utils expose status where appropriate.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant ChatClient as ChatClient
  participant UseChat as UseChat Hook
  participant UI as UI

  Client->>ChatClient: sendMessage(...)
  Note right of ChatClient: setStatus('submitted')
  ChatClient->>UseChat: onStatusChange('submitted')
  ChatClient->>ChatClient: onStreamStart -> setStatus('streaming')
  ChatClient->>UseChat: onStatusChange('streaming')
  ChatClient->>ChatClient: onStreamEnd -> setStatus('ready')
  ChatClient->>UseChat: onStatusChange('ready')
  ChatClient->>UseChat: onError -> setStatus('error')
  UseChat->>UI: expose status ('ready'|'submitted'|'streaming'|'error')
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • jherr

Poem

🐰
I hop from ready to submitted with cheer,
I twitch when streaming as chunks appear,
I flinch when errors give me a start,
Then back to ready — a thumping heart,
A rabbit's clap for lifecycle art 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a status property to useChat with specific lifecycle states.
Description check ✅ Passed The description follows the template with completed sections: concise changes summary, all checklist items addressed, and release impact indicated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/typescript/ai-preact/src/types.ts (1)

27-31: Inconsistency with React: onStreamStart should be omitted from UseChatOptions.

The React adapter omits 'onStreamStart' from UseChatOptions (since the hook manages it internally to track status transitions), but the Preact adapter does not. This inconsistency allows Preact users to pass onStreamStart while React users cannot.

Suggested fix
 export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> =
   Omit<
     ChatClientOptions<TTools>,
-    'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'
+    'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStreamStart'
   >
packages/typescript/ai-svelte/src/types.ts (1)

27-32: Inconsistency with React: onStreamStart should be omitted from CreateChatOptions.

Same issue as in Preact — the React adapter omits 'onStreamStart' from options, but Svelte does not. For consistency, all framework adapters should handle internal callbacks the same way.

Suggested fix
 export type CreateChatOptions<
   TTools extends ReadonlyArray<AnyClientTool> = any,
 > = Omit<
   ChatClientOptions<TTools>,
-  'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'
+  'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStreamStart'
 >
packages/typescript/ai-client/src/chat-client.ts (1)

31-81: Keep onStreamStart in sync in updateOptions.

updateOptions updates onResponse/onChunk/onFinish/onError but omits onStreamStart, so refreshed callbacks won’t be picked up. Please add it for parity with the other callbacks.

🛠️ Suggested fix
   updateOptions(options: {
     connection?: ConnectionAdapter
     body?: Record<string, any>
     tools?: ReadonlyArray<AnyClientTool>
     onResponse?: (response?: Response) => void | Promise<void>
     onChunk?: (chunk: StreamChunk) => void
     onFinish?: (message: UIMessage) => void
+    onStreamStart?: () => void
     onError?: (error: Error) => void
   }): void {
@@
     if (options.onFinish !== undefined) {
       this.callbacksRef.current.onFinish = options.onFinish
     }
+    if (options.onStreamStart !== undefined) {
+      this.callbacksRef.current.onStreamStart = options.onStreamStart
+    }
     if (options.onError !== undefined) {
       this.callbacksRef.current.onError = options.onError
     }
   }
packages/typescript/ai-preact/src/use-chat.ts (1)

1-10: Fix ESLint import order and type-only import usage.

The linter reports two errors: @tanstack/ai type import must occur after preact/hooks, and ChatClientState is only used as a type and should be imported with type.

🔧 Proposed import fix
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, ChatClientState } from '@tanstack/ai-client'
-import {
-  useCallback,
-  useEffect,
-  useId,
-  useMemo,
-  useRef,
-  useState,
-} from 'preact/hooks'
+import {
+  useCallback,
+  useEffect,
+  useId,
+  useMemo,
+  useRef,
+  useState,
+} from 'preact/hooks'
+import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-client/src/types.ts`:
- Around line 203-206: The JSDoc for the onStreamStart callback is missing a
space before the closing "*/"; update the comment above the onStreamStart?: ()
=> void declaration to match the project's JSDoc style (ensure a space before
the closing */, e.g. " * Callback when stream starts */" formatting) so it is
consistent with other comments in the file.

In `@packages/typescript/ai-svelte/src/create-chat.svelte.ts`:
- Around line 78-80: The onStreamStart handler currently only sets status =
'streaming' but doesn't call the user-provided callback; update the handler to
mirror the onFinish/onError pattern by invoking the passed-in callback (e.g.
call opts.onStreamStart?.() or onStreamStart?.() depending on how callbacks are
named) after setting status so user code is notified; reference the
onStreamStart handler and the existing onFinish/onError invocation pattern to
implement the call.
- Around line 1-2: Reorder the imports so that symbols from
'@tanstack/ai-client' come before '@tanstack/ai' and import ChatClientState as a
type-only import; specifically, update the top-level import statement to import
ChatClient and (type) ChatClientState from '@tanstack/ai-client' and then import
AnyClientTool and ModelMessage from '@tanstack/ai', keeping the rest of the file
(e.g. the $state<ChatClientState> usage) unchanged.

In `@packages/typescript/ai-vue/src/use-chat.ts`:
- Around line 48-50: The onStreamStart handler currently only sets status.value
= 'streaming' and ignores any user callback; update the onStreamStart
implementation to set status.value = 'streaming' then invoke the user-provided
callback (e.g., props.onStreamStart or options.onStreamStart /
callbacks.onStreamStart if available) in a safe way (check for existence before
calling) so consumer callbacks are forwarded while preserving the existing
status update.
🧹 Nitpick comments (5)
packages/typescript/ai-svelte/tests/use-chat.test.ts (1)

149-160: Consider adding status transition coverage for Svelte.

This test only verifies the initial 'ready' state. To catch regressions and align with other framework tests, consider driving a mock stream/error to assert 'submitted' → 'streaming' → 'ready'/'error' transitions.

packages/typescript/ai-vue/tests/use-chat.test.ts (1)

1-10: Fix import order to satisfy ESLint rules.

Type imports should occur after regular imports per the project's import order configuration.

♻️ Suggested fix
-import type { ModelMessage } from '@tanstack/ai'
 import { flushPromises } from '@vue/test-utils'
 import { describe, expect, it, vi } from 'vitest'
-import type { UIMessage } from '../src/types'
 import {
   createMockConnectionAdapter,
   createTextChunks,
   createToolCallChunks,
   renderUseChat,
 } from './test-utils'
+import type { ModelMessage } from '@tanstack/ai'
+import type { UIMessage } from '../src/types'
packages/typescript/ai-vue/tests/test-utils.ts (1)

1-5: Fix import order to satisfy ESLint rules.

Type imports should occur after regular imports per the project's import order configuration.

♻️ Suggested fix
-import type { UIMessage } from '@tanstack/ai-client'
 import { mount } from '@vue/test-utils'
 import { defineComponent } from 'vue'
-import type { UseChatOptions } from '../src/types'
 import { useChat } from '../src/use-chat'
+import type { UIMessage } from '@tanstack/ai-client'
+import type { UseChatOptions } from '../src/types'
packages/typescript/ai-react/src/use-chat.ts (1)

2-2: Use type-only import for ChatClientState.

ChatClientState is only used as a type, so it should be imported using the type keyword to enable better tree-shaking and satisfy the ESLint rule.

♻️ Suggested fix
-import { ChatClient, ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
packages/typescript/ai-solid/src/use-chat.ts (1)

7-9: Fix import order and use type-only import for ChatClientState.

Two issues flagged by ESLint:

  1. @tanstack/ai-client import should occur before the type import of @tanstack/ai
  2. ChatClientState is only used as a type
♻️ Suggested fix
 import {
   createEffect,
   createMemo,
   createSignal,
   createUniqueId,
 } from 'solid-js'
-
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
+import type { ChatClientState } from '@tanstack/ai-client'
 import type { UIMessage, UseChatOptions, UseChatReturn } from './types'

nikas-belogolov and others added 3 commits January 25, 2026 16:23
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@nikas-belogolov nikas-belogolov marked this pull request as draft January 25, 2026 14:44
@nikas-belogolov nikas-belogolov marked this pull request as ready for review January 25, 2026 14:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/typescript/ai-preact/src/use-chat.ts (1)

1-10: Resolve import order + type-only split (lint).

This triggers import/order and import/consistent-type-specifier-style.

🛠️ Suggested fix
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
-import {
-  useCallback,
-  useEffect,
-  useId,
-  useMemo,
-  useRef,
-  useState,
-} from 'preact/hooks'
+import {
+  useCallback,
+  useEffect,
+  useId,
+  useMemo,
+  useRef,
+  useState,
+} from 'preact/hooks'
+import { ChatClient } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
packages/typescript/ai-solid/src/use-chat.ts (1)

1-10: Fix import order + type-only split (lint).

This violates import/order and import/consistent-type-specifier-style.

🛠️ Suggested fix
 import {
   createEffect,
   createMemo,
   createSignal,
   createUniqueId,
 } from 'solid-js'
 
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
 import type { UIMessage, UseChatOptions, UseChatReturn } from './types'
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-preact/src/use-chat.ts`:
- Around line 76-78: The onStreamStart handler currently only calls
setStatus('streaming') but never invokes the user-provided callback; update the
handler inside useChat (the onStreamStart block) to call the passed-in user
callback after updating status (e.g., options.onStreamStart or onStreamStart
prop) using safe optional invocation (options?.onStreamStart?.(...)) and forward
any relevant args/events to it so the user's callback runs when streaming
starts.

In `@packages/typescript/ai-react/src/use-chat.ts`:
- Around line 1-3: The import currently mixes a type with a value import for
ChatClientState; change it to a top-level type-only import by moving
ChatClientState into its own import line using the `import type {
ChatClientState } from '@tanstack/ai-client'` form and leave `import {
ChatClient } from '@tanstack/ai-client'` (or vice versa) so ChatClientState is
imported purely as a type (refer to the ChatClient and ChatClientState symbols
in use-chat.ts).
- Around line 54-56: The onStreamStart handler currently only calls
setStatus("streaming") and never invokes the consumer's callback; update the
onStreamStart block in use-chat.ts (the onStreamStart handler) to call the
user-provided callback after updating status — e.g., keep setStatus("streaming")
then check for and invoke the external onStreamStart callback (the
prop/option/callback passed into the useChat/useChat hook) so consumers receive
the event.

In `@packages/typescript/ai-solid/src/use-chat.ts`:
- Around line 58-60: The onStreamStart handler currently only calls
setStatus('streaming') but doesn't invoke the consumer's callback; update the
onStreamStart implementation to call the user-provided onStreamStart callback
after setting status (e.g., if (props.onStreamStart)
props.onStreamStart(...args) or options.onStreamStart?.(args)), preserving any
arguments the stream start provides and guarding for its existence so both
setStatus('streaming') and the user's callback run.

In `@packages/typescript/ai-vue/src/use-chat.ts`:
- Around line 1-4: The file imports ChatClientState with a mixed value/type
import which violates the consistent-type-specifier-style rule; change the
imports to use a top-level type-only import for ChatClientState and keep
ChatClient as a value import — i.e., split the current "import { ChatClient,
type ChatClientState } from '@tanstack/ai-client'" into a value import for
ChatClient and a separate "import type { ChatClientState }" so ChatClientState
is only imported as a type; update references to ChatClientState and ChatClient
in use-chat.ts accordingly.
♻️ Duplicate comments (2)
packages/typescript/ai-svelte/src/create-chat.svelte.ts (1)

1-3: Fix import ordering + type-only split to satisfy lint.

ESLint still flags import/order and import/consistent-type-specifier-style here.

🛠️ Suggested fix
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
packages/typescript/ai-vue/src/use-chat.ts (1)

48-50: Forward onStreamStart to consumer callback.

Status changes but the user callback is skipped.

🛠️ Suggested fix
     onStreamStart: () => {
       status.value = 'streaming'
+      options.onStreamStart?.()
     },

nikas-belogolov and others added 2 commits January 25, 2026 19:31
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/typescript/ai-react/src/use-chat.ts`:
- Around line 1-4: Reorder the import statements to satisfy ESLint import-order
rules by placing React imports (useCallback, useEffect, useId, useMemo, useRef,
useState from 'react') before external package imports; move the line importing
ChatClient and ChatClientState from '@tanstack/ai-client' and the types
AnyClientTool and ModelMessage from '@tanstack/ai' after the React import so
that React comes first, then third‑party packages, keeping each import's
original named symbols (ChatClient, ChatClientState, AnyClientTool,
ModelMessage) unchanged.

In `@packages/typescript/ai-vue/src/use-chat.ts`:
- Around line 1-4: Reorder the imports so the concrete module imports from
'@tanstack/ai-client' (ChatClient, ChatClientState) appear before the type-only
import from '@tanstack/ai' (AnyClientTool, ModelMessage) to satisfy ESLint
ordering; keep the 'type' modifiers on type-only imports (e.g., type {
AnyClientTool, ModelMessage } and type { ChatClientState }) and preserve the
existing Vue imports (onScopeDispose, readonly, shallowRef, useId) after the
tanstack imports.
♻️ Duplicate comments (2)
packages/typescript/ai-react/src/use-chat.ts (1)

55-57: Forward onStreamStart to the consumer callback.

The internal handler updates status but doesn’t invoke the user’s onStreamStart, so external listeners never fire.

🛠️ Proposed fix
       onStreamStart: () => {
-        setStatus("streaming")
+        setStatus('streaming')
+        optionsRef.current.onStreamStart?.()
       },
packages/typescript/ai-vue/src/use-chat.ts (1)

49-50: Forward onStreamStart to user callbacks.

The hook updates status but doesn’t invoke options.onStreamStart, so consumers miss the event.

🛠️ Proposed fix
     onStreamStart: () => {
       status.value = 'streaming'
+      options.onStreamStart?.()
     },

@nx-cloud
Copy link

nx-cloud bot commented Jan 26, 2026

View your CI Pipeline Execution ↗ for commit 5168f0d

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 52s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 53s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-30 10:59:19 UTC

Copy link
Contributor

@AlemTuzlak AlemTuzlak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jherr Do you think we should collapse the status into the ChatClient internally instead of requiring state management per lib? I think that would be a sane way of approaching this and then the consumer libs just read it instead of having to deal with re-implementing the logic 5x times?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/typescript/ai-vue/tests/test-utils.ts`:
- Around line 1-5: Reorder the imports so type-only imports come after the value
import from '../src/use-chat' to satisfy ESLint import/order: keep value imports
like import { useChat } from '../src/use-chat' and runtime imports (mount,
defineComponent) earlier, then move type imports (import type { UIMessage } and
import type { UseChatOptions }) immediately after those, ensuring you use
"import type" for the type-only symbols (UIMessage, UseChatOptions) and leave
useChat, mount, and defineComponent as value imports.
♻️ Duplicate comments (2)
packages/typescript/ai-react/src/use-chat.ts (2)

1-4: Resolve ESLint import/order violation.

Line 1-4 should place React imports first and keep @tanstack/ai-client before the @tanstack/ai type import.

🧹 Proposed import reorder
-import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient } from '@tanstack/ai-client'
-import type { ChatClientState } from '@tanstack/ai-client'
-import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
+import { ChatClient } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
+import type { AnyClientTool, ModelMessage } from '@tanstack/ai'

55-65: Forward onStreamStart to the user callback.

Line 55-57 updates status but drops the consumer callback, so external listeners never fire.

🛠️ Suggested fix
       onStreamStart: () => {
         setStatus('streaming')
+        optionsRef.current.onStreamStart?.()
       },

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/typescript/ai-preact/tests/use-chat.test.ts`:
- Around line 1-10: Reorder the imports so type-only imports come after the
regular imports: move "import type { ModelMessage } from '@tanstack/ai'" and
"import type { UIMessage } from '../src/types'" to after the block that imports
createMockConnectionAdapter, createTextChunks, createToolCallChunks, and
renderUseChat from './test-utils' to satisfy the import/order ESLint rule; keep
the existing testing-library and vitest imports where they are and ensure only
type imports are relocated.

In `@packages/typescript/ai-vue/tests/use-chat.test.ts`:
- Around line 1-10: Move the type-only imports (ModelMessage, UIMessage) below
the regular imports from the test-utils module so they follow the
createMockConnectionAdapter, createTextChunks, createToolCallChunks,
renderUseChat imports; specifically, reorder the top of the file so the non-type
import block (createMockConnectionAdapter, createTextChunks,
createToolCallChunks, renderUseChat) appears before the type imports
(ModelMessage, UIMessage) to satisfy the import/order ESLint rule.
🧹 Nitpick comments (4)
packages/typescript/ai-client/src/chat-client.ts (2)

1-16: Import order flagged by linter.

ESLint reports that regular imports should occur before type imports from the same module. This is a style issue.

♻️ Suggested fix
-import type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'
 import {
   StreamProcessor,
   generateMessageId,
   normalizeToUIMessage,
 } from '@tanstack/ai'
+import type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'
 import type { ConnectionAdapter } from './connection-adapters'
 import type { ChatClientEventEmitter } from './events'
 import { DefaultChatClientEventEmitter } from './events'

81-91: Duplicate setStatus('error') call when errors occur in stream processing.

When an error occurs during stream processing, setError(error) is called at line 89, which internally calls setStatus('error') (lines 205-207). Then line 90 explicitly calls setStatus('error') again. While this is harmless (idempotent), it triggers onStatusChange twice with the same value.

♻️ Suggested fix - remove redundant call
       onError: (error: Error) => {
         this.setError(error)
-        this.setStatus('error')
         this.callbacksRef.current.onError(error)
       },
packages/typescript/ai-preact/src/use-chat.ts (1)

1-2: Import order and type specifier style flagged by linter.

ESLint reports import order issues and suggests using top-level type-only imports. This is a style consistency matter.

♻️ Suggested fix
+import {
+  useCallback,
+  useEffect,
+  useId,
+  useMemo,
+  useRef,
+  useState,
+} from 'preact/hooks'
+
 import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
-import {
-  useCallback,
-  useEffect,
-  useId,
-  useMemo,
-  useRef,
-  useState,
-} from 'preact/hooks'
+import type { ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
packages/typescript/ai-solid/src/use-chat.ts (1)

7-9: Import order and type specifier style flagged by linter.

ESLint reports import order issues and suggests using top-level type-only imports.

♻️ Suggested fix
 import {
   createEffect,
   createMemo,
   createSignal,
   createUniqueId,
 } from 'solid-js'
-
 import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
-import { ChatClient, type ChatClientState } from '@tanstack/ai-client'
+import type { ChatClientState } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
 import type { UIMessage, UseChatOptions, UseChatReturn } from './types'

Copy link
Contributor

@AlemTuzlak AlemTuzlak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really neat now! Thank you @nikas-belogolov , I'll let @jherr review it as well

Just please address my comment on the error status

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 27, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@247

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@247

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@247

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@247

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@247

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@247

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@247

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@247

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@247

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@247

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@247

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@247

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@247

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@247

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@247

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@247

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@247

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@247

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@247

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@247

commit: 5168f0d

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/typescript/ai-client/src/chat-client.ts`:
- Around line 88-91: The onError handler is redundantly calling
setStatus('error') after setError(error) (which already sets status), causing
duplicate onStatusChange notifications; remove the extra setStatus('error')
calls in the onError blocks so only setError(error) is invoked before calling
callbacksRef.current.onError(error); apply the same removal for the second
occurrence around the code that mirrors this logic (the one referenced at lines
205-207).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/typescript/ai-preact/tests/use-chat.test.ts`:
- Around line 566-650: Three status tests are commented out (initial status,
error transition, stop transition) in the use-chat test suite; locate the tests
using identifiers renderUseChat, createMockConnectionAdapter,
result.current.sendMessage, result.current.stop and either (A) re-enable and fix
them so they pass (restore the original it(...) blocks and address any
framework-specific failure causing assertions on result.current.status), or (B)
explicitly mark them as skipped with a reason using it.skip('reason', ...) or
add a TODO/FIXME comment above each block explaining why they are disabled and
linking to an issue; pick one approach and apply it consistently for the three
blocks so test intent and visibility are preserved.
🧹 Nitpick comments (3)
packages/typescript/ai-client/src/chat-client.ts (1)

1-16: Fix import order per ESLint rules.

The static analysis indicates import order violations. Type imports from @tanstack/ai should occur after regular imports, and other type imports should be grouped appropriately.

🔧 Suggested fix
-import type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'
 import {
   StreamProcessor,
   generateMessageId,
   normalizeToUIMessage,
 } from '@tanstack/ai'
-import type { ConnectionAdapter } from './connection-adapters'
-import type { ChatClientEventEmitter } from './events'
 import { DefaultChatClientEventEmitter } from './events'
+import type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'
+import type { ConnectionAdapter } from './connection-adapters'
+import type { ChatClientEventEmitter } from './events'
 import type {
   ChatClientOptions,
   ChatClientState,
   MessagePart,
   ToolCallPart,
   UIMessage,
 } from './types'
packages/typescript/ai-client/tests/chat-client.test.ts (1)

395-423: Consider adding explicit tests for initial status and error transitions.

The status test suite covers the happy path well but could be more comprehensive:

  1. Missing explicit test for initial status being 'ready' (constructor default)
  2. Missing dedicated test for status transitioning to 'error' (currently only tested implicitly in the error handling section)

This would align with the test coverage in other framework test files (e.g., Svelte tests have explicit error transition tests in the status section).

💡 Suggested additional tests
it('should have initial status of ready', () => {
  const adapter = createMockConnectionAdapter()
  const client = new ChatClient({ connection: adapter })
  expect(client.getStatus()).toBe('ready')
})

it('should transition to error on connection failure', async () => {
  const adapter = createMockConnectionAdapter({
    shouldError: true,
    error: new Error('Test error'),
  })
  const client = new ChatClient({ connection: adapter })

  await client.sendMessage('Test')
  expect(client.getStatus()).toBe('error')
})
packages/typescript/ai-vue/tests/use-chat.test.ts (1)

466-489: Consider adding a test for the 'error' status transition.

The test covers the happy path ('ready''submitted'/'streaming''ready'), but the 'error' status is not explicitly tested. Since the status property includes 'error' as a valid state, consider adding a test to verify the status transitions to 'error' when an error occurs.

♻️ Suggested test addition
     it('should transition through states during generation', async () => {
       // ... existing test
     })
+
+    it('should transition to error status on failure', async () => {
+      const error = new Error('Test error')
+      const adapter = createMockConnectionAdapter({
+        shouldError: true,
+        error,
+      })
+      const { result } = renderUseChat({ connection: adapter })
+
+      expect(result.current.status).toBe('ready')
+
+      await result.current.sendMessage('Test')
+      await flushPromises()
+
+      expect(result.current.status).toBe('error')
+      expect(result.current.error).toBeDefined()
+    })
   })

@jherr
Copy link
Contributor

jherr commented Jan 28, 2026

Looks good.

@AlemTuzlak
Copy link
Contributor

The PR has a bunch of eslint issues, you can repro by just running pnpm run test on the root, fix those up and good to go!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typescript/ai-solid/src/use-chat.ts (1)

63-73: Fix copy-paste comment error.

Line 65 mentions "React state" but this is the SolidJS adapter.

📝 Suggested fix
   // Sync initial messages on mount only
-  // Note: initialMessages are passed to ChatClient constructor, but we also
-  // set them here to ensure React state is in sync
+  // Note: initialMessages are passed to ChatClient constructor, but we also
+  // set them here to ensure Solid state is in sync
🧹 Nitpick comments (1)
packages/typescript/ai-solid/src/use-chat.ts (1)

7-9: Minor formatting inconsistency.

There's an extra blank line between SolidJS imports and the @tanstack/ai-client import. Consider removing for consistency with other framework adapters.

♻️ Suggested fix
 import {
   createEffect,
   createMemo,
   createSignal,
   createUniqueId,
 } from 'solid-js'
-
-import { ChatClient  } from '@tanstack/ai-client'
+import { ChatClient } from '@tanstack/ai-client'
 import type {ChatClientState} from '@tanstack/ai-client';

@nikas-belogolov
Copy link
Author

@AlemTuzlak fixed it, should be all good

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typescript/ai-preact/src/use-chat.ts (1)

26-72: Reset status to 'ready' when a new client instance is created.

ChatClient does not emit onStatusChange during construction—it initializes its internal status to 'ready' but does not notify the React component. If options.id changes while the client is in 'error' or 'streaming' state, the new ChatClient created by the useMemo will have status='ready' internally, but the React state will retain the old status until the next client operation triggers onStatusChange. This creates a stale UI state.

Add a useEffect to reset status when the client changes:

Suggested fix
  const client = useMemo(() => {
    // ...
  }, [clientId])

+  useEffect(() => {
+    setStatus('ready')
+  }, [client])
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-solid/src/use-chat.ts`:
- Around line 52-54: The hook's internal onStatusChange handler currently
overwrites any user-supplied callback so their callback never runs; in the
useChat hook update the handler that calls setStatus(newStatus) to also invoke
the user's callback from UseChatOptions (e.g., options.onStatusChange) after
updating state, i.e., chain or conditionally call the provided onStatusChange
with the newStatus so both setStatus and the user's onStatusChange run (refer to
the onStatusChange handler and setStatus in useChat and the UseChatOptions type
to locate the change).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants