> For the complete documentation index, see [llms.txt](https://docs.frayme.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.frayme.ai/agent-frameworks/ai-sdk.md).

# Vercel AI SDK

Add live, interactive UI to an AI SDK 6 chat: the server bridges a Frayme compose stream into `data-spec` message parts, and the client renders them with one component.

## How it fits

The AI SDK integration has two halves:

* **Server** — your agent calls the `frayme_compose` tool. Instead of returning the spec as model-visible text, the tool streams it to the browser as `data-spec` parts via `composeStreamToDataParts` (from `@frayme/runtime` — the server-safe root entrypoint).
* **Client** — `<FraymeMessageRenderer />` (from `@frayme/runtime/ai-sdk`) reads the `data-spec` parts off any `useChat` message and renders the spec progressively as it streams.

The tool definition comes from `@frayme/api/tools`. Its schema is Zod v4 (Standard Schema), so AI SDK 6's `tool()` accepts `inputSchema` directly — no conversion.

```bash
npm i @frayme/api @frayme/runtime ai @ai-sdk/react
```

## Server: the chat route

```ts
// app/api/chat/route.ts
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
  streamText,
  tool,
} from 'ai';
import Frayme from '@frayme/api';
import { composeToolDefinition } from '@frayme/api/tools';
import { composeStreamToDataParts } from '@frayme/runtime';

const frayme = new Frayme({ apiKey: process.env.FRAYME_API_KEY });

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      const result = streamText({
        model: 'your-provider/your-model', // any AI SDK model — your agent, your choice
        messages: convertToModelMessages(messages),
        stopWhen: stepCountIs(5),
        tools: {
          frayme_compose: tool({
            description: composeToolDefinition.description,
            inputSchema: composeToolDefinition.inputSchema,
            execute: async (input) => {
              // Stream the spec to the CLIENT as data parts — out-of-band,
              // never as model-visible text.
              const composeStream = frayme.compose.stream(input);
              for await (const part of composeStreamToDataParts(composeStream)) {
                writer.write({ type: 'data-spec', data: part });
              }
              const { generationId, model } = await composeStream.finalSpec();
              // The model only needs the correlation handle.
              return { generation_id: generationId, model, rendered: true };
            },
          }),
        },
      });
      writer.merge(result.toUIMessageStream());
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

`composeStreamToDataParts` maps the compose stream onto json-render's data-part protocol:

| Compose event       | Data part                    | Client effect                      |
| ------------------- | ---------------------------- | ---------------------------------- |
| `op`                | `{ type: 'patch', patch }`   | progressive render                 |
| `compose.restarted` | `{ type: 'flat', spec: {} }` | discard everything rendered so far |
| `compose.completed` | `{ type: 'flat', spec }`     | the validated final commit         |

The restart-discard contract is handled for you: a `flat` part replaces the whole snapshot, so a failed attempt can never leave stale UI on screen.

## Client: render messages

```tsx
// app/page.tsx
'use client';
import { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import {
  FraymeMessageRenderer,
  createDynamicActionForwarder,
} from '@frayme/runtime/ai-sdk';
import '@frayme/runtime/styles.css';

export default function Chat() {
  const { messages, sendMessage } = useChat();
  const [input, setInput] = useState('');
  const forwarder = createDynamicActionForwarder({ sendMessage });

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.parts.map((part, i) =>
            part.type === 'text' ? <p key={i}>{part.text}</p> : null,
          )}
          {/* Renders the data-spec parts this message carries; null if none. */}
          <FraymeMessageRenderer message={message} onDynamicAction={forwarder} />
        </div>
      ))}
      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input value={input} onChange={(e) => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

`FraymeMessageRenderer` defaults to `mode="progressive"` — parts stream in live, and the server already validated the spec before billing. All other [`FraymeRenderer` props](/sdk-reference/runtime.md) (`theme`, `components`, `catalog`, …) pass through.

## Routing actions back

When the user triggers a declared action (a form submit, an approve button), the renderer fires `onDynamicAction`. The forwarder gives you two sinks:

* **`sendMessage`** (shown above) — the action is delivered as the user's next chat message, e.g. `approveRefund: {"orderId":"4821"}`. Simple, but lossy: only `action` + `params` survive.
* **`onAction`** (preferred) — receives the full enriched event `{ action, event, params, state, generation_id }`, losslessly. Wire it to the `frayme_action` round-trip tool so the interaction recomposes the UI in context:

```ts
import { createActionTool } from '@frayme/api/tools';

const actionTool = createActionTool(frayme); // register alongside frayme_compose

// client:
const forwarder = createDynamicActionForwarder({
  onAction: (event) => sendMessage({ text: `__frayme_action__:${JSON.stringify(event)}` }),
});
```

{% hint style="info" %}
`useChat().addToolResult` is not a valid sink here: it resolves an *agent-initiated* tool call, and a user click has no pending call. Use `onAction` or `sendMessage`.
{% endhint %}

## Next steps

* [@frayme/api reference](/sdk-reference/api.md) — client options, streaming surface, errors
* [@frayme/runtime reference](/sdk-reference/runtime.md) — renderer props, theming
* [AG-UI](/agent-frameworks/ag-ui.md) — the same loop over the AG-UI protocol instead of AI SDK data parts


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.frayme.ai/agent-frameworks/ai-sdk.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
