---
title: "AI SDK 7: what changes for Vue apps · vuejs.school"
description: "AI SDK 7 brings back useChat for Vue, renames system to instructions and changes the stream helpers. Here is the exact diff for a Nuxt chat, plus tools."
canonical_url: "https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps"
meta:
  "og:description": "AI SDK 7 brings back useChat for Vue, renames system to instructions and changes the stream helpers. Here is the exact diff for a Nuxt chat, plus tools."
  "og:image": "https://vuejs.school/_og/d/a_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHM%2Cdescription_~QUkgU0RLIDcgYnJpbmdzIGJhY2sgdXNlQ2hhdCBmb3IgVnVlLCByZW5hbWVzIHN5c3RlbSB0byBpbnN0cnVjdGlvbnMgYW5kIGNoYW5nZXMgdGhlIHN0cmVhbSBoZWxwZXJzLiBIZXJlIGlzIHRoZSBleGFjdCBkaWZmIGZvciBhIE51eHQgY2hhdCwgcGx1cyB0b29scy4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9haS1zZGstNy13aGF0LWNoYW5nZXMtZm9yLXZ1ZS1hcHBzIg%2Cs_zoXyODwR6cHvAlvu.png"
  "og:image:alt": "AI SDK 7: what changes for Vue apps · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "AI SDK 7: what changes for Vue apps · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHM%2Cdescription_~QUkgU0RLIDcgYnJpbmdzIGJhY2sgdXNlQ2hhdCBmb3IgVnVlLCByZW5hbWVzIHN5c3RlbSB0byBpbnN0cnVjdGlvbnMgYW5kIGNoYW5nZXMgdGhlIHN0cmVhbSBoZWxwZXJzLiBIZXJlIGlzIHRoZSBleGFjdCBkaWZmIGZvciBhIE51eHQgY2hhdCwgcGx1cyB0b29scy4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9haS1zZGstNy13aGF0LWNoYW5nZXMtZm9yLXZ1ZS1hcHBzIg%2Cs_zoXyODwR6cHvAlvu.png"
  "twitter:image:alt": "AI SDK 7: what changes for Vue apps · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What shipped

The announcement groups the release into five areas: developing agents, running them, plugging in other agent harnesses, observing them, and going beyond text. The parts most Vue apps will notice:

- **A `reasoning` option** on `generateText` and `streamText` that maps to each provider's own reasoning settings.
- **Typed tool context.** A tool declares a `contextSchema`, and values such as API keys reach only that tool through `toolsContext`.
- **Tool approvals** set on the call with `toolApproval`, from automatic rules to asking the user.
- **Timeouts** for the whole call, each step, the gap between stream chunks and each tool.
- **Telemetry** registered once at startup, with OpenTelemetry moved to a separate `@ai-sdk/otel` package, plus per-step performance figures such as time to first output.

There is more for agent builders: a durable `WorkflowAgent`, an experimental `HarnessAgent`, a terminal UI, file and skill uploads, and experimental realtime voice and video generation. Two of the new UI pieces, the MCP Apps renderer and the realtime hook, ship for React. Vue gets something more useful day to day.

## useChat is back as a composable

AI SDK 5 replaced Vue's `useChat` with a `Chat` class. Version 4 of `@ai-sdk/vue` adds `useChat` again as a composable and deprecates the class, which keeps working so you can move one page at a time.

`useChat` returns `messages`, `status` and `error` as shallow refs, alongside `sendMessage`, `regenerate`, `stop`, `clearError`, `resumeStream`, `addToolOutput` and `addToolApprovalResponse`. The status values are unchanged: `submitted`, `streaming`, `ready` and `error`.

It also accepts a getter or a ref instead of a plain object, and creates a new chat whenever a value inside it changes. That is what you want when the chat belongs to a route. This is the `<script setup>` of a page at `app/pages/chats/[id].vue`:

*app/pages/chats/\[id].vue*

```
import { useChat } from '@ai-sdk/vue'
import { DefaultChatTransport } from 'ai'

const route = useRoute()
const { messages, sendMessage } = useChat(() => ({
  id: String(route.params.id),
  transport: new DefaultChatTransport({ api: `/api/chats/${route.params.id}` }),
}))
```

The composable's source shows the catch: recreating the chat resets `messages` to the initial list and `status` to `ready`. Put only values that really identify a different conversation inside the getter. A model picker or a text input in there would wipe the conversation every time it changes.

## Upgrade the packages and run the codemod

All AI SDK packages are now ESM only, and they need Node 22 or newer; the SDK is tested on 22, 24 and 26. A Nuxt app already uses ESM. Check your Node version, though: Nuxt 4.4.6 (18 May) raised its own floor to Node 22.12, or 24.11 on Node 24, but earlier 4.4 patches still allowed Node 20.

Upgrade the packages together, then run the codemod:

```
npm install ai@7 @ai-sdk/vue@4 @ai-sdk/openai@4 zod
npx @ai-sdk/codemod v7
```

The guide's codemod table lists more than thirty transformations, among them:

- `system` becomes `instructions`.
- `onFinish` becomes `onEnd`.
- `stepCountIs` becomes `isStepCount`.
- `fullStream` becomes `stream`.
- `experimental_telemetry` becomes `telemetry`.
- `experimental_context` in tool callbacks becomes `context`.
- Long-deprecated `experimental_` options, such as `experimental_activeTools`, get their stable names.

Many old names still work in version 7 as deprecated aliases, so code the codemod misses often keeps running, and most of those names show as deprecated in your editor. Removed options are different. TypeScript flags them, but the SDK no longer reads them, so code that runs anyway silently loses the setting: a leftover `experimental_activeTools` no longer limits which tools the model sees.

## Update the chat route and page

The route from May changes in three places:

1. `instructions` replaces `system`.
2. `toUIMessageStream` and `createUIMessageStreamResponse` join the `ai` import.
3. Those two functions build the response. They replace `result.toUIMessageStreamResponse()`, which still works but is deprecated. `toUIMessageStream` reads `result.stream`, the new name for `fullStream`.

*server/api/chat.post.ts*

```
import { createOpenAI } from '@ai-sdk/openai'
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, type UIMessage } from 'ai'

export default defineEventHandler(async (event) => {
  const { messages } = await readBody<{ messages: UIMessage[] }>(event)
  const openai = createOpenAI({ apiKey: useRuntimeConfig(event).openaiApiKey })

  const abort = new AbortController()
  event.node.res.on('close', () => abort.abort())

  const result = streamText({
    model: openai('gpt-5.1'),
    instructions: 'You are a patient Vue tutor. Keep answers short.',
    messages: await convertToModelMessages(messages),
    abortSignal: abort.signal,
  })

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      onError: () => 'The tutor is unavailable. Try again in a minute.',
    }),
  })
})
```

`onError` moves with the stream into `toUIMessageStream`. If you leave it out, the browser still gets the safe default, `An error occurred.`, rather than the server's error details.

The page changes in four places:

- The import names `useChat` instead of `Chat`.
- `new Chat()` becomes a destructured `useChat()`.
- In the script, `chat.sendMessage` becomes `sendMessage` and `chat.status` becomes `status.value`.
- The template drops the `chat.` prefix, because top-level refs unwrap in templates.

*app/pages/chat.vue*

```
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue'
import { DefaultChatTransport } from 'ai'

const input = ref('')
const { messages, status, error, sendMessage, stop } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
})
const busy = computed(() => status.value === 'submitted' || status.value === 'streaming')

function send() {
  if (!input.value.trim()) return
  sendMessage({ text: input.value })
  input.value = ''
}
</script>

<template>
  <div v-for="message in messages" :key="message.id">
    <strong>{{ message.role === 'user' ? 'You' : 'Tutor' }}</strong>
    <template v-for="(part, index) in message.parts" :key="index">
      <p v-if="part.type === 'text'">{{ part.text }}</p>
    </template>
  </div>
  <p v-if="error" role="alert">{{ error.message }}</p>
  <form @submit.prevent="send">
    <label>Message <input v-model="input" type="text"></label>
    <button type="submit" :disabled="busy">Send</button>
    <button v-if="busy" type="button" @click="stop()">Stop</button>
  </form>
</template>
```

## Changes the codemod cannot make

Two changes keep compiling but behave differently, so look for them by hand.

**System messages in `messages` are rejected by default.** If you store conversations and some contain `{ role: 'system' }` entries, version 7 refuses them with `Invalid prompt: System messages are not allowed in the prompt or messages fields. Use the instructions option instead.` Move the text to `instructions`. The guide offers `allowSystemInMessages: true` for old histories, with a warning: if users can edit those messages, they can rewrite your system prompt. Only use it for messages your server wrote.

**Results now cover every step.** In a call with tools, `usage`, `toolCalls`, `toolResults`, `content` and similar properties on the result used to describe the last step only. Now they add up all steps, and `usage` means what `totalUsage` meant. The old values live under `finalStep`. If you log token counts or bill users from `usage`, the numbers will rise for multi-step calls after the upgrade, and that is the correct total.

Three smaller changes in the guide are also left to you:

- Request bodies are no longer kept on results unless you opt in with `include`.
- `needsApproval` on a tool gives way to `toolApproval` on the call.
- On OpenAI's Responses API, setting a reasoning effort now turns on detailed reasoning summaries. Set `reasoningSummary` to `null` in the OpenAI provider options to keep them off.

## Add a typed tool to the chat

A tool is a function the model may call, with a schema for its input. Define it once in Nuxt's `shared/` folder so the server can run it and the page can infer its types:

*shared/tutor-tools.ts*

```
import { tool } from 'ai'
import { z } from 'zod'

const docsPages = {
  computed: 'https://vuejs.org/guide/essentials/computed.html',
  watchers: 'https://vuejs.org/guide/essentials/watchers.html',
  props: 'https://vuejs.org/guide/components/props.html',
} as const

export const tutorTools = {
  findDocsPage: tool({
    description: 'Find the official Vue docs page for a topic',
    inputSchema: z.object({ topic: z.enum(['computed', 'watchers', 'props']) }),
    execute: async ({ topic }) => ({ topic, url: docsPages[topic] }),
  }),
}
```

In the route, import it with `import { tutorTools } from '#shared/tutor-tools'`, add `isStepCount` to the `ai` import, and pass three more options to `streamText`:

*server/api/chat.post.ts*

```
tools: tutorTools,
stopWhen: isStepCount(3),
timeout: { totalMs: 60_000, chunkMs: 15_000 },
```

`stopWhen: isStepCount(3)` lets the model call the tool, read the result and then answer, in up to three steps. The new `timeout` ends the call after a minute, or sooner if the provider goes quiet for fifteen seconds mid-stream.

On the page, give `useChat` the message type so each tool part knows its input and output:

*app/pages/chat.vue*

```
import type { InferUITools, UIDataTypes, UIMessage } from 'ai'
import type { tutorTools } from '#shared/tutor-tools'

type TutorMessage = UIMessage<unknown, UIDataTypes, InferUITools<typeof tutorTools>>
const { messages, status, error, sendMessage, stop } = useChat<TutorMessage>({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
})
```

Then render the tool's part, named `tool-findDocsPage` after the key, next to the text part:

*app/pages/chat.vue*

```
<a v-else-if="part.type === 'tool-findDocsPage' && part.state === 'output-available'" :href="part.output.url">
  Read the docs on {{ part.output.topic }}
</a>
<p v-else-if="part.type === 'tool-findDocsPage' && part.state === 'output-error'">The docs lookup failed.</p>
<p v-else-if="part.type === 'tool-findDocsPage'">Looking up the docs…</p>
```

Check the type and the state in one condition, as here, so TypeScript knows `part.output` exists. Before that state the part is `input-streaming` or `input-available`, which the last line covers. If `execute` throws, the part ends in `output-error`; without its own branch it would fall through to "Looking up the docs…" and stay there.

## Upgrade now or wait

Upgrade a branch now. The codemod does the renames, the deprecated names keep running, and `useChat` makes the Vue code shorter. If your app relies on an experimental feature from 6, read its section of the guide first; that is where version 7 removed the most.

**Check it stuck**3 questions from the Tools and structured output with the AI SDK quiz.

[**Practice now**](https://vuejs.school/learn/ai/tools-and-structured-output-with-the-ai-sdk?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 7 min read**RAG in a Nuxt app: embeddings, retrieval and a chatbot that knows your docs**](https://vuejs.school/community/blog/rag-in-a-nuxt-app-a-chatbot-that-knows-your-docs) [Tutorial · 6 min read**Tools and structured output with the AI SDK 7**](https://vuejs.school/community/blog/tools-and-structured-output-with-the-ai-sdk-7)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-06-30","description":"AI SDK 7 brings back useChat for Vue, renames system to instructions and changes the stream helpers. Here is the exact diff for a Nuxt chat, plus tools.","name":"AI SDK 7: what changes for Vue apps","url":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps","breadcrumb":{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-06-30","description":"AI SDK 7 brings back useChat for Vue, renames system to instructions and changes the stream helpers. Here is the exact diff for a Nuxt chat, plus tools.","headline":"AI SDK 7: what changes for Vue apps","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHM%2Cdescription_~QUkgU0RLIDcgYnJpbmdzIGJhY2sgdXNlQ2hhdCBmb3IgVnVlLCByZW5hbWVzIHN5c3RlbSB0byBpbnN0cnVjdGlvbnMgYW5kIGNoYW5nZXMgdGhlIHN0cmVhbSBoZWxwZXJzLiBIZXJlIGlzIHRoZSBleGFjdCBkaWZmIGZvciBhIE51eHQgY2hhdCwgcGx1cyB0b29scy4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9haS1zZGstNy13aGF0LWNoYW5nZXMtZm9yLXZ1ZS1hcHBzIg%2Cs_zoXyODwR6cHvAlvu.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/ai-sdk-7-what-changes-for-vue-apps#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"AI SDK 7: what changes for Vue apps","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHM%2Cdescription_~QUkgU0RLIDcgYnJpbmdzIGJhY2sgdXNlQ2hhdCBmb3IgVnVlLCByZW5hbWVzIHN5c3RlbSB0byBpbnN0cnVjdGlvbnMgYW5kIGNoYW5nZXMgdGhlIHN0cmVhbSBoZWxwZXJzLiBIZXJlIGlzIHRoZSBleGFjdCBkaWZmIGZvciBhIE51eHQgY2hhdCwgcGx1cyB0b29scy4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9haS1zZGstNy13aGF0LWNoYW5nZXMtZm9yLXZ1ZS1hcHBzIg%2Cs_zoXyODwR6cHvAlvu.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~QUkgU0RLIDc6IHdoYXQgY2hhbmdlcyBmb3IgVnVlIGFwcHM%2Cdescription_~QUkgU0RLIDcgYnJpbmdzIGJhY2sgdXNlQ2hhdCBmb3IgVnVlLCByZW5hbWVzIHN5c3RlbSB0byBpbnN0cnVjdGlvbnMgYW5kIGNoYW5nZXMgdGhlIHN0cmVhbSBoZWxwZXJzLiBIZXJlIGlzIHRoZSBleGFjdCBkaWZmIGZvciBhIE51eHQgY2hhdCwgcGx1cyB0b29scy4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9haS1zZGstNy13aGF0LWNoYW5nZXMtZm9yLXZ1ZS1hcHBzIg%2Cs_zoXyODwR6cHvAlvu.png"}]}
```