Skip to content

Conversation

@birkskyum
Copy link
Member

@birkskyum birkskyum commented Dec 12, 2025

Introducing the

  • vue-ai-devtools

And an example with SSR, ported from react-start to vue-start:

  • ts-vue-start-chat

Using the new devtools in both

  • ts-vue-start-chat
  • ts-vue-chat (the using the non-tanstack vue-router)

🎯 Changes

✅ 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
    • Vue AI DevTools panel and devtools integration.
    • New Vue+TS chat example: model selector, multi-provider chat, streaming chat API, guitar recommendations, and shopping approval flow.
    • Client-side tools for wishlist/cart and recommend actions; approval UI for protected actions.
  • Docs
    • Added example README, CHANGELOG, and setup docs.
  • Assets
    • Added PWA manifest, robots.txt, Tailwind styling, and .gitignore.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

Adds a new Vue 3 example app (examples/ts-vue-start-chat) with routing, model selection, client/server chat (streaming /api/chat), guitar tools, UI components, and TanStack Vue AI devtools; also adds a new @tanstack/vue-ai-devtools package and integrates devtools into the existing ts-vue-chat example.

Changes

Cohort / File(s) Summary
Project manifests & config
examples/ts-vue-start-chat/package.json, examples/ts-vue-start-chat/tsconfig.json, examples/ts-vue-start-chat/vite.config.ts, examples/ts-vue-start-chat/.gitignore, examples/ts-vue-start-chat/CHANGELOG.md, examples/ts-vue-start-chat/README.md, examples/ts-vue-start-chat/public/manifest.json, examples/ts-vue-start-chat/public/robots.txt
New project metadata, TypeScript/Vite configs, scripts, docs, changelog, and static web assets for the ts-vue-start-chat example.
Components (ts-vue-start-chat)
examples/ts-vue-start-chat/src/components/ChatInput.vue, .../GuitarRecommendation.vue, .../Header.vue, .../Messages.vue, .../ClientOnly.vue
New Vue components: chat input (auto-resize/send/stop), guitar card, header/sidebar, message renderer with approval UI, and client-only mount wrapper.
Views (ts-vue-start-chat)
examples/ts-vue-start-chat/src/views/ChatView.vue, .../GuitarDetailView.vue, .../GuitarsView.vue, .../VueUIView.vue
Page-level views: chat UI (useChat + model selection + client tools), guitar listing/detail pages, and an ai-vue-ui based view.
Routing & server route
examples/ts-vue-start-chat/src/routes/__root.tsx, .../index.ts, .../vue-ui.ts, .../guitars/index.ts, .../guitars/$id.ts, .../api.chat.ts, examples/ts-vue-start-chat/src/routeTree.gen.ts, examples/ts-vue-start-chat/src/router.ts
TanStack Router setup and generated route tree; root layout injecting devtools; app routes; new /api/chat POST handler implementing provider/model selection, AI adapter instantiation, toolset integration, agent loop, streaming responses, and abort handling.
Data, tools & model selection
examples/ts-vue-start-chat/src/data/guitars.ts, .../lib/guitar-tools.ts, .../lib/model-selection.ts, .../styles.css
Guitar dataset; toolDefinitions (including needsApproval flags); model/provider options with localStorage persistence; Tailwind-based styles.
Client chat integration
examples/ts-vue-start-chat/src/views/ChatView.vue, examples/ts-vue-start-chat/src/views/VueUIView.vue, examples/ts-vue-start-chat/src/components/Messages.vue
Client-side wiring for useChat and fetchServerSentEvents: sending provider/model info, streaming updates, approval responses, and client tool implementations (wishlist/cart persistence).
ts-vue-chat updates
examples/ts-vue-chat/package.json, examples/ts-vue-chat/src/App.vue
Added @tanstack/vue-ai-devtools and @tanstack/vue-devtools deps; integrated aiDevtoolsPlugin and TanStackDevtools into App.vue.
vue-ai-devtools package manifest & tooling
packages/typescript/vue-ai-devtools/package.json, packages/typescript/vue-ai-devtools/CHANGELOG.md, packages/typescript/vue-ai-devtools/README.md, packages/typescript/vue-ai-devtools/tsconfig.json, packages/typescript/vue-ai-devtools/vite.config.ts
New package metadata, docs, changelog and build/test configuration for the Vue AI devtools package.
vue-ai-devtools implementation
packages/typescript/vue-ai-devtools/src/index.ts, .../plugin.ts, .../production.ts, .../server.ts, .../AiDevtoolsPanel.ts, .../AiDevtoolsPanelInProd.ts, .../createVuePanel.ts
Vue integration for AI DevTools: environment-conditional exports (dev/prod/SSR), plugin factories (no-op and full), panel wrapper components and createVuePanel utility that mounts DevTools core into Vue components.
Misc generated & helper files
examples/ts-vue-start-chat/src/routeTree.gen.ts, examples/ts-vue-start-chat/src/components/ClientOnly.vue
Generated route tree typings and small helper component for client-only rendering.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as ChatView / Messages
    participant Client as useChat Hook
    participant Server as /api/chat Route
    participant Provider as AI Provider
    participant Tools as Tool Executor

    User->>UI: Select model & send message
    UI->>Client: sendMessage(text)
    Client->>Server: POST /api/chat { provider, model, conversationId }
    Server->>Provider: Create adapter & run agent with tools
    loop Agent loop (<=20)
        Provider-->>Server: Response (text or tool call)
        alt Tool call requires approval
            Server-->>Client: Stream approval request
            Client->>UI: Render approve/deny
            User->>UI: Approve/deny
            UI->>Client: addToolApprovalResponse(decision)
            Client->>Server: Send approval decision
            Server->>Tools: Execute tool with approval
            Tools-->>Server: Tool result
        else Tool call (no approval)
            Server->>Tools: Execute tool
            Tools-->>Server: Tool result
        end
        Server-->>Client: Stream response parts
        Client->>UI: Update messages
    end
    UI->>User: Render conversation and recommendations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • packages/typescript/vue-ai-devtools: environment-conditional exports, createVuePanel mounting/unmount lifecycle and cleanup.
    • examples/ts-vue-start-chat/src/routes/api.chat.ts: provider/model selection, streaming, abort handling, agent loop and tool orchestration.
    • Tool definitions and Zod schemas in src/lib/guitar-tools.ts: ensure input/output shapes and needsApproval flags match client expectations.
    • Client-server approval flow: addToolApprovalResponse, streaming message format, and client handling (ChatView, Messages, ChatInput).
    • Generated route tree and router setup: type/runtime alignment with file-based routes.

Poem

🐰 I hopped through files to spin a chat so neat,

Guitars strum answers and models choose the beat,
Devtools peek in, tools ask to be sure,
Approve a wish, send to cart, then more—encore! 🎸✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description lists the key additions (vue-ai-devtools and ts-vue-start-chat example) and mentions integration points, but the 'Changes' section is empty with only a comment placeholder, leaving the motivation and detailed explanation of changes unfilled. Expand the 'Changes' section to explain the motivation, key features, and implementation details of vue-ai-devtools and ts-vue-start-chat to provide context for reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the two main additions: a new vue-ai-devtools package and a new ts-vue-start-chat example, which are the primary changes in this changeset.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ts-vue-start-chat

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9200f2c and 1e5bdc3.

📒 Files selected for processing (1)
  • examples/ts-vue-start-chat/.env.example (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • examples/ts-vue-start-chat/.env.example

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.

@nx-cloud
Copy link

nx-cloud bot commented Dec 12, 2025

View your CI Pipeline Execution ↗ for commit 1e5bdc3

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

☁️ Nx Cloud last updated this comment at 2025-12-12 15:49:37 UTC

@birkskyum birkskyum changed the title Ts vue start chat vue devtools and vue start chat Dec 12, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 12, 2025

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-devtools-core

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

@tanstack/ai-gemini

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

@tanstack/vue-ai-devtools

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

commit: 1e5bdc3

@birkskyum birkskyum changed the title vue devtools and vue start chat vue-ai-devtools and ts-vue-start-chat Dec 12, 2025
@birkskyum birkskyum marked this pull request as ready for review December 12, 2025 11:58
@birkskyum birkskyum requested a review from a team December 12, 2025 11:58
Copy link

@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: 18

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
examples/ts-vue-chat/package.json (1)

12-27: @tanstack/vue-devtools is not a valid npm package. The official TanStack Vue devtools package is @tanstack/vue-query-devtools (currently at v5.x–6.x). This import in App.vue will fail at build time. Replace @tanstack/vue-devtools: ^0.1.2 with the correct package @tanstack/vue-query-devtools and verify the component API (props config, plugins, eventBusConfig) matches the actual library version.

🟡 Minor comments (7)
examples/ts-vue-start-chat/README.md-38-65 (1)

38-65: Add a language to the fenced “Project Structure” block (MD040).

markdownlint flags the fence at Line 38 as missing a language.

-```
+```text
 src/
 ├── routes/           # File-based routes (TanStack Router/Start)
 ...
 └── views/            # Page components
     ├── ChatView.vue
     └── GuitarDetailView.vue
-```
+```

Also worth a quick check that the listed filenames/extensions (e.g. __root.tsx) match what’s actually in the example.

examples/ts-vue-start-chat/CHANGELOG.md-1-2 (1)

1-2: Changelog title doesn’t match the package/example name.

This looks like a copy/paste from the other example:

-# ts-vue-chat
+# ts-vue-start-chat
packages/typescript/vue-ai-devtools/README.md-7-40 (1)

7-40: Fix hard tabs in code fence + remove dangling ## header.

 // in jsx
-	<TanStackDevtools
-		config={{
-			position: 'bottom-right',
-		}}
-		plugins={[
-			{
-				name: 'Tanstack Router',
-				id: 'tanstack-router',
-				component: TanStackRouterDevtoolsPanel,
-			},
-			aiDevtoolsPlugin(),
-		]}
-		eventBusConfig={{
-			connectToServerBus: true,
-		}}
-	/>
+  <TanStackDevtools
+    config={{
+      position: 'bottom-right',
+    }}
+    plugins={[
+      {
+        name: 'Tanstack Router',
+        id: 'tanstack-router',
+        component: TanStackRouterDevtoolsPanel,
+      },
+      aiDevtoolsPlugin(),
+    ]}
+    eventBusConfig={{
+      connectToServerBus: true,
+    }}
+  />
 

-##


If you intend to keep a section after this, give it a real title (e.g. “## Notes”) and content.

</blockquote></details>
<details>
<summary>packages/typescript/vue-ai-devtools/vite.config.ts-1-38 (1)</summary><blockquote>

`1-38`: **Remove unsupported `cjs: false` option from tanstackViteConfig call.**

The `@tanstack/vite-config` function does not support a `cjs` option. Dual ESM/CJS output is handled via the `package.json` exports layout, not configuration parameters. Remove line 36 (`cjs: false,`) as it has no effect and creates confusion about the actual build output strategy.

</blockquote></details>
<details>
<summary>examples/ts-vue-start-chat/package.json-1-47 (1)</summary><blockquote>

`1-47`: **Use official `vue-router` or document community adapter limitation**

The example uses `@tanstack/vue-router@^1.140.5`, which is a community-maintained adapter—TanStack Router v1 only provides first-class support for React and Solid frameworks. While functional, this creates a maintenance/stability risk versus using the official `vue-router@^4.x` (as in the comparable `ts-vue-chat` example). Either switch to the standard Vue Router or add a note to the README explaining this is a community adapter.

Express 5.1.0 ESM and Node 24.8.0 satisfy all other compatibility requirements.

</blockquote></details>
<details>
<summary>examples/ts-vue-start-chat/src/views/ChatView.vue-59-72 (1)</summary><blockquote>

`59-72`: **Handle `selectedIndex === -1` to avoid a “no selection” `<select>` state.**  

```diff
-const selectedIndex = computed(() =>
-  MODEL_OPTIONS.findIndex(
+const selectedIndex = computed(() => {
+  const idx = MODEL_OPTIONS.findIndex(
     (opt) =>
       opt.provider === selectedModel.value.provider &&
       opt.model === selectedModel.value.model,
-  ),
-)
+  )
+  return idx === -1 ? 0 : idx
+})

Also applies to: 107-122

examples/ts-vue-start-chat/src/components/ChatInput.vue-14-31 (1)

14-31: Keydown handler should also respect isLoading to avoid blocking Enter behavior while loading.
Right now, Enter is prevented even though send is blocked in handleSend().

-const handleKeydown = (e: KeyboardEvent) => {
-  if (e.key === 'Enter' && !e.shiftKey && input.value.trim()) {
+const handleKeydown = (e: KeyboardEvent) => {
+  if (e.key === 'Enter' && !e.shiftKey && input.value.trim() && !props.isLoading) {
     e.preventDefault()
     handleSend()
   }
 }
🧹 Nitpick comments (15)
examples/ts-vue-start-chat/.gitignore (1)

1-22: LGTM; standard .gitignore for a Node/Vue example.

Optional: consider aligning with other examples by also ignoring common tooling outputs (e.g., coverage/, .turbo/, .cache/, .vite/) if those are generated in this example.

examples/ts-vue-start-chat/public/manifest.json (1)

4-20: Icon files exist; consider adding purpose field for enhanced PWA icon support.

All three icon files (favicon.ico, logo192.png, logo512.png) are present in the public directory. However, the manifest would benefit from the purpose field on icon definitions to indicate how they should be used (e.g., "maskable" for adaptive icons in modern PWA implementations). Consider updating the icons to include this field:

{
  "src": "logo192.png",
  "type": "image/png",
  "sizes": "192x192",
  "purpose": "any maskable"
}
packages/typescript/vue-ai-devtools/src/server.ts (1)

3-5: Avoid duplicating AiDevtoolsVueInit across entrypoints (type drift risk).
If AiDevtoolsVueInit is also declared in another module (e.g., panel/plugin entry), re-export it from a single source instead of redefining here.

Also consider renaming _propsprops since it’s used.

Also applies to: 18-27

examples/ts-vue-start-chat/src/views/GuitarDetailView.vue (1)

6-16: Harden route param parsing (parseInt radix + NaN handling); consider back fallback.

 const guitar = computed(() => {
-  const id = parseInt(params.value.id as string)
-  return guitars.find((g) => g.id === id)
+  const rawId = params.value.id
+  const id = typeof rawId === 'string' ? Number.parseInt(rawId, 10) : Number.NaN
+  return Number.isFinite(id) ? guitars.find((g) => g.id === id) : undefined
 })
 
 const handleBack = () => {
   router.history.back()
 }

Optional: if history can’t go back, navigate to the list route instead.

examples/ts-vue-start-chat/src/views/GuitarsView.vue (1)

1-40: Prefer typed navigation (to: '/guitars/$id' + params) + lazy-load images
This avoids manual string building/encoding and keeps navigation consistent with the file-route (/guitars/$id). Also consider loading="lazy" for the grid images.

-      <Link
+      <Link
         v-for="guitar in guitars"
         :key="guitar.id"
-        :to="`/guitars/${guitar.id}`"
+        :to="{ to: '/guitars/$id', params: { id: String(guitar.id) } }"
         class="block rounded-lg overflow-hidden border border-orange-500/20 bg-gray-800/50 hover:border-orange-500/40 transition-colors"
       >
 ...
           <img
             :src="guitar.image"
             :alt="guitar.name"
+            loading="lazy"
+            decoding="async"
             class="w-full h-full object-cover"
           />
examples/ts-vue-start-chat/tsconfig.json (1)

1-32: Consider a separate Node tsconfig (or include vite.config.ts) for full typecheck coverage
Right now check won’t typecheck non-src/ TS (e.g., vite.config.ts, server entrypoints), which is fine if you intentionally have a separate config—otherwise you may miss regressions.

examples/ts-vue-start-chat/src/components/Header.vue (1)

1-147: Add basic a11y hooks for the off-canvas menu (aria-expanded/controls + ESC close)
This improves keyboard/screen-reader behavior with minimal code.

 <script setup lang="ts">
 import { ref } from 'vue'
 import { Link } from '@tanstack/vue-router'

 const isOpen = ref(false)
+const navId = 'app-offcanvas-nav'
 </script>

 <template>
   <header class="p-4 flex items-center bg-gray-800 text-white shadow-lg">
     <button
       @click="isOpen = true"
       class="p-2 hover:bg-gray-700 rounded-lg transition-colors"
       aria-label="Open menu"
+      :aria-expanded="isOpen"
+      :aria-controls="navId"
     >
 ...
   <!-- Sidebar -->
   <aside
+    :id="navId"
     :class="[
 ...
     ]"
+    @keydown.esc="isOpen = false"
   >

(If you want to go one step further later: focus trap + preventing body scroll while open.)

examples/ts-vue-start-chat/src/components/GuitarRecommendation.vue (1)

1-54: Make id normalization explicit + add type="button"
Avoids NaN surprises and prevents accidental form submission when embedded.

 const props = defineProps<{
   id: string | number
 }>()

 const navigate = useNavigate()

-const guitar = computed(() => guitars.find((g) => g.id === +props.id))
+const guitarId = computed(() =>
+  typeof props.id === 'number' ? props.id : Number.parseInt(props.id, 10),
+)
+const guitar = computed(() => guitars.find((g) => g.id === guitarId.value))

 ...
         <button
           @click="handleViewDetails"
+          type="button"
           class="bg-gradient-to-r from-orange-500 to-red-600 text-white px-4 py-1.5 rounded-lg text-sm hover:opacity-90 transition-opacity"
         >
packages/typescript/vue-ai-devtools/src/plugin.ts (1)

5-10: Consider reusing a shared NullComponent (dedupe with src/index.ts / src/server.ts).
Not a correctness issue, but it’ll reduce drift between env entrypoints.

examples/ts-vue-start-chat/src/data/guitars.ts (1)

1-8: Type looks good; consider Guitar[] over Array<Guitar> for consistency (optional).

examples/ts-vue-start-chat/src/components/ChatInput.vue (1)

13-15: Prefer null-initialized DOM refs for correctness/clarity.

-const textareaRef = ref<HTMLTextAreaElement>()
+const textareaRef = ref<HTMLTextAreaElement | null>(null)
examples/ts-vue-start-chat/src/components/Messages.vue (1)

9-29: Consider improving message typing and “only autoscroll if already near bottom”.
Current { deep: true } + always-scroll will fight the user when they scroll up.

packages/typescript/vue-ai-devtools/src/index.ts (1)

14-19: AiDevtoolsPanelInProd = DevToolsPanelComponent naming is slightly confusing.
Since AiDevtoolsPanel is the one that becomes no-op outside dev, maybe rename to something like AiDevtoolsPanelActual (optional).

examples/ts-vue-start-chat/src/routes/__root.tsx (1)

51-66: Gate connectToServerBus to development to prevent unexpected production connections.
Although devtools are tree-shaken by default and excluded from production builds, explicitly setting connectToServerBus: true unconditionally creates a maintenance risk—especially in example code that developers copy into production applications. If devtools are ever intentionally included in production, this would cause unexpected connection attempts to a devtools server.

         <TanStackDevtools
           config={{
             position: 'bottom-right',
           }}
           plugins={[
             {
               name: 'Tanstack Router',
               id: 'tanstack-router',
               component: TanStackRouterDevtoolsPanel,
             },
             aiDevtoolsPlugin(),
           ]}
           eventBusConfig={{
-            connectToServerBus: true,
+            connectToServerBus: import.meta.env.DEV,
           }}
         />
examples/ts-vue-start-chat/src/views/VueUIView.vue (1)

58-64: Prefer stable <option> keys (provider+model) over array index
Index keys can cause odd DOM reuse if options reorder.

               <option
                 v-for="(option, index) in MODEL_OPTIONS"
-                :key="index"
+                :key="`${option.provider}:${option.model}`"
                 :value="index"
               >

Also applies to: 93-100

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 049eb8a and d9a1c48.

⛔ Files ignored due to path filters (14)
  • examples/ts-vue-start-chat/public/example-guitar-flowers.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-motherboard.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-racing.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-steamer-trunk.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-superhero.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-traveling.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-guitar-video-games.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/example-ukelele-tanstack.jpg is excluded by !**/*.jpg
  • examples/ts-vue-start-chat/public/favicon.ico is excluded by !**/*.ico
  • examples/ts-vue-start-chat/public/logo192.png is excluded by !**/*.png
  • examples/ts-vue-start-chat/public/logo512.png is excluded by !**/*.png
  • examples/ts-vue-start-chat/public/tanstack-circle-logo.png is excluded by !**/*.png
  • examples/ts-vue-start-chat/public/tanstack-word-logo-white.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (41)
  • examples/ts-vue-chat/package.json (1 hunks)
  • examples/ts-vue-chat/src/App.vue (1 hunks)
  • examples/ts-vue-start-chat/.gitignore (1 hunks)
  • examples/ts-vue-start-chat/CHANGELOG.md (1 hunks)
  • examples/ts-vue-start-chat/README.md (1 hunks)
  • examples/ts-vue-start-chat/package.json (1 hunks)
  • examples/ts-vue-start-chat/public/manifest.json (1 hunks)
  • examples/ts-vue-start-chat/public/robots.txt (1 hunks)
  • examples/ts-vue-start-chat/src/components/ChatInput.vue (1 hunks)
  • examples/ts-vue-start-chat/src/components/GuitarRecommendation.vue (1 hunks)
  • examples/ts-vue-start-chat/src/components/Header.vue (1 hunks)
  • examples/ts-vue-start-chat/src/components/Messages.vue (1 hunks)
  • examples/ts-vue-start-chat/src/data/guitars.ts (1 hunks)
  • examples/ts-vue-start-chat/src/lib/guitar-tools.ts (1 hunks)
  • examples/ts-vue-start-chat/src/lib/model-selection.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routeTree.gen.ts (1 hunks)
  • examples/ts-vue-start-chat/src/router.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routes/__root.tsx (1 hunks)
  • examples/ts-vue-start-chat/src/routes/api.chat.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routes/guitars/$id.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routes/guitars/index.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routes/index.ts (1 hunks)
  • examples/ts-vue-start-chat/src/routes/vue-ui.ts (1 hunks)
  • examples/ts-vue-start-chat/src/styles.css (1 hunks)
  • examples/ts-vue-start-chat/src/views/ChatView.vue (1 hunks)
  • examples/ts-vue-start-chat/src/views/GuitarDetailView.vue (1 hunks)
  • examples/ts-vue-start-chat/src/views/GuitarsView.vue (1 hunks)
  • examples/ts-vue-start-chat/src/views/VueUIView.vue (1 hunks)
  • examples/ts-vue-start-chat/tsconfig.json (1 hunks)
  • examples/ts-vue-start-chat/vite.config.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/CHANGELOG.md (1 hunks)
  • packages/typescript/vue-ai-devtools/README.md (1 hunks)
  • packages/typescript/vue-ai-devtools/package.json (1 hunks)
  • packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/createVuePanel.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/index.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/plugin.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/production.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/server.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/tsconfig.json (1 hunks)
  • packages/typescript/vue-ai-devtools/vite.config.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
examples/ts-solid-chat/src/lib/guitar-tools.ts (1)
  • addToCartToolServer (109-117)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/index.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
  • Route (45-130)
examples/ts-vue-start-chat/src/routes/__root.tsx (1)
  • Route (18-40)
examples/ts-vue-start-chat/src/routes/guitars/index.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/index.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
  • Route (45-130)
examples/ts-vue-start-chat/src/routes/__root.tsx (1)
  • Route (18-40)
examples/ts-vue-start-chat/src/routeTree.gen.ts (1)
examples/ts-vue-start-chat/src/router.ts (1)
  • getRouter (5-11)
examples/ts-vue-start-chat/src/routes/__root.tsx (3)
packages/typescript/vue-ai-devtools/src/index.ts (1)
  • aiDevtoolsPlugin (21-24)
packages/typescript/vue-ai-devtools/src/plugin.ts (1)
  • aiDevtoolsPlugin (16-23)
packages/typescript/vue-ai-devtools/src/server.ts (1)
  • aiDevtoolsPlugin (18-25)
packages/typescript/vue-ai-devtools/src/plugin.ts (3)
packages/typescript/vue-ai-devtools/src/index.ts (4)
  • aiDevtoolsPlugin (21-24)
  • AiDevtoolsVueInit (5-5)
  • AiDevtoolsVueInit (26-26)
  • AiDevtoolsPanel (14-17)
packages/typescript/vue-ai-devtools/src/production.ts (3)
  • aiDevtoolsPlugin (4-4)
  • AiDevtoolsVueInit (2-2)
  • AiDevtoolsPanel (1-1)
packages/typescript/vue-ai-devtools/src/server.ts (4)
  • aiDevtoolsPlugin (18-25)
  • AiDevtoolsVueInit (3-5)
  • AiDevtoolsPanel (15-15)
  • aiDevtoolsNoOpPlugin (27-27)
examples/ts-vue-start-chat/src/routes/index.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
  • Route (4-6)
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
  • Route (45-130)
examples/ts-vue-start-chat/src/routes/__root.tsx (1)
  • Route (18-40)
packages/typescript/vue-ai-devtools/src/index.ts (5)
packages/typescript/vue-ai-devtools/src/production.ts (2)
  • AiDevtoolsPanel (1-1)
  • aiDevtoolsPlugin (4-4)
packages/typescript/vue-ai-devtools/src/server.ts (3)
  • AiDevtoolsPanel (15-15)
  • AiDevtoolsPanelInProd (16-16)
  • aiDevtoolsPlugin (18-25)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)
  • AiDevtoolsPanel (22-40)
packages/typescript/ai/src/stream/processor.ts (1)
  • process (390-413)
packages/typescript/vue-ai-devtools/src/plugin.ts (1)
  • aiDevtoolsPlugin (16-23)
🪛 LanguageTool
packages/typescript/vue-ai-devtools/README.md

[grammar] ~5-~5: Il y a peut-être une erreur ici
Context: ... Vue Devtools for TanStack AI. ## Usage ts import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' import { TanStackDevtools } from '@tanstack/vue-devtools' const devtoolsPlugins = [aiDevtoolsPlugin()] // in .vue file <TanStackDevtools :config="devtoolsConfig" :plugins="devtoolsPlugins" :eventBusConfig="devtoolsEventBusConfig" /> // in jsx <TanStackDevtools config={{ position: 'bottom-right', }} plugins={[ { name: 'Tanstack Router', id: 'tanstack-router', component: TanStackRouterDevtoolsPanel, }, aiDevtoolsPlugin(), ]} eventBusConfig={{ connectToServerBus: true, }} /> ##

(QB_NEW_FR)

🪛 markdownlint-cli2 (0.18.1)
packages/typescript/vue-ai-devtools/README.md

21-21: Hard tabs
Column: 1

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


23-23: Hard tabs
Column: 1

(MD010, no-hard-tabs)


24-24: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 1

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 1

(MD010, no-hard-tabs)


28-28: Hard tabs
Column: 1

(MD010, no-hard-tabs)


29-29: Hard tabs
Column: 1

(MD010, no-hard-tabs)


30-30: Hard tabs
Column: 1

(MD010, no-hard-tabs)


31-31: Hard tabs
Column: 1

(MD010, no-hard-tabs)


32-32: Hard tabs
Column: 1

(MD010, no-hard-tabs)


33-33: Hard tabs
Column: 1

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 1

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 1

(MD010, no-hard-tabs)


36-36: Hard tabs
Column: 1

(MD010, no-hard-tabs)

examples/ts-vue-start-chat/README.md

38-38: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (22)
packages/typescript/vue-ai-devtools/CHANGELOG.md (1)

1-5: Verify release version + align with repo changelog conventions

## 0.0.3 with “Initial release” reads like a first publish, but the version suggests prior releases—please confirm it matches the package’s actual package.json version and intended publish/tag history, and consider adding minimal “Added” details (or conforming to the repo’s standard changelog/changesets format) for future readers.

examples/ts-vue-start-chat/src/styles.css (1)

1-95: Well-structured Tailwind CSS with a cohesive dark theme design.

The CSS file demonstrates solid Tailwind best practices: proper use of @layer directives to organize styles, consistent application of utilities via @apply, and a thoughtfully designed dark color scheme (gray-900 base with orange-400/500 accents). The prose/markdown styling (lines 12–77) comprehensively covers rendered content elements, and the button components (lines 79–95) provide four semantic variants with consistent padding, rounding, and hover behavior.

examples/ts-vue-start-chat/public/robots.txt (1)

1-3: The robots.txt file in examples is boilerplate that follows the pattern used across all example applications. Since these are reference implementations in the repository rather than deployed services, the robots.txt configuration does not require review or modification. Developers deploying an example to a live service would adjust robots.txt as needed for their hosting context.

examples/ts-vue-start-chat/vite.config.ts (1)

1-28: Verify Vite plugin order with @tanstack/vue-start + Vue plugins.

Order can be sensitive here (especially tanstackStart() relative to vue() / vueJsx()). Please confirm the example builds and runs (dev + prod build), and adjust ordering if Start expects to wrap/precede Vue transforms.

packages/typescript/vue-ai-devtools/src/server.ts (1)

7-17: SSR no-op surface looks solid.
Null component returning null is a clean way to ensure devtools are inert on server/SSR.

packages/typescript/vue-ai-devtools/tsconfig.json (1)

1-14: TS project config looks reasonable for a publishable package (declarations + composite).
No immediate issues spotted.

examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)

1-6: Simple route wiring looks good.

examples/ts-vue-start-chat/src/routes/index.ts (1)

1-6: LGTM.

examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)

1-6: LGTM.

packages/typescript/vue-ai-devtools/src/production.ts (1)

1-4: Clean production re-export surface
Exports look consistent and the type-only export avoids runtime coupling.

examples/ts-vue-start-chat/src/router.ts (1)

1-11: [rewritten comment]
[classification tag]

examples/ts-vue-start-chat/src/routes/__root.tsx (1)

18-40: Root head() looks fine; stylesheet URL import is appropriate for Vite.

examples/ts-vue-start-chat/src/components/ChatInput.vue (1)

42-92: Template wiring looks solid (disabled state + stop button).

examples/ts-vue-start-chat/src/views/ChatView.vue (1)

75-86: get body() reactive getter is a nice way to keep provider/model in sync.

examples/ts-vue-start-chat/src/data/guitars.ts (1)

10-93: No action needed. All referenced images exist in examples/ts-vue-start-chat/public/ and will render correctly. The image paths in the code correctly reference the public assets directory.

packages/typescript/vue-ai-devtools/src/plugin.ts (1)

16-32: Add an explicit return type for the plugin descriptor if available from @tanstack/vue-devtools.
This helps catch shape regressions (e.g., missing props) early. If no exported type exists, consider defining a local type to document the expected shape.

examples/ts-vue-start-chat/src/lib/model-selection.ts (1)

79-113: Storage helpers look solid for SSR
Good window guard + try/catch; getStoredModelPreference() also correctly normalizes to an entry from MODEL_OPTIONS.

examples/ts-vue-start-chat/src/routeTree.gen.ts (2)

11-43: Otherwise LGTM for a generated route tree
No manual edits recommended.

Also applies to: 44-140


1-10: Generated file: verify it's excluded from lint/format in repo config

The file header indicates it should be excluded from linting and formatting, but .prettierignore and eslint.config.js don't reference this file or the examples/ directory. The file relies on eslint-disable and ts-nocheck headers as workarounds, but these don't prevent prettier from formatting it if the format task runs. Consider adding a pattern like examples/**/*.gen.ts to .prettierignore and ensuring eslint also skips it via config rather than relying on file-level directives alone.

examples/ts-vue-start-chat/src/lib/guitar-tools.ts (1)

1-3: No action needed. The needsApproval option is fully supported in @tanstack/ai and is actively tested in the codebase. Approvals will trigger correctly when set to true. The option name and behavior remain stable.

examples/ts-vue-start-chat/src/views/VueUIView.vue (1)

44-46: The client tool output contract is correct as implemented

The recommendGuitarToolDef explicitly defines its output schema as z.object({ id: z.number() }) (lines 33-35 in guitar-tools.ts), and the client implementation returns { id: +id } which matches this contract exactly. The outputSchema is the definitive specification of the tool's output contract, not a minimum requirement. The @tanstack/ai-vue-ui Chat component accepts tool outputs as output?: any with no additional field validation, meaning the tool will execute correctly with the schema-defined fields only.

Likely an incorrect or invalid review comment.

examples/ts-vue-start-chat/src/routes/api.chat.ts (1)

92-107: This pattern is intentional and fully supported—no issue here.

The framework explicitly handles mixing .server() tools with raw toolDefinitions. Tools without execute functions are automatically treated as client-side tools: the framework detects them, emits tool-input-available events to the client, and waits for the client to execute them via their .client() implementations defined in VueUIView.vue. Tools with needsApproval work correctly in this flow. The code matches the documented pattern in the toolDefinition API documentation.

Comment on lines 4 to 24
import { markRaw } from 'vue'
import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools'
import { TanStackDevtools } from '@tanstack/vue-devtools'
const devtoolsConfig = { position: 'bottom-right' as const }
const devtoolsPlugins = [aiDevtoolsPlugin()]
const devtoolsEventBusConfig = { connectToServerBus: true }
</script>

<template>
<div class="min-h-screen bg-gray-900">
<Header />
<router-view />

<TanStackDevtools
:config="devtoolsConfig"
:plugins="devtoolsPlugins"
:eventBusConfig="devtoolsEventBusConfig"
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd examples/ts-vue-chat && find . -name "App.vue" -type f

Repository: TanStack/ai

Length of output: 68


🏁 Script executed:

cat -n examples/ts-vue-chat/src/App.vue | head -40

Repository: TanStack/ai

Length of output: 885


Gate devtools to import.meta.env.DEV and wrap plugin with markRaw.

The TanStackDevtools component is currently rendered unconditionally, which means the devtools UI and server bus connection will ship in production builds of this example. Additionally, markRaw is imported but not applied to the plugin descriptor.

 const devtoolsConfig = { position: 'bottom-right' as const }
 
-const devtoolsPlugins = [aiDevtoolsPlugin()]
+const devtoolsPlugins = [markRaw(aiDevtoolsPlugin())]
 
 const devtoolsEventBusConfig = { connectToServerBus: true }
 </script>
 
 <template>
   <div class="min-h-screen bg-gray-900">
     <Header />
     <router-view />
 
-    <TanStackDevtools
+    <TanStackDevtools v-if="import.meta.env.DEV"
       :config="devtoolsConfig"
       :plugins="devtoolsPlugins"
       :eventBusConfig="devtoolsEventBusConfig"
     />
📝 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 { markRaw } from 'vue'
import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools'
import { TanStackDevtools } from '@tanstack/vue-devtools'
const devtoolsConfig = { position: 'bottom-right' as const }
const devtoolsPlugins = [aiDevtoolsPlugin()]
const devtoolsEventBusConfig = { connectToServerBus: true }
</script>
<template>
<div class="min-h-screen bg-gray-900">
<Header />
<router-view />
<TanStackDevtools
:config="devtoolsConfig"
:plugins="devtoolsPlugins"
:eventBusConfig="devtoolsEventBusConfig"
/>
import { markRaw } from 'vue'
import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools'
import { TanStackDevtools } from '@tanstack/vue-devtools'
const devtoolsConfig = { position: 'bottom-right' as const }
const devtoolsPlugins = [markRaw(aiDevtoolsPlugin())]
const devtoolsEventBusConfig = { connectToServerBus: true }
</script>
<template>
<div class="min-h-screen bg-gray-900">
<Header />
<router-view />
<TanStackDevtools v-if="import.meta.env.DEV"
:config="devtoolsConfig"
:plugins="devtoolsPlugins"
:eventBusConfig="devtoolsEventBusConfig"
/>
🤖 Prompt for AI Agents
In examples/ts-vue-chat/src/App.vue around lines 4 to 24, the TanStackDevtools
is rendered unconditionally and the aiDevtoolsPlugin isn't wrapped with markRaw;
change the module to gate devtools to development only by creating a const isDev
= import.meta.env.DEV and only build
devtoolsConfig/devtoolsPlugins/devtoolsEventBusConfig when isDev is true, wrap
the plugin descriptor as markRaw(aiDevtoolsPlugin()) when populating
devtoolsPlugins, and conditionally render the <TanStackDevtools> in the template
with a v-if="isDev" so no devtools UI or server bus is included in production
builds.

Comment on lines +31 to +33
const renderMarkdown = (content: string) => {
return marked(content, { async: false }) as string
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

marked library sanitize output default behavior XSS security

💡 Result:

Short answer: marked does NOT sanitize output by default. The built-in sanitize/sanitizer options were deprecated/removed (marked recommends using an external sanitizer such as DOMPurify, sanitize‑html, or insane), so you must run the HTML output through a sanitizer yourself to prevent XSS. [1][2]

What to do (brief):

  • Treat marked output as unsafe if it includes user input.
  • Pipe marked’s HTML through a proven sanitizer (DOMPurify recommended) before inserting into the DOM. [1][3]

Sources:

  • marked docs — sanitize/sanitizer removed; use a sanitize library. [1]
  • Security writeups on marked XSS history and the need for external sanitization. [2]
  • Example project wrapping marked+DOMPurify (safe-by-default approach). [3]

🏁 Script executed:

find . -name "Messages.vue" -type f

Repository: TanStack/ai

Length of output: 162


🏁 Script executed:

cat -n ./examples/ts-vue-start-chat/src/components/Messages.vue | head -100

Repository: TanStack/ai

Length of output: 3823


🏁 Script executed:

grep -n "DOMPurify\|sanitize\|xss" ./examples/ts-vue-start-chat/src/components/Messages.vue

Repository: TanStack/ai

Length of output: 37


Unsanitized v-html from marked() is an XSS risk; sanitize output before rendering.

The marked library does not sanitize HTML by default. Run the rendered output through a sanitizer such as DOMPurify before injecting into the DOM:

import DOMPurify from 'dompurify'

const renderMarkdown = (content: string) => {
  return DOMPurify.sanitize(marked(content, { async: false }))
}

This applies to lines 31–33 and 84–88.

🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/components/Messages.vue around lines 31–33 and
84–88, the output of marked() is injected via v-html without sanitization which
allows XSS; import a sanitizer (e.g., DOMPurify) at the top of the file and pass
the marked() result through DOMPurify.sanitize before returning or binding it so
the HTML is cleaned (ensure the sanitized value is typed/returned as string and
replace direct marked(...) usage in both places with the sanitized result).

Comment on lines +91 to +106
<div
v-else-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2"
>
<p class="text-white font-medium mb-2">
🔒 Approval Required: {{ part.name }}
</p>
<div class="text-gray-300 text-sm mb-3">
<pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
JSON.stringify(JSON.parse(part.arguments), null, 2)
}}</pre>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid JSON.parse inside the template (can throw and break the whole message list).
Move parsing into a helper that returns a safe fallback string.

 <script setup lang="ts">
@@
 const renderMarkdown = (content: string) => {
   return marked(content, { async: false }) as string
 }
+
+const safePrettyJson = (value: string) => {
+  try {
+    return JSON.stringify(JSON.parse(value), null, 2)
+  } catch {
+    return value
+  }
+}
 </script>
@@
-                <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
-                  JSON.stringify(JSON.parse(part.arguments), null, 2)
-                }}</pre>
+                <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
+                  safePrettyJson(part.arguments)
+                }}</pre>
📝 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
<div
v-else-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2"
>
<p class="text-white font-medium mb-2">
🔒 Approval Required: {{ part.name }}
</p>
<div class="text-gray-300 text-sm mb-3">
<pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
JSON.stringify(JSON.parse(part.arguments), null, 2)
}}</pre>
</div>
<div
v-else-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2"
>
<p class="text-white font-medium mb-2">
🔒 Approval Required: {{ part.name }}
</p>
<div class="text-gray-300 text-sm mb-3">
<pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
safePrettyJson(part.arguments)
}}</pre>
</div>
Suggested change
<div
v-else-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2"
>
<p class="text-white font-medium mb-2">
🔒 Approval Required: {{ part.name }}
</p>
<div class="text-gray-300 text-sm mb-3">
<pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
JSON.stringify(JSON.parse(part.arguments), null, 2)
}}</pre>
</div>
const safePrettyJson = (value: string) => {
try {
return JSON.stringify(JSON.parse(value), null, 2)
} catch {
return value
}
}
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/components/Messages.vue around lines 91 to
106, the template currently calls JSON.parse(JSON.stringify(...)) directly which
can throw and break rendering; move the parsing/formatting into a component
method or computed property that wraps JSON.parse in a try/catch and returns a
safe pretty-printed string fallback (e.g., original part.arguments or '{}' or an
error note) if parsing fails, then update the template to bind to that helper
result instead of calling JSON.parse in-template.

Comment on lines +50 to +80
export const addToWishListToolDef = toolDefinition({
name: 'addToWishList',
description: "Add a guitar to the user's wish list (requires approval)",
inputSchema: z.object({
guitarId: z.string(),
}),
outputSchema: z.object({
success: z.boolean(),
guitarId: z.string(),
totalItems: z.number(),
}),
needsApproval: true,
})

// Tool definition for add to cart (server + client)
export const addToCartToolDef = toolDefinition({
name: 'addToCart',
description: 'Add a guitar to the shopping cart (requires approval)',
inputSchema: z.object({
guitarId: z.string(),
quantity: z.number(),
}),
outputSchema: z.object({
success: z.boolean(),
cartId: z.string(),
guitarId: z.string(),
quantity: z.number(),
totalItems: z.number(),
}),
needsApproval: true,
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix guitarId type mismatch (inventory uses number ids, tools require string)
getGuitars returns id: number, and the system prompt tells the model to use that ID—yet addToWishList/addToCart demand guitarId: string. This can cause Zod validation failures during tool calls.

 export const addToWishListToolDef = toolDefinition({
@@
   inputSchema: z.object({
-    guitarId: z.string(),
+    guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
   }),
@@
 export const addToCartToolDef = toolDefinition({
@@
   inputSchema: z.object({
-    guitarId: z.string(),
-    quantity: z.number(),
+    guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
+    quantity: z.number().int().positive(),
   }),
📝 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
export const addToWishListToolDef = toolDefinition({
name: 'addToWishList',
description: "Add a guitar to the user's wish list (requires approval)",
inputSchema: z.object({
guitarId: z.string(),
}),
outputSchema: z.object({
success: z.boolean(),
guitarId: z.string(),
totalItems: z.number(),
}),
needsApproval: true,
})
// Tool definition for add to cart (server + client)
export const addToCartToolDef = toolDefinition({
name: 'addToCart',
description: 'Add a guitar to the shopping cart (requires approval)',
inputSchema: z.object({
guitarId: z.string(),
quantity: z.number(),
}),
outputSchema: z.object({
success: z.boolean(),
cartId: z.string(),
guitarId: z.string(),
quantity: z.number(),
totalItems: z.number(),
}),
needsApproval: true,
})
export const addToWishListToolDef = toolDefinition({
name: 'addToWishList',
description: "Add a guitar to the user's wish list (requires approval)",
inputSchema: z.object({
guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
}),
outputSchema: z.object({
success: z.boolean(),
guitarId: z.string(),
totalItems: z.number(),
}),
needsApproval: true,
})
// Tool definition for add to cart (server + client)
export const addToCartToolDef = toolDefinition({
name: 'addToCart',
description: 'Add a guitar to the shopping cart (requires approval)',
inputSchema: z.object({
guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
quantity: z.number().int().positive(),
}),
outputSchema: z.object({
success: z.boolean(),
cartId: z.string(),
guitarId: z.string(),
quantity: z.number(),
totalItems: z.number(),
}),
needsApproval: true,
})
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/lib/guitar-tools.ts around lines 50 to 80, the
tool input schemas declare guitarId as z.string() but the app uses numeric IDs
(getGuitars returns id: number); change the inputSchema for both addToWishList
and addToCart to use z.number() for guitarId (and adjust any callers to pass
numbers if they currently pass strings) so Zod validation matches the inventory
ID type.

Comment on lines +9 to +75
export const MODEL_OPTIONS: Array<ModelOption> = [
// OpenAI
{ provider: 'openai', model: 'gpt-4o', label: 'OpenAI - GPT-4o' },
{ provider: 'openai', model: 'gpt-4o-mini', label: 'OpenAI - GPT-4o Mini' },
{ provider: 'openai', model: 'gpt-5', label: 'OpenAI - GPT-5' },
{
provider: 'openai',
model: 'gpt-5.2-2025-12-11',
label: 'OpenAI - GPT-5.2',
},

// Anthropic
{
provider: 'anthropic',
model: 'claude-sonnet-4-5-20250929',
label: 'Anthropic - Claude Sonnet 4.5',
},
{
provider: 'anthropic',
model: 'claude-opus-4-5-20251101',
label: 'Anthropic - Claude Opus 4.5',
},
{
provider: 'anthropic',
model: 'claude-haiku-4-0-20250514',
label: 'Anthropic - Claude Haiku 4.0',
},

// Gemini
{
provider: 'gemini',
model: 'gemini-2.0-flash-exp',
label: 'Gemini - 2.0 Flash',
},
{
provider: 'gemini',
model: 'gemini-exp-1206',
label: 'Gemini - Exp 1206 (Pro)',
},

// Ollama
{
provider: 'ollama',
model: 'mistral:7b',
label: 'Ollama - Mistral 7B',
},
{
provider: 'ollama',
model: 'mistral',
label: 'Ollama - Mistral',
},
{
provider: 'ollama',
model: 'gpt-oss:20b',
label: 'Ollama - GPT-OSS 20B',
},
{
provider: 'ollama',
model: 'granite4:3b',
label: 'Ollama - Granite4 3B',
},
{
provider: 'ollama',
model: 'smollm',
label: 'Ollama - SmolLM',
},
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@tanstack/ai openai adapter supported models gpt-4o gpt-5

💡 Result:

Yes — the @tanstack/ai OpenAI adapter supports gpt-4o and gpt-5 (you can pass model: "gpt-4o" or model: "gpt-5" to chat/summarize/etc.). [1][2]

Sources:

  • TanStack AI Core API (examples using model: "gpt-4o"). [1]
  • Per-model type-safety docs (examples and capability notes for "gpt-5"). [2]

🌐 Web query:

@tanstack/ai anthropic adapter supported models claude sonnet opus haiku

💡 Result:

Yes — the @tanstack/ai Anthropic adapter supports Claude-family models including Sonnet, Opus, and Haiku. Documented available models include (examples):

  • claude-3-5-sonnet-20241022 (Claude 3.5 Sonnet)
  • claude-3-opus-20240229 (Claude 3 Opus)
  • claude-3-haiku-20240307 (Claude 3 Haiku)
  • claude-3-5-sonnet-20240620, claude-2.1, claude-2.0, etc.

The docs also note support for newer Claude Sonnet/Opus snapshots (e.g., claude-sonnet-4-5-20250929, claude-opus-4-5-20251101) and provider-specific options like extended thinking. [1]

Sources:
[1] TanStack AI — Anthropic adapter (Available models & examples).


🌐 Web query:

@tanstack/ai gemini adapter supported models gemini-2.0-flash

💡 Result:

Short answer: Not officially — the TanStack Gemini adapter docs list only "gemini-pro", "gemini-pro-vision" and "gemini-ultra" as supported model names, and do not mention "gemini-2.0-flash". [1][2]

Notes/sources:

  • TanStack Gemini adapter docs (supported model names). [1]
  • Google Gemini model listing showing the "gemini-2.0-flash" model exists. [2]

If you want, I can try an example showing whether the adapter accepts "gemini-2.0-flash" (it may work if the adapter simply forwards the model string to the Gemini API).


🌐 Web query:

@tanstack/ai ollama adapter supported models

💡 Result:

The Ollama adapter can use whatever models you have installed in your local Ollama server — there’s no fixed list in the adapter itself. Common models shown in the TanStack docs include llama2, llama3, mistral, codellama, phi, gemma, etc. To see exactly which models your adapter can use, run:

ollama list

Sources: TanStack AI Ollama adapter docs and package notes. [1][2]

References
[1] TanStack AI — Ollama Adapter docs.
[2] npm / ai-sdk-ollama (examples of commonly used Ollama models).


🏁 Script executed:

# Search for server-side model validation
fd -e ts -e js | xargs rg -l "api.*chat\|validateModel\|MODEL_OPTIONS" | head -20

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Look for api.chat implementation to check server validation
fd -type f -name "*chat*" | grep -E "\.(ts|js)$"

Repository: TanStack/ai

Length of output: 225


🏁 Script executed:

# Check if there's any allowlist or validation logic already in place
rg "model.*valid|allowlist|ALLOWED.*MODEL|validate.*model" -i

Repository: TanStack/ai

Length of output: 1719


🌐 Web query:

OpenAI gpt-4o-mini model ID API

💡 Result:

Model ID: gpt-4o-mini

Aliases / snapshots you can also use: gpt-4o-mini-2024-07-18 (and the base alias gpt-4o-mini). [1]

Typical API endpoints that accept this model:

  • v1/chat/completions (chat)
  • v1/responses (Responses API)
  • v1/realtime (Realtime)
  • v1/fine-tuning (fine-tune support)
    (See model page and GPT-4o docs for full endpoint support and rate/pricing details.) [1][2]

If you want, I can show a minimal curl or Python example using the model. [1][2]

Sources: OpenAI model docs (gpt-4o-mini, GPT-4o docs). [1][2]


🌐 Web query:

OpenAI gpt-5.2-2025-12-11 model identifier

💡 Result:

The model identifier is gpt-5.2-2025-12-11. OpenAI also exposes API aliases: gpt-5.2 (Thinking), gpt-5.2-chat-latest (Instant), and gpt-5.2-pro (Pro). (platform.openai.com)

Citations:


🌐 Web query:

Anthropic claude-haiku-4-0-20250514 model ID

💡 Result:

There is no Anthropic model named "claude-haiku-4-0-20250514". Anthropic’s documented model IDs include:

  • claude-opus-4-20250514 (Opus 4). [1]
  • claude-sonnet-4-20250514 (Sonnet 4). [1]
  • claude-3-5-haiku-20241022 (Haiku 3.5). [1]

If you meant a Haiku-family model, use claude-3-5-haiku-20241022; if you meant a Claude 4 snapshot, use claude-opus-4-20250514 or claude-sonnet-4-20250514. [1][2]

Sources: Anthropic models overview. [1] [2]


🌐 Web query:

Google Gemini gemini-exp-1206 model supported

💡 Result:

Short answer: gemini-exp-1206 was an experimental/preview Gemini model (announced Dec 6, 2024) and was made available to Gemini Advanced users, but it is experimental (not a stable, supported production model) and has been intermittently removed/retired in favor of newer Gemini releases. See Google’s announcement and the Vertex AI docs about experimental/retired Gemini models. [1][2][3]

Sources

  • Google blog announcement (gemini-exp-1206 preview). [1]
  • Reports of the model’s removal/return and replacement by newer experimental/pro models. [2]
  • Vertex AI / Gemini model guidance (experimental models, retirement behavior, and API/location requirements). [3]

🏁 Script executed:

# Find the server-side chat API handler
fd -type f -name "*" | xargs rg "chat|model" | grep -i "server\|api\|handler" | head -30

Repository: TanStack/ai

Length of output: 2516


🏁 Script executed:

# Look in the example project structure for api or server files
find . -type f \( -name "*.ts" -o -name "*.js" \) | grep -E "(api|server)" | head -20

Repository: TanStack/ai

Length of output: 854


🏁 Script executed:

# Search for where MODEL_OPTIONS is actually used
rg "MODEL_OPTIONS" -B 3 -A 3

Repository: TanStack/ai

Length of output: 24293


🏁 Script executed:

# Read the api.chat.ts file for the ts-vue-start-chat example
cat -n examples/ts-vue-start-chat/src/routes/api.chat.ts | head -100

Repository: TanStack/ai

Length of output: 4169


🏁 Script executed:

# Check if there's any model validation or allowlist in the server
rg "model.*valid|validate.*model|ALLOWED.*MODEL|MODEL.*LIST" -i --max-count=20

Repository: TanStack/ai

Length of output: 2019


Remove unsupported model identifiers and add server-side allowlist validation

Three model identifiers are invalid and will fail or behave unexpectedly:

  • claude-haiku-4-0-20250514 (Anthropic): Use claude-3-5-haiku-20241022 instead
  • gemini-2.0-flash-exp (Gemini): Not in official TanStack adapter docs; use documented variants or validate against Vertex AI
  • gemini-exp-1206 (Gemini): Experimental/retired model, avoid using

The server currently accepts any model string without validation (line 94 in api.chat.ts). Add server-side checks to validate data.model against an allowlist exported from a shared module, ensuring only supported models can be sent to adapters.

Comment on lines +30 to +68
"exports": {
".": {
"browser": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"node": {
"import": {
"types": "./dist/esm/server.d.ts",
"default": "./dist/esm/server.js"
}
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./production": {
"browser": {
"import": {
"types": "./dist/esm/production.d.ts",
"default": "./dist/esm/production.js"
}
},
"node": {
"import": {
"types": "./dist/esm/server.d.ts",
"default": "./dist/esm/server.js"
}
},
"import": {
"types": "./dist/esm/production.d.ts",
"default": "./dist/esm/production.js"
}
},
"./package.json": "./package.json"
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Node.js package.json exports resolution order "browser" "node" "import" conditions bundler

💡 Result:

Short answer: condition keys in package.json "exports" are matched in object order (first match wins). But runtimes/resolvers use their own default condition priority lists (so which keys are checked first depends on the resolver). Node checks its built-in conditions in a specific priority (e.g. import/require before node, and default last), while bundlers (esbuild, Vite, Parcel, etc.) publish different default orderings — so "browser" vs "node" vs "import" will be chosen according to the resolver’s condition priority, not an absolute universal order. Always put the most-specific conditions first and include a "default" fallback. [1][2][3][4]

Notes / references:

  • Node.js docs: condition matching uses object order; Node’s built-in guidance: use import/require before node and default last. "default" should be last. [2][5]. (nodejs.org)
  • Parcel: conditions are matched in the object order defined in package.json; Parcel lists supported conditions (import, require, node, browser, default, etc.). (parceljs.org)
  • Community guide (examples): different tools have different default condition lists (e.g. Vite, esbuild differ), so bundlers may match "import" or "browser" before "node". Always design exports with explicit ordering and a "default" fallback. (hirok.io)

If you want, tell me which resolver/bundler you care about (Node, webpack, Rollup, Vite, esbuild, Parcel) and I’ll give the exact priority that resolver uses.

Citations:


🌐 Web query:

Vite Rollup Webpack conditional exports browser node SSR prerender pitfalls

💡 Result:

Here are the common pitfalls and practical mitigations when using conditional exports (package.json exports/browser), Vite/Rollup/Webpack, and when targeting browser vs Node (SSR / prerender):

  1. Confusing resolution order between "exports" and legacy "browser" (Webpack/Rollup/Vite).

    • Problem: tools differ in whether they use package.json "exports", the "browser" field, or browser-specific conditions inside "exports" — causing different builds to pick different entry files. [1][2]
    • Mitigation: prefer using conditional exports entirely (include "browser" condition inside "exports") and avoid relying on the legacy top-level "browser" field; test resolution with each bundler/resolver you support. [1][3]
  2. Dual-published packages (CJS + ESM) and interop mismatches.

    • Problem: mixing CJS and ESM across the graph can produce duplicated singletons, broken instanceof checks, or wrong default/named exports due to different interop implementations. Bundlers may add wrappers like __esModule which changes runtime shape. [4][5]
    • Mitigation: publish a clear single-entry ESM build when possible; if dual-publishing, ensure "exports" maps subpaths for both "import" and "require" and test runtime interop across your target environments. Use consistent package boundaries in monorepos. [4][5]
  3. Bundler-specific exports/condition support gaps.

    • Problem: some versions of Webpack, Rollup plugins, or resolve plugins may not implement all package exports condition logic (e.g., custom condition names like "worker", "browser", "node", "import", "require"), so a subpath may resolve differently in dev vs build or in SSR vs client. [1][4]
    • Mitigation: pin tool versions; read resolver docs (resolve.exportsFields in Webpack); add fallbacks in "exports" for broadest conditions (include "default" plus "import"/"require"/"node"/"browser"). Test both dev, build, SSR and prerender. [1][4]
  4. Prerender / SSR surprises with browser-only code.

    • Problem: prerender/SSR runs in Node — importing a module that resolves to browser-only code (e.g., DOM APIs) via conditional exports causes runtime errors during build/prerender. Tools may still resolve to browser entry unexpectedly. [2][3]
    • Mitigation: ensure server-side conditions in "exports" (e.g., "node" or "default") point to Node-safe implementations. Use separate server-side entry points or guard imports behind dynamic imports / typeof window checks. Add tests that run the prerender build. [2][3]
  5. Monorepo / workspace symlinked-package behavior.

    • Problem: bundlers may treat workspace packages as source (not published artifact), causing resolution to ignore "exports" or produce inconsistent graphs. This can cause duplicated code or unresolved imports. [4]
    • Mitigation: build/publish packages locally when testing, or configure resolver to respect package boundaries; align package.json fields (type/module/main/exports) across workspaces. [4]
  6. Different dev vs production resolver behavior.

    • Problem: dev servers (Vite) use native ESM resolution and long-form paths, while production bundles (Rollup) and Webpack may perform different tree-shaking/resolution — leading to runtime differences between dev, prerender, and production. [3][4]
    • Mitigation: test full build + preview + prerender flows; use integration tests that exercise SSR and static generation; avoid relying on dev-only resolution quirks.
  7. Lack of clear diagnostics when an "exports" subpath is missing.

    • Problem: when "exports" exists, invalid specifiers throw ModuleNotFound errors; some bundlers give unhelpful messages making debugging harder. [1]
    • Mitigation: keep simple subpath mappings for common imports, include fallback "default", and add a debug build that prints resolved paths or use resolver logging.

Quick recommended checklist

  • Use conditional "exports" with explicit keys: "import", "require", "node", "browser", and "default". Test each combination. [1][4]
  • Provide server-safe fallback entries and avoid DOM-only code in server resolution paths. [2][3]
  • Test in all targeted flows: dev server, production bundle, SSR runtime, and prerender pipeline. [2][3][4]
  • Pin bundler/resolver versions and read their resolve/export docs (Webpack resolve.exportsFields, Rollup resolver plugins, Vite resolver). [1][4]
  • For monorepos, ensure published package shape is identical to local dev shape (or test with published tarball). [4]

Sources:

  • Webpack discussion on exports vs browser field behavior. [1]
  • vite-plugin-ssr (prerender/SSG and doNotPrerender) docs. [2][3]
  • Rollup/plugins interop and dual-package issues. [4]
  • Article on Rollup bundling pitfalls (duals, workspaces) and recommendations. [5]

🌐 Web query:

SSR frameworks conditional exports wrong condition selected "browser" "node"

💡 Result:

Cause (short): package.json conditional exports are matched by the resolver against an ordered list of "conditions". Tools (Vite, bundlers) and Node each have their own allowed-condition order — if "browser" appears earlier in the matcher list (or the exports object is ordered with browser first) the browser branch will be selected even when running SSR under Node. Node itself ignores unknown conditions (like browser) unless the tool honors them; object order in exports is significant. [1][2][3]

How to fix

  • If you use Vite:
    • Adjust resolve.conditions or ssr.resolve.conditions so "node" (or "import"/"default") precedes "browser". Example: set ssr.resolve.externalConditions / ssr.resolve.conditions to include 'node' first. Also ensure NODE_OPTIONS='--conditions ...' matches at runtime if you add custom conditions. [1][2]
  • If you control the package: prefer providing a "node" branch and a "default" fallback (or move node/default earlier than browser) so Node/tools will pick the correct implementation. Node recommends including a "default" branch. [3]
  • Workarounds: externalize the dependency for SSR, force CommonJS/ES entry selection with mainFields or by importing the node-specific path (e.g., import 'pkg/dist/node/...').

References

  1. Vite resolve/SSR docs (resolve.conditions, ssr.resolve.externalConditions). [Vite docs]. (v4.vite.dev)
  2. Vite issue / community discussion showing browser branch selected during bundling; explains ordering matters. (lightrun.com)
  3. Node.js ESM conditional exports docs — condition matching order, significance of object order, and recommendation to include "default" / "node" branches. (nodejs.org)

If you want, paste your package.json exports and your Vite (or other bundler) config and I’ll show the exact change.

Citations:


🏁 Script executed:

# Check the exact order and structure of exports in the file
cd packages/typescript/vue-ai-devtools
cat -n package.json | sed -n '30,68p'

Repository: TanStack/ai

Length of output: 1249


Conditional export order creates SSR risk: "browser" should not precede "node"/"import".

The current export structure resolves "browser" before "node" and the fallback "import" condition. During SSR/prerender in Node environments, some bundlers (Vite, Webpack) may select the browser entry despite running under Node, causing DOM-dependent code to execute during build and fail. Reorder the conditions to place "import" or "node" before "browser" to ensure Node-safe implementations are selected during server-side rendering. For example, move the generic "import" fallback (lines 44–47) before the "browser" object (lines 32–37) at each export level.

🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/package.json around lines 30 to 68, the
exports conditional order places "browser" before "node" and the generic
"import" fallback which can cause bundlers to pick browser builds during SSR;
reorder each export block so "import" (or "node") appears before "browser" —
i.e., move the generic "import" entry (types/default) above the "browser" object
in both the main "." export and the "./production" export so Node/server-safe
implementations are selected during SSR/prerender.

Comment on lines 1 to 20
/// <reference types="vite/client" />
import { AiDevtoolsCore } from '@tanstack/ai-devtools-core'
import { createVuePanel, type DevtoolsPanelProps } from './createVuePanel'
import { defineComponent, h } from 'vue'
import type { PropType } from 'vue'

export interface AiDevtoolsVueInit {
/**
* Theme for the devtools UI.
* Defaults to 'dark'.
*/
theme?: DevtoolsPanelProps['theme']
}

const [InternalPanel] = createVuePanel(
AiDevtoolsCore as unknown as new (
_props: AiDevtoolsVueInit,
) => InstanceType<typeof AiDevtoolsCore>,
)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "package.json" | head -20

Repository: TanStack/ai

Length of output: 890


🏁 Script executed:

find . -type f -name "tsconfig.json" | head -20

Repository: TanStack/ai

Length of output: 932


🏁 Script executed:

find . -type f -name "env.d.ts"

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

find . -type d -name "vue-ai-devtools"

Repository: TanStack/ai

Length of output: 92


🏁 Script executed:

find . -type d -name "*devtools*" | grep -v node_modules

Repository: TanStack/ai

Length of output: 206


🏁 Script executed:

ls -la packages/typescript/ | grep -i devtools

Repository: TanStack/ai

Length of output: 314


🏁 Script executed:

ls -la packages/typescript/vue-ai-devtools/

Repository: TanStack/ai

Length of output: 537


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/package.json

Repository: TanStack/ai

Length of output: 2124


🏁 Script executed:

ls -la packages/typescript/vue-ai-devtools/src/

Repository: TanStack/ai

Length of output: 540


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/vite.config.ts

Repository: TanStack/ai

Length of output: 926


🏁 Script executed:

find packages/typescript/ai-devtools -name "*.ts" -type f

Repository: TanStack/ai

Length of output: 664


🏁 Script executed:

find packages -name "*devtools-core*" -type d

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts

Repository: TanStack/ai

Length of output: 1123


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/src/index.ts

Repository: TanStack/ai

Length of output: 803


🏁 Script executed:

find packages -path "*/ai-devtools-core*" -type d

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

rg "class AiDevtoolsCore" --type ts -A 20

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/tsconfig.json

Repository: TanStack/ai

Length of output: 366


🏁 Script executed:

rg "vite" packages/typescript/vue-ai-devtools/src/ --type ts

Repository: TanStack/ai

Length of output: 151


🏁 Script executed:

rg "HMR\|import.meta" packages/typescript/vue-ai-devtools/src/ --type ts

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

grep -r "/// <reference" packages/typescript/vue-ai-devtools/

Repository: TanStack/ai

Length of output: 151


🏁 Script executed:

ls -la packages/typescript/vue-ai-devtools/dist/ 2>/dev/null || echo "No dist directory"

Repository: TanStack/ai

Length of output: 72


🏁 Script executed:

rg "import.*vite" packages/typescript/vue-ai-devtools/src/ --type ts

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

cat packages/typescript/ai-devtools/src/index.ts | head -30

Repository: TanStack/ai

Length of output: 271


Remove the unused vite/client triple-slash reference that will leak into published type definitions.

The /// <reference types="vite/client" /> directive (line 1) serves no purpose in this file—there are no Vite-specific usages (no HMR, import.meta, etc.). With declaration: true in tsconfig.json, TypeScript preserves this directive in generated .d.ts files, forcing library consumers to have vite/client types available even if they don't use Vite, which breaks type checking for consumers without Vite as a dependency.

-/// <reference types="vite/client" />

Also verify that AiDevtoolsCore's constructor signature matches the expected new (props: AiDevtoolsVueInit) signature. The current cast (line 16–18) masks potential type mismatches and should be replaced with direct type compatibility.

🤖 Prompt for AI Agents
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts lines 1–20: remove
the top-line triple-slash directive `/// <reference types="vite/client" />` (it
is unused and will be emitted into .d.ts files); then replace the unsafe cast of
AiDevtoolsCore to a constructor type with a proper typing — either update
AiDevtoolsCore's exported type to include a constructor signature matching new
(props: AiDevtoolsVueInit) => InstanceType<typeof AiDevtoolsCore> and pass that
type into createVuePanel, or change the createVuePanel call to accept
AiDevtoolsCore directly without the cast so TypeScript will surface any
mismatch; ensure no lingering Vite references remain in the file.

Comment on lines +19 to +66
const props = {
theme: {
type: String as () => DevtoolsPanelProps['theme'],
},
devtoolsProps: {
type: Object as () => TComponentProps,
},
}

const Panel = defineComponent({
props,
setup(config) {
const devToolRef = ref<HTMLElement | null>(null)
// Keep devtools instance non-reactive to avoid proxying,
// since private fields (e.g. #isMounted) break on proxies.
let devtools: TCoreDevtoolsClass | null = null
let didMount = false
let isUnmounted = false

onMounted(async () => {
const instance = new CoreClass(config.devtoolsProps as TComponentProps)
devtools = instance

if (devToolRef.value) {
await instance.mount(devToolRef.value, config.theme ?? 'dark')
if (isUnmounted) {
// If we unmounted before mount finished, clean up.
try {
instance.unmount()
} catch {
// ignore
}
} else {
didMount = true
}
}
})

onUnmounted(() => {
isUnmounted = true
if (didMount) {
try {
devtools?.unmount()
} catch {
// ignore
}
}
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make devtoolsProps required (or defaulted) + honor devtoolsProps.theme + harden async mount cleanup.

Right now, devtoolsProps can be omitted at runtime (Line 23-25) but is assumed present (Line 39). Also, TComponentProps extends DevtoolsPanelProps, but config.devtoolsProps.theme is ignored (Line 43).

Suggested tightening:

   const props = {
     theme: {
       type: String as () => DevtoolsPanelProps['theme'],
     },
     devtoolsProps: {
       type: Object as () => TComponentProps,
+      required: true,
     },
   }

   const Panel = defineComponent({
     props,
     setup(config) {
       const devToolRef = ref<HTMLElement | null>(null)
       // Keep devtools instance non-reactive to avoid proxying,
       // since private fields (e.g. #isMounted) break on proxies.
       let devtools: TCoreDevtoolsClass | null = null
       let didMount = false
       let isUnmounted = false

       onMounted(async () => {
-        const instance = new CoreClass(config.devtoolsProps as TComponentProps)
-        devtools = instance
-
-        if (devToolRef.value) {
-          await instance.mount(devToolRef.value, config.theme ?? 'dark')
+        const el = devToolRef.value
+        if (!el) return
+
+        const instance = new CoreClass(config.devtoolsProps as TComponentProps)
+        devtools = instance
+
+        const theme = (config.theme ?? config.devtoolsProps?.theme ?? 'dark') as Theme
+        try {
+          await instance.mount(el, theme)
           if (isUnmounted) {
             // If we unmounted before mount finished, clean up.
             try {
               instance.unmount()
             } catch {
               // ignore
             }
           } else {
             didMount = true
           }
-        }
+        } catch {
+          try {
+            instance.unmount()
+          } catch {
+            // ignore
+          }
+        }
       })

       onUnmounted(() => {
         isUnmounted = true
-        if (didMount) {
-          try {
-            devtools?.unmount()
-          } catch {
-            // ignore
-          }
-        }
+        try {
+          // ok even if mount never completed
+          devtools?.unmount()
+        } catch {
+          // ignore
+        }
       })

This keeps the “wrapper can make devtoolsProps optional” pattern, while making the exported Panel safer/less surprising for direct consumers.

🤖 Prompt for AI Agents
packages/typescript/vue-ai-devtools/src/createVuePanel.ts around lines 19 to 66:
devtoolsProps is declared optional but used as required and its theme is
ignored; the async mount logic can leave an instance untracked if unmounted
during await. Make devtoolsProps required or provide a safe default (e.g.,
default: () => ({})) so config.devtoolsProps is always defined, use the theme
from config.devtoolsProps.theme with fallback to config.theme and then 'dark'
when calling mount, assign devtools = instance before awaiting mount and capture
a local mountedInstance flag or check devtools !== null when deciding to
unmount, and in the onUnmounted cleanup always check for the concrete instance
and call unmount inside try/catch to harden async mount/unmount races.

Comment on lines 7 to 25
const NullComponent = /* #__PURE__ */ defineComponent({
name: 'NullAiDevtoolsPanel',
setup() {
return () => null
},
})

export const AiDevtoolsPanel =
process.env.NODE_ENV !== 'development'
? NullComponent
: DevToolsPanelComponent

export const AiDevtoolsPanelInProd = DevToolsPanelComponent

export const aiDevtoolsPlugin =
process.env.NODE_ENV !== 'development'
? plugin.aiDevtoolsNoOpPlugin
: plugin.aiDevtoolsPlugin

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find vite/build configuration files
find . -maxdepth 3 -type f \( -name "vite.config.*" -o -name "tsconfig.json" -o -name "package.json" \) | head -20

Repository: TanStack/ai

Length of output: 827


🏁 Script executed:

# Check how environment variables are used elsewhere in the codebase
rg "process\.env\.(NODE_ENV|DEV|PROD)" --type ts --type js -A 2 | head -50

Repository: TanStack/ai

Length of output: 1837


🏁 Script executed:

# Also check for import.meta.env usage
rg "import\.meta\.env" --type ts --type js -A 2 | head -50

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Look at the package.json to understand the library setup
find . -name "package.json" -path "*/vue-ai-devtools/*" -o -name "package.json" | xargs grep -l "vue-ai-devtools" | head -5

Repository: TanStack/ai

Length of output: 183


🏁 Script executed:

# Check the index.ts file and surrounding context
cat -n packages/typescript/vue-ai-devtools/src/index.ts | head -40

Repository: TanStack/ai

Length of output: 985


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/package.json

Repository: TanStack/ai

Length of output: 2124


🏁 Script executed:

# Check if there's a vite config in the root or packages directory
find . -maxdepth 2 -name "vite.config.*" -o -name "tsup.config.*" | head -10

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Check the root package.json for build setup
head -50 package.json

Repository: TanStack/ai

Length of output: 2651


🏁 Script executed:

# Check for build scripts in root
rg "build.*vue-ai-devtools|NODE_ENV" package.json | head -20

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

find packages/typescript -maxdepth 2 -name "vite.config.*" -o -name "vitest.config.*"

Repository: TanStack/ai

Length of output: 1141


🏁 Script executed:

# Check if there's a shared vite config at package root level
ls -la packages/typescript/ | grep -E "vite|tsup|build"

Repository: TanStack/ai

Length of output: 37


🌐 Web query:

Vite library build process.env.NODE_ENV replacement ESM best practices

💡 Result:

Short best-practices for handling process.env.NODE_ENV in Vite-built ESM libraries

  1. Prefer import.meta.env (Vite’s model)
  • Use import.meta.env (and VITE_* names for user vars) for build-time/static replacements in apps. It’s the Vite-recommended API. [1][2]
  1. For libraries, avoid forcing process.env replacements
  • Vite’s library mode does NOT automatically replace process.env.* so consumers can control runtime behavior. Prefer leaving process.env references untouched in your distributed ESM (so downstream bundlers / runtimes decide). If you want runtime configurability, explicitly expose a config API instead of baking NODE_ENV into your bundle. [0][1]
  1. If you need static replacement at build time
  • Add a define or replacement at build config time (vite define or rollup/@rollup/plugin-replace) to statically inline a value:
    • vite.config.ts: define: { 'process.env.NODE_ENV': JSON.stringify(modeOrValue) }
      This will enable dead‑code elimination for conditionals that rely on NODE_ENV. [0][4]
  1. Consider esm-friendly solutions for cross-bundler runtime detection
  • Use libraries like esm-env or import-meta-env when you need consistent environment-condition detection across ESM outputs and runtimes (they use package export conditions / runtime shims rather than hard string replaces). These are helpful for library authors who want broad bundler/runtime compatibility. [5][6]
  1. Packaging / exports notes for ESM libraries
  • If publishing ESM (package.json "type":"module" or .mjs), ensure your exports and file extensions match consumer expectations. Avoid assuming Node’s process globals in browser-targeted ESM. Prefer explicit runtime config or documented expectations (e.g., “consumer must set NODE_ENV or pass config”). [0][7]

Quick recommended pattern for library authors

  • Internally use import.meta.env for build-time-only behavior in app code; for library features that must be configurable at runtime, accept an options object or read from global/runtime APIs (not implicitly from process.env). If you must optimize for production builds, allow library consumers to opt into static replacement (document how to use define/replace). [1][0]

Sources
[0] Vite — Build / Library mode (env note).
[1] Vite — Env variables & modes.
[2] Vite — Building for Production / library mode (env paragraph).
[4] StackOverflow — define process.env.NODE_ENV in Vite builds.
[5] npm: esm-env package.
[6] import-meta-env project (runtime env solution).


🏁 Script executed:

cat packages/typescript/vue-ai-devtools/vite.config.ts

Repository: TanStack/ai

Length of output: 926


🏁 Script executed:

cat packages/typescript/react-ai-devtools/vite.config.ts

Repository: TanStack/ai

Length of output: 850


🏁 Script executed:

cat packages/typescript/ai-devtools/vite.config.ts

Repository: TanStack/ai

Length of output: 664


🏁 Script executed:

find . -name "@tanstack/vite-config" -o -name "vite-config" -type d | head -10

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Search for where tanstackViteConfig is defined
rg "export.*tanstackViteConfig|function tanstackViteConfig" --type ts --type js | head -20

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Check if it's in node_modules or an internal package
find . -path "*/node_modules" -prune -o -name "*.ts" -type f -exec grep -l "tanstackViteConfig" {} \; | head -10

Repository: TanStack/ai

Length of output: 734


🏁 Script executed:

# Look for vite-config package in packages directory
ls -la packages/ | grep -i config

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Check root package.json for tanstack/vite-config dependency
rg "@tanstack/vite-config" package.json

Repository: TanStack/ai

Length of output: 92


🌐 Web query:

@tanstack/vite-config process.env.NODE_ENV define library mode

💡 Result:

Short answer — define it in your Vite config (your custom config merged with tanstackViteConfig). Example:

export default mergeConfig(
defineConfig(({ mode }) => ({
define: { 'process.env.NODE_ENV': JSON.stringify(mode) },
})),
tanstackViteConfig({ entry: './src/index.ts', srcDir: './src' }),
)

This forces static replacement of process.env.NODE_ENV for library builds (or set it to '"production"' directly). (main.vitejs.dev)

Citations:


Add explicit define for process.env.NODE_ENV in vite.config.ts

The code relies on process.env.NODE_ENV for conditional exports, but the vite.config.ts does not include a define to ensure static replacement during the build. Add to the config:

define: { 'process.env.NODE_ENV': JSON.stringify(mode) }

Without this, process.env.NODE_ENV may be undefined at runtime in consumers' bundlers, breaking the conditional logic. This applies to all devtools packages (vue, react, solid).

🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/src/index.ts around lines 7 to 25 the
code conditionally exports components/plugins based on process.env.NODE_ENV, but
the project's vite.config.ts does not statically define that variable for
builds; update the vite.config.ts for this and the other devtools packages (vue,
react, solid) to include a define entry that maps 'process.env.NODE_ENV' to
JSON.stringify(mode) (or the build mode variable you use) so the conditional
exports are replaced at build time and won't be undefined at runtime.

Copy link

@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

♻️ Duplicate comments (3)
examples/ts-vue-chat/src/App.vue (1)

3-10: Gate devtools to development-only and mark plugin as non-reactive (avoid shipping server-bus in prod).
This is currently rendered/configured unconditionally, and connectToServerBus: true may create an unintended prod connection surface.

<script setup lang="ts">
 import Header from './components/Header.vue'
 import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools'
 import { TanStackDevtools } from '@tanstack/vue-devtools'
+import { markRaw } from 'vue'

-const devtoolsConfig = { position: 'bottom-right' as const }
-
-const devtoolsPlugins = [aiDevtoolsPlugin()]
-
-const devtoolsEventBusConfig = { connectToServerBus: true }
+const isDev = import.meta.env.DEV
+const devtoolsConfig = isDev ? ({ position: 'bottom-right' as const }) : undefined
+const devtoolsPlugins = isDev ? [markRaw(aiDevtoolsPlugin())] : []
+const devtoolsEventBusConfig = isDev ? ({ connectToServerBus: true }) : undefined
</script>

<template>
  <div class="min-h-screen bg-gray-900">
    <Header />
    <router-view />

-    <TanStackDevtools
+    <TanStackDevtools
+      v-if="isDev"
       :config="devtoolsConfig"
       :plugins="devtoolsPlugins"
       :eventBusConfig="devtoolsEventBusConfig"
     />
  </div>
</template>
@tanstack/vue-devtools TanStackDevtools: does it require plugin descriptors to be wrapped with Vue markRaw to avoid reactivity warnings, and is there a recommended pattern for dev-only inclusion?

Also applies to: 18-22

packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (2)

1-6: Remove vite/client triple-slash reference to avoid leaking Vite types to consumers.

With declaration builds, this directive can propagate into published .d.ts and make non‑Vite consumers fail type-checking.

-/// <reference types="vite/client" />
-
 import { AiDevtoolsCore } from '@tanstack/ai-devtools-core'
 import { defineComponent, h } from 'vue'
 import { createVuePanel } from './createVuePanel'
 import type { DevtoolsPanelProps } from './createVuePanel'
 import type { PropType } from 'vue'

16-20: Avoid as unknown as on AiDevtoolsCore; let TypeScript validate the constructor contract.

The cast can mask a real mismatch between AiDevtoolsVueInit and @tanstack/ai-devtools-core’s constructor. Prefer typing via createVuePanel generics (or update/export a proper init type from @tanstack/ai-devtools-core).

-const [InternalPanel] = createVuePanel(
-  AiDevtoolsCore as unknown as new (
-    _props: AiDevtoolsVueInit,
-  ) => InstanceType<typeof AiDevtoolsCore>,
-)
+const [InternalPanel] = createVuePanel<AiDevtoolsVueInit, AiDevtoolsCore>(
+  AiDevtoolsCore,
+)
#!/bin/bash
set -euo pipefail

# 1) Inspect the actual ctor signature we are adapting to.
rg -n --type=ts -C3 "export class AiDevtoolsCore|class AiDevtoolsCore|constructor\s*\(" packages/typescript/ai-devtools-core packages/typescript/vue-ai-devtools || true

# 2) Confirm `createVuePanel` signature to ensure the generic call is correct.
rg -n --type=ts -C3 "function createVuePanel|export function createVuePanel" packages/typescript/vue-ai-devtools/src/createVuePanel.ts

# 3) Find duplicate definitions of AiDevtoolsVueInit to assess drift risk.
rg -n --type=ts -C2 "export interface AiDevtoolsVueInit" packages/typescript/vue-ai-devtools/src
🧹 Nitpick comments (1)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)

8-14: Avoid duplicating AiDevtoolsVueInit across entrypoints; extract to a shared types.ts to prevent drift.

Given packages/typescript/vue-ai-devtools/src/server.ts also defines AiDevtoolsVueInit (per provided snippet), a shared source-of-truth reduces the chance prod and SSR diverge later (and avoids subtle “same-name, different-type” exports).

Also applies to: 22-41

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9a1c48 and dae3d83.

📒 Files selected for processing (3)
  • examples/ts-vue-chat/src/App.vue (1 hunks)
  • examples/ts-vue-start-chat/package.json (1 hunks)
  • packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/ts-vue-start-chat/package.json
🧰 Additional context used
🧬 Code graph analysis (1)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (2)
packages/typescript/vue-ai-devtools/src/server.ts (1)
  • AiDevtoolsVueInit (3-5)
packages/typescript/vue-ai-devtools/src/createVuePanel.ts (2)
  • DevtoolsPanelProps (4-6)
  • createVuePanel (12-93)

Copy link

@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

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-devtools/src/store/ai-context.tsx (1)

1327-1345: Don’t spread raw chat:started payload into Conversation (and verify requestId mapping)

  1. updateConversation(clientId, { status: 'active', ...e.payload }) can store non-Conversation keys into the conversation object (due to the key as keyof Conversation cast). Prefer explicitly picking known fields (e.g., model, provider, options, providerOptions, status).
  2. Please confirm the chat flow sets requestToConversation somewhere; otherwise chat:completed / usage:tokens may never resolve a conversation for a request.
-          updateConversation(clientId, { status: 'active', ...e.payload })
+          updateConversation(clientId, {
+            status: 'active',
+            model: e.payload.model,
+            provider: e.payload.provider,
+            options: e.payload.options,
+            providerOptions: e.payload.providerOptions,
+          })
#!/bin/bash
# Verify chat requests are mapped for chat:completed/usage:tokens lookups
rg -n --type=ts -C3 'requestToConversation\.set\('
rg -n --type=ts -C3 "on\\('chat:started'" packages/typescript/ai-devtools/src/store/ai-context.tsx
rg -n --type=ts -C3 "on\\('chat:completed'" packages/typescript/ai-devtools/src/store/ai-context.tsx
♻️ Duplicate comments (1)
packages/typescript/vue-ai-devtools/src/index.ts (1)

15-25: Ensure process.env.NODE_ENV is properly defined at build time.

The conditional exports rely on process.env.NODE_ENV being statically replaced during the build. As noted in the previous review, without explicit define configuration in vite.config.ts, this variable may be undefined at runtime in consumer bundles, causing the wrong code path to be taken.

🧹 Nitpick comments (2)
packages/typescript/ai-devtools/src/store/ai-context.tsx (1)

360-376: Guard the message merge to avoid clobbering existing fields during dedupe

Object.assign(m, message) can overwrite existing optional fields (e.g., chunks, toolCalls, usage) if the incoming “duplicate” message is partial. Consider a merge that only applies defined keys (and/or special-cases arrays to merge instead of replace).

-      produce((m: Message) => Object.assign(m, message)),
+      produce((m: Message) => {
+        for (const [k, v] of Object.entries(message) as Array<
+          [keyof Message, Message[keyof Message]]
+        >) {
+          if (v !== undefined) (m as any)[k] = v
+        }
+      }),
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)

9-13: Review the double type assertion for potential type mismatches.

The as unknown as pattern suggests a type incompatibility between AiDevtoolsCore and the expected constructor signature. Consider verifying whether:

  • The types can be aligned to avoid the double assertion
  • The AiDevtoolsCore type definition correctly matches the createVuePanel expectations

This pattern works but may hide underlying type issues.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 676994c and 748876e.

📒 Files selected for processing (5)
  • examples/ts-vue-start-chat/src/routes/__root.tsx (1 hunks)
  • packages/typescript/ai-devtools/src/store/ai-context.tsx (2 hunks)
  • packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/index.ts (1 hunks)
  • packages/typescript/vue-ai-devtools/src/production.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/typescript/ai-devtools/src/store/ai-context.tsx (2)
examples/vanilla-chat/src/main.js (1)
  • message (34-34)
packages/typescript/ai-devtools/src/store/ai-store.ts (1)
  • Message (2-2)
packages/typescript/vue-ai-devtools/src/index.ts (5)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)
  • AiDevtoolsPanel (23-41)
packages/typescript/vue-ai-devtools/src/server.ts (3)
  • AiDevtoolsPanel (15-15)
  • AiDevtoolsPanelInProd (16-16)
  • aiDevtoolsPlugin (18-25)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)
  • AiDevtoolsPanelInProd (16-34)
packages/typescript/vue-ai-devtools/src/production.ts (2)
  • AiDevtoolsPanelInProd (1-1)
  • aiDevtoolsPlugin (10-17)
packages/typescript/vue-ai-devtools/src/plugin.ts (1)
  • aiDevtoolsPlugin (16-23)
🔇 Additional comments (9)
examples/ts-vue-start-chat/src/routes/__root.tsx (3)

1-16: LGTM! Clean imports.

The imports are well-organized and correctly use Vite's ?url suffix for the CSS file.


42-76: Well-structured root component.

The JSX layout correctly integrates the Header, Outlet, DevTools, and Scripts. The dual-plugin configuration for Router and AI devtools is appropriate for this application.


67-69: > Likely an incorrect or invalid review comment.

packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)

15-34: LGTM! Clean component structure.

The component properly handles optional props with sensible defaults and correctly forwards them to the InternalPanel. The theme resolution logic with fallback is well-implemented.

packages/typescript/vue-ai-devtools/src/production.ts (2)

1-2: LGTM! Clean production entry re-exports.

The aliasing of AiDevtoolsPanelInProd as AiDevtoolsPanel provides a consistent interface for production consumers while maintaining clear internal naming.


7-17: LGTM! Consistent plugin implementation.

The plugin function correctly mirrors the development plugin structure while using the production-ready component. The API surface is consistent and well-documented.

packages/typescript/vue-ai-devtools/src/index.ts (3)

1-6: LGTM! Clean imports and type exports.

The imports are well-organized, bringing in both development and production variants for conditional exports. The type re-export provides necessary public API surface.


8-13: LGTM! Appropriate no-op component for production.

The NullComponent with /* #__PURE__ */ annotation enables proper tree-shaking in production builds. The implementation is minimal and correct.


27-27: LGTM! Helpful type alias for plugin context.

The type alias AiDevtoolsVuePluginInit provides a clearer name when used in plugin registration contexts.

Comment on lines +18 to +40
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack AI Vue Start Chat',
},
],
links: [
{
rel: 'stylesheet',
href: appCss,
},
],
}),
component: RootComponent,
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify the head API structure - title placement looks incorrect.

The title property is placed inside the meta array at line 29, but HTML titles are typically not meta tags. Additionally, the charSet property uses camelCase, which should be verified against the TanStack Router API.

Please verify the correct structure for TanStack Vue Router's head API:

#!/bin/bash
# Search for head API usage patterns in TanStack Router examples or tests
rg -n -C3 'head.*:.*\(' --type=ts --type=tsx -g '!node_modules/**'

# Search for title property usage in head config
rg -n -C3 'title:' --type=ts --type=tsx -g '!node_modules/**' | head -50
TanStack Vue Router head API documentation for meta tags and title
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/routes/__root.tsx around lines 18 to 40, the
head config incorrectly places title inside the meta array and uses camelCase
charSet; update the head shape to match the TanStack Router head API by moving
title out of the meta array to a top-level title property, replace the meta item
{ charSet: 'utf-8' } with { charset: 'utf-8' } (lowercase key) and keep other
meta entries as { name, content } objects, preserving the links array; verify
the final object conforms to the router's head schema.

@@ -0,0 +1,34 @@
/// <reference types="vite/client" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts

Repository: TanStack/ai

Length of output: 1091


🏁 Script executed:

# Check if any Vite-specific APIs are used in this file
rg -n "import\.meta|__VITE_|import_meta_" packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Also check if other files in the same directory use vite/client reference
fd -e ts -e tsx . packages/typescript/vue-ai-devtools/src/ | head -20 | xargs grep -l "vite/client" 2>/dev/null

Repository: TanStack/ai

Length of output: 178


🏁 Script executed:

# Check if AiDevtoolsPanel.ts uses Vite-specific APIs
rg -n "import\.meta|__VITE_|import_meta_" packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts

Repository: TanStack/ai

Length of output: 37


Remove the unnecessary Vite client types reference.

This file contains no Vite-specific APIs (no import.meta, __VITE_ globals, etc.). The /// <reference types="vite/client" /> directive is unused boilerplate that should be removed.

🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts around line
1, remove the unnecessary triple-slash reference to "vite/client" because this
file doesn't use any Vite-specific globals or APIs; delete the line '///
<reference types="vite/client" />' and ensure no other references require that
directive so the file compiles without that unused directive.

Copy link

@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

🧹 Nitpick comments (1)
examples/ts-vue-start-chat/src/components/ClientOnly.vue (1)

11-13: Consider adding an optional fallback slot.

The template correctly defers rendering until mount. For improved UX, you could optionally support a fallback slot to display placeholder content during SSR or before the component mounts:

 <template>
-  <slot v-if="mounted" />
+  <slot v-if="mounted" />
+  <slot v-else name="fallback" />
 </template>

This would allow consumers to provide placeholder content when needed, reducing layout shifts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 748876e and 5d04f7a.

📒 Files selected for processing (3)
  • examples/ts-vue-start-chat/src/components/ClientOnly.vue (1 hunks)
  • examples/ts-vue-start-chat/src/routes/__root.tsx (1 hunks)
  • examples/ts-vue-start-chat/vite.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/ts-vue-start-chat/src/routes/__root.tsx
  • examples/ts-vue-start-chat/vite.config.ts
🔇 Additional comments (1)
examples/ts-vue-start-chat/src/components/ClientOnly.vue (1)

1-9: LGTM! Standard Vue 3 composition API implementation.

The script logic correctly implements the ClientOnly pattern using Vue 3's composition API. The component will defer rendering its slot content until after the client-side mount, which is the expected behavior for SSR scenarios.

@birkskyum birkskyum requested review from brenelz and jherr December 12, 2025 15:25
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.

2 participants