Skip to content

feat: multi-capability assistants β€” defineAssistant (server) + useAssistant (client)#930

Open
AlemTuzlak wants to merge 4 commits into
mainfrom
feat/define-use-assistant
Open

feat: multi-capability assistants β€” defineAssistant (server) + useAssistant (client)#930
AlemTuzlak wants to merge 4 commits into
mainfrom
feat/define-use-assistant

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Adds a multi-capability assistant layer: declare capabilities once on the server, consume them all from one typed client hook.

  • Server (@tanstack/ai) β€” defineAssistant(config): an inert registry of per-capability callbacks (chat, image, audio, speech, video, transcription, summarize). Each callback just invokes the existing activity (chat(), generateImage(), …). It exposes a single handler(request) that parses the AG-UI request, routes by a capability discriminator, invokes the callback, and streams the result back over the existing SSE wire. Nothing is constructed until a request arrives.
  • Client core (@tanstack/ai-client) β€” AssistantClient composes one ChatClient + one GenerationClient per declared capability behind a single connection, each tagging its capability. AssistantSystem<TDef> maps the declared capabilities to fully-typed surfaces.
  • Framework hooks β€” useAssistant in @tanstack/ai-react, @tanstack/ai-solid, @tanstack/ai-vue, and createAssistant in @tanstack/ai-svelte. No generics at the call site β€” types are inferred from the passed definition. Each capability entry is the corresponding primitive hook's own return (useChat / useGeneration).
// server: assistant.ts (shared, inert)
export const assistant = defineAssistant({
  chat: (req) => chat({ ...openaiText('gpt-5.5'), messages: req.messages }),
  image: (req) => generateImage({ adapter: openaiImage('gpt-image-2'), prompt: req.prompt }),
})
// route: export const POST = (request) => assistant.handler(request)

// client
const system = useAssistant(assistant, { connection: fetchServerSentEvents('/api/assistant') })
system.chat.sendMessage('hi')
await system.image.generate({ prompt: 'a fox' })
// chaining is manual: feed system.image.result into system.chat.sendMessage([...])

Design & plan

Brainstormed and specced before implementation; the design doc and implementation plan drove a wave-by-wave build with per-wave review.

Testing

  • Unit tests in every touched package: server handler dispatch (chat SSE, one-shot generation:result, 400 on unknown/inherited-prototype capability), AssistantClient composition + dispose, AssistantSystem type-level assertions, and each framework hook (declared-capability surface, generate β†’ result, sendMessage β†’ messages).
  • E2E (testing/e2e): a /assistant page driving useAssistant against a real /api/assistant handler. The chat capability is verified end-to-end through the assistant handler (streams a mocked response). The image one-shot leg is test.skipped with a documented reason β€” image generation is mocked programmatically via registerMediaFixtures (needs match.endpoint), which isn't wired for the assistant prompt; the one-shot dispatch itself is covered by the server + hook unit tests.
  • Docs kiira-clean (783/783 snippets, no broken links); new docs/assistant/ guide + ai-core/assistant agent skill (+ sibling cross-links); changeset (minor) for the six packages.

Type-inference upgrade (follow-on commits)

assistant.chat is now fully inferred from the chat callback's return:

  • Structured output β€” a chat callback with outputSchema gives typed assistant.chat.final (Schema | null) and assistant.chat.partial (DeepPartial<Schema>), across React/Solid/Vue/Svelte.
  • Tool types β€” tools declared in the chat callback type assistant.chat.messages tool-call parts with no client re-declaration. chat: { tools } remains, optional, for client-executed tools' runtime implementations (their code can't cross the wire).

Enabled by an optional type-only phantom on chat()'s return (ChatResultMeta<TTools, TSchema, TStream>) β€” runtime unchanged, still assignable everywhere.

Note on local test:pr

Two failures show up only in a local Windows run, in packages this PR does not modify (empty diff for ai-sandbox/ai-acp), pulled in via Nx affected β€” they pass on CI (Linux):

  • @tanstack/ai-sandbox:test:lib β€” one Windows path-separator assertion (/tmp/… vs \tmp\…); 190/191 pass.
  • @tanstack/ai-acp:test:lib β€” sandbox/ACP integration tests spawning real harnesses time out under a contended local Windows worktree.

(ai-e2e:build briefly failed on CI because the assistant test page imported the multi-provider adapter factory, pulling ollama's node:fs into the browser bundle β€” fixed by using the openai adapters directly.)

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added multi-capability assistants supporting chat, image, audio, speech, video, transcription, and summarization through one typed interface.
    • Added defineAssistant for server configuration and useAssistant/createAssistant integrations for React, Solid, Vue, and Svelte.
    • Added structured chat output support with live partial and final results.
    • Added assistant package entry points and an end-to-end assistant example.
  • Documentation

    • Added assistant overview, scenarios, and multiple-assistant guidance.
  • Tests

    • Added coverage for capability routing, generation, streaming, typing, and cleanup.

@github-actions

Copy link
Copy Markdown
Contributor

πŸš€ Changeset Version Preview

19 package(s) bumped directly, 26 bumped as dependents.

πŸŸ₯ Major bumps

Package Version Reason
@tanstack/ai-angular 0.2.3 β†’ 1.0.0 Changeset
@tanstack/ai-anthropic 0.16.1 β†’ 1.0.0 Changeset
@tanstack/ai-bedrock 0.1.2 β†’ 1.0.0 Changeset
@tanstack/ai-fal 0.9.10 β†’ 1.0.0 Changeset
@tanstack/ai-gemini 0.19.1 β†’ 1.0.0 Changeset
@tanstack/ai-grok 0.14.7 β†’ 1.0.0 Changeset
@tanstack/ai-groq 0.5.1 β†’ 1.0.0 Changeset
@tanstack/ai-mistral 0.2.1 β†’ 1.0.0 Changeset
@tanstack/ai-ollama 0.8.14 β†’ 1.0.0 Changeset
@tanstack/ai-openai 0.16.0 β†’ 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.8 β†’ 1.0.0 Changeset
@tanstack/ai-preact 0.10.3 β†’ 1.0.0 Changeset
@tanstack/ai-react 0.16.4 β†’ 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.2 β†’ 1.0.0 Changeset
@tanstack/ai-solid 0.14.3 β†’ 1.0.0 Changeset
@tanstack/ai-svelte 0.14.3 β†’ 1.0.0 Changeset
@tanstack/ai-vue 0.14.3 β†’ 1.0.0 Changeset
@tanstack/ai-acp 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.6 β†’ 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.9 β†’ 1.0.0 Dependent
@tanstack/ai-codex 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.32 β†’ 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.45 β†’ 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.45 β†’ 1.0.0 Dependent
@tanstack/ai-opencode 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.13 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.2 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 β†’ 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 β†’ 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.12 β†’ 1.0.0 Dependent
@tanstack/openai-base 0.9.7 β†’ 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.40.0 β†’ 0.41.0 Changeset
@tanstack/ai-client 0.20.0 β†’ 0.21.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.22 β†’ 0.4.23 Dependent
@tanstack/ai-isolate-cloudflare 0.2.36 β†’ 0.2.37 Dependent
@tanstack/ai-mcp 0.2.3 β†’ 0.2.4 Dependent
@tanstack/ai-vue-ui 0.2.31 β†’ 0.2.32 Dependent
@tanstack/preact-ai-devtools 0.1.65 β†’ 0.1.66 Dependent
@tanstack/react-ai-devtools 0.2.65 β†’ 0.2.66 Dependent
@tanstack/solid-ai-devtools 0.2.65 β†’ 0.2.66 Dependent

@nx-cloud

nx-cloud Bot commented Jul 13, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution β†— for commit 71f4748

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... βœ… Succeeded 19s View β†—

☁️ Nx Cloud last updated this comment at 2026-07-14 16:32:32 UTC

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▢️ Resume reviews
  • πŸ” Trigger review
πŸ“ Walkthrough

Walkthrough

This PR introduces multi-capability assistants via defineAssistant (server) and useAssistant/createAssistant (client) across React, Solid, Svelte, and Vue. It adds server-side capability routing/SSE handling, typed client composition, structured-output helpers, package exports, documentation, and end-to-end test coverage.

Changes

Multi-capability assistants

Layer / File(s) Summary
Server assistant definition and routing
packages/ai/src/activities/assistant/types.ts, packages/ai/src/activities/assistant/index.ts, packages/ai/src/activities/chat/index.ts, packages/ai/src/types.ts, packages/ai/src/index.ts, packages/ai/src/assistant.ts, packages/ai/vite.config.ts, packages/ai/package.json, packages/ai/tests/activities/assistant/index.test.ts, packages/ai/tests/activities/chat/chat-result-meta.test.ts
Defines assistant request/capability contracts, defineAssistant with capability-validated SSE routing (400 on unknown capability), chat result inference metadata (ChatResultMeta), package exports/build entries, and routing/inference tests.
Typed assistant client composition
packages/ai-client/src/assistant-types.ts, packages/ai-client/src/assistant-client.ts, packages/ai-client/src/assistant-structured.ts, packages/ai-client/src/assistant.ts, packages/ai-client/package.json, packages/ai-client/vite.config.ts, packages/ai-client/tests/assistant-*.test.ts
Introduces AssistantClientOptions, AssistantSystem capability surfaces, AssistantClient sub-client management, computeStructuredParts, barrel exports, and runtime/type tests.
React assistant hook
packages/ai-react/src/use-assistant.ts, packages/ai-react/src/assistant.ts, packages/ai-react/package.json, packages/ai-react/vite.config.ts, packages/ai-react/tests/use-assistant.test.tsx
Adds useAssistant, syncing AssistantClient callbacks into React state and exposing chat/one-shot surfaces with disposal on unmount.
Solid assistant hook
packages/ai-solid/src/use-assistant.ts, packages/ai-solid/src/assistant.ts, packages/ai-solid/package.json, packages/ai-solid/tsdown.config.ts, packages/ai-solid/tests/use-assistant.test.tsx
Adds Solid useAssistant, wiring reactive signals for chat and one-shot state with lifecycle mount/dispose and capability guards.
Svelte assistant factory
packages/ai-svelte/src/create-assistant.svelte.ts, packages/ai-svelte/src/assistant.ts, packages/ai-svelte/package.json, packages/ai-svelte/tests/create-assistant.svelte.test.ts
Adds Svelte createAssistant rune-based factory exposing reactive capability surfaces with explicit dispose().
Vue assistant composable
packages/ai-vue/src/use-assistant.ts, packages/ai-vue/src/assistant.ts, packages/ai-vue/package.json, packages/ai-vue/tsdown.config.ts, packages/ai-vue/tests/use-assistant.test.ts
Adds Vue useAssistant composable with reactive refs for chat/one-shot state and lifecycle disposal.
Assistant documentation and release metadata
docs/assistant/*, docs/config.json, packages/ai/skills/ai-core/*, .changeset/assistant-multi-capability.md
Adds assistant overview/scenarios/multiple-assistants docs, navigation entries, SKILL.md guidance/cross-references, and release changeset.
Assistant end-to-end route and coverage
testing/e2e/src/routes/assistant.tsx, testing/e2e/src/routes/api.assistant.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/src/lib/*, testing/e2e/fixtures/assistant/basic.json, testing/e2e/tests/assistant.spec.ts
Adds client/server assistant routes, generated route typings, feature-support/config entries, fixtures, and provider-driven e2e tests.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant useAssistant
  participant AssistantClient
  participant AssistantHandler
  participant CapabilityCallback

  Client->>useAssistant: call system.chat.sendMessage / system.image.generate
  useAssistant->>AssistantClient: dispatch via chat or one-shot sub-client
  AssistantClient->>AssistantHandler: POST request with forwardedProps.capability
  AssistantHandler->>AssistantHandler: validate capability against declared keys
  AssistantHandler->>CapabilityCallback: invoke matching callback
  CapabilityCallback-->>AssistantHandler: stream or promise result
  AssistantHandler-->>AssistantClient: SSE response
  AssistantClient-->>useAssistant: update chat/one-shot reactive state
  useAssistant-->>Client: render updated system surface
Loading

Suggested reviewers: tombeckenham, crutchcorn, schiller-manuel

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check name Status Explanation
Title check βœ… Passed The title clearly summarizes the main change: adding multi-capability assistants via defineAssistant and useAssistant.
Description check βœ… Passed The description covers the change, testing, and release impact, though it doesn't follow the exact template headings or include the checklist items.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/define-use-assistant

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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@930

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@930

@tanstack/ai-anthropic

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

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@930

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@930

@tanstack/ai-client

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

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@930

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@930

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@930

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@930

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@930

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@930

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@930

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@930

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@930

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@930

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@930

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@930

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@930

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@930

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@930

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@930

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@930

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@930

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@930

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@930

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@930

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@930

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@930

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 71f4748

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-react/src/use-assistant.ts`:
- Line 70: Exclude the framework-internal callbacks option from both hooks’
public options types. Update the options parameter Omit in React’s useAssistant
and Vue’s useAssistant to omit both assistant and callbacks, preserving all
other AssistantClientOptions fields.

In `@packages/ai-solid/src/use-assistant.ts`:
- Around line 302-303: The AssistantSystem casts unnecessarily pass through
unknown, triggering no-unnecessary-type-assertion. In
packages/ai-solid/src/use-assistant.ts lines 302-303, update the cast in the
assembled system return to AssistantSystem<TDef, TChatTools> directly; in
packages/ai-svelte/src/create-assistant.svelte.ts lines 271-274, cast directly
to AssistantSystem<TDef, TChatTools> & { dispose: () => void } while preserving
the existing behavior.
- Around line 102-160: Remove the second argument from the createMemo call that
constructs the AssistantClient in the client memo, leaving the existing callback
and AssistantClient configuration unchanged.

In `@packages/ai-vue/src/use-assistant.ts`:
- Around line 62-68: Align the callbacks type contract in useAssistant with the
corresponding React hook and shared callback fix. Update the
AssistantClientOptions usage and any related generic or omitted option types so
callbacks are correctly typed while keeping assistant excluded from
caller-provided options.

In `@packages/ai/skills/ai-core/assistant/SKILL.md`:
- Around line 88-98: Update all three snippets in SKILL.md that import from
../lib/assistant to import the exported blogAssistant symbol, including the
route and client examples; ensure client locals do not conflict with that import
while continuing to pass blogAssistant to useAssistant.

In `@packages/ai/src/activities/assistant/index.ts`:
- Around line 29-40: Validate the parsed body in the assistant request handler
before accessing properties through bodyRecord. Reject null and all non-object
JSON values with the existing 400 invalid-body response, while preserving the
forwardedProps/data fallback for valid object bodies.

In `@packages/ai/src/activities/assistant/types.ts`:
- Around line 83-89: Update the AssistantConfig.chat type and its surrounding
definitions in types.ts to match the dispatcher’s actual async-iterable-only
contract. Remove the unsupported Promise<string> and Promise<object> return
variants while preserving ChatStream and StructuredOutputStream<any>, so
non-streaming chat calls are rejected at compile time.

In `@testing/e2e/src/routes/assistant.tsx`:
- Around line 9-12: Reorder the imports in the assistant route so the type-only
Provider import from '`@/lib/types`' appears before the internal value imports
ChatUI, createTextAdapter, and createImageAdapter, satisfying the configured
ESLint import/order rule.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c3307a4-0ffb-47bd-95a3-4b720f7fcfa8

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5fcaf90 and cdb601c.

πŸ“’ Files selected for processing (36)
  • .changeset/assistant-multi-capability.md
  • docs/assistant/index.md
  • docs/config.json
  • packages/ai-client/src/assistant-client.ts
  • packages/ai-client/src/assistant-types.ts
  • packages/ai-client/src/index.ts
  • packages/ai-client/tests/assistant-client.test.ts
  • packages/ai-react/src/index.ts
  • packages/ai-react/src/use-assistant.ts
  • packages/ai-react/tests/use-assistant.test.tsx
  • packages/ai-solid/src/index.ts
  • packages/ai-solid/src/use-assistant.ts
  • packages/ai-solid/tests/use-assistant.test.tsx
  • packages/ai-svelte/src/create-assistant.svelte.ts
  • packages/ai-svelte/src/index.ts
  • packages/ai-svelte/tests/create-assistant.svelte.test.ts
  • packages/ai-vue/src/index.ts
  • packages/ai-vue/src/use-assistant.ts
  • packages/ai-vue/tests/use-assistant.test.ts
  • packages/ai/skills/ai-core/assistant/SKILL.md
  • packages/ai/skills/ai-core/chat-experience/SKILL.md
  • packages/ai/skills/ai-core/custom-backend-integration/SKILL.md
  • packages/ai/skills/ai-core/media-generation/SKILL.md
  • packages/ai/src/activities/assistant/index.ts
  • packages/ai/src/activities/assistant/types.ts
  • packages/ai/src/activities/index.ts
  • packages/ai/src/index.ts
  • packages/ai/tests/activities/assistant/index.test.ts
  • testing/e2e/fixtures/assistant/basic.json
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.assistant.ts
  • testing/e2e/src/routes/assistant.tsx
  • testing/e2e/tests/assistant.spec.ts

Comment thread packages/ai-react/src/use-assistant.ts Outdated
Comment thread packages/ai-solid/src/use-assistant.ts Outdated
Comment thread packages/ai-vue/src/use-assistant.ts Outdated
Comment thread packages/ai/skills/ai-core/assistant/SKILL.md
Comment on lines +29 to +40
let body: unknown
try {
body = await request.json()
} catch {
return new Response('Invalid JSON body', { status: 400 })
}

const bodyRecord = body as Record<string, unknown>
const forwardedProps: Record<string, unknown> =
(bodyRecord.forwardedProps as Record<string, unknown> | undefined) ??
(bodyRecord.data as Record<string, unknown> | undefined) ??
{}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟑 Minor | ⚑ Quick win

Guard against non-object JSON bodies (e.g. literal null).

request.json() can resolve to null (or any primitive) as valid JSON. The try/catch only covers .json() itself; bodyRecord.forwardedProps at line 38 will throw on a null body since that access is unguarded, turning a bad request into an unhandled rejection instead of a 400.

πŸ›‘οΈ Validate body shape before destructuring
     const bodyRecord = body as Record<string, unknown>
+    if (typeof body !== 'object' || body === null) {
+      return new Response('Invalid JSON body', { status: 400 })
+    }
+    const bodyRecord = body as Record<string, unknown>
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/activities/assistant/index.ts` around lines 29 - 40, Validate
the parsed body in the assistant request handler before accessing properties
through bodyRecord. Reject null and all non-object JSON values with the existing
400 invalid-body response, while preserving the forwardedProps/data fallback for
valid object bodies.

Comment thread packages/ai/src/activities/assistant/types.ts
Comment on lines +9 to +12
import { ChatUI } from '@/components/ChatUI'
import { createTextAdapter } from '@/lib/providers'
import { createImageAdapter } from '@/lib/media-providers'
import type { Provider } from '@/lib/types'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟑 Minor | ⚑ Quick win

Fix ESLint import/order error: move type import before internal value imports.

ESLint reports an error on line 12 β€” @/lib/types type import should occur before @/components/ChatUI. This is configured as an error and may fail the lint step in CI.

πŸ”§ Proposed fix
 import { fetchServerSentEvents, useAssistant } from '`@tanstack/ai-react`'
+import type { Provider } from '`@/lib/types`'
 import { ChatUI } from '`@/components/ChatUI`'
 import { createTextAdapter } from '`@/lib/providers`'
 import { createImageAdapter } from '`@/lib/media-providers`'
-import type { Provider } from '`@/lib/types`'
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ChatUI } from '@/components/ChatUI'
import { createTextAdapter } from '@/lib/providers'
import { createImageAdapter } from '@/lib/media-providers'
import type { Provider } from '@/lib/types'
import type { Provider } from '`@/lib/types`'
import { ChatUI } from '`@/components/ChatUI`'
import { createTextAdapter } from '`@/lib/providers`'
import { createImageAdapter } from '`@/lib/media-providers`'
🧰 Tools
πŸͺ› ESLint

[error] 12-12: @/lib/types type import should occur before import of @/components/ChatUI

(import/order)

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/assistant.tsx` around lines 9 - 12, Reorder the
imports in the assistant route so the type-only Provider import from
'`@/lib/types`' appears before the internal value imports ChatUI,
createTextAdapter, and createImageAdapter, satisfying the configured ESLint
import/order rule.

Source: Linters/SAST tools

@AlemTuzlak AlemTuzlak force-pushed the feat/define-use-assistant branch from b50eeae to 4b65ae5 Compare July 14, 2026 10:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/ai/src/activities/assistant/index.ts (1)

36-40: 🩺 Stability & Availability | 🟑 Minor | ⚑ Quick win

Still missing the null-body guard flagged previously.

request.json() can resolve to null (or another primitive) as valid JSON; the try/catch only covers parsing itself. bodyRecord.forwardedProps here dereferences a property on a non-object, throwing before either the chat or one-shot branch is reached β€” for every capability, not just chat.

πŸ›‘οΈ Validate body shape before destructuring
-    const bodyRecord = body as Record<string, unknown>
+    if (typeof body !== 'object' || body === null) {
+      return new Response('Invalid JSON body', { status: 400 })
+    }
+    const bodyRecord = body as Record<string, unknown>
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/activities/assistant/index.ts` around lines 36 - 40, Add a
body-shape guard before the cast and property access in the request handling
flow: only treat non-null object bodies as records, and use an empty record for
null or primitive JSON values. Ensure this validation applies before both the
chat and one-shot capability branches so forwardedProps extraction never
dereferences an invalid body.
testing/e2e/src/routes/assistant.tsx (1)

7-8: πŸ“ Maintainability & Code Quality | 🟑 Minor | ⚑ Quick win

Fix ESLint import/order error: move type import before internal value imports.

ESLint reports an error because the type import from @/lib/types appears after the value import from @/components/ChatUI. Reorder them to satisfy the rule and prevent CI lint failures.

πŸ”§ Proposed fix
-import { ChatUI } from '`@/components/ChatUI`'
-import type { Provider } from '`@/lib/types`'
+import type { Provider } from '`@/lib/types`'
+import { ChatUI } from '`@/components/ChatUI`'
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/assistant.tsx` around lines 7 - 8, Reorder the imports
in the assistant route so the type-only Provider import from `@/lib/types` appears
before the ChatUI value import from `@/components/ChatUI`, satisfying the ESLint
import/order rule without changing usage.

Source: Linters/SAST tools

packages/ai-solid/src/use-assistant.ts (1)

320-321: πŸ“ Maintainability & Code Quality | 🟑 Minor | ⚑ Quick win

Unnecessary unknown cast triggers ESLint error.

In both files, the dynamically assembled system object can be directly cast to AssistantSystem<TDef, TChatTools> without the intermediate unknown hop. Removing it resolves the @typescript-eslint/no-unnecessary-type-assertion error while preserving the same type behavior.

  • packages/ai-solid/src/use-assistant.ts#L320-L321: change the cast to return system as AssistantSystem<TDef, TChatTools>
  • packages/ai-vue/src/use-assistant.ts#L246-L247: change the cast to return system as AssistantSystem<TDef, TChatTools>
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/src/use-assistant.ts` around lines 320 - 321, Remove the
unnecessary unknown intermediary from the system casts in
packages/ai-solid/src/use-assistant.ts#L320-L321 and
packages/ai-vue/src/use-assistant.ts#L246-L247, changing both returns to cast
system directly as AssistantSystem<TDef, TChatTools>. Preserve the existing
dynamically assembled system behavior and surrounding lint suppression.

Source: Linters/SAST tools

🧹 Nitpick comments (3)
packages/ai-solid/tests/use-assistant.test.tsx (2)

1-2: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Place unit test files alongside the source they cover.

As per coding guidelines, unit tests should be placed in *.test.ts (or .tsx) files alongside the source modules they cover, rather than in a separate tests/ directory.

  • packages/ai-solid/tests/use-assistant.test.tsx#L1-L2: Move this file to packages/ai-solid/src/use-assistant.test.tsx and update relative imports (e.g., useAssistant and test-utils).
  • packages/ai-vue/tests/use-assistant.test.ts#L1-L2: Move this file to packages/ai-vue/src/use-assistant.test.ts and update relative imports.
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/tests/use-assistant.test.tsx` around lines 1 - 2, Move
packages/ai-solid/tests/use-assistant.test.tsx to
packages/ai-solid/src/use-assistant.test.tsx and update its relative imports,
including useAssistant and test-utils; move
packages/ai-vue/tests/use-assistant.test.ts to
packages/ai-vue/src/use-assistant.test.ts and update its relative imports
accordingly.

Source: Coding guidelines


5-7: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Fix import order.

The static analysis tool indicates that the type import for @tanstack/ai should occur after the import of ./test-utils.

♻️ Proposed fix
-import type { StreamChunk } from '`@tanstack/ai`'
 import { useAssistant } from '../src/use-assistant.js'
 import { createTextChunks } from './test-utils'
+import type { StreamChunk } from '`@tanstack/ai`'
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/tests/use-assistant.test.tsx` around lines 5 - 7, Reorder
the imports in the use-assistant test so the local ./test-utils import appears
before the type-only `@tanstack/ai` import, while preserving the existing imported
symbols.

Source: Linters/SAST tools

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

23-29: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Remove unnecessary type assertion.

The static analysis tool points out that the as unknown as Array<StreamChunk> assertion is unnecessary, as the returned array already satisfies the function's return type.

♻️ Proposed fix
     {
       type: 'RUN_FINISHED',
       runId: 'run-1',
       finishReason: 'stop',
       timestamp: Date.now(),
     },
-  ] as unknown as Array<StreamChunk>
+  ]
 }
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-vue/tests/use-assistant.test.ts` around lines 23 - 29, Remove the
unnecessary β€œas unknown as Array<StreamChunk>” assertion from the stream chunk
fixture in the use-assistant test, leaving the array directly typed through its
existing context and preserving the current entries unchanged.

Source: Linters/SAST tools

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-vue/src/use-assistant.ts`:
- Line 140: Update the Vue composable initialization flow in useAssistant to
synchronize the initial state from the newly instantiated AssistantClient,
matching the Solid implementation. Populate chatMessages and the other exposed
state refs from the client’s initial values immediately after client creation,
including configured initialMessages.

---

Duplicate comments:
In `@packages/ai-solid/src/use-assistant.ts`:
- Around line 320-321: Remove the unnecessary unknown intermediary from the
system casts in packages/ai-solid/src/use-assistant.ts#L320-L321 and
packages/ai-vue/src/use-assistant.ts#L246-L247, changing both returns to cast
system directly as AssistantSystem<TDef, TChatTools>. Preserve the existing
dynamically assembled system behavior and surrounding lint suppression.

In `@packages/ai/src/activities/assistant/index.ts`:
- Around line 36-40: Add a body-shape guard before the cast and property access
in the request handling flow: only treat non-null object bodies as records, and
use an empty record for null or primitive JSON values. Ensure this validation
applies before both the chat and one-shot capability branches so forwardedProps
extraction never dereferences an invalid body.

In `@testing/e2e/src/routes/assistant.tsx`:
- Around line 7-8: Reorder the imports in the assistant route so the type-only
Provider import from `@/lib/types` appears before the ChatUI value import from
`@/components/ChatUI`, satisfying the ESLint import/order rule without changing
usage.

---

Nitpick comments:
In `@packages/ai-solid/tests/use-assistant.test.tsx`:
- Around line 1-2: Move packages/ai-solid/tests/use-assistant.test.tsx to
packages/ai-solid/src/use-assistant.test.tsx and update its relative imports,
including useAssistant and test-utils; move
packages/ai-vue/tests/use-assistant.test.ts to
packages/ai-vue/src/use-assistant.test.ts and update its relative imports
accordingly.
- Around line 5-7: Reorder the imports in the use-assistant test so the local
./test-utils import appears before the type-only `@tanstack/ai` import, while
preserving the existing imported symbols.

In `@packages/ai-vue/tests/use-assistant.test.ts`:
- Around line 23-29: Remove the unnecessary β€œas unknown as Array<StreamChunk>”
assertion from the stream chunk fixture in the use-assistant test, leaving the
array directly typed through its existing context and preserving the current
entries unchanged.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca6b9103-9cce-4c5c-8d5d-b3519b3fdff0

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 6ec3013 and 4b65ae5.

πŸ“’ Files selected for processing (52)
  • .changeset/assistant-multi-capability.md
  • docs/assistant/index.md
  • docs/config.json
  • packages/ai-client/package.json
  • packages/ai-client/src/assistant-client.ts
  • packages/ai-client/src/assistant-structured.ts
  • packages/ai-client/src/assistant-types.ts
  • packages/ai-client/src/assistant.ts
  • packages/ai-client/tests/assistant-client.test.ts
  • packages/ai-client/tests/assistant-structured.test.ts
  • packages/ai-client/vite.config.ts
  • packages/ai-react/package.json
  • packages/ai-react/src/assistant.ts
  • packages/ai-react/src/use-assistant.ts
  • packages/ai-react/tests/use-assistant.test.tsx
  • packages/ai-react/vite.config.ts
  • packages/ai-solid/package.json
  • packages/ai-solid/src/assistant.ts
  • packages/ai-solid/src/use-assistant.ts
  • packages/ai-solid/tests/use-assistant.test.tsx
  • packages/ai-solid/tsdown.config.ts
  • packages/ai-svelte/package.json
  • packages/ai-svelte/src/assistant.ts
  • packages/ai-svelte/src/create-assistant.svelte.ts
  • packages/ai-svelte/tests/create-assistant.svelte.test.ts
  • packages/ai-vue/package.json
  • packages/ai-vue/src/assistant.ts
  • packages/ai-vue/src/use-assistant.ts
  • packages/ai-vue/tests/use-assistant.test.ts
  • packages/ai-vue/tsdown.config.ts
  • packages/ai/package.json
  • packages/ai/skills/ai-core/assistant/SKILL.md
  • packages/ai/skills/ai-core/chat-experience/SKILL.md
  • packages/ai/skills/ai-core/custom-backend-integration/SKILL.md
  • packages/ai/skills/ai-core/media-generation/SKILL.md
  • packages/ai/src/activities/assistant/index.ts
  • packages/ai/src/activities/assistant/types.ts
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/assistant.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/tests/activities/assistant/index.test.ts
  • packages/ai/tests/activities/chat/chat-result-meta.test.ts
  • packages/ai/vite.config.ts
  • testing/e2e/fixtures/assistant/basic.json
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.assistant.ts
  • testing/e2e/src/routes/assistant.tsx
  • testing/e2e/tests/assistant.spec.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/ai/tests/activities/chat/chat-result-meta.test.ts
  • docs/config.json
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/lib/features.ts
  • packages/ai/src/types.ts
  • testing/e2e/src/routes/api.assistant.ts
  • packages/ai-svelte/tests/create-assistant.svelte.test.ts
  • packages/ai/src/activities/assistant/types.ts
  • .changeset/assistant-multi-capability.md
  • packages/ai-react/tests/use-assistant.test.tsx
  • packages/ai-client/src/assistant-structured.ts
  • testing/e2e/src/lib/feature-support.ts
  • packages/ai-react/src/use-assistant.ts
  • packages/ai/src/activities/chat/index.ts
  • packages/ai-client/src/assistant-client.ts
  • packages/ai-client/tests/assistant-structured.test.ts
  • testing/e2e/tests/assistant.spec.ts
  • packages/ai-client/src/assistant-types.ts
  • testing/e2e/src/routeTree.gen.ts

}),
},
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

Sync initial chat state.

Unlike the Solid implementation which manually synchronizes the initial chat state after client instantiation, the Vue composable omits this step. Consequently, chatMessages and other state refs will incorrectly remain empty/default initially, even if the AssistantClient was initialized with properties like initialMessages.

πŸ› Proposed fix
     },
   })
 
+  if (client.chat) {
+    chatMessages.value = client.chat.getMessages()
+    chatIsLoading.value = client.chat.getIsLoading()
+    chatError.value = client.chat.getError()
+    chatStatus.value = client.chat.getStatus()
+    chatIsSubscribed.value = client.chat.getIsSubscribed()
+    chatConnectionStatus.value = client.chat.getConnectionStatus()
+    chatSessionGenerating.value = client.chat.getSessionGenerating()
+  }
+
   onMounted(() => {
     client.chat?.mountDevtools()
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (client.chat) {
chatMessages.value = client.chat.getMessages()
chatIsLoading.value = client.chat.getIsLoading()
chatError.value = client.chat.getError()
chatStatus.value = client.chat.getStatus()
chatIsSubscribed.value = client.chat.getIsSubscribed()
chatConnectionStatus.value = client.chat.getConnectionStatus()
chatSessionGenerating.value = client.chat.getSessionGenerating()
}
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-vue/src/use-assistant.ts` at line 140, Update the Vue composable
initialization flow in useAssistant to synchronize the initial state from the
newly instantiated AssistantClient, matching the Solid implementation. Populate
chatMessages and the other exposed state refs from the client’s initial values
immediately after client creation, including configured initialMessages.

@AlemTuzlak AlemTuzlak force-pushed the feat/define-use-assistant branch from 4b65ae5 to 0acb9a9 Compare July 14, 2026 10:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
docs/config.json (1)

314-319: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Remove updatedAt for new documentation pages.

As per coding guidelines, new pages should only set addedAt. The updatedAt field is reserved for substantive content changes to existing pages. Since this page is newly added in this PR, please remove updatedAt.

♻️ Proposed fix
         {
           "label": "Overview",
           "to": "assistant/overview",
-          "addedAt": "2026-07-13",
-          "updatedAt": "2026-07-14"
+          "addedAt": "2026-07-14"
         },
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/config.json` around lines 314 - 319, Remove the updatedAt property from
the new β€œOverview” documentation page entry, keeping its label, destination, and
addedAt metadata unchanged.

Source: Coding guidelines

packages/ai-solid/src/use-assistant.ts (1)

251-256: πŸš€ Performance & Scalability | πŸ”΅ Trivial | ⚑ Quick win

Memoize computeStructuredParts instead of recomputing on every access.

Unlike the Vue (computed) and Svelte ($derived) implementations, these getters call computeStructuredParts(chatState().messages) directly and unmemoized β€” twice per read when both partial and final are accessed. For large message histories this recomputes the full derivation on every render/read.

♻️ Proposed fix
+  const structuredParts = createMemo(() =>
+    computeStructuredParts(chatState().messages),
+  )
+
   for (const capability of assistant.capabilities) {
     if (capability === 'chat') {
       system.chat = {
         ...
         get partial() {
-          return computeStructuredParts(chatState().messages).partial
+          return structuredParts().partial
         },
         get final() {
-          return computeStructuredParts(chatState().messages).final
+          return structuredParts().final
         },
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/src/use-assistant.ts` around lines 251 - 256, Memoize the
result of computeStructuredParts for the assistant state instead of invoking it
directly in the partial and final getters. Reuse the memoized result from both
getters so each messages state is derived once per update, matching the
computed/derived behavior of the other implementations.
packages/ai-solid/tests/use-assistant.test.tsx (1)

5-7: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Reorder imports to satisfy linter.

Static analysis tools indicate that the type import for @tanstack/ai should be placed after the relative imports to adhere to the project's import order conventions.

♻️ Proposed fix
-import type { StreamChunk } from '`@tanstack/ai`'
 import { useAssistant } from '../src/use-assistant.js'
 import { createTextChunks } from './test-utils'
+import type { StreamChunk } from '`@tanstack/ai`'
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/tests/use-assistant.test.tsx` around lines 5 - 7, Reorder
the imports in the use-assistant test so the relative imports, including
useAssistant and createTextChunks, appear before the type import from
`@tanstack/ai`, preserving all existing imports and behavior.

Source: Linters/SAST tools

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

23-29: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Remove unnecessary type assertion.

Static analysis tools flag that the double assertion as unknown as Array<StreamChunk> is unnecessary because the returned array literal already correctly satisfies the function's explicit Array<StreamChunk> return type.

♻️ Proposed fix
     {
       type: 'RUN_FINISHED',
       runId: 'run-1',
       finishReason: 'stop',
       timestamp: Date.now(),
     },
-  ] as unknown as Array<StreamChunk>
+  ]
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-vue/tests/use-assistant.test.ts` around lines 23 - 29, Remove the
unnecessary `as unknown as Array<StreamChunk>` assertion from the stream chunk
array in the test fixture, relying on the function’s explicit
`Array<StreamChunk>` return type and preserving the existing array contents.

Source: Linters/SAST tools

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-solid/src/use-assistant.ts`:
- Around line 320-321: Remove the unnecessary unknown cast from all three
AssistantSystem assertions. In packages/ai-solid/src/use-assistant.ts lines
320-321, use a direct AssistantSystem<TDef, TChatTools> cast; in
packages/ai-svelte/src/create-assistant.svelte.ts lines 293-296, preserve the
dispose intersection while casting directly; and in
packages/ai-vue/src/use-assistant.ts lines 246-248, use the direct
AssistantSystem<TDef, TChatTools> cast. Update each eslint-disable comment to
target the applicable rule or remove it if no longer needed.

In `@packages/ai-svelte/src/create-assistant.svelte.ts`:
- Line 164: Update the post-construction synchronization in the assistant setup
around client.chat?.getMessages() so it initializes all seven chat state fields
from client.chat, including chatIsLoading, chatError, chatStatus,
chatIsSubscribed, chatConnectionStatus, and chatSessionGenerating, while
preserving the existing messages fallback.

In `@packages/ai-vue/src/use-assistant.ts`:
- Around line 246-248: Update the return in the Vue assistant composable to
remove the unnecessary unknown-based type assertion and its misplaced
eslint-disable comment. Return system directly as AssistantSystem<TDef,
TChatTools>, matching the shared fix used by the Solid and Svelte hooks.

---

Nitpick comments:
In `@docs/config.json`:
- Around line 314-319: Remove the updatedAt property from the new β€œOverview”
documentation page entry, keeping its label, destination, and addedAt metadata
unchanged.

In `@packages/ai-solid/src/use-assistant.ts`:
- Around line 251-256: Memoize the result of computeStructuredParts for the
assistant state instead of invoking it directly in the partial and final
getters. Reuse the memoized result from both getters so each messages state is
derived once per update, matching the computed/derived behavior of the other
implementations.

In `@packages/ai-solid/tests/use-assistant.test.tsx`:
- Around line 5-7: Reorder the imports in the use-assistant test so the relative
imports, including useAssistant and createTextChunks, appear before the type
import from `@tanstack/ai`, preserving all existing imports and behavior.

In `@packages/ai-vue/tests/use-assistant.test.ts`:
- Around line 23-29: Remove the unnecessary `as unknown as Array<StreamChunk>`
assertion from the stream chunk array in the test fixture, relying on the
function’s explicit `Array<StreamChunk>` return type and preserving the existing
array contents.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8c2c137-6831-442b-90e4-edef2675d6d3

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 4b65ae5 and 0acb9a9.

πŸ“’ Files selected for processing (54)
  • .changeset/assistant-multi-capability.md
  • docs/assistant/multiple-assistants.md
  • docs/assistant/overview.md
  • docs/assistant/scenarios.md
  • docs/config.json
  • packages/ai-client/package.json
  • packages/ai-client/src/assistant-client.ts
  • packages/ai-client/src/assistant-structured.ts
  • packages/ai-client/src/assistant-types.ts
  • packages/ai-client/src/assistant.ts
  • packages/ai-client/tests/assistant-client.test.ts
  • packages/ai-client/tests/assistant-structured.test.ts
  • packages/ai-client/vite.config.ts
  • packages/ai-react/package.json
  • packages/ai-react/src/assistant.ts
  • packages/ai-react/src/use-assistant.ts
  • packages/ai-react/tests/use-assistant.test.tsx
  • packages/ai-react/vite.config.ts
  • packages/ai-solid/package.json
  • packages/ai-solid/src/assistant.ts
  • packages/ai-solid/src/use-assistant.ts
  • packages/ai-solid/tests/use-assistant.test.tsx
  • packages/ai-solid/tsdown.config.ts
  • packages/ai-svelte/package.json
  • packages/ai-svelte/src/assistant.ts
  • packages/ai-svelte/src/create-assistant.svelte.ts
  • packages/ai-svelte/tests/create-assistant.svelte.test.ts
  • packages/ai-vue/package.json
  • packages/ai-vue/src/assistant.ts
  • packages/ai-vue/src/use-assistant.ts
  • packages/ai-vue/tests/use-assistant.test.ts
  • packages/ai-vue/tsdown.config.ts
  • packages/ai/package.json
  • packages/ai/skills/ai-core/assistant/SKILL.md
  • packages/ai/skills/ai-core/chat-experience/SKILL.md
  • packages/ai/skills/ai-core/custom-backend-integration/SKILL.md
  • packages/ai/skills/ai-core/media-generation/SKILL.md
  • packages/ai/src/activities/assistant/index.ts
  • packages/ai/src/activities/assistant/types.ts
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/assistant.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/tests/activities/assistant/index.test.ts
  • packages/ai/tests/activities/chat/chat-result-meta.test.ts
  • packages/ai/vite.config.ts
  • testing/e2e/fixtures/assistant/basic.json
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.assistant.ts
  • testing/e2e/src/routes/assistant.tsx
  • testing/e2e/tests/assistant.spec.ts
🚧 Files skipped from review as they are similar to previous changes (39)
  • packages/ai-vue/src/assistant.ts
  • packages/ai-svelte/src/assistant.ts
  • packages/ai-solid/src/assistant.ts
  • testing/e2e/fixtures/assistant/basic.json
  • testing/e2e/tests/assistant.spec.ts
  • packages/ai-solid/package.json
  • testing/e2e/src/lib/types.ts
  • packages/ai-client/package.json
  • packages/ai-client/src/assistant-structured.ts
  • packages/ai-solid/tsdown.config.ts
  • packages/ai/src/index.ts
  • packages/ai-vue/tsdown.config.ts
  • packages/ai-react/vite.config.ts
  • packages/ai-svelte/package.json
  • packages/ai-react/src/assistant.ts
  • packages/ai/tests/activities/chat/chat-result-meta.test.ts
  • packages/ai-client/src/assistant.ts
  • packages/ai-client/tests/assistant-structured.test.ts
  • packages/ai/vite.config.ts
  • packages/ai-client/tests/assistant-client.test.ts
  • packages/ai-svelte/tests/create-assistant.svelte.test.ts
  • packages/ai/src/types.ts
  • testing/e2e/src/lib/feature-support.ts
  • packages/ai-react/package.json
  • packages/ai/src/assistant.ts
  • testing/e2e/src/routes/api.assistant.ts
  • testing/e2e/src/lib/features.ts
  • packages/ai-client/vite.config.ts
  • packages/ai/src/activities/assistant/types.ts
  • packages/ai-vue/package.json
  • packages/ai/package.json
  • packages/ai-react/tests/use-assistant.test.tsx
  • packages/ai/tests/activities/assistant/index.test.ts
  • packages/ai-react/src/use-assistant.ts
  • packages/ai-client/src/assistant-client.ts
  • packages/ai-client/src/assistant-types.ts
  • packages/ai/src/activities/assistant/index.ts
  • packages/ai/src/activities/chat/index.ts
  • testing/e2e/src/routeTree.gen.ts

Comment thread packages/ai-solid/src/use-assistant.ts Outdated
Comment on lines +320 to +321
// eslint-disable-next-line no-restricted-syntax -- built dynamically per declared capability; TS can't structurally verify the assembled object against the mapped AssistantSystem type
return system as unknown as AssistantSystem<TDef, TChatTools>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟑 Minor | ⚑ Quick win

Drop the unnecessary unknown hop in all three AssistantSystem casts β€” the eslint-disable-next-line targets the wrong rule. Each site casts the assembled system object through unknown before AssistantSystem<...>, which the object is already structurally assignable to; the disable comment silences no-restricted-syntax while ESLint actually flags no-unnecessary-type-assertion at each of these lines, so the error persists in CI.

  • packages/ai-solid/src/use-assistant.ts#L320-L321: change system as unknown as AssistantSystem<TDef, TChatTools> to system as AssistantSystem<TDef, TChatTools>.
  • packages/ai-svelte/src/create-assistant.svelte.ts#L293-L296: change system as unknown as AssistantSystem<TDef, TChatTools> & { dispose: () => void } to system as AssistantSystem<TDef, TChatTools> & { dispose: () => void }.
  • packages/ai-vue/src/use-assistant.ts#L246-L248: change system as unknown as AssistantSystem<TDef, TChatTools> to system as AssistantSystem<TDef, TChatTools>.
🧰 Tools
πŸͺ› ESLint

[error] 321-321: This assertion is unnecessary since the receiver accepts the original type of the expression.

(@typescript-eslint/no-unnecessary-type-assertion)

πŸ“ Affects 3 files
  • packages/ai-solid/src/use-assistant.ts#L320-L321 (this comment)
  • packages/ai-svelte/src/create-assistant.svelte.ts#L293-L296
  • packages/ai-vue/src/use-assistant.ts#L246-L248
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/src/use-assistant.ts` around lines 320 - 321, Remove the
unnecessary unknown cast from all three AssistantSystem assertions. In
packages/ai-solid/src/use-assistant.ts lines 320-321, use a direct
AssistantSystem<TDef, TChatTools> cast; in
packages/ai-svelte/src/create-assistant.svelte.ts lines 293-296, preserve the
dispose intersection while casting directly; and in
packages/ai-vue/src/use-assistant.ts lines 246-248, use the direct
AssistantSystem<TDef, TChatTools> cast. Update each eslint-disable comment to
target the applicable rule or remove it if no longer needed.

Source: Linters/SAST tools

},
})

chatMessages = client.chat?.getMessages() ?? []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

Initial chat state sync is incomplete β€” only messages is pulled from the constructed client.

chatIsLoading, chatError, chatStatus, chatIsSubscribed, chatConnectionStatus, and chatSessionGenerating stay at their $state defaults instead of being read from the just-constructed client.chat. If the sub-client's actual initial state diverges from these defaults (e.g. resuming a thread), the returned system will report stale values until the next callback fires. The Solid implementation of this same hook syncs all seven fields after construction.

πŸ› Proposed fix
-  chatMessages = client.chat?.getMessages() ?? []
+  if (client.chat) {
+    chatMessages = client.chat.getMessages()
+    chatIsLoading = client.chat.getIsLoading()
+    chatError = client.chat.getError()
+    chatStatus = client.chat.getStatus()
+    chatIsSubscribed = client.chat.getIsSubscribed()
+    chatConnectionStatus = client.chat.getConnectionStatus()
+    chatSessionGenerating = client.chat.getSessionGenerating()
+  }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
chatMessages = client.chat?.getMessages() ?? []
if (client.chat) {
chatMessages = client.chat.getMessages()
chatIsLoading = client.chat.getIsLoading()
chatError = client.chat.getError()
chatStatus = client.chat.getStatus()
chatIsSubscribed = client.chat.getIsSubscribed()
chatConnectionStatus = client.chat.getConnectionStatus()
chatSessionGenerating = client.chat.getSessionGenerating()
}
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-svelte/src/create-assistant.svelte.ts` at line 164, Update the
post-construction synchronization in the assistant setup around
client.chat?.getMessages() so it initializes all seven chat state fields from
client.chat, including chatIsLoading, chatError, chatStatus, chatIsSubscribed,
chatConnectionStatus, and chatSessionGenerating, while preserving the existing
messages fallback.

Comment on lines +246 to +248
// eslint-disable-next-line no-restricted-syntax -- composable return shape (nested refs per capability) diverges from the framework-agnostic AssistantSystem type (plain values); TS can't structurally relate the two
return system as unknown as AssistantSystem<TDef, TChatTools>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟠 Major | ⚑ Quick win

eslint-disable-next-line targets the wrong rule; type-assertion error remains.

Same mismatch as the Solid/Svelte hooks: the disable comment silences no-restricted-syntax, but ESLint flags no-unnecessary-type-assertion here (system is already structurally assignable to AssistantSystem<TDef, TChatTools> without the unknown hop). See the consolidated comment for the shared fix.

🧰 Tools
πŸͺ› ESLint

[error] 247-247: This assertion is unnecessary since the receiver accepts the original type of the expression.

(@typescript-eslint/no-unnecessary-type-assertion)

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-vue/src/use-assistant.ts` around lines 246 - 248, Update the
return in the Vue assistant composable to remove the unnecessary unknown-based
type assertion and its misplaced eslint-disable comment. Return system directly
as AssistantSystem<TDef, TChatTools>, matching the shared fix used by the Solid
and Svelte hooks.

Source: Linters/SAST tools

Declare an assistant's capabilities once on the server and consume them
from one typed client hook, over a single endpoint.

Server (`@tanstack/ai/assistant`)
- `defineAssistant(config)`: an inert registry of per-capability callbacks
  (chat, image, audio, speech, video, transcription, summarize), each
  invoking an existing activity. Exposes one `handler(request)` that parses
  the AG-UI request, routes by a `capability` discriminator, and streams the
  result back over the existing SSE wire. Nothing is constructed until a
  request arrives.
- Every callback β€” chat and one-shots alike β€” receives the same validated
  AG-UI envelope: `chat` gets `AssistantChatRequest`; one-shots get their
  input plus `threadId`/`runId`/`parentRunId`/`state`/`aguiContext`/
  `forwardedProps` and the raw `request`, all parsed through the same
  request validator (`chatParamsFromRequestBody`). Untrusted input fields
  (e.g. `req.prompt`) stay `unknown` so callbacks narrow before use.

Client core (`@tanstack/ai-client/assistant`)
- `AssistantClient` composes one `ChatClient` + one `GenerationClient` per
  declared capability behind a single connection. `AssistantSystem<TDef>`
  maps declared capabilities to fully-typed surfaces, inferred from the
  definition β€” no call-site generics.
- The imperative methods resolve to their results, so capabilities chain
  with a plain `await` instead of reading reactive state (which is stale in
  an async closure under React): `assistant.<oneshot>.generate(input)`
  resolves to the generation result (`TResult | null`), and
  `assistant.chat.sendMessage(...)` resolves to the validated structured
  `final` when the chat callback declared an `outputSchema`, otherwise to
  the messages array. Reactive `.result`/`.messages`/`.partial`/`.final`
  remain for rendering.

Framework hooks
- `useAssistant` for React / Solid / Vue and `createAssistant` for Svelte
  (`@tanstack/ai-<fw>/assistant`). Each capability entry is the corresponding
  primitive hook's own return (`useChat` / `useGeneration`).

Chat type inference
- `chat()`'s return carries an optional type-only phantom
  (`ChatResultMeta<TTools, TSchema, TStream>`), so `assistant.chat` infers
  typed `partial`/`final` from an `outputSchema` and types message tool-call
  parts from the callback's tools β€” no client re-declaration. Runtime
  unchanged. Client-executed tools still pass through `chat: { tools }` for
  their browser implementations (their code can't cross the wire).

Docs (`docs/assistant`): a 3-page section β€” Overview (what an assistant is,
when to reach for one vs. single hooks, and typed chat/tools/structured
output), Scenarios (sequential pipeline + CMS content workflow, chained via
the resolved `await` returns), and Multiple Assistants (specialized
per-surface assistants across routes, plus composing two assistants together
on one page). Plus an `ai-core/assistant` agent skill, E2E coverage (chat
verified end-to-end; image one-shot skipped pending an aimock media fixture),
and a changeset.
@AlemTuzlak AlemTuzlak force-pushed the feat/define-use-assistant branch from 0acb9a9 to 114416c Compare July 14, 2026 13:06
autofix-ci Bot and others added 3 commits July 14, 2026 13:08
A topic β†’ finished blog post demo of the multi-capability assistant. One
`defineAssistant` route hosts structured-output chat, image, and speech;
the page drafts the post, then illustrates and narrates it in parallel via
the resolved `await` returns, and renders the result as an editorial
article (hero image, Markdown body, voice-over player) in a two-column
studio layout. Failed image/speech steps degrade gracefully with an honest
per-step status; narration text is Markdown-stripped and length-capped for
the TTS limit.
`useAssistant` now accepts a per-one-shot-capability `{ onResult, forwardedProps }`
option (keyed by capability name), mirroring the standalone generation hooks.
`onResult` runs on the raw backend result and its return type becomes
`assistant.<capability>.result` (via `InferGenerationOutput`); the parameter is
typed as the raw result. Full inference both directions β€” locked by an
`expectTypeOf` test. Threaded through `AssistantClient` into each
`GenerationClient`, across all four framework hooks.

Also refactors the Blog Studio example to derive 100% of its UI state from the
assistant surface β€” zero `useState`/`useEffect`/`useRef`. Chat
`isLoading`/`partial`/`final` and each one-shot's `result`/`status`/`error`
drive the stepper, hero, and article; the voice-over uses `speech.onResult` to
expose a ready `{ src }` data URL (no object-URL, no cleanup).

Docs (assistant overview) and the ai-core/assistant skill updated for the new
option.
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.

1 participant