# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- packages/mcp-docs-server/README.md ---
# Assistant-UI MCP Docs Server

A Model Context Protocol (MCP) server that provides AI assistants with direct access to assistant-ui's documentation and examples.

> **📖 Full Documentation**  
> For detailed installation instructions, troubleshooting, and advanced usage, visit the [complete documentation](https://www.assistant-ui.com/docs/mcp-docs-server).

## Installation

### Claude Code

```bash
# Add to current project
claude mcp add assistant-ui -- npx -y @assistant-ui/mcp-docs-server

# Or add globally for all projects
claude mcp add --scope user assistant-ui -- npx -y @assistant-ui/mcp-docs-server
```

### Claude Desktop

Add to your Claude Desktop configuration:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "assistant-ui": {
      "command": "npx",
      "args": ["-y", "@assistant-ui/mcp-docs-server"]
    }
  }
}
```

### Cursor

Add to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):

```json
{
  "mcpServers": {
    "assistant-ui": {
      "command": "npx",
      "args": ["-y", "@assistant-ui/mcp-docs-server"]
    }
  }
}
```

### Windsurf

Add to `~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "assistant-ui": {
      "command": "npx",
      "args": ["-y", "@assistant-ui/mcp-docs-server"]
    }
  }
}
```

### VSCode

Add to `~/.vscode/mcp.json` (Windows users: see [full docs](https://www.assistant-ui.com/docs/mcp-docs-server) for Windows-specific config):

```json
{
  "servers": {
    "assistant-ui": {
      "command": "npx",
      "args": ["-y", "@assistant-ui/mcp-docs-server"],
      "type": "stdio"
    }
  }
}
```

### Zed

Add to `settings.json` (open via `cmd+,` or `zed: open settings`):

```json
{
  "context_servers": {
    "assistant-ui": {
      "command": {
        "path": "npx",
        "args": ["-y", "@assistant-ui/mcp-docs-server"],
        "env": {}
      },
      "settings": {}
    }
  }
}
```

## Tools

- **assistantUIDocs** - Retrieve documentation by path
- **assistantUIExamples** - Access complete example projects

## Managing the Server

### Claude Code

```bash
# View configured servers
claude mcp list

# Get server details
claude mcp get assistant-ui

# Remove the server
claude mcp remove assistant-ui

# Restart the server
claude mcp restart assistant-ui
```

## License

MIT


## Links discovered
- [complete documentation](https://www.assistant-ui.com/docs/mcp-docs-server)
- [full docs](https://www.assistant-ui.com/docs/mcp-docs-server)

--- apps/docs/content/examples/index.mdx ---
---
title: Examples
description: Explore interactive examples and implementations of assistant-ui
---

import { ExampleCard } from "@/components/examples/ExampleCard";
import { INTERNAL_EXAMPLES, COMMUNITY_EXAMPLES } from "@/lib/examples";
import { Button } from "@/components/ui/button";

<header className="text-center">
<h1 className="mt-6 mb-4 font-medium text-4xl">Examples</h1>

<span className="mb-16 block text-muted-foreground">
  Explore our collection of examples that demonstrate different ways to integrate assistant-ui into your applications.
</span>
</header>

<div className="grid gap-6 lg:grid-cols-2 xl:grid-cols-3">
  {INTERNAL_EXAMPLES.map((item) => (
    <ExampleCard key={item.title} {...item} />
  ))}
</div>

<h2 className="mt-16 mb-8 font-medium text-2xl">Community Examples</h2>

<div className="grid gap-6 lg:grid-cols-2 xl:grid-cols-3">
  {COMMUNITY_EXAMPLES.map((item) => (
    <ExampleCard key={item.title} {...item} />
  ))}
</div>

<div className="my-16 flex flex-col items-center gap-4">
  <span className="font-medium text-xl">Looking for more examples?</span>
  <Button asChild>
    <a href="/showcase">Check out the community showcase</a>
  </Button>
</div>


## Links discovered
- [Check out the community showcase](https://github.com/assistant-ui/assistant-ui/blob/main/showcase.md)

--- apps/docs/content/docs/getting-started.mdx ---
---
title: Getting Started
---

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Callout } from "fumadocs-ui/components/callout";
import { Card, Cards } from "fumadocs-ui/components/card";
import { InstallCommand } from "@/components/docs/install-command";

## Start with a new project

![animated gif showing the steps to create a new project](../../../../.github/assets/assistant-ui-starter.gif)

<Steps>
  <Step>

### Initialize assistant-ui

**Create a new project:**

```sh
# Create a new project with the default template
npx assistant-ui@latest create

# Or choose one of the following templates:
# Assistant Cloud for baked in persistence and thread management
npx assistant-ui@latest create -t cloud

# LangGraph
npx assistant-ui@latest create -t langgraph

# MCP support
npx assistant-ui@latest create -t mcp
```

**Add assistant-ui to an existing React project:**

```sh
# Add assistant-ui to an existing React project
npx assistant-ui@latest init
```

  </Step>
  <Step>

### Add API key

Add a new `.env` file to your project with your OpenAI API key:

```
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# chat history -- sign up for free on https://cloud.assistant-ui.com
# NEXT_PUBLIC_ASSISTANT_BASE_URL="https://..."
```

  </Step>
  <Step>

### Start the app

```sh
npm run dev
```

  </Step>
</Steps>

## Manual installation

<Callout>
  We recommend `npx assistant-ui init` to setup existing projects.
</Callout>

<Steps>
  <Step>

### Add assistant-ui

<InstallCommand shadcn={["thread", "thread-list"]} manualSetupInstructions />

  </Step>
  <Step>

### Setup Backend Endpoint

Install provider SDK:

<Tabs groupId="provider" items={["OpenAI", "Anthropic", "Azure", "AWS", "Gemini", "GCP", "Groq", "Fireworks", "Cohere", "Ollama", "Chrome AI"]}>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/openai"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/anthropic"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/azure"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/amazon-bedrock"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/google"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/google-vertex"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/openai"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/openai"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "@ai-sdk/cohere"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "ollama-ai-provider-v2"]} />
  </Tab>
  <Tab>
    <InstallCommand npm={["ai", "@assistant-ui/react-ai-sdk", "chrome-ai"]} />
  </Tab>
</Tabs>

Add an API endpoint:

<Tabs groupId="provider" items={["OpenAI", "Anthropic", "Azure", "AWS", "Gemini", "GCP", "Groq", "Fireworks", "Cohere", "Ollama", "Chrome AI"]}>
```ts title="/app/api/chat/route.ts" tab="OpenAI"
import { openai } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: openai("gpt-4o-mini"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Anthropic"
import { anthropic } from "@ai-sdk/anthropic";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: anthropic("claude-3-5-sonnet-20240620"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Azure"
import { azure } from "@ai-sdk/azure";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: azure("your-deployment-name"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="AWS"
import { bedrock } from "@ai-sdk/amazon-bedrock";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: bedrock("anthropic.claude-3-5-sonnet-20240620-v1:0"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Gemini"
import { google } from "@ai-sdk/google";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: google("gemini-2.0-flash"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="GCP"
import { vertex } from "@ai-sdk/google-vertex";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: vertex("gemini-1.5-pro"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Groq"
import { createOpenAI } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

const groq = createOpenAI({
  apiKey: process.env.GROQ_API_KEY ?? "",
  baseURL: "https://api.groq.com/openai/v1",
});

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: groq("llama3-70b-8192"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Fireworks"
import { createOpenAI } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

const fireworks = createOpenAI({
  apiKey: process.env.FIREWORKS_API_KEY ?? "",
  baseURL: "https://api.fireworks.ai/inference/v1",
});

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: fireworks("accounts/fireworks/models/firefunction-v2"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Cohere"
import { cohere } from "@ai-sdk/cohere";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: cohere("command-r-plus"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Ollama"
import { ollama } from "ollama-ai-provider-v2";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: ollama("llama3"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```ts title="/app/api/chat/route.ts" tab="Chrome AI"
import { chromeai } from "chrome-ai";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: chromeai(),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

</Tabs>

Define environment variables:

<Tabs groupId="provider" items={["OpenAI", "Anthropic", "Azure", "AWS", "Gemini", "GCP", "Groq", "Fireworks", "Cohere", "Ollama", "Chrome AI"]}>

```sh title="/.env.local" tab="OpenAI"
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Anthropic"
ANTHROPIC_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Azure"
AZURE_RESOURCE_NAME="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AZURE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="AWS"
AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_REGION="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Gemini"
GOOGLE_GENERATIVE_AI_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="GCP"
GOOGLE_VERTEX_PROJECT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_VERTEX_LOCATION="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_APPLICATION_CREDENTIALS="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Groq"
GROQ_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Fireworks"
FIREWORKS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh title="/.env.local" tab="Cohere"
COHERE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```sh tab="Ollama"
<none>
```

```sh tab="Chrome AI"
<none>
```

</Tabs>

If you aren't using Next.js, you can also deploy this endpoint to Cloudflare Workers, or any other serverless platform.

  </Step>

  <Step>

### Use it in your app

<Tabs items={["Thread", "AssistantModal"]}>

```tsx title="/app/page.tsx" tab="Thread"
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
import { ThreadList } from "@/components/assistant-ui/thread-list";
import { Thread } from "@/components/assistant-ui/thread";

const MyApp = () => {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({
      api: "/api/chat",
    }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <div>
        <ThreadList />
        <Thread />
      </div>
    </AssistantRuntimeProvider>
  );
};
```

```tsx title="/app/page.tsx" tab="AssistantModal"
// run `npx shadcn@latest add @assistant-ui/assistant-modal`

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
import { AssistantModal } from "@/components/assistant-ui/assistant-modal";

const MyApp = () => {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({
      api: "/api/chat",
    }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <AssistantModal />
    </AssistantRuntimeProvider>
  );
};
```

</Tabs>

</Step>
</Steps>

## What's Next?

<Cards>
  <Card
    title="Pick a Runtime"
    description="Choose the right runtime for your needs"
    href="/docs/runtimes/pick-a-runtime"
  />
  <Card
    title="Generative UI"
    description="Create rich UI components for tool executions"
    href="/docs/guides/ToolUI"
  />
  <Card
    title="Add Persistence"
    description="Save and restore chat conversations"
    href="/docs/cloud/overview"
  />
  <Card
    title="Examples"
    description="Explore full implementations and demos"
    href="https://github.com/assistant-ui/assistant-ui/tree/main/examples"
  />
</Cards>


## Links discovered
- [animated gif showing the steps to create a new project](https://github.com/assistant-ui/assistant-ui/blob/main/.github/assets/assistant-ui-starter.gif)

--- packages/cli/src/lib/install-ai-sdk-lib.ts ---
import { installPackageIfNeeded } from "./utils/package-installer";

export default async function installAiSdkLib(): Promise<void> {
  await installPackageIfNeeded({
    packageName: "@assistant-ui/react-ai-sdk",
    importPatterns: ["@assistant-ui/react-ai-sdk"],
    promptMessage:
      "AI SDK imports were added but @assistant-ui/react-ai-sdk is not installed. Do you want to install it? (Y/n) ",
    skipMessage:
      "@assistant-ui/react-ai-sdk is already installed. Skipping installation.",
    notFoundMessage: "No AI SDK imports found; skipping installation.",
  });
}


--- packages/cli/src/lib/install-edge-lib.ts ---
import { installPackageIfNeeded } from "./utils/package-installer";

export default async function installEdgeLib(): Promise<void> {
  await installPackageIfNeeded({
    packageName: "@assistant-ui/react-ai-sdk",
    importPatterns: [
      "@assistant-ui/react-edge",
      "@assistant-ui/react-ai-sdk",
      "useChatRuntime",
    ],
    promptMessage:
      "Edge Runtime imports were detected but @assistant-ui/react-ai-sdk is not installed. Do you want to install it? (Y/n) ",
    skipMessage:
      "@assistant-ui/react-ai-sdk is already installed. Skipping installation.",
    notFoundMessage: "No Edge Runtime imports found; skipping installation.",
  });
}


--- packages/cli/src/lib/install-ui-lib.ts ---
import { installPackageIfNeeded } from "./utils/package-installer";

export default async function installReactUILib(): Promise<void> {
  await installPackageIfNeeded({
    packageName: "@assistant-ui/react-ui",
    importPatterns: ["@assistant-ui/react-ui"],
    promptMessage:
      "React UI imports were added but @assistant-ui/react-ui is not installed. Do you want to install it? (Y/n) ",
    skipMessage:
      "@assistant-ui/react-ui is already installed. Skipping installation.",
    notFoundMessage: "No React UI imports found; skipping installation.",
  });
}


--- packages/cli/test/utils/package-installer.test.ts ---
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { installPackageIfNeeded } from "../../src/lib/utils/package-installer";
import * as packageManager from "../../src/lib/utils/package-manager";
import * as fileScanner from "../../src/lib/utils/file-scanner";

vi.mock("../../src/lib/utils/package-manager");
vi.mock("../../src/lib/utils/file-scanner");

describe("package-installer utilities", () => {
  let testDir: string;
  let consoleLogSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), "cli-test-"));
    consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
  });

  afterEach(() => {
    if (fs.existsSync(testDir)) {
      fs.rmSync(testDir, { recursive: true, force: true });
    }
    consoleLogSpy.mockRestore();
    vi.clearAllMocks();
  });

  describe("installPackageIfNeeded", () => {
    it("should skip installation when import patterns not found", async () => {
      vi.mocked(fileScanner.scanForImport).mockReturnValue(false);

      await installPackageIfNeeded({
        packageName: "@assistant-ui/react",
        importPatterns: ["@assistant-ui/react"],
        promptMessage: "Install?",
        skipMessage: "Already installed",
        notFoundMessage: "Not found",
      });

      expect(consoleLogSpy).toHaveBeenCalledWith("Not found");
      expect(packageManager.isPackageInstalled).not.toHaveBeenCalled();
    });

    it("should skip installation when package already installed", async () => {
      vi.mocked(fileScanner.scanForImport).mockReturnValue(true);
      vi.mocked(packageManager.isPackageInstalled).mockReturnValue(true);

      await installPackageIfNeeded({
        packageName: "@assistant-ui/react",
        importPatterns: ["@assistant-ui/react"],
        promptMessage: "Install?",
        skipMessage: "Already installed",
        notFoundMessage: "Not found",
      });

      expect(consoleLogSpy).toHaveBeenCalledWith("Already installed");
      expect(packageManager.installPackage).not.toHaveBeenCalled();
    });

    it("should prompt for installation when package needed but not installed", async () => {
      vi.mocked(fileScanner.scanForImport).mockReturnValue(true);
      vi.mocked(packageManager.isPackageInstalled).mockReturnValue(false);
      vi.mocked(packageManager.askQuestion).mockResolvedValue("y");
      vi.mocked(packageManager.installPackage).mockResolvedValue(true);

      await installPackageIfNeeded({
        packageName: "@assistant-ui/react",
        importPatterns: ["@assistant-ui/react"],
        promptMessage: "Install @assistant-ui/react? (Y/n) ",
        skipMessage: "Already installed",
        notFoundMessage: "Not found",
      });

      expect(packageManager.askQuestion).toHaveBeenCalledWith(
        "Install @assistant-ui/react? (Y/n) ",
      );
      expect(packageManager.installPackage).toHaveBeenCalledWith(
        "@assistant-ui/react",
      );
    });

    it("should skip installation when user declines", async () => {
      vi.mocked(fileScanner.scanForImport).mockReturnValue(true);
      vi.mocked(packageManager.isPackageInstalled).mockReturnValue(false);
      vi.mocked(packageManager.askQuestion).mockResolvedValue("n");

      await installPackageIfNeeded({
        packageName: "@assistant-ui/react",
        importPatterns: ["@assistant-ui/react"],
        promptMessage: "Install?",
        skipMessage: "Already installed",
        notFoundMessage: "Not found",
      });

      expect(packageManager.installPackage).not.toHaveBeenCalled();
      expect(consoleLogSpy).toHaveBeenCalledWith("Skipping installation.");
    });

    it("should install when user provides empty input (default yes)", async () => {
      vi.mocked(fileScanner.scanForImport).mockReturnValue(true);
      vi.mocked(packageManager.isPackageInstalled).mockReturnValue(false);
      vi.mocked(packageManager.askQuestion).mockResolvedValue("");
      vi.mocked(packageManager.installPackage).mockResolvedValue(true);

      await installPackageIfNeeded({
        packageName: "@assistant-ui/react",
        importPatterns: ["@assistant-ui/react"],
        promptMessage: "Install?",
        skipMessage: "Already installed",
        notFoundMessage: "Not found",
      });

      expect(packageManager.installPackage).toHaveBeenCalledWith(
        "@assistant-ui/react",
      );
    });
  });
});


--- apps/docs/content/docs/api-reference/overview.mdx ---
---
title: Overview
---

import { Callout } from "fumadocs-ui/components/callout";

import { Component, ContextLevel, RuntimeHooks } from "./context";

export const contextColors = {
  "Assistant Context": "#4a86e8",
  "Thread Context": "#45a049",
  "Composer Context": "#ff9933",
  "Message Context": "#bb2244",
  "MessagePart Context": "#268bd2",
  "Attachment Context": "#FFB6C1",
  "ThreadListItem Context": "#c678dd",
};

<Callout title="Work in progress" type="warn">
  This page is under construction. Most links will not work yet.
</Callout>

## Cloud

- [`AssistantCloud`](#assistant-cloud)

## Runtime API

### AI SDK

- [`useChatRuntime`](#use-chat-runtime)
- [`useAISDKRuntime`](#use-aisdk-runtime)

### Data Stream

- [`useDataStreamRuntime`](#use-data-stream-runtime)
- [`useCloudRuntime`](#use-cloud-runtime)
- [`toLanguageModelMessages`](#to-language-model-messages)

### LangGraph

- [`useLangGraphRuntime`](#use-lang-graph-runtime)

### Local Runtime

- [`useLocalRuntime`](#use-local-runtime)

### External Store Runtime

- [`useExternalStoreRuntime`](#use-external-store-runtime)
- [`createMessageConverter`](#create-message-converter)

### Thread List Runtime

- [`useRemoteThreadListRuntime`](#use-remote-thread-list-runtime)
- [`useCloudThreadListRuntime`](#use-cloud-thread-list-runtime)

## Runtime Adapters

### Attachment

- [`AttachmentAdapter`](#attachment-adapter)
- [`SimpleImageAttachmentAdapter`](#simple-image-attachment-adapter)
- [`SimpleTextAttachmentAdapter`](#simple-text-attachment-adapter)
- [`CompositeAttachmentAdapter`](#composite-attachment-adapter)

### Feedback

- [`FeedbackAdapter`](#feedback-adapter)

### Speech

- [`SpeechSynthesisAdapter`](#speech-synthesis-adapter)
- [`WebSpeechSynthesisAdapter`](#web-speech-synthesis-adapter)

## Highest Level Context Providers

<Component
  name="AssistantRuntimeProvider"
  isContextProvider={true}
  providedContexts={[
    { name: "Assistant Context", color: contextColors["Assistant Context"] },
    { name: "Thread Context", color: contextColors["Thread Context"] },
    {
      name: "Thread Composer Context",
      color: contextColors["Composer Context"],
      link: "#composer-context",
    },
  ]}
  docsLink="./context-providers/AssistantRuntimeProvider"
  tooltip="Provides the highest level context for the assistant-ui"
  props="runtime={runtime}"
>
  <Component name="Thread" isLink={true} />
  <Component name="ThreadList" isLink={true} />
</Component>

<Component
  name="TextMessagePartProvider"
  isContextProvider={true}
  providedContexts={[
    {
      name: "Text MessagePart Context",
      color: contextColors["MessagePart Context"],
      link: "#MessagePart-context",
    },
  ]}
  docsLink="./context-providers/TextMessagePartProvider"
  tooltip="Provides context for text message parts"
  props="text={text}"
>
  <Component name="MessagePart" isLink={true} />
</Component>

<ContextLevel color={contextColors["Assistant Context"]}>

## Assistant Context

The context available to components inside `<AssistantRuntimeProvider />`. You usually wrap your entire application in this context.

### AssistantRuntime

Programmatically access the assistant's state and actions.

- [`useAssistantRuntime`](#use-assistant-runtime)

### Instructions

Add system prompt instructions

- [`useAssistantInstructions`](#use-assistant-instructions)

### Tool UI

Register tool UIs

- [`makeAssistantTool`](#make-assistant-tool)
- [`makeAssistantToolUI`](#make-assistant-tool-ui)
- [`useAssistantTool`](#use-assistant-tool)
- [`useAssistantToolUI`](#use-assistant-tool-ui)

Programmatically access the list of registered tool UIs (Experimental)

- [`useToolUIs`](#use-tool-uis)
- [`useToolUIsStore`](#use-tool-uis-store)

### ThreadListPrimitive

Shows a list of threads and allows the user to switch between them.

<Component
  name="ThreadListPrimitive.Root"
  docsLink="#thread-list-primitive-root"
  tooltip="Root component for the thread list"
>
  <Component
    name="ThreadListPrimitive.New"
    docsLink="#thread-list-primitive-new"
    tooltip="Component for creating a new thread"
  />
  <Component
    name="ThreadListPrimitive.Items"
    isContextProvider={true}
    providedContexts={[
      {
        name: "ThreadListItem Context",
        color: contextColors["ThreadListItem Context"],
      },
    ]}
    docsLink="#thread-list-primitive-items"
    tooltip="Container for thread list items, provides context for individual items"
    props="components={...}"
  />
</Component>

</ContextLevel>

<ContextLevel color={contextColors["Thread Context"]}>

## Thread Context

The context for a single thread. Currently always corresponds to the runtime's main thread.

### ThreadRuntime

Programmatically access the thread's state and actions.

- [`useThread`](#use-thread)
- [`useThreadComposer`](#use-thread-composer)
- [`useThreadRuntime`](#use-thread-runtime)

### ModelContext

- [`useThreadModelContext`](#use-thread-model-context)

### ThreadViewportStore

- [`useThreadViewport`](#use-thread-viewport)
- [`useThreadViewportStore`](#use-thread-viewport-store)

### ThreadPrimitive

A conversation thread.

<Component
  name="ThreadPrimitive.Root"
  docsLink="#thread-primitive-root"
  tooltip="Root component for a thread"
>
  <Component
    name="ThreadPrimitive.Viewport"
    docsLink="#thread-primitive-viewport"
    tooltip="Viewport for the thread content"
  >
    <Component
      name="ThreadPrimitive.Messages"
      isContextProvider={true}
      providedContexts={[
        { name: "Message Context", color: contextColors["Message Context"] },
        {
          name: "Edit Composer Context",
          color: contextColors["Composer Context"],
          link: "#composer-context",
        },
      ]}
      docsLink="#thread-primitive-messages"
      tooltip="Container for thread messages, provides context for messages and edit composer"
    >
      <Component name="Message" isLink={true} />
    </Component>
    <Component
      name="ThreadPrimitive.ScrollToBottom"
      docsLink="#thread-primitive-scroll-to-bottom"
      tooltip="Scrolls to the bottom of the thread"
    />
    <Component
      name="ThreadPrimitive.Empty"
      docsLink="#thread-primitive-empty"
      tooltip="Displayed when the thread is empty"
    />
    <Component
      name="AssistantIf"
      docsLink="#assistant-if"
      tooltip="Conditional rendering based on assistant state"
    />
    <Component
      name="ThreadPrimitive.Suggestion"
      docsLink="#thread-primitive-suggestion"
      tooltip="Displays suggestions in the thread"
    />
    <Component name="Composer" isLink={true} />
  </Component>
</Component>

### AssistantModalPrimitive

A floating modal that usually appears in the lower right corner of the screen. Common for support use cases.

<Component
  name="AssistantModalPrimitive.Root"
  docsLink="#assistant-modal-primitive-root"
  tooltip="Root component for the assistant modal"
>
  <Component
    name="AssistantModalPrimitive.Trigger"
    docsLink="#assistant-modal-primitive-trigger"
    tooltip="Trigger to open the assistant modal"
  />
  <Component
    name="AssistantModalPrimitive.Anchor"
    docsLink="#assistant-modal-primitive-anchor"
    tooltip="Anchor point for the assistant modal"
  />
  <Component
    name="AssistantModalPrimitive.Content"
    docsLink="#assistant-modal-primitive-content"
    tooltip="Content of the assistant modal"
  />
</Component>

</ContextLevel>

<ContextLevel color={contextColors["Composer Context"]}>

## Composer Context

Manages the state and actions for the message composer

### ComposerRuntime

- [`useComposer`](#use-composer)
- [`useComposerRuntime`](#use-composer-runtime)

### ComposerPrimitive

<Component
  name="ComposerPrimitive.Root"
  docsLink="#composer-primitive-root"
  tooltip="Root component for the composer"
>
  <Component
    name="ComposerPrimitive.Input"
    docsLink="#composer-primitive-input"
    tooltip="Input field for composing messages"
  />
  <Component
    name="ComposerPrimitive.Send"
    docsLink="#composer-primitive-send"
    tooltip="Button to send the composed message"
  />
  <Component
    name="ComposerPrimitive.Cancel"
    docsLink="#composer-primitive-cancel"
    tooltip="Button to cancel composing"
  />
  <Component
    name="ComposerPrimitive.Attachments"
    isContextProvider={true}
    providedContexts={[
      {
        name: "Attachment Context",
        color: contextColors["Attachment Context"],
      },
    ]}
    docsLink="#composer-primitive-attachments"
    tooltip="Manages attachments in the composer"
  />
  <Component
    name="ComposerPrimitive.AddAttachment"
    docsLink="#composer-primitive-add-attachment"
    tooltip="Button to add an attachment"
  />
</Component>

</ContextLevel>

<ContextLevel color={contextColors["Message Context"]}>

## Message Context

Manages the state and actions for individual messages

### MessageRuntime

- [`useMessage`](#use-message)
- [`useEditComposer`](#use-edit-composer)
- [`useMessageRuntime`](#use-message-runtime)

### MessageUtilsStore

- [`useMessageUtils`](#use-message-utils)
- [`useMessageUtilsStore`](#use-message-utils-store)

### MessagePrimitive

<Component
  name="MessagePrimitive.Root"
  docsLink="#message-primitive-root"
  tooltip="Root component for a message"
>
  <Component
    name="MessagePrimitive.Parts"
    isContextProvider={true}
    providedContexts={[
      {
        name: "MessagePart Context",
        color: contextColors["MessagePart Context"],
      },
    ]}
    docsLink="#message-primitive-parts"
    tooltip="Displays the parts of the message"
  />
  <Component
    name="MessagePrimitive.Attachments"
    isContextProvider={true}
    providedContexts={[
      {
        name: "Attachment Context",
        color: contextColors["Attachment Context"],
      },
    ]}
    docsLink="#message-primitive-attachments"
    tooltip="Displays attachments in the message"
  />
  <Component
    name="AssistantIf"
    docsLink="#assistant-if"
    tooltip="Conditional rendering based on assistant state"
  />
</Component>

### ActionBarPrimitive

<Component
  name="ActionBarPrimitive.Root"
  docsLink="#action-bar-primitive-root"
  tooltip="Root component for the action bar"
>
  <Component
    name="ActionBarPrimitive.Copy"
    docsLink="#action-bar-primitive-copy"
    tooltip="Copies the message content"
  />
  <Component
    name="ActionBarPrimitive.Edit"
    docsLink="#action-bar-primitive-edit"
    tooltip="Edits the message"
  />
  <Component
    name="ActionBarPrimitive.Reload"
    docsLink="#action-bar-primitive-reload"
    tooltip="Reloads the message"
  />
  <Component
    name="ActionBarPrimitive.Speak"
    docsLink="#action-bar-primitive-speak"
    tooltip="Speaks the message content"
  />
  <Component
    name="ActionBarPrimitive.StopSpeaking"
    docsLink="#action-bar-primitive-stop-speaking"
    tooltip="Stops speaking the message"
  />
  <Component
    name="ActionBarPrimitive.FeedbackPositive"
    docsLink="#action-bar-primitive-feedback-positive"
    tooltip="Provides positive feedback"
  />
  <Component
    name="ActionBarPrimitive.FeedbackNegative"
    docsLink="#action-bar-primitive-feedback-negative"
    tooltip="Provides negative feedback"
  />
</Component>

### BranchPickerPrimitive

<Component
  name="BranchPickerPrimitive.Root"
  docsLink="#branch-picker-primitive-root"
  tooltip="Root component for the branch picker"
>
  <Component
    name="BranchPickerPrimitive.Previous"
    docsLink="#branch-picker-primitive-previous"
    tooltip="Navigates to the previous branch"
  />
  <Component
    name="BranchPickerPrimitive.Number"
    docsLink="#branch-picker-primitive-number"
    tooltip="Displays the current branch number"
  />
  <Component
    name="BranchPickerPrimitive.Next"
    docsLink="#branch-picker-primitive-next"
    tooltip="Navigates to the next branch"
  />
</Component>

</ContextLevel>

<ContextLevel color={contextColors["MessagePart Context"]}>

## MessagePart Context

Manages the state and actions for message parts within messages

### MessagePartRuntime

- [`useMessagePart`](#use-content-part)
- [`useMessagePartText`](#use-content-part-text)
- [`useMessagePartReasoning`](#use-content-part-reasoning)
- [`useMessagePartRuntime`](#use-content-part-runtime)

### MessagePartPrimitive

<Component
  name="MessagePartPrimitive.Text"
  docsLink="#content-part-text"
  tooltip="Represents a text part of the message content"
/>

### MarkdownText

<Component
  name="MarkdownText"
  docsLink="#markdown-text"
  tooltip="Renders markdown text in the message"
/>

</ContextLevel>

<ContextLevel color={contextColors["Attachment Context"]}>

## Attachment Context

Manages the state and actions for attachments in messages and composer

### AttachmentRuntime

- [`useAttachment`](#use-attachment)
- [`useAttachmentRuntime`](#use-attachment-runtime)

### AttachmentPrimitive

<Component
  name="AttachmentPrimitive.Root"
  docsLink="#attachment-primitive-root"
  tooltip="Root component for an attachment"
>
  <Component
    name="AttachmentPrimitive.Name"
    docsLink="#attachment-primitive-name"
    tooltip="Displays the name of the attachment"
  />
  <Component
    name="AttachmentPrimitive.Delete"
    docsLink="#attachment-primitive-delete"
    tooltip="Deletes the attachment"
  />
  <Component
    name="AttachmentPrimitive.Thumb"
    docsLink="#attachment-primitive-thumb"
    tooltip="Displays a thumbnail of the attachment"
  />
</Component>

</ContextLevel>

<ContextLevel color={contextColors["ThreadListItem Context"]}>

## ThreadListItem Context

Manages the state and actions for individual thread list items

### ThreadListItemRuntime

- [`useThreadListItem`](#use-thread-list-item)
- [`useThreadListItemRuntime`](#use-thread-list-item-runtime)

### ThreadListItem

<Component
  name="ThreadListItemPrimitive.Root"
  docsLink="#thread-list-item-primitive-root"
  tooltip="Root component for a thread list item"
>
  <Component
    name="ThreadListItemPrimitive.Trigger"
    docsLink="#thread-list-item-primitive-trigger"
    tooltip="Trigger for thread list item actions"
  >
    <Component
      name="ThreadListItemPrimitive.Name"
      docsLink="#thread-list-item-primitive-name"
      tooltip="Displays the name of the thread"
    />
  </Component>
  <Component
    name="ThreadListItemPrimitive.Archive"
    docsLink="#thread-list-item-primitive-archive"
    tooltip="Archives the thread"
  />
  <Component
    name="ThreadListItemPrimitive.Unarchive"
    docsLink="#thread-list-item-primitive-unarchive"
    tooltip="Unarchives the thread"
  />
  <Component
    name="ThreadListItemPrimitive.Delete"
    docsLink="#thread-list-item-primitive-delete"
    tooltip="Deletes the thread"
  />
  <Component
    name="ThreadListItemPrimitive.Rename"
    docsLink="#thread-list-item-primitive-rename"
    tooltip="Renames the thread"
  />
</Component>

</ContextLevel>

## Utilities

- [`useThreadViewportAutoscroll`](#use-thread-viewport-autoscroll)
- [`useInlineRender`](#use-inline-render)


--- apps/docs/content/docs/cloud/overview.mdx ---
---
title: Overview
---

Assistant Cloud is a hosted service built for assistant-ui frontends that offers comprehensive thread management and message history. It automatically persists threads, supports human-in-the-loop workflows, and integrates with common auth providers to seamlessly allow users to resume conversations at any point.


## Features

### Thread management

Using our `<ThreadList />` component, show the users a list of conversations. Allow the users to seamlessly switch between conversations and even let long running tasks run in the background.

Assistant Cloud automatically persists a list of threads and corresponding metadata. It also automatically generates a title for conversations based on the initial messages.

Supported backends:

- AI SDK
- LangGraph
- Custom

### Chat history

For every conversation, Assistant Cloud can store the history of messages, allowing the user to resume the conversation at any point in time.
This supports human in the loop workflows, where the execution of an agent is interrupted until user feedback is collected.

Supported backends:

- AI SDK
- LangGraph
- Custom (currently only Local Runtime)

### Authorization

Assistant Cloud integrates with your auth provider (Clerk, Auth0, Supabase, Firebase, ...) to identify your users and authorize them to access just the conversations they are allowed to see.

Supported auth providers:

- Clerk
- Auth0
- Supabase
- Firebase
- Your own

## Getting Started

To get started, create an account at [Assistant Cloud Dashboard](https://cloud.assistant-ui.com/) and follow one of the walkthroughs for your preferred backend:

- [AI SDK](/docs/cloud/persistence/ai-sdk)
- [LangGraph](/docs/cloud/persistence/langgraph)

You can also check out our example repositories to see how to integrate Assistant Cloud with your frontend:

- [With AI SDK](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-cloud)
- [With LangGraph](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-langgraph)


## Links discovered
- [Assistant Cloud Dashboard](https://cloud.assistant-ui.com/)
- [AI SDK](https://github.com/assistant-ui/assistant-ui/blob/main/docs/cloud/persistence/ai-sdk.md)
- [LangGraph](https://github.com/assistant-ui/assistant-ui/blob/main/docs/cloud/persistence/langgraph.md)
- [With AI SDK](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-cloud)
- [With LangGraph](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-langgraph)

--- packages/cli/src/lib/utils/package-installer.ts ---
import { scanForImport } from "./file-scanner";
import {
  isPackageInstalled,
  askQuestion,
  installPackage,
} from "./package-manager";
import { logger } from "./logger";

export interface PackageInstallConfig {
  packageName: string;
  importPatterns: string[];
  promptMessage: string;
  skipMessage: string;
  notFoundMessage: string;
}

export async function installPackageIfNeeded(
  config: PackageInstallConfig,
): Promise<void> {
  const found = scanForImport(config.importPatterns);

  if (!found) {
    logger.info(config.notFoundMessage);
    return;
  }

  if (isPackageInstalled(config.packageName)) {
    logger.info(config.skipMessage);
    return;
  }

  const answer = await askQuestion(config.promptMessage);
  if (answer === "" || answer.toLowerCase().startsWith("y")) {
    await installPackage(config.packageName);
  } else {
    logger.info("Skipping installation.");
  }
}


--- examples/store-example/README.md ---
# @assistant-ui/store Example App

This is a Next.js application demonstrating the `@assistant-ui/store` package.

## Features Demonstrated

- **Client Registry**: Module augmentation for type-safe client definitions
- **tapClientList**: Managing lists with index and key lookup
- **tapAssistantEmit**: Emitting and subscribing to scoped events
- **Derived**: Creating derived client scopes from parent resources
- **Provider Pattern**: Scoped access to list items via FooProvider
- **Component Composition**: Render props pattern with components prop

## Getting Started

```bash
# Install dependencies (from monorepo root)
pnpm install

# Run the development server
cd examples/store-example
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) to see the example.

## Project Structure

- `lib/store/foo-scope.ts` - Type definitions via module augmentation:
  - Client registry definitions (foo, fooList)
  - State, methods, meta, and events types
- `lib/store/foo-store.tsx` - Store implementation with:
  - Resource implementations (FooItemResource, FooListResource)
  - Provider component (FooProvider)
  - FooList mapping component
- `lib/example-app.tsx` - Example app with styled components:
  - Foo component with update/delete actions
  - EventLog component demonstrating event subscriptions
  - ExampleApp with layout and styling
- `app/page.tsx` - Main page that renders the ExampleApp

## Key Concepts

### Client Registry

```typescript
declare module "@assistant-ui/store" {
  interface ClientRegistry {
    foo: {
      state: { id: string; bar: string };
      methods: {
        getState: () => FooState;
        updateBar: (newBar: string) => void;
        remove: () => void;
      };
      meta: {
        source: "fooList";
        query: { index: number } | { key: string };
      };
      events: {
        "foo.updated": { id: string; newValue: string };
        "foo.removed": { id: string };
      };
    };
  }
}
```

### Resource Implementation

```typescript
const FooListResource = resource(
  ({ initialValues }): ClientOutput<"fooList"> => {
    const emit = tapAssistantEmit();

    const foos = tapClientList({
      initialValues: initialValues ? [/* ... */] : [],
      getKey: (foo) => foo.id,
      resource: FooItemResource,
    });

    return {
      state: { foos: foos.state },
      methods: {
        getState: () => state,
        foo: foos.get,
        addFoo: () => { /* ... */ },
      },
    };
  },
);
```

### Provider Pattern with Derived

```typescript
const FooProvider = ({ index, children }) => {
  const aui = useAssistantClient({
    foo: Derived({
      source: "fooList",
      query: { index },
      get: (aui) => aui.fooList().foo({ index }),
    }),
  });
  return <AssistantProvider client={aui}>{children}</AssistantProvider>;
};
```

### Event Subscriptions

```typescript
// Subscribe to specific events within a scope
useAssistantEvent("foo.updated", (payload) => {
  console.log(`Updated to: ${payload.newValue}`);
});

// Subscribe to all events using wildcard
useAssistantEvent("*", (data) => {
  console.log(data.event, data.payload);
});
```

## Learn More

- [@assistant-ui/store Documentation](../store/README.md)
- [Next.js Documentation](https://nextjs.org/docs)


## Links discovered
- [http://localhost:3000](http://localhost:3000)
- [@assistant-ui/store Documentation](https://github.com/assistant-ui/assistant-ui/blob/main/examples/store/README.md)
- [Next.js Documentation](https://nextjs.org/docs)

--- examples/with-ai-sdk-v5/README.md ---
# AI SDK v5 Example

This example demonstrates how to use `@assistant-ui/react-ai-sdk-v5` with the Vercel AI SDK v5.

## Getting Started

1. Install dependencies:

```bash
npm install
```

2. Set up your environment variables:

```bash
cp .env.example .env.local
```

Add your Anthropic API key to `.env.local`:

```
ANTHROPIC_API_KEY=your-api-key-here
```

3. Run the development server:

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) to see the result.

## Key Features

- Uses the new AI SDK v5 with `@ai-sdk/react` and `@ai-sdk/anthropic`
- Integrates with `@assistant-ui/react` using the new `useChatRuntime` hook
- No RSC support (client-side only)
- Simplified integration with the `useChatRuntime` hook that wraps AI SDK v5's `useChat`
- Automatically uses `AssistantChatTransport` to pass system messages and frontend tools to the backend

## Custom Transport Configuration

By default, `useChatRuntime` uses `AssistantChatTransport` which automatically forwards system messages and frontend tools to the backend.

### Custom API URL with Forwarding

When customizing the API URL, you must explicitly use `AssistantChatTransport` to keep system/tools forwarding:

```typescript
import { AssistantChatTransport } from "@assistant-ui/react-ai-sdk";

const runtime = useChatRuntime({
  transport: new AssistantChatTransport({
    api: "/my-custom-api/chat", // Custom URL with system/tools forwarding
  }),
});
```

### Disable System/Tools Forwarding

To use the standard AI SDK transport without forwarding:

```typescript
import { DefaultChatTransport } from "ai";

const runtime = useChatRuntime({
  transport: new DefaultChatTransport(), // No system/tools forwarding
});
```

## API Route

The API route at `/api/chat` uses the new `streamText` function from AI SDK v5 to handle chat completions.


## Links discovered
- [http://localhost:3000](http://localhost:3000)

--- examples/with-assistant-transport/README.md ---
# Assistant Transport Example

This example demonstrates how to use assistant-ui with the `useAssistantTransportRuntime` hook to connect to a custom backend server that implements the assistant-transport protocol.

## Overview

The Assistant Transport runtime allows you to connect assistant-ui to any backend server that can handle:

- `AddMessageCommand` - for sending user messages
- `AddToolResultCommand` - for sending tool execution results
- Streaming responses using the `assistant-stream` format

## Prerequisites

Before running this example, you'll need:

1. A backend server that implements the assistant-transport protocol
2. Node.js 18+ installed
3. pnpm package manager

## Getting Started

### 1. Install Dependencies

```bash
pnpm install
```

### 2. Set Up Environment Variables

Copy the example environment file:

```bash
cp .env.local.example .env.local
```

Update the `NEXT_PUBLIC_API_URL` in `.env.local` to point to your backend server:

```env
NEXT_PUBLIC_API_URL=http://localhost:8000/assistant
```

### 3. Start the Development Server

```bash
pnpm dev
```

The application will be available at [http://localhost:3000](http://localhost:3000).

## Backend Server Requirements

Your backend server should:

1. Accept POST requests at the configured endpoint (e.g., `/assistant`)
2. Handle the following command types in the request body:
   - `AddMessageCommand`: `{ type: "add-message", message: { role: "user", parts: [...] } }`
   - `AddToolResultCommand`: `{ type: "add-tool-result", toolCallId: string, result: object }`
3. Return streaming responses using the `assistant-stream` format
4. Include CORS headers to allow requests from the frontend

### Example Request Format

```json
{
  "commands": [
    {
      "type": "add-message",
      "message": {
        "role": "user",
        "parts": [
          {
            "type": "text",
            "text": "Hello, how are you?"
          }
        ]
      }
    }
  ],
  "system": "You are a helpful assistant",
  "tools": {
    "get_weather": {
      "description": "Get weather information",
      "parameters": {
        "type": "object",
        "properties": {
          "location": { "type": "string" }
        }
      }
    }
  }
}
```

## Project Structure

```
examples/with-assistant-transport/
├── app/
│   ├── globals.css              # Global styles with Tailwind CSS
│   ├── layout.tsx              # Root layout component
│   ├── MyRuntimeProvider.tsx   # Custom runtime provider using useAssistantTransportRuntime
│   └── page.tsx               # Main page component
├── components/
│   └── assistant-ui/
│       └── thread.tsx         # Thread component for the chat interface
├── package.json               # Project dependencies
├── tailwind.config.js         # Tailwind CSS configuration
├── tsconfig.json             # TypeScript configuration
├── next.config.js            # Next.js configuration
└── README.md                 # This file
```

## Key Features

- **Custom Runtime**: Uses `useAssistantTransportRuntime` to connect to any backend
- **Streaming Support**: Handles real-time streaming responses from the server
- **Tool Support**: Supports tool calling between frontend and backend
- **Error Handling**: Includes proper error handling and loading states
- **Modern UI**: Built with Tailwind CSS and Radix UI components

## Backend Examples

For a complete working backend example, check out:

- `python/assistant-transport-backend` - Python FastAPI server with assistant-stream integration

## Customization

### Modifying the Runtime Configuration

Edit `app/MyRuntimeProvider.tsx` to customize:

- **API Endpoint**: Change the `api` URL
- **Headers**: Add authentication or other headers
- **Body Parameters**: Add additional request parameters
- **Event Handlers**: Customize response, error, and completion handling
- **State Converter**: Modify how backend state is converted to frontend state

### Styling

The project uses Tailwind CSS for styling. Modify `app/globals.css` and `tailwind.config.js` to customize the appearance.

## Troubleshooting

### Backend Connection Issues

1. Ensure your backend server is running and accessible
2. Check CORS configuration on your backend
3. Verify the API endpoint URL in your `.env.local` file
4. Check the browser console for network errors

### Runtime Errors

1. Verify the backend response format matches assistant-stream expectations
2. Check that the state converter function properly transforms your backend state
3. Ensure all required dependencies are installed

## Learn More

- [assistant-ui Documentation](https://www.assistant-ui.com/docs)
- [Assistant Transport Runtime API](https://www.assistant-ui.com/docs/runtimes/assistant-transport)
- [Next.js Documentation](https://nextjs.org/docs)


## Links discovered
- [http://localhost:3000](http://localhost:3000)
- [assistant-ui Documentation](https://www.assistant-ui.com/docs)
- [Assistant Transport Runtime API](https://www.assistant-ui.com/docs/runtimes/assistant-transport)
- [Next.js Documentation](https://nextjs.org/docs)

--- examples/with-langgraph/README.md ---
# LangGraph Example

[Hosted Demo](https://assistant-ui-langgraph.vercel.app/)

This example demonstrates how to use LangChain LangGraph with assistant-ui.

It is meant to be used with the backend found at LangGraph's Stockbroker example: https://github.com/bracesproul/langgraphjs-examples/tree/main/stockbroker

You need to set the following environment variables:

```env
NEXT_PUBLIC_API_URL=https://stockbrokeragent-bracesprouls-projects.vercel.app/api
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=stockbroker
```

To run the example, run the following commands:

```sh
npm install
npm run dev
```


## Links discovered
- [Hosted Demo](https://assistant-ui-langgraph.vercel.app/)

--- examples/with-parent-id-grouping/README.md ---
# Parent ID Grouping Example

This example demonstrates how to use the parent ID feature in assistant-ui to group related message parts together.

## Features

- **Parent ID Support**: Message parts can have a `parentId` field that groups them together
- **Visual Grouping**: Related parts are displayed in collapsible groups
- **Custom Group Component**: The example shows how to create a custom Group component that:
  - Shows grouped parts in a bordered container
  - Provides expand/collapse functionality
  - Displays meaningful labels for each group
  - Leaves ungrouped parts (without parentId) as-is

## How it works

1. **Message Structure**: The example uses the external store runtime with predefined messages that include parts with `parentId` fields:

   ```typescript
   {
     type: "text",
     text: "Some related text",
     parentId: "research-climate-causes"
   }
   ```

2. **Grouping Component**: Uses `MessagePrimitive.Unstable_PartsGroupedByParentId` which automatically:
   - Groups parts by their `parentId`
   - Maintains order based on first occurrence
   - Places ungrouped parts after grouped ones

3. **Custom Rendering**: The `ParentIdGroup` component provides:
   - Collapsible sections for each group
   - Custom styling with borders and backgrounds
   - Meaningful labels based on the parent ID

## Running the Example

```bash
# Install dependencies
npm install

# Run the development server
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) to see the example.

## Key Components

- `MyRuntimeProvider.tsx`: Sets up the external store with dummy messages containing parent IDs
- `thread.tsx`: Contains the custom `ParentIdGroup` component and uses `Unstable_PartsGroupedByParentId`

## Use Cases

This pattern is useful for:

- Grouping research sources with their related findings
- Organizing multi-step tool executions
- Creating hierarchical content structures
- Showing related content in collapsible sections


## Links discovered
- [http://localhost:3000](http://localhost:3000)

--- packages/cli/src/codemods/v0-11/README.md ---
# v0.11 Migration: ContentPart to MessagePart

This migration automatically updates your codebase to use the new MessagePart naming convention instead of the deprecated ContentPart naming.

## What this migration does

### 1. Renames all ContentPart types to MessagePart types

**Before:**

```typescript
import {
  TextContentPart,
  ToolCallContentPart,
  ContentPartStatus,
  ThreadUserContentPart,
  ThreadAssistantContentPart
} from "@assistant-ui/react";

function processContent(part: TextContentPart): void {
  console.log(part.text);
}

const MyTool: ToolCallContentPartComponent = ({ toolName }) => {
  return <div>{toolName}</div>;
};
```

**After:**

```typescript
import {
  TextMessagePart,
  ToolCallMessagePart,
  MessagePartStatus,
  ThreadUserMessagePart,
  ThreadAssistantMessagePart
} from "@assistant-ui/react";

function processContent(part: TextMessagePart): void {
  console.log(part.text);
}

const MyTool: ToolCallMessagePartComponent = ({ toolName }) => {
  return <div>{toolName}</div>;
};
```

### 2. Updates MessagePrimitive.Content to MessagePrimitive.Parts

**Before:**

```jsx
import { MessagePrimitive } from "@assistant-ui/react";

function MyComponent() {
  return <MessagePrimitive.Content components={{ Text: MyText }} />;
}
```

**After:**

```jsx
import { MessagePrimitive } from "@assistant-ui/react";

function MyComponent() {
  return <MessagePrimitive.Parts components={{ Text: MyText }} />;
}
```

### 3. Renames ContentPart hooks to MessagePart hooks

**Before:**

```typescript
import {
  useContentPart,
  useContentPartText,
  useContentPartRuntime,
} from "@assistant-ui/react";

function MyComponent() {
  const part = useContentPart();
  const text = useContentPartText();
  const runtime = useContentPartRuntime();
  return null;
}
```

**After:**

```typescript
import {
  useMessagePart,
  useMessagePartText,
  useMessagePartRuntime,
} from "@assistant-ui/react";

function MyComponent() {
  const part = useMessagePart();
  const text = useMessagePartText();
  const runtime = useMessagePartRuntime();
  return null;
}
```

### 4. Updates ContentPartPrimitive to MessagePartPrimitive

**Before:**

```jsx
import { ContentPartPrimitive } from "@assistant-ui/react";

function MyComponent() {
  return (
    <div>
      <ContentPartPrimitive.Text />
      <ContentPartPrimitive.Image />
    </div>
  );
}
```

**After:**

```jsx
import { MessagePartPrimitive } from "@assistant-ui/react";

function MyComponent() {
  return (
    <div>
      <MessagePartPrimitive.Text />
      <MessagePartPrimitive.Image />
    </div>
  );
}
```

### 5. Renames provider components

**Before:**

```jsx
import { TextContentPartProvider } from "@assistant-ui/react";

function MyComponent() {
  return (
    <TextContentPartProvider text="Hello" isRunning={false}>
      <div>Content</div>
    </TextContentPartProvider>
  );
}
```

**After:**

```jsx
import { TextMessagePartProvider } from "@assistant-ui/react";

function MyComponent() {
  return (
    <TextMessagePartProvider text="Hello" isRunning={false}>
      <div>Content</div>
    </TextMessagePartProvider>
  );
}
```

## Complete type mapping

| Old Name                     | New Name                     |
| ---------------------------- | ---------------------------- |
| `TextContentPart`            | `TextMessagePart`            |
| `ReasoningContentPart`       | `ReasoningMessagePart`       |
| `SourceContentPart`          | `SourceMessagePart`          |
| `ImageContentPart`           | `ImageMessagePart`           |
| `FileContentPart`            | `FileMessagePart`            |
| `Unstable_AudioContentPart`  | `Unstable_AudioMessagePart`  |
| `ToolCallContentPart`        | `ToolCallMessagePart`        |
| `ContentPartStatus`          | `MessagePartStatus`          |
| `ToolCallContentPartStatus`  | `ToolCallMessagePartStatus`  |
| `ThreadUserContentPart`      | `ThreadUserMessagePart`      |
| `ThreadAssistantContentPart` | `ThreadAssistantMessagePart` |
| `ContentPartRuntime`         | `MessagePartRuntime`         |
| `ContentPartState`           | `MessagePartState`           |
| `useContentPart`             | `useMessagePart`             |
| `useContentPartRuntime`      | `useMessagePartRuntime`      |
| `useContentPartText`         | `useMessagePartText`         |
| `useContentPartReasoning`    | `useMessagePartReasoning`    |
| `useContentPartSource`       | `useMessagePartSource`       |
| `useContentPartFile`         | `useMessagePartFile`         |
| `useContentPartImage`        | `useMessagePartImage`        |
| `ContentPartPrimitive`       | `MessagePartPrimitive`       |
| `TextContentPartProvider`    | `TextMessagePartProvider`    |
| `MessagePrimitive.Content`   | `MessagePrimitive.Parts`     |

## How to run this migration

### As part of the full upgrade

```bash
npx @assistant-ui/cli upgrade
```

### Run this specific migration only

```bash
npx @assistant-ui/cli codemod v0-11/content-part-to-message-part <path>
```

Where `<path>` is the path to your source code directory (e.g., `src/` or `.`).

### Options

- `--dry` - Preview changes without applying them
- `--print` - Print the transformed code to stdout
- `--verbose` - Show detailed transformation logs

### Example usage

```bash
# Preview changes without applying them
npx @assistant-ui/cli codemod v0-11/content-part-to-message-part src/ --dry

# Apply the transformation to your src directory
npx @assistant-ui/cli codemod v0-11/content-part-to-message-part src/

# Apply to entire project
npx @assistant-ui/cli codemod v0-11/content-part-to-message-part .
```

## What files are affected?

This migration will process all `.js`, `.jsx`, `.ts`, and `.tsx` files that contain imports from `@assistant-ui/*` packages. It safely ignores:

- Files in `node_modules/`
- Built files in `dist/`, `build/` directories
- Minified files (`*.min.js`, `*.bundle.js`)
- Files that don't import from assistant-ui packages

## Notes

- This migration preserves all functionality - it's purely a naming change
- The old ContentPart APIs are now deprecated and will be removed in a future version
- The migration is safe to run multiple times
- If you have custom code that extends these types, you may need to update your type definitions manually after running the migration


--- packages/safe-content-frame/demo/index.html ---
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Safe Content Frame Demo</title>
  <style>
    * {
      box-sizing: border-box;
    }
    body {
      font-family: system-ui, -apple-system, sans-serif;
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
      background: #f5f5f5;
    }
    h1 {
      color: #333;
    }
    .demo-container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    .input-section {
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .output-section {
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    textarea {
      width: 100%;
      height: 300px;
      font-family: monospace;
      font-size: 14px;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      resize: vertical;
    }
    button {
      margin-top: 10px;
      padding: 10px 20px;
      background: #0070f3;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }
    button:hover {
      background: #0060df;
    }
    #frame-container {
      width: 100%;
      height: 400px;
      border: 1px solid #ddd;
      border-radius: 4px;
      overflow: hidden;
    }
    .status {
      margin-top: 10px;
      padding: 10px;
      background: #f0f0f0;
      border-radius: 4px;
      font-size: 12px;
      font-family: monospace;
    }
    .error {
      background: #fee;
      color: #c00;
    }
    .success {
      background: #efe;
      color: #060;
    }
  </style>
</head>
<body>
  <h1>Safe Content Frame Demo</h1>
  <p>Test rendering untrusted HTML content in a sandboxed iframe.</p>

  <div class="demo-container">
    <div class="input-section">
      <h3>HTML Content</h3>
      <textarea id="html-input"><h1>Hello from Safe Content Frame!</h1>
<p>This content is rendered in a <strong>sandboxed iframe</strong>.</p>
<style>
  body {
    font-family: system-ui;
    padding: 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    min-height: 100vh;
    margin: 0;
  }
  h1 { margin-top: 0; }
</style>
<script>
  console.log('Script executed in sandbox!');
  document.body.innerHTML += '<p>✅ JavaScript is working!</p>';
</script></textarea>
      <button id="render-btn">Render Content</button>
      <div id="status" class="status">Ready to render</div>
    </div>

    <div class="output-section">
      <h3>Rendered Output</h3>
      <div id="frame-container"></div>
    </div>
  </div>

  <script type="module">
    import { SafeContentFrame } from '../src/index.ts';

    const scf = new SafeContentFrame('demo', {
      sandbox: ['allow-scripts'],
    });

    const htmlInput = document.getElementById('html-input');
    const renderBtn = document.getElementById('render-btn');
    const frameContainer = document.getElementById('frame-container');
    const status = document.getElementById('status');

    let currentFrame = null;

    renderBtn.addEventListener('click', async () => {
      try {
        status.textContent = 'Rendering...';
        status.className = 'status';

        // Dispose previous frame
        if (currentFrame) {
          currentFrame.dispose();
          currentFrame = null;
        }

        // Clear container
        frameContainer.innerHTML = '';

        const html = htmlInput.value;
        currentFrame = await scf.renderHtml(html, frameContainer);

        status.textContent = `✅ Rendered successfully!\nOrigin: ${currentFrame.origin}`;
        status.className = 'status success';

        // Try to wait for full load
        try {
          await currentFrame.fullyLoadedPromiseWithTimeout(5000);
          status.textContent += '\n✅ Content fully loaded';
        } catch (_e) {
          status.textContent += '\n⏱️ Load timeout (content may still be loading)';
        }
      } catch (error) {
        status.textContent = `❌ Error: ${error.message}`;
        status.className = 'status error';
        console.error(error);
      }
    });
  </script>
</body>
</html>


--- examples/store-example/next.config.ts ---
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;


--- examples/with-ag-ui/next.config.ts ---
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;


--- examples/with-ai-sdk-v5/next.config.js ---
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ["@assistant-ui/react", "@assistant-ui/react-ai-sdk"],
};

export default nextConfig;


--- packages/react-ai-sdk/README.md ---
# `@assistant-ui/react-ai-sdk`

Vercel AI SDK integration for `@assistant-ui/react`.

## Features

- Seamless integration with Vercel AI SDK v5
- Automatic system message and frontend tools forwarding via `AssistantChatTransport`
- Support for custom transport configuration

## Usage

### Basic Setup

```typescript
import { useChatRuntime } from '@assistant-ui/react-ai-sdk';
import { AssistantRuntimeProvider } from '@assistant-ui/react';

function App() {
  // By default, uses AssistantChatTransport which forwards system messages and tools
  const runtime = useChatRuntime();

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* Your assistant-ui components */}
    </AssistantRuntimeProvider>
  );
}
```

### Custom Transport

When you need to customize the transport configuration:

```typescript
import { DefaultChatTransport } from "ai";
import { AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";

// Custom API URL while keeping system/tools forwarding
const runtime = useChatRuntime({
  transport: new AssistantChatTransport({
    api: "/my-custom-api/chat",
  }),
});

// Or disable system/tools forwarding entirely
const runtime = useChatRuntime({
  transport: new DefaultChatTransport(),
});
```

**Important:** When customizing the API URL, you must explicitly use `AssistantChatTransport` to keep frontend system messages and tools forwarding.

## AssistantChatTransport vs DefaultChatTransport

- **AssistantChatTransport** (default): Automatically forwards system messages and frontend tools from the assistant-ui context to your backend API
- **DefaultChatTransport**: Standard AI SDK transport without automatic forwarding


--- python/assistant-ui-sync-server-api/README.md ---
# assistant-ui Sync Server API

A Python client library for interacting with assistant-ui sync server backends, providing the same API structure as the JavaScript/TypeScript `useChatRuntime`.

## Installation

```bash
pip install assistant-ui-sync-server-api
```

Or using uv:

```bash
uv add assistant-ui-sync-server-api
```

## Usage

### Basic Example

```python
import asyncio
from assistant_ui import AssistantClient

# Create a client
client = AssistantClient(base_url="https://api.example.com")

# Create messages
messages = [
    {
        "role": "user",
        "content": [{"type": "text", "text": "Hello, how are you?"}]
    }
]

# Send a chat request to a specific thread
async def main():
    thread = client.threads("thread-123")
    response = await thread.chat(
        messages=messages,
        system="You are a helpful assistant."
    )
    print(response.json())
    await client.close()

asyncio.run(main())
```

### Using Context Managers

```python
# Async context manager
async with AssistantClient(base_url="https://api.example.com") as client:
    thread = client.threads("thread-123")
    response = await thread.chat(messages=messages)

# Sync context manager
with AssistantClient(base_url="https://api.example.com") as client:
    thread = client.threads("thread-123")
    response = thread.chat_sync(messages=messages)
```

### Authentication

```python
# Static headers
client = AssistantClient(
    base_url="https://api.example.com",
    headers={"Authorization": "Bearer your-token"}
)

# Dynamic headers (async)
async def get_auth_headers():
    token = await fetch_token()
    return {"Authorization": f"Bearer {token}"}

client = AssistantClient(
    base_url="https://api.example.com",
    headers=get_auth_headers
)
```

### Using Tools

```python
tools = {
    "get_weather": {
        "description": "Get the current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location"]
        }
    }
}

thread = client.threads("thread-123")
response = await thread.chat(
    messages=messages,
    tools=tools
)
```

### Canceling Operations

```python
thread = client.threads("thread-123")

# Start a long-running chat
chat_task = asyncio.create_task(thread.chat(messages=messages))

# Cancel it
await thread.cancel()
chat_task.cancel()
```

### Message Types

The package supports various message types matching the assistant-ui format:

```python
from assistant_ui.types import Message

# Text message
text_message: Message = {
    "role": "user",
    "content": [{"type": "text", "text": "Hello!"}]
}

# Image message
image_message: Message = {
    "role": "user",
    "content": [
        {"type": "text", "text": "What's in this image?"},
        {"type": "image", "image": "https://example.com/image.jpg"}
    ]
}

# File message
file_message: Message = {
    "role": "user",
    "content": [
        {"type": "file", "data": "https://example.com/file.pdf", "mimeType": "application/pdf"}
    ]
}

# Assistant message with tool calls
assistant_message: Message = {
    "role": "assistant",
    "content": [
        {"type": "text", "text": "I'll help you with that."},
        {
            "type": "tool-call",
            "toolCallId": "call-123",
            "toolName": "get_weather",
            "args": {"location": "San Francisco"}
        }
    ]
}

# Tool result message
tool_message: Message = {
    "role": "tool",
    "content": [
        {
            "type": "tool-result",
            "toolCallId": "call-123",
            "toolName": "get_weather",
            "result": {"temperature": 72, "condition": "sunny"},
            "isError": False
        }
    ]
}
```

## API Reference

### `AssistantClient`

Main client for interacting with assistant-ui backends.

**Constructor:**

```python
AssistantClient(
    base_url: str,
    headers: Optional[Dict[str, str] | Callable] = None,
    timeout: Optional[float] = None,
    **kwargs
)
```

**Methods:**

- `threads(thread_id: str) -> ThreadClient`: Get a ThreadClient for a specific thread
- `close()`: Close the async client
- `close_sync()`: Close the sync client

### `ThreadClient`

Client for interacting with a specific thread.

**Methods:**

- `chat(messages, system=None, tools=None, **kwargs)`: Send an async chat request
- `chat_sync(messages, system=None, tools=None, **kwargs)`: Send a sync chat request
- `cancel()`: Cancel the current async operation
- `cancel_sync()`: Cancel the current sync operation

## Type Definitions

The package includes TypedDict definitions for all message types and configuration options, providing full type hints for better IDE support and type checking.

## Development

This package is part of the assistant-ui monorepo. To contribute:

1. Clone the main repository
2. Navigate to `python/assistant-ui`
3. Install dependencies with `uv sync`
4. Run tests with `uv run pytest`


--- packages/react-ai-sdk/CHANGELOG.md ---
# @assistant-ui/react-ai-sdk

## 1.1.19

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [bae3aa2]
- Updated dependencies [e8ea57b]
  - @assistant-ui/react@0.11.50
  - assistant-stream@0.2.45
  - assistant-cloud@0.1.11

## 1.1.18

### Patch Changes

- 89aec17: feat: AI SDK frontend tool execution cancellation support
  fix: AI SDK isRunning status when running frontend tools
- Updated dependencies [89aec17]
- Updated dependencies [ee7040f]
- Updated dependencies [bd27465]
- Updated dependencies [a3e9549]
- Updated dependencies [206616b]
- Updated dependencies [7aa77b5]
  - assistant-stream@0.2.44
  - @assistant-ui/react@0.11.49

## 1.1.17

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [ba26b22]
- Updated dependencies [d169e4f]
- Updated dependencies [da9f8a6]
- Updated dependencies [01c31fe]
  - @assistant-ui/react@0.11.48
  - assistant-stream@0.2.43
  - assistant-cloud@0.1.10

## 1.1.16

### Patch Changes

- c4142ac: fix(react-ai-sdk): pass runConfig metadata to backend API request

## 1.1.15

### Patch Changes

- ab8953b: feat(react): add `allowNesting` option to allow wrapping runtimes with custom thread list adapters
- Updated dependencies [ab8953b]
  - @assistant-ui/react@0.11.46

## 1.1.14

### Patch Changes

- ec662cd: chore: update dependencies
- cdb5ea5: mark tool call as complete once user sends new message when tool calling
- 5dd925e: feat(ai-sdk): allow updates to headers/body
- Updated dependencies [ec662cd]
  - assistant-stream@0.2.42
  - assistant-cloud@0.1.9
  - @assistant-ui/react@0.11.45

## 1.1.13

### Patch Changes

- 4f6afef: feat: unified json schema
- Updated dependencies [4f6afef]
  - @assistant-ui/react@0.11.44

## 1.1.12

### Patch Changes

- faed815: feat: AI SDK error toolOutput support
- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - assistant-stream@0.2.41
  - assistant-cloud@0.1.8
  - @assistant-ui/react@0.11.40

## 1.1.11

### Patch Changes

- 0bcbb58: feat: custom `toCreateMessage` callback
  fix: use AI SDK's idGenerator function for new messages
- b408005: feat(react-ai-sdk): Integrate AI SDK v5 data parts in message content
- Updated dependencies [b408005]
- Updated dependencies [7a6d9ca]
- Updated dependencies [70d5966]
- Updated dependencies [3754bdd]
- Updated dependencies [0a4bdc1]
  - @assistant-ui/react@0.11.39

## 1.1.10

### Patch Changes

- 34d1c78: fix(react-ai-sdk): correctly initialize history loading state
- Updated dependencies [66a13a0]
- Updated dependencies [4e3877e]
- Updated dependencies [eef682b]
  - @assistant-ui/react@0.11.38
  - assistant-cloud@0.1.7

## 1.1.9

### Patch Changes

- 81b581f: feat: display AI SDK errors
- 6d2c134: feat: useChatRuntime should use the assistant ui thread id remote id as the threadid by default
- 2fc7e99: chore: update deps
- Updated dependencies [3ab9484]
- Updated dependencies [7a88ead]
- Updated dependencies [81b581f]
- Updated dependencies [2fc7e99]
  - @assistant-ui/react@0.11.36
  - assistant-stream@0.2.39
  - assistant-cloud@0.1.6

## 1.1.8

### Patch Changes

- 953db24: chore: update deps
- Updated dependencies [953db24]
- Updated dependencies
  - assistant-stream@0.2.37
  - assistant-cloud@0.1.5
  - @assistant-ui/react@0.11.34

## 1.1.7

### Patch Changes

- chore: update deps
- Updated dependencies
  - assistant-stream@0.2.36
  - assistant-cloud@0.1.4
  - @assistant-ui/react@0.11.31

## 1.1.6

### Patch Changes

- a5f9dd5: Export missing types for custom runtime integration
- Updated dependencies [92dfb0f]
  - @assistant-ui/react@0.11.29

## 1.1.5

### Patch Changes

- e6a46e4: chore: update deps
- Updated dependencies [e6a46e4]
  - assistant-stream@0.2.34
  - assistant-cloud@0.1.3
  - @assistant-ui/react@0.11.27

## 1.1.4

### Patch Changes

- e81784b: feat: Tool Call interrupt() resume() API
- Updated dependencies [e8d6d7b]
- Updated dependencies [e81784b]
  - @assistant-ui/react@0.11.22
  - assistant-stream@0.2.32

## 1.1.3

### Patch Changes

- e46e4d3: fix: guard threadItem access for useAISDKRuntime
- Updated dependencies [c0f5003]
  - assistant-stream@0.2.31

## 1.1.2

### Patch Changes

- 8812f86: chore: update deps
- Updated dependencies [8812f86]
  - assistant-stream@0.2.30
  - assistant-cloud@0.1.2

## 1.1.1

### Patch Changes

- 68ef242: feat(ui): load external history only when thread has remote id
- Updated dependencies [2c6198a]
  - @assistant-ui/react@0.11.19

## 1.1.0

### Patch Changes

- 39ac2f3: feat: AI SDK v5 import support
- Updated dependencies [39ac2f3]
- Updated dependencies [5437dbe]
  - @assistant-ui/react@0.11.0

## 1.0.7

### Patch Changes

- d7d9058: fix: cloud chat history not loading in some configurations

## 1.0.6

### Patch Changes

- 860bf42: feat: useAISDKRuntime cloud history support (without useChatRuntime)
- Updated dependencies [3498c99]
  - @assistant-ui/react@0.10.50

## 1.0.5

### Patch Changes

- 90fc83b: fixes attachment naming
- e64b20c: fix: persistence only saving the first two messages

## 1.0.4

### Patch Changes

- 9235fe1: update dep array in external history adapter

## 1.0.3

### Patch Changes

- ceedf45: feat: pass run-config to ai-sdk metadata to let user decide what to do after
- 5504836: pass callsettings in extra body object to AI chat transport
- Updated dependencies [a80dcff]
  - @assistant-ui/react@0.10.43

## 1.0.2

### Patch Changes

- 672db5a: feat: frontend function calling support
- 12e0a77: chore: update deps
- Updated dependencies [12e0a77]
  - assistant-stream@0.2.23
  - assistant-cloud@0.1.1
  - @assistant-ui/react@0.10.42

## 1.0.1

### Patch Changes

- eda5558: feat: AI SDK custom UIMessage type support
- Updated dependencies [eda5558]
  - @assistant-ui/react@0.10.41

## 1.0.0

### Patch Changes

- de215fd: fix: history loading
- Updated dependencies [179f8b7]
  - assistant-cloud@0.1.0
  - @assistant-ui/react@0.10.40

## 0.11.5

### Patch Changes

- a4389da: feat: AI SDK v5 assistant-cloud thread history support
- Updated dependencies [a4389da]
  - @assistant-ui/react@0.10.39

## 0.11.4

### Patch Changes

- 979ee67: feat: forward system and tools to the backend for useChatRuntime
- 979ee67: feat: assistant cloud support for AI SDK v5
- 979ee67: feat: add AssistantChatTransport
- Updated dependencies [979ee67]
  - @assistant-ui/react@0.10.38

## 0.11.3

### Patch Changes

- 2ef6cae: feat: Add useChatRuntime as new recommended entry point for AI-SDK V5
- Updated dependencies [f32b6a4]
  - @assistant-ui/react@0.10.37

## 0.11.1

### Patch Changes

- 20ffa06: fix: Don't omit attachments from `AISDKRuntimeAdapter` type
- Updated dependencies [ed78407]
- Updated dependencies [77ce337]
- Updated dependencies [f59959e]
  - @assistant-ui/react@0.10.36

## 0.11.0

### Patch Changes

- 0f063e0: chore: update dependencies
- Updated dependencies [0f063e0]
- Updated dependencies [5d8b074]
  - assistant-stream@0.2.22
  - @assistant-ui/react@0.10.34


--- packages/react-ai-sdk/src/frontendTools.ts ---
import { jsonSchema } from "ai";
import type { JSONSchema7 } from "json-schema";

export const frontendTools = (
  tools: Record<string, { description?: string; parameters: JSONSchema7 }>,
) =>
  Object.fromEntries(
    Object.entries(tools).map(([name, tool]) => [
      name,
      {
        ...(tool.description ? { description: tool.description } : undefined),
        inputSchema: jsonSchema(tool.parameters),
      },
    ]),
  );


--- packages/react-ai-sdk/src/index.ts ---
export * from "./ui";
export { frontendTools } from "./frontendTools";


--- python/assistant-ui-sync-server-api/tests/__init__.py ---
# Tests for assistant-ui package

--- python/assistant-ui-sync-server-api/tests/test_client.py ---
"""Tests for the assistant-ui-sync-server-api client."""

import pytest
import httpx
import warnings
from unittest.mock import AsyncMock, Mock, patch
from assistant_ui import AssistantClient
from assistant_ui.types import Message, AssistantTransportCommand


@pytest.mark.asyncio
async def test_basic_chat():
    """Test basic async chat functionality."""
    client = AssistantClient(
        base_url="https://api.example.com",
        headers={"Authorization": "Bearer test"}
    )
    
    messages: list[Message] = [
        {
            "role": "user",
            "content": [{"type": "text", "text": "Hello"}],
        }
    ]
    
    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200
    mock_response.json.return_value = {"response": "Hi there!"}
    mock_response.text = "Success"
    
    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client
        
        thread = client.threads("thread-123")

        # Suppress deprecation warning for this test
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", DeprecationWarning)
            response = await thread.chat(
                messages=messages,
                system="You are helpful"
            )

        assert response.status_code == 200

        # Check the request was made correctly
        call_args = mock_client.request.call_args
        payload = call_args[1]["json"]

        assert payload["threadId"] == "thread-123"
        assert payload["messages"] == messages
        assert payload["system"] == "You are helpful"
        # Commands should also be present due to conversion
        assert "commands" in payload
    
    await client.close()


@pytest.mark.asyncio
async def test_chat_with_tools():
    """Test chat with tools."""
    async with AssistantClient(base_url="https://api.example.com") as client:
        messages: list[Message] = []
        tools = {"search": {"description": "Search the web"}}
        
        mock_response = AsyncMock()
        mock_response.is_success = True
        mock_response.status_code = 200
        
        with patch.object(client, "_ensure_async_client") as mock_ensure:
            mock_client = AsyncMock()
            mock_client.request = AsyncMock(return_value=mock_response)
            mock_ensure.return_value = mock_client
            
            thread = client.threads("thread-456")
            await thread.chat(messages=messages, tools=tools)
            
            # Check tools were included
            call_args = mock_client.request.call_args
            assert call_args[1]["json"]["tools"] == tools


@pytest.mark.asyncio
async def test_cancel():
    """Test cancel functionality."""
    client = AssistantClient(base_url="https://api.example.com")
    
    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200
    
    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client
        
        thread = client.threads("thread-789")
        await thread.cancel()
        
        # Check cancel request was made
        mock_client.request.assert_called_once_with(
            "POST",
            "/api/cancel",
            headers={"Content-Type": "application/json"},
            json={"threadId": "thread-789"}
        )
    
    await client.close()


def test_sync_chat():
    """Test synchronous chat."""
    with AssistantClient(base_url="https://api.example.com") as client:
        messages: list[Message] = [
            {
                "role": "assistant",
                "content": [
                    {"type": "text", "text": "I can help."},
                    {
                        "type": "tool-call",
                        "toolCallId": "call-123",
                        "toolName": "search",
                        "args": {"query": "weather"}
                    }
                ],
            }
        ]
        
        mock_response = Mock()
        mock_response.is_success = True
        mock_response.status_code = 200
        mock_response.text = "Success"
        
        with patch.object(client, "_ensure_sync_client") as mock_ensure:
            mock_client = Mock()
            mock_client.request = Mock(return_value=mock_response)
            mock_ensure.return_value = mock_client
            
            thread = client.threads("thread-sync")
            response = thread.chat_sync(messages=messages)
            
            assert response.status_code == 200


@pytest.mark.asyncio
async def test_async_headers():
    """Test with async header function."""
    async def get_headers():
        return {"Authorization": "Bearer dynamic-token"}
    
    client = AssistantClient(
        base_url="https://api.example.com",
        headers=get_headers
    )
    
    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200
    
    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client
        
        thread = client.threads("thread-async-headers")
        await thread.chat(messages=[])
        
        # Check dynamic headers were used
        call_args = mock_client.request.call_args
        assert call_args[1]["headers"]["Authorization"] == "Bearer dynamic-token"
    
    await client.close()


def test_sync_with_async_headers_raises():
    """Test that sync methods raise error with async headers."""
    async def get_headers():
        return {"Authorization": "Bearer token"}
    
    client = AssistantClient(
        base_url="https://api.example.com",
        headers=get_headers
    )
    
    thread = client.threads("thread-123")
    
    with pytest.raises(ValueError) as exc_info:
        thread.chat_sync(messages=[])
    
    assert "async header functions" in str(exc_info.value)


@pytest.mark.asyncio
async def test_error_handling():
    """Test error handling."""
    client = AssistantClient(base_url="https://api.example.com")
    
    mock_response = AsyncMock()
    mock_response.is_success = False
    mock_response.status_code = 500
    mock_response.text = "Internal Server Error"
    
    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client
        
        thread = client.threads("thread-error")
        
        with pytest.raises(Exception) as exc_info:
            await thread.chat(messages=[])
        
        assert "Request failed with status 500" in str(exc_info.value)
    
    await client.close()


@pytest.mark.asyncio
async def test_context_manager():
    """Test async context manager."""
    async with AssistantClient(base_url="https://api.example.com") as client:
        assert client._async_client is None  # Not created until first use
        
        # Force client creation
        mock_response = AsyncMock()
        mock_response.is_success = True
        
        with patch("httpx.AsyncClient") as mock_async_client_class:
            mock_instance = AsyncMock()
            mock_instance.request = AsyncMock(return_value=mock_response)
            mock_async_client_class.return_value = mock_instance
            
            thread = client.threads("test")
            await thread.chat(messages=[])
            
            assert client._async_client is not None
    
    # After context exit, client should be closed
    assert client._async_client is None


def test_sync_context_manager():
    """Test sync context manager."""
    with AssistantClient(base_url="https://api.example.com") as client:
        assert client._sync_client is None  # Not created until first use
        
        # Force client creation
        mock_response = Mock()
        mock_response.is_success = True
        
        with patch("httpx.Client") as mock_client_class:
            mock_instance = Mock()
            mock_instance.request = Mock(return_value=mock_response)
            mock_client_class.return_value = mock_instance
            
            thread = client.threads("test")
            thread.chat_sync(messages=[])
            
            assert client._sync_client is not None
    
    # After context exit, client should be closed
    assert client._sync_client is None


@pytest.mark.asyncio
async def test_all_parameters():
    """Test chat with all possible parameters."""
    client = AssistantClient(
        base_url="https://api.example.com",
        headers={"X-API-Key": "test-key"},
        timeout=30.0
    )
    
    messages: list[Message] = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Analyze this:"},
                {"type": "image", "image": "https://example.com/img.jpg"},
                {"type": "file", "data": "https://example.com/doc.pdf", "mimeType": "application/pdf"}
            ],
            "unstable_id": "msg-123",
        }
    ]
    
    tools = {
        "analyze": {
            "description": "Analyze content",
            "parameters": {
                "type": "object",
                "properties": {"content": {"type": "string"}},
                "required": ["content"]
            }
        }
    }
    
    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200
    
    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client
        
        thread = client.threads("thread-all-params")
        await thread.chat(
            messages=messages,
            tools=tools,
            system="You are an analyzer",
            unstable_assistantMessageId="asst-456",
            runConfig={"model": "gpt-4"},
            state={"session": "abc"},
            custom_field="custom_value"
        )
        
        # Check all parameters were included
        call_args = mock_client.request.call_args
        payload = call_args[1]["json"]
        
        assert payload["threadId"] == "thread-all-params"
        assert payload["system"] == "You are an analyzer"
        assert payload["messages"] == messages
        assert payload["tools"] == tools
        assert payload["unstable_assistantMessageId"] == "asst-456"
        assert payload["runConfig"] == {"model": "gpt-4"}
        assert payload["state"] == {"session": "abc"}
        assert payload["custom_field"] == "custom_value"

    await client.close()


@pytest.mark.asyncio
async def test_chat_with_commands():
    """Test chat with commands instead of messages."""
    client = AssistantClient(
        base_url="https://api.example.com",
        headers={"Authorization": "Bearer test"}
    )

    commands: list[AssistantTransportCommand] = [
        {
            "type": "add-message",
            "message": {
                "role": "user",
                "parts": [{"type": "text", "text": "Hello with commands"}]
            }
        },
        {
            "type": "add-tool-result",
            "toolCallId": "call-789",
            "toolName": "search",
            "result": {"data": "search results"},
            "isError": False,
            "artifact": None
        }
    ]

    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200

    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client

        thread = client.threads("thread-cmd")
        response = await thread.chat(commands=commands)

        assert response.status_code == 200

        # Check the request was made with commands
        call_args = mock_client.request.call_args
        payload = call_args[1]["json"]

        assert payload["threadId"] == "thread-cmd"
        assert payload["commands"] == commands
        assert "messages" not in payload

    await client.close()


@pytest.mark.asyncio
async def test_chat_with_deprecated_messages():
    """Test that messages parameter shows deprecation warning."""
    client = AssistantClient(base_url="https://api.example.com")

    messages: list[Message] = [
        {
            "role": "user",
            "content": [{"type": "text", "text": "Using deprecated API"}],
        }
    ]

    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200

    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client

        thread = client.threads("thread-deprecated")

        # Check deprecation warning
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            await thread.chat(messages=messages)

            assert len(w) == 1
            assert issubclass(w[0].category, DeprecationWarning)
            assert "messages' parameter is deprecated" in str(w[0].message)

        # Check both messages and commands were sent
        call_args = mock_client.request.call_args
        payload = call_args[1]["json"]

        assert payload["messages"] == messages
        assert "commands" in payload
        assert len(payload["commands"]) > 0

    await client.close()


@pytest.mark.asyncio
async def test_chat_messages_and_commands_together():
    """Test using both messages and commands together."""
    client = AssistantClient(base_url="https://api.example.com")

    messages: list[Message] = [
        {
            "role": "assistant",
            "content": [{"type": "text", "text": "Previous message"}],
        }
    ]

    commands: list[AssistantTransportCommand] = [
        {
            "type": "add-message",
            "message": {
                "role": "user",
                "parts": [{"type": "text", "text": "New command"}]
            }
        }
    ]

    mock_response = AsyncMock()
    mock_response.is_success = True
    mock_response.status_code = 200

    with patch.object(client, "_ensure_async_client") as mock_ensure:
        mock_client = AsyncMock()
        mock_client.request = AsyncMock(return_value=mock_response)
        mock_ensure.return_value = mock_client

        thread = client.threads("thread-both")

        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            await thread.chat(messages=messages, commands=commands)

        # Check both were merged
        call_args = mock_client.request.call_args
        payload = call_args[1]["json"]

        assert payload["messages"] == messages
        assert "commands" in payload
        # Should have original command plus converted message
        assert len(payload["commands"]) >= 2

    await client.close()


def test_sync_chat_with_commands():
    """Test synchronous chat with commands."""
    with AssistantClient(base_url="https://api.example.com") as client:
        commands: list[AssistantTransportCommand] = [
            {
                "type": "add-message",
                "message": {
                    "role": "assistant",
                    "parts": [{"type": "text", "text": "Sync command test"}]
                }
            }
        ]

        mock_response = Mock()
        mock_response.is_success = True
        mock_response.status_code = 200

        with patch.object(client, "_ensure_sync_client") as mock_ensure:
            mock_client = Mock()
            mock_client.request = Mock(return_value=mock_response)
            mock_ensure.return_value = mock_client

            thread = client.threads("thread-sync-cmd")
            response = thread.chat_sync(commands=commands)

            assert response.status_code == 200

            # Check commands were sent
            call_args = mock_client.request.call_args
            payload = call_args[1]["json"]

            assert payload["commands"] == commands

--- python/assistant-ui-sync-server-api/src/assistant_ui/client.py ---
from typing import Dict, List, Any, Optional, Union, Callable, Awaitable
import warnings
import httpx
from .types import (
    Message,
    Tool,
    AssistantTransportCommand,
    AddMessageCommand,
    AddToolResultCommand
)


def _convert_messages_to_commands(messages: List[Message]) -> List[AssistantTransportCommand]:
    """
    Convert legacy messages format to commands format.

    Args:
        messages: List of messages in the legacy format

    Returns:
        List of commands in the new format
    """
    commands: List[AssistantTransportCommand] = []

    for message in messages:
        role = message.get("role")

        if role == "system":
            # System messages can't be directly converted to commands
            # They should be passed via the system parameter instead
            continue

        elif role == "user":
            content = message.get("content", [])
            parts = []

            for part in content:
                part_type = part.get("type")
                if part_type == "text":
                    parts.append({"type": "text", "text": part.get("text", "")})
                elif part_type == "image":
                    parts.append({"type": "image", "image": part.get("image", "")})
                # FilePart cannot be converted to command format

            if parts:
                command: AddMessageCommand = {
                    "type": "add-message",
                    "message": {
                        "role": "user",
                        "parts": parts
                    }
                }
                commands.append(command)

        elif role == "assistant":
            content = message.get("content", [])
            text_parts = []

            for part in content:
                part_type = part.get("type")
                if part_type == "text":
                    text_parts.append({"type": "text", "text": part.get("text", "")})
                elif part_type == "tool-call":
                    # Tool calls are handled separately, not in add-message
                    continue

            if text_parts:
                command: AddMessageCommand = {
                    "type": "add-message",
                    "message": {
                        "role": "assistant",
                        "parts": text_parts
                    }
                }
                commands.append(command)

        elif role == "tool":
            # Convert tool results to commands
            content = message.get("content", [])
            for part in content:
                if part.get("type") == "tool-result":
                    tool_command: AddToolResultCommand = {
                        "type": "add-tool-result",
                        "toolCallId": part.get("toolCallId", ""),
                        "toolName": part.get("toolName", ""),
                        "result": part.get("result"),
                        "isError": part.get("isError", False),
                        "artifact": None  # artifact not available in legacy format
                    }
                    commands.append(tool_command)

    return commands


class ThreadClient:
    """Client for interacting with a specific thread."""
    
    def __init__(self, client: "AssistantClient", thread_id: str):
        self._client = client
        self._thread_id = thread_id
    
    async def chat(
        self,
        messages: Optional[List[Message]] = None,
        commands: Optional[List[AssistantTransportCommand]] = None,
        system: Optional[str] = None,
        tools: Optional[Dict[str, Tool]] = None,
        unstable_assistantMessageId: Optional[str] = None,
        runConfig: Optional[Dict[str, Any]] = None,
        state: Optional[Any] = None,
        **kwargs: Any
    ) -> httpx.Response:
        """
        Send a chat request for this thread.

        Args:
            messages: (Deprecated) List of messages in the conversation
            commands: List of commands to execute
            system: System prompt
            tools: Dictionary of available tools
            unstable_assistantMessageId: Optional assistant message ID
            runConfig: Optional run configuration
            state: Optional state data
            **kwargs: Additional parameters to include in the request body

        Returns:
            httpx.Response object containing the backend response
        """
        # Build request payload
        payload = {
            "threadId": self._thread_id,
        }

        # Handle commands and messages
        if messages is not None:
            warnings.warn(
                "The 'messages' parameter is deprecated and will be removed in a future version. "
                "Use 'commands' parameter instead.",
                DeprecationWarning,
                stacklevel=2
            )

            # Convert messages to commands
            converted_commands = _convert_messages_to_commands(messages)

            # If both messages and commands provided, merge them
            if commands is not None:
                commands = commands + converted_commands
            else:
                commands = converted_commands

            # Still send messages for backward compatibility
            payload["messages"] = messages

        if commands is not None:
            payload["commands"] = commands

        if system is not None:
            payload["system"] = system

        if tools is not None:
            payload["tools"] = tools

        if unstable_assistantMessageId is not None:
            payload["unstable_assistantMessageId"] = unstable_assistantMessageId

        if runConfig is not None:
            payload["runConfig"] = runConfig

        if state is not None:
            payload["state"] = state

        # Add any additional kwargs
        payload.update(kwargs)

        # Make the request
        return await self._client._make_request("POST", "/api/chat", json=payload)
    
    def chat_sync(
        self,
        messages: Optional[List[Message]] = None,
        commands: Optional[List[AssistantTransportCommand]] = None,
        system: Optional[str] = None,
        tools: Optional[Dict[str, Tool]] = None,
        unstable_assistantMessageId: Optional[str] = None,
        runConfig: Optional[Dict[str, Any]] = None,
        state: Optional[Any] = None,
        **kwargs: Any
    ) -> httpx.Response:
        """
        Synchronous version of chat method.

        Args:
            messages: (Deprecated) List of messages in the conversation
            commands: List of commands to execute
            system: System prompt
            tools: Dictionary of available tools
            unstable_assistantMessageId: Optional assistant message ID
            runConfig: Optional run configuration
            state: Optional state data
            **kwargs: Additional parameters to include in the request body

        Returns:
            httpx.Response object containing the backend response
        """
        # Build request payload
        payload = {
            "threadId": self._thread_id,
        }

        # Handle commands and messages
        if messages is not None:
            warnings.warn(
                "The 'messages' parameter is deprecated and will be removed in a future version. "
                "Use 'commands' parameter instead.",
                DeprecationWarning,
                stacklevel=2
            )

            # Convert messages to commands
            converted_commands = _convert_messages_to_commands(messages)

            # If both messages and commands provided, merge them
            if commands is not None:
                commands = commands + converted_commands
            else:
                commands = converted_commands

            # Still send messages for backward compatibility
            payload["messages"] = messages

        if commands is not None:
            payload["commands"] = commands

        if system is not None:
            payload["system"] = system

        if tools is not None:
            payload["tools"] = tools

        if unstable_assistantMessageId is not None:
            payload["unstable_assistantMessageId"] = unstable_assistantMessageId

        if runConfig is not None:
            payload["runConfig"] = runConfig

        if state is not None:
            payload["state"] = state

        # Add any additional kwargs
        payload.update(kwargs)

        # Make the request
        return self._client._make_request_sync("POST", "/api/chat", json=payload)
    
    async def cancel(self) -> httpx.Response:
        """
        Cancel the current operation for this thread.
        
        Returns:
            httpx.Response object containing the backend response
        """
        return await self._client._make_request(
            "POST", 
            "/api/cancel",
            json={"threadId": self._thread_id}
        )
    
    def cancel_sync(self) -> httpx.Response:
        """
        Synchronous version of cancel method.
        
        Returns:
            httpx.Response object containing the backend response
        """
        return self._client._make_request_sync(
            "POST", 
            "/api/cancel",
            json={"threadId": self._thread_id}
        )


class AssistantClient:
    """Main client for interacting with assistant-ui backends."""
    
    def __init__(
        self,
        base_url: str,
        headers: Optional[Union[Dict[str, str], Callable[[], Union[Dict[str, str], Awaitable[Dict[str, str]]]]]] = None,
        timeout: Optional[float] = None,
        **kwargs: Any
    ):
        """
        Initialize the AssistantClient.
        
        Args:
            base_url: Base URL for the API (e.g., "https://api.example.com")
            headers: Optional headers to include with requests
            timeout: Optional timeout for requests
            **kwargs: Additional arguments passed to httpx client
        """
        self.base_url = base_url.rstrip("/")
        self._headers = headers
        self._timeout = timeout
        self._client_kwargs = kwargs
        self._async_client: Optional[httpx.AsyncClient] = None
        self._sync_client: Optional[httpx.Client] = None
    
    def threads(self, thread_id: str) -> ThreadClient:
        """
        Get a ThreadClient for a specific thread.
        
        Args:
            thread_id: The ID of the thread
            
        Returns:
            ThreadClient instance for the specified thread
        """
        return ThreadClient(self, thread_id)
    
    async def _get_headers(self) -> Dict[str, str]:
        """Get headers, resolving async functions if needed."""
        if self._headers is None:
            return {"Content-Type": "application/json"}
        
        if callable(self._headers):
            headers = self._headers()
            if hasattr(headers, "__await__"):
                headers = await headers
        else:
            headers = self._headers
        
        if isinstance(headers, dict):
            headers = dict(headers)  # Make a copy
            headers.setdefault("Content-Type", "application/json")
        else:
            headers = {"Content-Type": "application/json"}
        
        return headers
    
    def _get_headers_sync(self) -> Dict[str, str]:
        """Get headers synchronously."""
        if self._headers is None:
            return {"Content-Type": "application/json"}
        
        if callable(self._headers):
            headers = self._headers()
            if hasattr(headers, "__await__"):
                raise ValueError("Synchronous methods do not support async header functions")
        else:
            headers = self._headers
        
        if isinstance(headers, dict):
            headers = dict(headers)  # Make a copy
            headers.setdefault("Content-Type", "application/json")
        else:
            headers = {"Content-Type": "application/json"}
        
        return headers
    
    async def _ensure_async_client(self) -> httpx.AsyncClient:
        """Ensure async client is initialized."""
        if self._async_client is None:
            self._async_client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=self._timeout,
                **self._client_kwargs
            )
        return self._async_client
    
    def _ensure_sync_client(self) -> httpx.Client:
        """Ensure sync client is initialized."""
        if self._sync_client is None:
            self._sync_client = httpx.Client(
                base_url=self.base_url,
                timeout=self._timeout,
                **self._client_kwargs
            )
        return self._sync_client
    
    async def _make_request(
        self,
        method: str,
        path: str,
        **kwargs: Any
    ) -> httpx.Response:
        """Make an async HTTP request."""
        client = await self._ensure_async_client()
        headers = await self._get_headers()
        
        # Merge headers
        if "headers" in kwargs:
            headers.update(kwargs["headers"])
        kwargs["headers"] = headers
        
        response = await client.request(method, path, **kwargs)
        
        if not response.is_success:
            raise Exception(f"Request failed with status {response.status_code}: {response.text}")
        
        return response
    
    def _make_request_sync(
        self,
        method: str,
        path: str,
        **kwargs: Any
    ) -> httpx.Response:
        """Make a synchronous HTTP request."""
        client = self._ensure_sync_client()
        headers = self._get_headers_sync()
        
        # Merge headers
        if "headers" in kwargs:
            headers.update(kwargs["headers"])
        kwargs["headers"] = headers
        
        response = client.request(method, path, **kwargs)
        
        if not response.is_success:
            raise Exception(f"Request failed with status {response.status_code}: {response.text}")
        
        return response
    
    async def close(self):
        """Close the async client if it's open."""
        if self._async_client:
            await self._async_client.aclose()
            self._async_client = None
    
    def close_sync(self):
        """Close the sync client if it's open."""
        if self._sync_client:
            self._sync_client.close()
            self._sync_client = None
    
    async def __aenter__(self):
        """Async context manager entry."""
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit."""
        await self.close()
    
    def __enter__(self):
        """Sync context manager entry."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Sync context manager exit."""
        self.close_sync()

--- packages/react-ai-sdk/src/ui/index.ts ---
export { useAISDKRuntime } from "./use-chat/useAISDKRuntime";
export { useChatRuntime } from "./use-chat/useChatRuntime";
export type { UseChatRuntimeOptions } from "./use-chat/useChatRuntime";
export { AssistantChatTransport } from "./use-chat/AssistantChatTransport";


--- python/assistant-ui-sync-server-api/src/assistant_ui/__init__.py ---
from .client import AssistantClient, ThreadClient
from .types import (
    Message,
    TextPart,
    ImagePart,
    FilePart,
    ToolCallPart,
    ToolResultPart,
    Tool,
    ToolParameters,
    SystemMessage,
    UserMessage,
    AssistantMessage,
    ToolMessage,
)

__all__ = [
    "AssistantClient",
    "ThreadClient",
    "Message",
    "SystemMessage",
    "UserMessage",
    "AssistantMessage",
    "ToolMessage",
    "TextPart",
    "ImagePart",
    "FilePart",
    "ToolCallPart",
    "ToolResultPart",
    "Tool",
    "ToolParameters",
]

--- CONTRIBUTING.md ---
## CONTRIBUTING

A big welcome and thank you for considering contributing to assistant-ui! It’s people like you that make it a reality for users in our community.

You can contribute by opening an issue, or by making a pull request. For large pull requests, we ask that you open an issue first to discuss the changes before submitting a pull request.

### Setting up your environment

You need to have Node.js installed on your computer. We develop with the latest LTS version of Node.js.

Install the dependencies:

```sh
pnpm install
```

Make an initial build:

```sh
pnpm turbo build
```

(some packages rely on build outputs from other packages, even if you want to start the project in development mode)

### Running the project

To run the docs project in development mode:

```sh
cd apps/docs
pnpm dev
```

To run the examples project in development mode:

```sh
cd examples/<your-example>
pnpm dev
```

### Adding a changeset

Every pull request that changes packages must include a changeset, otherwise your changes won't be published to npm.

Note, this does not apply to packages like `@assistant-ui/docs` or `@assistant-ui/shadcn-registry` which are not published to npm, they are deployed on Vercel.

Create a changeset by running:

```sh
pnpm changeset
```

This will detect which packages changed and prompt you to select type (major, minor, patch) and a description of your changes. For now, most changes in assistant-ui should be classified as a patch.

If you forget to add a changeset before merging, create a new PR and run `pnpm changeset` locally to create a changeset. You'll be prompted to manually select the packages that were changed, set update type, and add description. Commit the changeset file, push the changes, and merge the PR.

You can also add changesets on open PRs directly from GitHub using the changeset bot's link in PR comments.

### Releasing

Our CI checks for changesets in `.changeset/` on `main` and will create an "update versions" PR which versions the packages, updates the changelog, and publishes the packages to npm on merge.


--- SECURITY.md ---
# Reporting Security Issues

We take security bugs in assistant-ui seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/assistant-ui/assistant-ui/security/advisories/new) tab.

Our team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.


## Links discovered
- ["Report a Vulnerability"](https://github.com/assistant-ui/assistant-ui/security/advisories/new)

--- packages/assistant-stream/CHANGELOG.md ---
# assistant-stream

## 0.2.45

### Patch Changes

- e8ea57b: chore: update deps

## 0.2.44

### Patch Changes

- 89aec17: feat: AI SDK frontend tool execution cancellation support
  fix: AI SDK isRunning status when running frontend tools

## 0.2.43

### Patch Changes

- 01c31fe: chore: update dependencies

## 0.2.42

### Patch Changes

- ec662cd: chore: update dependencies

## 0.2.41

### Patch Changes

- 2c33091: chore: update deps

## 0.2.40

### Patch Changes

- ef58020: feat(assistant-stream): parallel tool calling

## 0.2.39

### Patch Changes

- 2fc7e99: chore: update deps

## 0.2.38

### Patch Changes

- 2fc5c3d: feat: AssistantTransport wire format

## 0.2.37

### Patch Changes

- 953db24: chore: update deps

## 0.2.36

### Patch Changes

- chore: update deps

## 0.2.35

### Patch Changes

- 7c5943b: fix: detect duplicate tool calls in data stream

## 0.2.34

### Patch Changes

- e6a46e4: chore: update deps

## 0.2.33

### Patch Changes

- 3caad00: refactor: rename interrupt -> human for tool input handling

## 0.2.32

### Patch Changes

- e81784b: feat: Tool Call interrupt() resume() API

## 0.2.31

### Patch Changes

- c0f5003: fix(stream): always use async to flush controller appends

## 0.2.30

### Patch Changes

- 8812f86: chore: update deps

## 0.2.29

### Patch Changes

- d19ebab: feat(assistant-transport): surface stream errors to caller

## 0.2.28

### Patch Changes

- feat: throttle only on stream resume

## 0.2.27

### Patch Changes

- fix: make accumulator throttling smoother

## 0.2.26

### Patch Changes

- 0f21c70: feat: throttle AssistantMessageAccumulator emits

## 0.2.25

### Patch Changes

- 2e7a10f: feat: createInitialMessage unstable_state support
  feat: export AssistantMetaTransformStream

## 0.2.24

### Patch Changes

- cf26771: feat: export AssistantTransformStream API

## 0.2.23

### Patch Changes

- 12e0a77: chore: update deps

## 0.2.22

### Patch Changes

- 0f063e0: chore: update dependencies

## 0.2.21

### Patch Changes

- fix: race condition in RunController when using parentIds

## 0.2.20

### Patch Changes

- f23fdb6: feat: add parent ID grouping for message parts

## 0.2.19

### Patch Changes

- 20a4649: fix: preserve message error statuses, avoid being overwritten by finish chunks
- 9793e64: fix: if tool calls have no argsText, assume empty object instead of crashing

## 0.2.18

### Patch Changes

- 65b3ff1: chore: update deps

## 0.2.17

### Patch Changes

- 644abb8: chore: update deps

## 0.2.16

### Patch Changes

- de00319: fix: add assistant-stream/utils subpath import workaround

## 0.2.15

### Patch Changes

- 51b8493: feat: add missing exports

## 0.2.14

### Patch Changes

- 52e18bc: feat: ToolResponseLike

## 0.2.13

### Patch Changes

- fix: Last is not a partial call attempt 3

## 0.2.12

### Patch Changes

- fix: add another workaround for Last is not a partial call

## 0.2.11

### Patch Changes

- fix: Last is not a partial call error

## 0.2.10

### Patch Changes

- chore: update deps

## 0.2.9

### Patch Changes

- fix: PlainTextEncoder should ignore part-start/finish

## 0.2.8

### Patch Changes

- fix: close argsTextController when setResponse is called

## 0.2.7

### Patch Changes

- 5cb9598: feat(assistant-stream): ObjectStream

## 0.2.6

### Patch Changes

- 0809c9f: feat: add missing exports from 'assistant-stream'

## 0.2.5

### Patch Changes

- c4c60cf: fix: server-side tool results should be forwarded to StreamCallController
- 73a6ff1: feat: Tool.type

## 0.2.4

### Patch Changes

- 98a680e: chore: update deps

## 0.2.3

### Patch Changes

- 30ae924: fix: disabled tools should still execute if invoked

## 0.2.2

### Patch Changes

- fix: ESM without bundler compat

## 0.2.1

### Patch Changes

- fix: correctly include Typescript declarations

## 0.2.0

### Patch Changes

- 557c3f7: build: drop CJS builds

## 0.1.8

### Patch Changes

- fix: types in ESM

## 0.1.7

### Patch Changes

- 51104f0: feat: ship declarationMaps

## 0.1.6

### Patch Changes

- feat: export AssistantStreamController

## 0.1.5

### Patch Changes

- 2e4a7c1: fix: correctly forward tool result from data stream

## 0.1.4

### Patch Changes

- 62c2af7: feat: tool.streamCall API
- b9c731a: chore: update dependencies

## 0.1.3

### Patch Changes

- c0c9422: feat: useToolArgsFieldStatus

## 0.1.2

### Patch Changes

- chore: update deps

## 0.1.1

### Patch Changes

- fix: throw error when LineDecoderStream ends with incomplete line instead of emitting it

## 0.1.0

### Patch Changes

- 1f65c94: fix: ToolResponse instanceof check via named symbol
- 8df35f6: feat: fix duplicate tool calls appearing from ai-sdk
- 476cbfb: fix: make text-delta support reasoning part type

## 0.0.32

### Patch Changes

- 545a17c: fix: do not crash on tool call with empty argsText

## 0.0.31

### Patch Changes

- 93c3eb4: fix: drop ToolResponseBrand

## 0.0.30

### Patch Changes

- a22bc7a: refactor: merge setResult and setArtifact to setResponse
- 39aecd7: chore: update dependencies

## 0.0.29

### Patch Changes

- feat: expose assitant-stream ToolResponse API

## 0.0.28

### Patch Changes

- 40579cd: feat: ToolResponse support

## 0.0.27

### Patch Changes

- fix: assistant-stream appendText must only append to the very last part

## 0.0.26

### Patch Changes

- c4d7b29: feat: tool call artifacts

## 0.0.25

### Patch Changes

- fix: frontend tool call enqueue bug

## 0.0.24

### Patch Changes

- chore: bump assistant-stream

## 0.0.23

### Patch Changes

- 439ae67: fix: properly emit tool-call args-text finish

## 0.0.22

### Patch Changes

- b07603d: feat: assistant-stream rewrite

## 0.0.21

### Patch Changes

- fix: pin nanoid version for CJS compat

## 0.0.20

### Patch Changes

- 7f7ab5e: refactor: assitant-stream API

## 0.0.19

### Patch Changes

- 72e66db: chore: update dependencies

## 0.0.18

### Patch Changes

- b44a7ad: feat: error message part
- 22272e6: chore: update dependencies

## 0.0.17

### Patch Changes

- 70ccbe6: feat: AssistantMessageStream

## 0.0.16

### Patch Changes

- 345f3d5: chore: update dependencies

## 0.0.15

### Patch Changes

- 4c2bf58: chore: update dependencies

## 0.0.14

### Patch Changes

- 982a6a2: chore: update dependencies

## 0.0.13

### Patch Changes

- ec3b8cc: chore: update dependencies

## 0.0.12

### Patch Changes

- ignore unsupported data stream parts

## 0.0.11

### Patch Changes

- 4c54273: chore: update dependencies

## 0.0.10

### Patch Changes

- interop with module resolution node

## 0.0.8

### Patch Changes

- 2112ce8: chore: update dependencies

## 0.0.7

### Patch Changes

- 933b8c0: chore: update deps

## 0.0.6

### Patch Changes

- c59d8b5: chore: update dependencies

## 0.0.5

### Patch Changes

- 1ada091: chore: update deps

## 0.0.4

### Patch Changes

- ff5b86c: chore: update deps

## 0.0.3

### Patch Changes

- d2375cd: build: disable bundling in UI package releases

## 0.0.1

### Patch Changes

- fb32e61: chore: update deps

## 0.0.0

### Patch Changes

- fb46305: chore: update dependencies


--- packages/cli/CHANGELOG.md ---
# assistant-ui

## 0.0.69

### Patch Changes

- e8ea57b: chore: update deps

## 0.0.68

### Patch Changes

- 01c31fe: chore: update dependencies

## 0.0.67

### Patch Changes

- ec662cd: chore: update dependencies

## 0.0.66

### Patch Changes

- 9d50ed9: refactor assistant-ui cli

## 0.0.65

### Patch Changes

- 2c33091: chore: update deps

## 0.0.64

### Patch Changes

- 2fc7e99: chore: update deps

## 0.0.63

### Patch Changes

- 953db24: chore: update deps

## 0.0.62

### Patch Changes

- chore: update deps

## 0.0.61

### Patch Changes

- e6a46e4: chore: update deps

## 0.0.60

### Patch Changes

- 8812f86: chore: update deps

## 0.0.59

### Patch Changes

- 59e27c8: feat: add new Assistant Cloud template for easy thread management and persistence
- 6649f70: feat: auto-update assistant-cloud

## 0.0.58

### Patch Changes

- 12e0a77: chore: update deps

## 0.0.57

### Patch Changes

- 0f063e0: chore: update dependencies

## 0.0.56

### Patch Changes

- 65b3ff1: chore: update deps
- 2731323: - feat: Add codemod v0-11/content-part-to-message-part for ContentPart to MessagePart migration
  - Migration automatically updates imports, types, hooks, and JSX components
  - Renames `MessagePrimitive.Content` to `MessagePrimitive.Parts`

## 0.0.55

### Patch Changes

- 644abb8: chore: update deps

## 0.0.54

### Patch Changes

- chore: update deps

## 0.0.53

### Patch Changes

- c7ea752: feat: MCP template

## 0.0.52

### Patch Changes

- 98a680e: chore: update deps

## 0.0.51

### Patch Changes

- fix: ESM without bundler compat

## 0.0.50

### Patch Changes

- fix: correctly include Typescript declarations

## 0.0.49

### Patch Changes

- 557c3f7: build: drop CJS builds

## 0.0.48

### Patch Changes

- chore: update deps

## 0.0.47

### Patch Changes

- 1ad0696: feat: assistant-ui update CLI command

## 0.0.46

### Patch Changes

- c77ef43: feat: assistant-ui update CLI command

## 0.0.45

### Patch Changes

- b9c731a: chore: update dependencies

## 0.0.44

### Patch Changes

- cli: drop version option

## 0.0.43

### Patch Changes

- 94e9f71: feat(cli): add command tailwind v4 compat

## 0.0.42

### Patch Changes

- chore: update deps

## 0.0.41

### Patch Changes

- cdca350: feat: codemods performance improvement

## 0.0.41

### Patch Changes

- feat: add migration for v0-9

## 0.0.40

### Patch Changes

- d2988ff: fix: init command does not work on empty folders
- 39aecd7: chore: update dependencies

## 0.0.39

### Patch Changes

- 1d56298: fix: make CLI's add command work w new templates

## 0.0.38

### Patch Changes

- 65a2c7c: feat: assistant-ui init CLI

## 0.0.37

### Patch Changes

- 72e66db: chore: update dependencies

## 0.0.36

### Patch Changes

- 8190d09: fix: fileURLToPath to get codemods

## 0.0.35

### Patch Changes

- 1a42993: fix: always use npx for jscodeshift

## 0.0.34

### Patch Changes

- c760cb3: fix: node 20 support

## 0.0.33

### Patch Changes

- 0a23a70: fix: node 20 support

## 0.0.32

### Patch Changes

- 6703842: feat: codemod to migrate to @assistant-ui/react-ui

## 0.0.31

### Patch Changes

- cefd975: fix: use mjs entrypoint

## 0.0.30

### Patch Changes

- f3368ad: feat: codemod for v0.8 migration

## 0.0.29

### Patch Changes

- 22272e6: chore: update dependencies

## 0.0.28

### Patch Changes

- 345f3d5: chore: update dependencies

## 0.0.27

### Patch Changes

- 4c2bf58: chore: update dependencies

## 0.0.26

### Patch Changes

- 982a6a2: chore: update dependencies

## 0.0.25

### Patch Changes

- ec3b8cc: chore: update dependencies

## 0.0.24

### Patch Changes

- 4c54273: chore: update dependencies

## 0.0.23

### Patch Changes

- 2112ce8: chore: update dependencies

## 0.0.22

### Patch Changes

- 938e734: fix: correctly pass component URLs to shadcn CLI

## 0.0.21

### Patch Changes

- 933b8c0: chore: update deps

## 0.0.20

### Patch Changes

- c59d8b5: chore: update dependencies

## 0.0.18

### Patch Changes

- 1ada091: chore: update deps

## 0.0.17

### Patch Changes

- ff5b86c: chore: update deps

## 0.0.16

### Patch Changes

- d2375cd: build: disable bundling in UI package releases

## 0.0.15

### Patch Changes

- fb32e61: chore: update deps

## 0.0.14

### Patch Changes

- fb46305: chore: update dependencies

## 0.0.13

### Patch Changes

- d8bd40b: chore: update dependencies

## 0.0.12

### Patch Changes

- c438773: feat: allow disabling ComposerInput keyboard shortcuts
- e1ae3d0: chore: update dependencies

## 0.0.11

### Patch Changes

- 155d6e7: chore: update dependencies

## 0.0.10

### Patch Changes

- c348553: chore: update dependencies

## 0.0.9

### Patch Changes

- 7faa03b: cli: create -t langgraph

## 0.0.8

### Patch Changes

- 7d7bbce: fix: create command windows compatibility

## 0.0.7

### Patch Changes

- 9a55735: chore: update deps

## 0.0.6

### Patch Changes

- ab031a0: fix: make `create` directory argument optional

## 0.0.5

### Patch Changes

- 36f3a1f: chore: update dependencies
- 1f8cc5e: refactor: make cli package more lightweight
- 3810443: feat: npx assistant-ui create


--- packages/cloud/CHANGELOG.md ---
# assistant-cloud

## 0.1.11

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [e8ea57b]
  - assistant-stream@0.2.45

## 0.1.10

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [01c31fe]
  - assistant-stream@0.2.43

## 0.1.9

### Patch Changes

- ec662cd: chore: update dependencies
- Updated dependencies [ec662cd]
  - assistant-stream@0.2.42

## 0.1.8

### Patch Changes

- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - assistant-stream@0.2.41

## 0.1.7

### Patch Changes

- 4e3877e: feat: Add thread fetching capability to remote thread list adapter
  - Add `fetch` method to `RemoteThreadListAdapter` interface
  - Implement `fetch` in cloud adapter to retrieve individual threads
  - Enhance `switchToThread` to automatically fetch and load threads not present in the current list
  - Add `get` method to `AssistantCloudThreads` for individual thread retrieval

## 0.1.6

### Patch Changes

- 2fc7e99: chore: update deps
- Updated dependencies [2fc7e99]
  - assistant-stream@0.2.39

## 0.1.5

### Patch Changes

- 953db24: chore: update deps
- Updated dependencies [953db24]
  - assistant-stream@0.2.37

## 0.1.4

### Patch Changes

- chore: update deps
- Updated dependencies
  - assistant-stream@0.2.36

## 0.1.3

### Patch Changes

- e6a46e4: chore: update deps
- Updated dependencies [e6a46e4]
  - assistant-stream@0.2.34

## 0.1.2

### Patch Changes

- 8812f86: chore: update deps
- Updated dependencies [8812f86]
  - assistant-stream@0.2.30

## 0.1.1

### Patch Changes

- 12e0a77: chore: update deps
- Updated dependencies [12e0a77]
  - assistant-stream@0.2.23

## 0.1.0

### Minor Changes

- 179f8b7: Add format parameter support to assistant-cloud client library
  - Add optional `format` query parameter to `AssistantCloudThreadMessages.list()` method
  - Update cloud history adapter to pass format parameter when loading messages
  - Enables backend-level message format conversion when supported by the cloud backend

## 0.0.4

### Patch Changes

- 0f063e0: chore: update dependencies
- Updated dependencies [0f063e0]
  - assistant-stream@0.2.22

## 0.0.3

### Patch Changes

- 65b3ff1: chore: update deps
- Updated dependencies [65b3ff1]
  - assistant-stream@0.2.18

## 0.0.2

### Patch Changes

- 644abb8: chore: update deps
- Updated dependencies [644abb8]
  - assistant-stream@0.2.17


--- packages/create-assistant-ui/CHANGELOG.md ---
# create-assistant-ui

## 0.0.35

### Patch Changes

- e8ea57b: chore: update deps

## 0.0.34

### Patch Changes

- 01c31fe: chore: update dependencies

## 0.0.33

### Patch Changes

- ec662cd: chore: update dependencies

## 0.0.32

### Patch Changes

- 2c33091: chore: update deps

## 0.0.31

### Patch Changes

- 2fc7e99: chore: update deps

## 0.0.30

### Patch Changes

- 953db24: chore: update deps

## 0.0.29

### Patch Changes

- chore: update deps

## 0.0.28

### Patch Changes

- e6a46e4: chore: update deps

## 0.0.27

### Patch Changes

- 8812f86: chore: update deps

## 0.0.26

### Patch Changes

- 12e0a77: chore: update deps

## 0.0.25

### Patch Changes

- 0f063e0: chore: update dependencies

## 0.0.24

### Patch Changes

- 65b3ff1: chore: update deps

## 0.0.23

### Patch Changes

- 644abb8: chore: update deps

## 0.0.22

### Patch Changes

- chore: update deps

## 0.0.21

### Patch Changes

- 98a680e: chore: update deps

## 0.0.20

### Patch Changes

- fix: ESM without bundler compat

## 0.0.19

### Patch Changes

- fix: correctly include Typescript declarations

## 0.0.18

### Patch Changes

- 557c3f7: build: drop CJS builds

## 0.0.17

### Patch Changes

- chore: update deps

## 0.0.16

### Patch Changes

- b9c731a: chore: update dependencies

## 0.0.15

### Patch Changes

- 94e9f71: fix: make create CLI work

## 0.0.14

### Patch Changes

- chore: update deps

## 0.0.13

### Patch Changes

- 39aecd7: chore: update dependencies

## 0.0.12

### Patch Changes

- fix: create CLI

## 0.0.11

### Patch Changes

- 72e66db: chore: update dependencies

## 0.0.10

### Patch Changes

- c760cb3: fix: node 20 support

## 0.0.9

### Patch Changes

- 22272e6: chore: update dependencies

## 0.0.8

### Patch Changes

- 345f3d5: chore: update dependencies

## 0.0.7

### Patch Changes

- 4c2bf58: chore: update dependencies

## 0.0.6

### Patch Changes

- 982a6a2: chore: update dependencies

## 0.0.5

### Patch Changes

- ec3b8cc: chore: update dependencies

## 0.0.4

### Patch Changes

- 4c54273: chore: update dependencies

## 0.0.3

### Patch Changes

- 2112ce8: chore: update dependencies

## 0.0.2

### Patch Changes

- 933b8c0: chore: update deps


--- packages/react-a2a/CHANGELOG.md ---
# @assistant-ui/react-a2a

## 0.1.4

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [bae3aa2]
- Updated dependencies [e8ea57b]
  - @assistant-ui/react@0.11.50
  - assistant-stream@0.2.45

## 0.1.3

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [ba26b22]
- Updated dependencies [d169e4f]
- Updated dependencies [da9f8a6]
- Updated dependencies [01c31fe]
  - @assistant-ui/react@0.11.48
  - assistant-stream@0.2.43

## 0.1.2

### Patch Changes

- ec662cd: chore: update dependencies
- Updated dependencies [ec662cd]
  - assistant-stream@0.2.42
  - @assistant-ui/react@0.11.45

## 0.1.1

### Patch Changes

- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - assistant-stream@0.2.41
  - @assistant-ui/react@0.11.40


--- packages/react-ag-ui/CHANGELOG.md ---
# @assistant-ui/react-ag-ui

## 0.0.6

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [bae3aa2]
- Updated dependencies [e8ea57b]
  - @assistant-ui/react@0.11.50
  - assistant-stream@0.2.45

## 0.0.5

### Patch Changes

- Updated dependencies [89aec17]
- Updated dependencies [ee7040f]
- Updated dependencies [bd27465]
- Updated dependencies [a3e9549]
- Updated dependencies [206616b]
- Updated dependencies [7aa77b5]
  - assistant-stream@0.2.44
  - @assistant-ui/react@0.11.49

## 0.0.4

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [ba26b22]
- Updated dependencies [d169e4f]
- Updated dependencies [da9f8a6]
- Updated dependencies [01c31fe]
  - @assistant-ui/react@0.11.48
  - assistant-stream@0.2.43

## 0.0.3

### Patch Changes

- ec662cd: chore: update dependencies
- Updated dependencies [ec662cd]
  - assistant-stream@0.2.42
  - @assistant-ui/react@0.11.45

## 0.0.2

### Patch Changes

- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - assistant-stream@0.2.41
  - @assistant-ui/react@0.11.40

## 0.0.1

### Patch Changes

- Updated dependencies [ef58020]
  - assistant-stream@0.2.40


--- packages/react-data-stream/CHANGELOG.md ---
# @assistant-ui/react-data-stream

## 0.11.12

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [bae3aa2]
- Updated dependencies [e8ea57b]
  - @assistant-ui/react@0.11.50
  - assistant-stream@0.2.45

## 0.11.11

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [ba26b22]
- Updated dependencies [d169e4f]
- Updated dependencies [da9f8a6]
- Updated dependencies [01c31fe]
  - @assistant-ui/react@0.11.48
  - assistant-stream@0.2.43

## 0.11.10

### Patch Changes

- ec662cd: chore: update dependencies
- Updated dependencies [ec662cd]
  - assistant-stream@0.2.42
  - @assistant-ui/react@0.11.45

## 0.11.9

### Patch Changes

- 4f6afef: feat: unified json schema
- Updated dependencies [4f6afef]
  - @assistant-ui/react@0.11.44

## 0.11.8

### Patch Changes

- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - assistant-stream@0.2.41
  - @assistant-ui/react@0.11.40

## 0.11.7

### Patch Changes

- b408005: feat(react-ai-sdk): Integrate AI SDK v5 data parts in message content
- Updated dependencies [b408005]
- Updated dependencies [7a6d9ca]
- Updated dependencies [70d5966]
- Updated dependencies [3754bdd]
- Updated dependencies [0a4bdc1]
  - @assistant-ui/react@0.11.39

## 0.11.6

### Patch Changes

- 2fc7e99: chore: update deps
- Updated dependencies [3ab9484]
- Updated dependencies [7a88ead]
- Updated dependencies [81b581f]
- Updated dependencies [2fc7e99]
  - @assistant-ui/react@0.11.36
  - assistant-stream@0.2.39

## 0.11.5

### Patch Changes

- 953db24: chore: update deps
- Updated dependencies [953db24]
- Updated dependencies
  - assistant-stream@0.2.37
  - @assistant-ui/react@0.11.34

## 0.11.4

### Patch Changes

- chore: update deps
- Updated dependencies
  - assistant-stream@0.2.36
  - @assistant-ui/react@0.11.31

## 0.11.3

### Patch Changes

- e6a46e4: chore: update deps
- Updated dependencies [e6a46e4]
  - assistant-stream@0.2.34
  - @assistant-ui/react@0.11.27

## 0.11.2

### Patch Changes

- e81784b: feat: Tool Call interrupt() resume() API
- Updated dependencies [e8d6d7b]
- Updated dependencies [e81784b]
  - @assistant-ui/react@0.11.22
  - assistant-stream@0.2.32

## 0.11.1

### Patch Changes

- 8812f86: chore: update deps
- Updated dependencies [8812f86]
  - assistant-stream@0.2.30

## 0.11.0

### Patch Changes

- Updated dependencies [39ac2f3]
- Updated dependencies [5437dbe]
  - @assistant-ui/react@0.11.0

## 0.10.1

### Patch Changes

- 12e0a77: chore: update deps
- Updated dependencies [12e0a77]
  - assistant-stream@0.2.23
  - @assistant-ui/react@0.10.42


--- packages/react-devtools/CHANGELOG.md ---
# @assistant-ui/react-devtools

## 0.1.11

### Patch Changes

- e8ea57b: chore: update deps
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [bae3aa2]
- Updated dependencies [e8ea57b]
- Updated dependencies [bae3aa2]
  - @assistant-ui/tap@0.3.3
  - @assistant-ui/react@0.11.50

## 0.1.10

### Patch Changes

- 01c31fe: chore: update dependencies
- Updated dependencies [ba26b22]
- Updated dependencies [d169e4f]
- Updated dependencies [da9f8a6]
- Updated dependencies [01c31fe]
  - @assistant-ui/react@0.11.48
  - @assistant-ui/tap@0.3.2

## 0.1.9

### Patch Changes

- ec662cd: chore: update dependencies
- Updated dependencies [ec662cd]
  - @assistant-ui/react@0.11.45
  - @assistant-ui/tap@0.3.1

## 0.1.8

### Patch Changes

- 2c33091: chore: update deps
- Updated dependencies [2c33091]
  - @assistant-ui/react@0.11.40
  - @assistant-ui/tap@0.2.2

## 0.1.7

### Patch Changes

- 2fc7e99: chore: update deps
- Updated dependencies [3ab9484]
- Updated dependencies [dbc4ec7]
- Updated dependencies [7a88ead]
- Updated dependencies [81b581f]
- Updated dependencies [2fc7e99]
  - @assistant-ui/react@0.11.36
  - @assistant-ui/tap@0.1.5

## 0.1.6

### Patch Changes

- 953db24: chore: update deps
- Updated dependencies [953db24]
- Updated dependencies
  - @assistant-ui/react@0.11.34
  - @assistant-ui/tap@0.1.4

## 0.1.5

### Patch Changes

- chore: update deps
- Updated dependencies
  - @assistant-ui/react@0.11.31
  - @assistant-ui/tap@0.1.3

## 0.1.4

### Patch Changes

- e6a46e4: chore: update deps
- Updated dependencies [e6a46e4]
  - @assistant-ui/react@0.11.27
  - @assistant-ui/tap@0.1.2

## 0.1.3

### Patch Changes

- 8812f86: chore: update deps

## 0.1.2

### Patch Changes

- 16b1106: fix dark mode not working
- Updated dependencies [94fcc39]
  - @assistant-ui/react@0.11.20

## 0.1.1

### Patch Changes

- 2c6198a: fix: thread empty should return false while thread is loading
  fix: devtools hydration warning
- Updated dependencies [2c6198a]
  - @assistant-ui/react@0.11.19


--- .changeset/README.md ---
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)


## Links discovered
- [in our repository](https://github.com/changesets/changesets)
- [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

--- README.md ---
<a href="https://www.assistant-ui.com">
  <img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" alt="assistant-ui Header" width="100%" style="width: 1000px" />
</a>

<p align="center">
  <a href="https://www.assistant-ui.com">Product</a> ·
  <a href="https://www.assistant-ui.com/docs/getting-started">Documentation</a> ·
  <a href="https://www.assistant-ui.com/examples">Examples</a> ·
  <a href="https://discord.gg/S9dwgCNEFs">Discord Community</a> ·
  <a href="https://cal.com/simon-farshid/assistant-ui">Contact Sales</a>
</p>

[![npm version](https://img.shields.io/npm/v/assistant-ui)](https://www.npmjs.com/package/@assistant-ui/react)
[![npm downloads](https://img.shields.io/npm/dm/@assistant-ui/react)](https://www.npmjs.com/package/@assistant-ui/react)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/assistant-ui/assistant-ui)
[![Manta Graph badge](https://getmanta.ai/api/badges?text=Manta%20Graph&link=assistant-ui)](https://getmanta.ai/assistant-ui)
[![Weave Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.workweave.ai%2Fapi%2Frepository%2Fbadge%2Forg_GhSIrtWo37b5B3Mv0At3wQ1Q%2F722184017&cacheSeconds=3600)](https://app.workweave.ai/reports/repository/org_GhSIrtWo37b5B3Mv0At3wQ1Q/722184017)
![Backed by Y Combinator](https://img.shields.io/badge/Backed_by-Y_Combinator-orange)

- [⭐️ Star us on GitHub](https://github.com/assistant-ui/assistant-ui)

## The UX of ChatGPT in your React app 💬🚀

**assistant-ui** is an open source TypeScript/React library to build production-grade AI chat experiences fast.

- Handles streaming, auto-scrolling, accessibility, and real-time updates for you
- Fully composable primitives inspired by shadcn/ui and cmdk — customize every pixel
- Works with your stack: AI SDK, LangGraph, Mastra, or any custom backend
- Broad model support out of the box (OpenAI, Anthropic, Mistral, Perplexity, AWS Bedrock, Azure, Google Gemini, Hugging Face, Fireworks, Cohere, Replicate, Ollama) with easy extension to custom APIs

## Why assistant-ui

- **Fast to production**: battle-tested primitives, built-in streaming and attachments
- **Designed for customization**: composable pieces instead of a monolithic widget
- **Great DX**: sensible defaults, keyboard shortcuts, a11y, and strong TypeScript
- **Enterprise-ready**: optional chat history and analytics via Assistant Cloud

## Getting Started

Run one of the following in your terminal:

```bash
npx assistant-ui create   # new project
npx assistant-ui init     # add to existing project
```

[![assistant-ui starter template](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/assistant-ui-starter.gif)](https://youtu.be/k6Dc8URmLjk)

## Features

- **Build**: composable primitives to create any chat UX (message list, input, thread, toolbar) and a polished shadcn/ui theme you can fully customize.

- **Ship**: production-ready UX out of the box — streaming, auto-scroll, retries, attachments, markdown and code highlighting — plus keyboard shortcuts and accessibility by default.

- **Generate**: render tool calls and JSON as components, collect human approvals inline, and enable safe frontend actions.

- **Integrate**: works with AI SDK, LangGraph, Mastra, or custom backends; broad provider support; optional chat history and analytics via Assistant Cloud (single env var).

## Backends

- **Assistant Cloud**: managed chat persistence and analytics. Deploy with the Cloud Starter template; bring any model/provider.

- **AI SDK**: integration with Vercel AI SDK; connect to any supported provider.

- **LangGraph**: integration with LangGraph and LangGraph Cloud; connect via LangChain providers.

- **Mastra**: integration with Mastra agents/workflows/RAG; model routing via Vercel AI SDK; optional Mastra Cloud.

- **Custom**: use assistant-ui on top of your own backend/streaming protocol.

## Customization

assistant-ui takes a Radix-style approach: instead of a single monolithic chat component, you compose primitives and bring your own styles. We provide a great starter config; you control everything else.

![Overview of components](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/components.png)

Sample customization to make a Perplexity lookalike:

![Perplexity clone created with assistant-ui](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/perplexity.gif)

## Demos

[![Short Demo](https://img.youtube.com/vi/ZW56UHlqTCQ/hqdefault.jpg)](https://youtu.be/ZW56UHlqTCQ)

[![Long Demo](https://img.youtube.com/vi/9eLKs9AM4tU/hqdefault.jpg)](https://youtu.be/9eLKs9AM4tU)

## Traction

Hundreds of projects use assistant-ui to build in-app AI assistants, including companies like LangChain, AthenaIntelligence, Browser Use, and more.

With >400k monthly downloads, assistant-ui is one of the most popular UI libraries for building AI chat.

<img src="https://github.com/user-attachments/assets/b2d7854a-8ce6-4862-bafc-875a85418df2" alt="Growth" width="400" />

## Community & Support

- [Check out example demos](https://www.assistant-ui.com/)
- [Read the docs](https://www.assistant-ui.com/docs/)
- [Join our Discord](https://discord.com/invite/S9dwgCNEFs)
- [Book a sales call](https://cal.com/simon-farshid/assistant-ui)

---

Backed by Y Combinator. Building something with assistant-ui? We’d love to hear from you.


## Links discovered
- [![npm version](https://img.shields.io/npm/v/assistant-ui)
- [![npm downloads](https://img.shields.io/npm/dm/@assistant-ui/react)
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)
- [![Manta Graph badge](https://getmanta.ai/api/badges?text=Manta%20Graph&link=assistant-ui)
- [![Weave Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.workweave.ai%2Fapi%2Frepository%2Fbadge%2Forg_GhSIrtWo37b5B3Mv0At3wQ1Q%2F722184017&cacheSeconds=3600)
- [Backed by Y Combinator](https://img.shields.io/badge/Backed_by-Y_Combinator-orange)
- [⭐️ Star us on GitHub](https://github.com/assistant-ui/assistant-ui)
- [![assistant-ui starter template](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/assistant-ui-starter.gif)
- [Overview of components](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/components.png)
- [Perplexity clone created with assistant-ui](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/perplexity.gif)
- [![Short Demo](https://img.youtube.com/vi/ZW56UHlqTCQ/hqdefault.jpg)
- [![Long Demo](https://img.youtube.com/vi/9eLKs9AM4tU/hqdefault.jpg)
- [Check out example demos](https://www.assistant-ui.com/)
- [Read the docs](https://www.assistant-ui.com/docs/)
- [Join our Discord](https://discord.com/invite/S9dwgCNEFs)
- [Book a sales call](https://cal.com/simon-farshid/assistant-ui)
- [<img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" alt="assistant-ui Header" width="100%" style="width: 1000px" />](https://www.assistant-ui.com)
- [Product](https://www.assistant-ui.com)
- [Documentation](https://www.assistant-ui.com/docs/getting-started)
- [Examples](https://www.assistant-ui.com/examples)
- [Discord Community](https://discord.gg/S9dwgCNEFs)
- [Contact Sales](https://cal.com/simon-farshid/assistant-ui)

--- apps/devtools-extension/background.ts ---
// Background service worker
// Routes messages between content scripts and devtools panels

interface TabConnection {
  port: chrome.runtime.Port;
}

// Track connections from devtools panels (keyed by tab ID)
const devtoolsConnections = new Map<number, TabConnection>();

// Listen for connections from devtools panels
chrome.runtime.onConnect.addListener((port) => {
  console.log("[assistant-ui DevTools] Port connected:", port.name);

  if (port.name.startsWith("devtools-panel-")) {
    // Extract tab ID from port name (format: "devtools-panel-123")
    const tabId = parseInt(port.name.replace("devtools-panel-", ""), 10);

    if (!Number.isNaN(tabId)) {
      console.log(
        `[assistant-ui DevTools] DevTools panel connected for tab ${tabId}`,
      );

      // Store the connection
      devtoolsConnections.set(tabId, {
        port,
      });

      // Handle messages from devtools panel to forward to content script
      port.onMessage.addListener((message) => {
        console.log(
          `[assistant-ui DevTools] Message from panel for tab ${tabId}:`,
          message,
        );

        // Forward to content script
        // The panel sends the actual message directly, we just forward it
        chrome.tabs
          .sendMessage(tabId, {
            type: "TO_PAGE",
            payload: message,
          })
          .catch((error) => {
            console.error(
              `[assistant-ui DevTools] Failed to send to tab ${tabId}:`,
              error,
            );
          });
      });

      // Clean up on disconnect
      port.onDisconnect.addListener(() => {
        console.log(
          `[assistant-ui DevTools] DevTools panel disconnected for tab ${tabId}`,
        );
        devtoolsConnections.delete(tabId);
      });
    }
  }
});

// Listen for messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  const tabId = sender.tab?.id;

  if (!tabId) {
    console.warn("[assistant-ui DevTools] Message from unknown tab");
    return;
  }

  console.log(
    `[assistant-ui DevTools] Message from tab ${tabId}:`,
    message.type,
  );

  switch (message.type) {
    case "FROM_PAGE":
      // Forward message from page to devtools panel
      const devtoolsConnection = devtoolsConnections.get(tabId);
      if (devtoolsConnection) {
        console.log(
          `[assistant-ui DevTools] Forwarding to panel for tab ${tabId}`,
        );
        devtoolsConnection.port.postMessage(message.payload);
      } else {
        console.debug(`[assistant-ui DevTools] No panel open for tab ${tabId}`);
      }

      sendResponse({ success: true });
      break;

    default:
      console.debug(
        "[assistant-ui DevTools] Unknown message type:",
        message.type,
      );
  }

  return true; // Keep message channel open
});

// Clean up when tabs are closed
chrome.tabs.onRemoved.addListener((tabId) => {
  devtoolsConnections.delete(tabId);
});

console.log("[assistant-ui DevTools] Background service worker initialized");


--- apps/devtools-extension/content.ts ---
// Content script - runs in isolated context
// Bridges communication between the page (inject script) and the extension (background/devtools)

(function () {
  console.log("[assistant-ui DevTools] Content script loaded");

  // Inject the inject.js script into the page context
  const script = document.createElement("script");
  script.src = chrome.runtime.getURL("inject.js");
  script.onload = function () {
    script.remove();
  };
  (document.head || document.documentElement).appendChild(script);

  // Listen for messages from the page (inject script / ExtensionHost)
  window.addEventListener("message", (event) => {
    // Only accept messages from the same window
    if (event.source !== window) return;

    // Handle messages from ExtensionHost (forwarding to DevTools)
    if (event.data.source === "assistant-ui-devtools-page") {
      // Forward to background/devtools
      chrome.runtime
        .sendMessage({
          type: "FROM_PAGE",
          payload: event.data.payload,
        })
        .catch((error) => {
          // DevTools might not be open yet, ignore error
          console.debug("[assistant-ui DevTools] DevTools not ready:", error);
        });
    }
  });

  // Listen for messages from background/devtools to forward to the page
  chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
    if (message.type === "TO_PAGE") {
      // Forward to page (ExtensionHost)
      // The payload is the actual message from the iframe
      window.postMessage(
        {
          source: "assistant-ui-devtools-iframe",
          payload: message.payload,
        },
        "*",
      );
      sendResponse({ success: true });
    }
    return true; // Keep message channel open for async response
  });

  console.log("[assistant-ui DevTools] Content script initialized");
})();


--- apps/devtools-extension/devtools.ts ---
// DevTools initialization script
// Creates the assistant-ui panel when DevTools are opened

console.log("[assistant-ui DevTools] DevTools script loaded");

chrome.devtools.panels.create(
  "assistant-ui",
  "icon32.png",
  "devtools-panel.html",
  () => {
    console.log("[assistant-ui DevTools] Panel created");
  },
);


--- apps/devtools-extension/inject.ts ---
// Inject script - runs in page context
// This script creates an ExtensionHost instance to bridge DevTools with the page

import { ExtensionHost } from "@assistant-ui/react-devtools";

(function () {
  let extensionHost: ExtensionHost | undefined;

  // Function to check for assistant-ui and initialize ExtensionHost
  const initializeExtensionHost = () => {
    // Check if assistant-ui DevTools hook exists
    const hook = window.__ASSISTANT_UI_DEVTOOLS_HOOK__;

    if (hook && !extensionHost) {
      console.log(
        "[assistant-ui DevTools] Detected assistant-ui, initializing ExtensionHost",
      );
      console.log("[assistant-ui DevTools] APIs found:", hook.apis?.size || 0);

      // Create ExtensionHost instance
      extensionHost = new ExtensionHost();
      console.log("[assistant-ui DevTools] ExtensionHost created successfully");
    }
  };

  // Check immediately
  initializeExtensionHost();

  // Check when DOM is ready (for late-loading apps)
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initializeExtensionHost);
  }

  // Periodically check for late initialization (SPA apps)
  let checkCount = 0;
  const checkInterval = setInterval(() => {
    checkCount++;
    initializeExtensionHost();

    // Stop checking after 10 seconds or if host is initialized
    if (checkCount > 20 || extensionHost) {
      clearInterval(checkInterval);
    }
  }, 500);
})();


--- apps/devtools-frame/next.config.ts ---
import type { NextConfig } from "next";

const nextConfig: NextConfig = {};

export default nextConfig;


--- apps/devtools-frame/tailwind.config.ts ---
import type { Config } from "tailwindcss";

const config: Config = {
  darkMode: "class",
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./hooks/**/*.{js,ts,jsx,tsx,mdx}",
    "./lib/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

export default config;


--- apps/registry/scripts/build-registry.ts ---
import { promises as fs, readFileSync } from "node:fs";
import * as path from "node:path";
import { registry } from "../src/registry";
import { RegistryItem } from "@/src/schema";

const REGISTRY_PATH = path.join(process.cwd(), "dist");
const REGISTRY_INDEX_PATH = path.join(REGISTRY_PATH, "registry.json");

async function buildRegistry(registry: RegistryItem[]) {
  await fs.mkdir(REGISTRY_PATH, { recursive: true });

  for (const item of registry) {
    const files = item.files?.map((file) => {
      const content = readFileSync(path.join(process.cwd(), file.path), "utf8");

      // No transformation - just return content as-is
      return {
        content,
        ...file,
      };
    });

    const payload = {
      $schema: "https://ui.shadcn.com/schema/registry-item.json",
      ...item,
      files,
    };

    const p = path.join(REGISTRY_PATH, `${item.name}.json`);
    await fs.mkdir(path.dirname(p), { recursive: true });

    await fs.writeFile(p, JSON.stringify(payload, null, 2), "utf8");
  }

  const registryIndex = {
    $schema: "https://ui.shadcn.com/schema/registry.json",
    name: "assistant-ui",
    homepage: "https://assistant-ui.com",
    items: registry,
  };

  await fs.writeFile(
    REGISTRY_INDEX_PATH,
    JSON.stringify(registryIndex, null, 2),
    "utf8",
  );
}

await buildRegistry(registry);


--- apps/devtools-extension/static/devtools.html ---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>assistant-ui DevTools</title>
  </head>
  <body>
    <script src="devtools.js"></script>
  </body>
</html>


--- apps/devtools-extension/static/devtools-panel.html ---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>assistant-ui DevTools Panel</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        font-family:
          -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif;
        background-color: #f8f9fa;
        overflow: hidden;
      }
      #root {
        width: 100%;
        height: 100vh;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script src="devtools-panel.js"></script>
  </body>
</html>


--- apps/registry/src/registry.ts ---
import { RegistryItem } from "./schema";

export const registry: RegistryItem[] = [
  {
    name: "shimmer-style",
    type: "registry:style",
    cssVars: {
      theme: {
        "--animate-shimmer":
          "shimmer-sweep var(--shimmer-duration, 1000ms) linear infinite both",
      },
    },
    css: {
      "@keyframes shimmer-sweep": {
        from: {
          "background-position": "150% 0",
        },
        to: {
          "background-position": "-100% 0",
        },
      },
    },
  },
  {
    name: "chat/b/ai-sdk-quick-start/json",
    type: "registry:page",
    files: [
      {
        type: "registry:page",
        path: "app/ai-sdk/assistant.tsx",
        target: "app/assistant.tsx",
      },
    ],
    registryDependencies: [
      "https://r.assistant-ui.com/ai-sdk-backend.json",
      "https://r.assistant-ui.com/thread.json",
      "https://r.assistant-ui.com/thread-list.json",
    ],
    meta: {
      importSpecifier: "Assistant",
      moduleSpecifier: "@/app/assistant",
      nextVersion: "15.1.6",
    },
  },
  {
    name: "ai-sdk-backend",
    type: "registry:page",
    files: [
      {
        type: "registry:page",
        path: "app/api/chat/route.ts",
        target: "app/api/chat/route.ts",
      },
    ],
    dependencies: ["@assistant-ui/react-ai-sdk", "ai", "@ai-sdk/openai"],
  },
  {
    name: "thread",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/thread.tsx",
      },
    ],
    dependencies: ["@assistant-ui/react", "lucide-react"],
    registryDependencies: [
      "button",
      "https://r.assistant-ui.com/attachment.json",
      "https://r.assistant-ui.com/markdown-text.json",
      "https://r.assistant-ui.com/tooltip-icon-button.json",
      "https://r.assistant-ui.com/tool-fallback.json",
    ],
  },
  {
    name: "markdown-text",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/markdown-text.tsx",
      },
    ],
    registryDependencies: [
      "https://r.assistant-ui.com/tooltip-icon-button.json",
    ],
    dependencies: [
      "@assistant-ui/react-markdown",
      "lucide-react",
      "remark-gfm",
    ],
  },
  {
    name: "reasoning",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/reasoning.tsx",
      },
    ],
    registryDependencies: [
      "collapsible",
      "https://r.assistant-ui.com/markdown-text.json",
    ],
    dependencies: ["@assistant-ui/react", "lucide-react", "tw-shimmer"],
    css: {
      "@import": "tw-shimmer",
    },
  },
  {
    name: "thread-list",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/thread-list.tsx",
      },
    ],
    registryDependencies: [
      "button",
      "skeleton",
      "https://r.assistant-ui.com/tooltip-icon-button.json",
    ],
    dependencies: ["@assistant-ui/react", "lucide-react"],
  },
  {
    name: "attachment",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/attachment.tsx",
      },
    ],
    registryDependencies: [
      "dialog",
      "tooltip",
      "avatar",
      "https://r.assistant-ui.com/tooltip-icon-button.json",
    ],
    dependencies: ["@assistant-ui/react", "lucide-react", "zustand"],
  },
  {
    name: "follow-up-suggestions",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/follow-up-suggestions.tsx",
      },
    ],
    registryDependencies: [],
    dependencies: ["@assistant-ui/react"],
  },
  {
    name: "tooltip-icon-button",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/tooltip-icon-button.tsx",
      },
    ],
    registryDependencies: ["tooltip", "button"],
  },
  {
    name: "syntax-highlighter",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/syntax-highlighter.tsx",
      },
    ],
    dependencies: [
      "@assistant-ui/react-syntax-highlighter",
      "react-syntax-highlighter",
      "@types/react-syntax-highlighter",
    ],
  },
  {
    name: "assistant-modal",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/assistant-modal.tsx",
      },
    ],
    dependencies: ["@assistant-ui/react", "lucide-react"],
    registryDependencies: [
      "https://r.assistant-ui.com/thread.json",
      "https://r.assistant-ui.com/tooltip-icon-button.json",
    ],
  },
  {
    name: "assistant-sidebar",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/assistant-sidebar.tsx",
      },
    ],
    dependencies: ["@assistant-ui/react"],
    registryDependencies: [
      "resizable",
      "https://r.assistant-ui.com/thread.json",
    ],
  },
  {
    name: "tool-fallback",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/tool-fallback.tsx",
      },
    ],
    dependencies: ["@assistant-ui/react", "lucide-react"],
    registryDependencies: ["button"],
  },
  {
    name: "shiki-highlighter",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/shiki-highlighter.tsx",
      },
    ],
    dependencies: ["react-shiki"],
  },
  {
    name: "mermaid-diagram",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/mermaid-diagram.tsx",
      },
    ],
    dependencies: [
      "mermaid",
      "@assistant-ui/react",
      "@assistant-ui/react-markdown",
    ],
  },
  {
    name: "threadlist-sidebar",
    type: "registry:component",
    files: [
      {
        type: "registry:component",
        path: "components/assistant-ui/threadlist-sidebar.tsx",
      },
    ],
    dependencies: ["lucide-react"],
    registryDependencies: [
      "sidebar",
      "https://r.assistant-ui.com/thread-list.json",
    ],
  },
];


--- packages/cli/README.md ---
# `assistant-ui` CLI

The `assistant-ui` CLI for adding components and dependencies to your project.

## Usage

Use the `init` command to initialize dependencies for a new or existing project.

The `init` command installs dependencies, adds components, and configures your project for assistant-ui.

```bash
npx assistant-ui@latest init
```

## create

Use the `create` command to scaffold a new Next.js project with assistant-ui.

The `create` command uses `create-next-app` with assistant-ui starter templates.

```bash
npx assistant-ui@latest create my-app
```

You can choose from multiple templates:

```bash
# Default template with Vercel AI SDK
npx assistant-ui@latest create my-app

# With Assistant Cloud for persistence
npx assistant-ui@latest create my-app -t cloud

# With LangGraph integration
npx assistant-ui@latest create my-app -t langgraph

# With MCP support
npx assistant-ui@latest create my-app -t mcp
```

## add

Use the `add` command to add components to your project.

The `add` command adds a component to your project and installs all required dependencies.

```bash
npx assistant-ui@latest add [component]
```

### Example

```bash
npx assistant-ui@latest add thread
```

You can also add multiple components at once:

```bash
npx assistant-ui@latest add thread thread-list assistant-modal
```

## update

Use the `update` command to update all assistant-ui packages to their latest versions.

```bash
npx assistant-ui@latest update
```

## upgrade

Use the `upgrade` command to automatically migrate your codebase when upgrading to a new major version.

The `upgrade` command runs codemods to transform your code and prompts to install new dependencies.

```bash
npx assistant-ui@latest upgrade
```

## Documentation

Visit https://assistant-ui.com/docs/cli to view the full documentation.

## License

Licensed under the [MIT license](https://github.com/assistant-ui/assistant-ui/blob/main/LICENSE).


## Links discovered
- [MIT license](https://github.com/assistant-ui/assistant-ui/blob/main/LICENSE)

--- packages/create-assistant-ui/README.md ---
# `create-assistant-ui`

This package contains the command line interface for `create-assistant-ui`.

## Usage

```sh
npm create assistant-ui
```


--- packages/react-a2a/README.md ---
# `@assistant-ui/react-a2a`

A2A integration for `@assistant-ui/react`.


--- packages/react-data-stream/README.md ---
# `@assistant-ui/react-data-stream`

Data Stream protocol integration for `@assistant-ui/react`.


--- packages/react-devtools/README.md ---
# @assistant-ui/react-devtools

React-first development tools for assistant-ui components. This package ships the reusable React helpers, runtime adapters, and UI necessary to embed the DevTools experience in any host application.

## Features

- **Component Library**: React components for debugging assistant-ui experiences
- **Event Logging**: Track and inspect assistant-ui events and state changes
- **Context Viewer**: View Assistant API context and state in real-time
- **Embeddable Host**: Frame bridges that power custom hosts, including the Chrome extension

## Installation

### As React Component Library

```bash
npm install @assistant-ui/react-devtools
```

### As Chrome Extension

See `apps/devtools-extension` for the standalone Chrome extension source and build scripts (`pnpm --filter @assistant-ui/devtools-extension run build`).

## Usage

### React Components

```tsx
import { DevToolsUI, DevToolsModal } from '@assistant-ui/react-devtools';

// Use the full DevTools UI
<DevToolsUI />

// Or use as a modal overlay
<DevToolsModal />
```

### Chrome Extension

The Chrome extension now lives under `apps/devtools-extension` as a separate workspace app. It consumes this package at build time to reuse the shared runtime and UI.

## Build Scripts

- `npm run build` - Build the React component library
- `npm run build:lib` - Alias for `build`
- `npm run dev` - Development build with watch mode

## Package Structure

```
packages/react-devtools/
├── src/                     # React component library source
│   ├── DevToolsHooks.ts     # Core devtools functionality
│   ├── DevToolsModal.tsx    # Modal wrapper component (iframe host)
│   └── index.ts             # Main exports
└── scripts/                 # Build scripts
```

## Development

The devtools package builds with `@assistant-ui/x-buildutils` to transpile the TypeScript sources that power the React helpers and adapters consumed across the workspace.

## License

MIT


--- packages/react-hook-form/README.md ---
# `@assistant-ui/react-hook-form`

React Hook Form integration for `@assistant-ui/react`.

Simply replace `useForm` with `useAssistantForm` to give the chatbot the ability to interact with your form.


--- packages/react-langgraph/README.md ---
# `@assistant-ui/react-langgraph`

LangGraph integration for `@assistant-ui/react`.


--- packages/react-markdown/README.md ---
# `@assistant-ui/react-markdown`

`react-markdown` integration for `@assistant-ui/react`.


--- packages/react-syntax-highlighter/README.md ---
# `@assistant-ui/react-syntax-highlighter`

`react-syntax-highlighter` integration for `@assistant-ui/react`.


--- packages/react/README.md ---
<a href="https://www.assistant-ui.com">
  <img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" alt="assistant-ui Header" width="100%" style="width: 1000px" />
</a>

<p align="center">
  <a href="https://www.assistant-ui.com">Product</a> ·
  <a href="https://www.assistant-ui.com/docs/getting-started">Documentation</a> ·
  <a href="https://www.assistant-ui.com/examples">Examples</a> ·
  <a href="https://discord.gg/S9dwgCNEFs">Discord Community</a> ·
  <a href="https://cal.com/simon-farshid/assistant-ui">Contact Sales</a>
</p>

[![Weave Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.workweave.ai%2Fapi%2Frepository%2Fbadge%2Forg_GhSIrtWo37b5B3Mv0At3wQ1Q%2F722184017&cacheSeconds=3600)](https://app.workweave.ai/reports/repository/org_GhSIrtWo37b5B3Mv0At3wQ1Q/722184017)
![Backed by Y Combinator](https://img.shields.io/badge/Backed_by-Y_Combinator-orange)

- [⭐️ Star us on GitHub](https://github.com/assistant-ui/assistant-ui)

## The UX of ChatGPT in your React app 💬🚀

**assistant-ui** is an open source TypeScript/React library for AI chat.

The library handles essential chat features such as auto-scrolling, accessibility, and real-time updates, while providing easy integration with LangGraph, AI SDK and custom backends.

The API of assistant-ui is inspired by libraries like shadcn/ui and cmdk. Instead of a single monolithic chat component, developers get primitive components that can be fully customized.

We have wide model provider support (OpenAI, Anthropic, Mistral, Perplexity, AWS Bedrock, Azure, Google Gemini, Hugging Face, Fireworks, Cohere, Replicate, Ollama) out of the box and the ability to integrate custom APIs.

## Getting Started

You can get started by running `npx assistant-ui create` (new project) or `npx assistant-ui init` (existing project) in your terminal.

[![assistant-ui starter template](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/assistant-ui-starter.gif)](https://youtu.be/k6Dc8URmLjk)

## Features

- shadcn/ui
  - Radix UI-inspired primitives for AI Chat
  - Beautiful shadcn/ui theme to get you started
- Chat UI
  - Streaming, Auto-scrolling, Markdown, Code Highlighting, File Attachments, and more
- Keyboard shortcuts and accessibility features
- Generative UI
  - Map LLM tool calls and JSONs to custom UI components
- Frontend tool calls
  - Let LLMs take action in your frontend application
- Human tool calls
  - Human approvals and input collection
- Chat history and analytics
  - Sign up for assistant-cloud and configure by simply setting an environment variable

## Choose your backend

- AI SDK
  - First class integration into AI SDK by Vercel. Connect to any LLM provider supported by AI SDK.
- LangGraph
  - First class integration into LangGraph and LangGraph Cloud. Connect to any LLM provider supported by LangChain.
- Custom
  - Use assistant-ui as the visualization layer on top your own backend/streaming protocols.

## Customization

The API of assistant-ui is inspired by libraries like Radix UI and cmdk. Instead of a single monolithic chat component, we give you composable primitives and a great starter configuration. You have full control over the look and feel of every pixel while leaving auto-scrolling, LLM streaming and accessibility to us.

![Overview of components](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/components.png)

Sample customization to make a perplexity lookalike:

![Perplexity clone created with assistant-ui](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/perplexity.gif)

## **Demo Video**

[![Short Demo](https://img.youtube.com/vi/ZW56UHlqTCQ/hqdefault.jpg)](https://youtu.be/ZW56UHlqTCQ)

[![Long Demo](https://img.youtube.com/vi/9eLKs9AM4tU/hqdefault.jpg)](https://youtu.be/9eLKs9AM4tU)

## Traction

Hundreds of projects use assistant-ui to build in-app AI assistants, including companies like LangChain, AthenaIntelligence, Browser Use, and more.

With >50k+ monthly downloads, assistant-ui is the most popular UI library for AI chat.

<img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/growth.png" alt="Growth" style="max-width: 400px;">

## 2025 Q1 Roadmap

- [x] Assistant Cloud
- [x] Chat Persistence
- [x] React 19, Tailwind v4, NextJS 19 support
- [x] Improved Markdown rendering performance
- [x] LangGraph `interrupt()` support
- [x] Open in v0 support
- [ ] Improved documentation (work in progress)
- [ ] OpenAI Realtime Voice (work in progress)
- [ ] Resume interrupted LLM calls (work in progress)
- [ ] Native PDF attachment support
- [ ] Follow-up suggestions

## Next Steps

- [Check out example demos](https://www.assistant-ui.com/)
- [Read our docs](https://www.assistant-ui.com/docs/)
- [Join our Discord](https://discord.com/invite/S9dwgCNEFs)
- [Book a sales call](https://cal.com/simon-farshid/assistant-ui)


## Links discovered
- [![Weave Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.workweave.ai%2Fapi%2Frepository%2Fbadge%2Forg_GhSIrtWo37b5B3Mv0At3wQ1Q%2F722184017&cacheSeconds=3600)
- [Backed by Y Combinator](https://img.shields.io/badge/Backed_by-Y_Combinator-orange)
- [⭐️ Star us on GitHub](https://github.com/assistant-ui/assistant-ui)
- [![assistant-ui starter template](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/assistant-ui-starter.gif)
- [Overview of components](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/components.png)
- [Perplexity clone created with assistant-ui](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/perplexity.gif)
- [![Short Demo](https://img.youtube.com/vi/ZW56UHlqTCQ/hqdefault.jpg)
- [![Long Demo](https://img.youtube.com/vi/9eLKs9AM4tU/hqdefault.jpg)
- [Check out example demos](https://www.assistant-ui.com/)
- [Read our docs](https://www.assistant-ui.com/docs/)
- [Join our Discord](https://discord.com/invite/S9dwgCNEFs)
- [Book a sales call](https://cal.com/simon-farshid/assistant-ui)
- [<img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" alt="assistant-ui Header" width="100%" style="width: 1000px" />](https://www.assistant-ui.com)
- [Product](https://www.assistant-ui.com)
- [Documentation](https://www.assistant-ui.com/docs/getting-started)
- [Examples](https://www.assistant-ui.com/examples)
- [Discord Community](https://discord.gg/S9dwgCNEFs)
- [Contact Sales](https://cal.com/simon-farshid/assistant-ui)

--- python/assistant-stream/README.md ---
## assistant-stream


--- python/assistant-transport-backend-langgraph/README.md ---
# Assistant Transport Backend with LangGraph

This is a LangGraph-based implementation of the assistant transport backend, providing streaming chat capabilities using FastAPI, assistant-stream, and LangGraph.

## Features

- Streaming responses using LangGraph's astream and astream_events
- Synchronization of LangGraph state to the frontend
- Support for both message streaming and state updates
- Compatible with the assistant-ui frontend

## Installation

### Using uv (Recommended)

1. Initialize and install dependencies:
```bash
uv init --name assistant-transport-backend-langgraph --package
uv add fastapi uvicorn[standard] assistant-stream pydantic python-dotenv langgraph langchain langchain-core langchain-openai httpx
# Or simply:
uv sync
```

2. Set up environment variables:
```bash
cp .env.example .env
# Edit .env to add your OpenAI API key
```

### Using pip

1. Install dependencies:
```bash
pip install -r requirements.txt
```

2. Set up environment variables:
```bash
cp .env.example .env
# Edit .env to add your OpenAI API key
```

## Configuration

The server can be configured via environment variables:

- `HOST`: Server host (default: 0.0.0.0)
- `PORT`: Server port (default: 8001)
- `DEBUG`: Enable debug mode (default: false)
- `LOG_LEVEL`: Log level (default: info)
- `CORS_ORIGINS`: CORS origins (default: http://localhost:3000)
- `OPENAI_API_KEY`: Your OpenAI API key (required)

## Running the Server

### Using uv
```bash
uv run python main.py
```

Or with uvicorn directly:
```bash
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8001
```

### Using standard Python
```bash
python main.py
```

Or with uvicorn directly:
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8001
```

## API Endpoints

### POST /api/chat
Main chat endpoint that processes commands and streams responses using LangGraph.

Request body:
```json
{
  "commands": [
    {
      "type": "add-message",
      "message": {
        "role": "user",
        "parts": [
          {
            "type": "text",
            "text": "Hello, how are you?"
          }
        ]
      }
    }
  ],
  "system": "Optional system prompt",
  "state": {}
}
```

### GET /health
Health check endpoint.

## How It Works

1. The server receives chat requests at `/api/chat`
2. Commands are converted to LangGraph messages (HumanMessage, AIMessage, etc.)
3. The LangGraph processes the messages through its nodes
4. Two streaming tasks run concurrently:
   - `astream` provides state updates
   - `astream_events` provides message streaming
5. Both streams are synchronized to the frontend using `append_langgraph_event`
6. The response is streamed back using assistant-stream's DataStreamResponse

## Integration with Frontend

This backend is designed to work with the assistant-ui frontend. Update your frontend configuration to point to this server:

```typescript
const runtime = useExternalStoreRuntime({
  endpoint: "http://localhost:8001/api/chat"
});
```

## Customizing the LangGraph

You can customize the graph in the `create_graph()` function. Currently, it implements a simple chat node using OpenAI's GPT-4o-mini model. You can:

- Add more nodes for different functionalities
- Implement tool calling
- Add conditional edges
- Integrate with different LLMs
- Add memory or persistence

Example of adding a tool node:
```python
from langgraph.prebuilt import ToolExecutor

def create_graph():
    workflow = StateGraph(GraphState)

    # Add nodes
    workflow.add_node("chat", chat_node)
    workflow.add_node("tools", tool_node)

    # Add conditional routing
    workflow.add_conditional_edges(
        "chat",
        should_use_tools,
        {
            "tools": "tools",
            "end": END
        }
    )

    return workflow.compile()
```

--- python/assistant-transport-backend/README.md ---
# Assistant Transport Backend

A simple Python server that demonstrates the assistant-transport protocol using FastAPI and assistant-stream. This backend returns static responses to show how the streaming protocol works with assistant-ui frontend applications.

## Features

- 🚀 **FastAPI-based** - High-performance async server
- 📡 **Streaming Responses** - Real-time responses using assistant-stream
- 🔄 **State Management** - Uses assistant-stream's object-stream state utilities
- 🔌 **Assistant-Transport Protocol** - Full compatibility with assistant-ui
- 🌐 **CORS Enabled** - Works with any frontend origin
- 📦 **Simple Setup** - Minimal dependencies
- 🧪 **Static Responses** - No API keys required, perfect for testing

## Prerequisites

- Python 3.9 or higher
- pip package manager

## Quick Start

### 1. Install Dependencies

```bash
# Install the package and dependencies
pip install -e .

# Or install dependencies directly
pip install -r requirements.txt
```

### 2. Configure Environment (Optional)

```bash
# Copy example environment file
cp .env.example .env
# Edit .env file if needed
```

Default configuration:
```env
# Server Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=true

# CORS Configuration  
CORS_ORIGINS=http://localhost:3000
```

### 3. Start the Server

```bash
# Using the installed command
assistant-transport-backend

# Or run directly
python main.py

# Or using uvicorn
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```

The server will be available at:
- **API**: http://localhost:8000
- **Health Check**: http://localhost:8000/health
- **Assistant Endpoint**: http://localhost:8000/assistant

## API Endpoints

### `POST /assistant`

Main endpoint that implements the assistant-transport protocol.

**Request Format:**
```json
{
  "commands": [
    {
      "type": "add-message",
      "message": {
        "role": "user",
        "parts": [
          {"type": "text", "text": "Hello!"}
        ]
      }
    }
  ],
  "system": "You are a helpful assistant",
  "tools": {},
  "runConfig": {}
}
```

**Response:** Streaming response using assistant-stream format with static responses.

### `GET /health`

Health check endpoint that returns server status and current conversation state.

### `POST /cancel`

Cancel the current request (placeholder for request cancellation).

## Static Response Patterns

The backend recognizes these message patterns and returns appropriate static responses:

- **Greetings** (`hello`, `hi`) → Welcome message
- **Weather** (`weather`) → Sunny static response
- **What/What is** → Explanation of what the backend does
- **Help** → Available command information
- **Other messages** → Acknowledgment with echo

## Integration with Frontend

This backend works with the `with-assistant-transport` frontend example:

1. Start backend: `python main.py`
2. Start frontend: `cd ../../examples/with-assistant-transport && pnpm dev`
3. Frontend connects to `http://localhost:8000/assistant`

## Project Structure

```
python/assistant-transport-backend/
├── main.py                    # FastAPI server and main entry point
├── pyproject.toml            # Project configuration and dependencies
├── requirements.txt          # Pip requirements file
├── setup.py                  # Automated setup script
├── .env.example             # Environment variables template
└── README.md               # This file
```

## How It Works

### Assistant-Stream Integration

The backend uses `assistant_stream.create_run()` to create a streaming controller that:

1. **Manages State**: Updates conversation state with messages
2. **Streams Text**: Uses `controller.append_text()` for character-by-character streaming
3. **Updates State**: Uses `controller.state` to update the object-stream state
4. **Handles Protocol**: Automatically formats responses for assistant-transport

### State Management

```python
# Initialize state
conversation_state = {
    "messages": [],
    "provider": "static"
}

# Create run controller with state
controller = create_run(conversation_state)

# Update state during processing
controller.state.provider = "processing"
controller.state.messages.append(new_message)

# Stream text responses
for char in response_text:
    controller.append_text(char)
```

## Development

### Running in Development Mode

```bash
# Enable debug mode and auto-reload
DEBUG=true python main.py
```

### Adding Response Patterns

Edit the static response logic in `main.py`:

```python
# Add new patterns
if "goodbye" in user_message:
    response_text = "Goodbye! Thanks for testing the assistant-transport backend!"
```

### Testing

```bash
# Test the health endpoint
curl http://localhost:8000/health

# Test the assistant endpoint
curl -X POST http://localhost:8000/assistant \
  -H "Content-Type: application/json" \
  -d '{"commands":[{"type":"add-message","message":{"role":"user","parts":[{"type":"text","text":"Hello!"}]}}]}'
```

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `HOST` | Server host | `0.0.0.0` |
| `PORT` | Server port | `8000` |
| `DEBUG` | Enable debug mode | `false` |
| `LOG_LEVEL` | Logging level | `info` |
| `CORS_ORIGINS` | Allowed CORS origins | `http://localhost:3000` |

## License

This project is part of the assistant-ui monorepo and follows the same MIT licensing terms.

## Learn More

- [assistant-ui Documentation](https://docs.assistant-ui.com)
- [Assistant Transport Protocol](https://docs.assistant-ui.com/runtimes/assistant-transport)
- [assistant-stream Package](https://github.com/assistant-ui/assistant-ui/tree/main/python/assistant-stream)
- [FastAPI Documentation](https://fastapi.tiangolo.com)

## Links discovered
- [assistant-ui Documentation](https://docs.assistant-ui.com)
- [Assistant Transport Protocol](https://docs.assistant-ui.com/runtimes/assistant-transport)
- [assistant-stream Package](https://github.com/assistant-ui/assistant-ui/tree/main/python/assistant-stream)
- [FastAPI Documentation](https://fastapi.tiangolo.com)

--- python/state-test/README.md ---
# State Management Test

This is a test project for the `assistant-stream` state management functionality. It demonstrates various state operations and updates over time.

## Features

- **Simple State Test**: Basic state updates with primitive values
- **Complex Test**: Nested state updates with objects and arrays
- **String Operations**: String concatenation and method testing
- **List Operations**: List manipulation with append, extend, and other operations
- **Dictionary Operations**: Dictionary manipulation with various methods

## Setup

1. Install the requirements:

   ```
   pip install -r requirements.txt
   ```

2. Run the server:

   ```
   python server.py
   ```

3. Open your browser to [http://localhost:8000](http://localhost:8000)

4. Click on the different test buttons to see state updates in action

## Implementation Details

This test server demonstrates the following state management features:

- Primitive values (strings, numbers, booleans)
- Nested state objects
- String operations (concatenation, methods)
- List operations (append, extend, indexing)
- Dictionary operations (get, setdefault, keys/values)

Each test endpoint updates state over time with various operations to showcase the functionality of the `StateProxy` and `StateManager` classes.


## Links discovered
- [http://localhost:8000](http://localhost:8000)

--- python/assistant-stream/README_LANGGRAPH.md ---
# LangGraph Integration for assistant-stream

This document describes the LangGraph integration for the assistant-stream package.

## Installation

To use the LangGraph integration, install the assistant-stream package with the langgraph extra:

```bash
pip install assistant-stream[langgraph]
```

This will install the required dependencies including `langchain-core`.

## Usage

The main function provided by this integration is `append_langgraph_event`, which allows you to append LangGraph events to the state managed by a RunController.

### Function Signature

```python
def append_langgraph_event(
    controller: Any,
    namespace: str,
    type: str,
    payload: Any
) -> None
```

### Parameters

- **controller**: A RunController instance from LangGraph that contains a `state` attribute
- **namespace**: The namespace for the event (currently not used but required for future compatibility)
- **type**: The type of event, either `"message"` or `"updates"`
- **payload**: The payload for the event, format depends on the event type

### Event Types

#### Message Events (`type="message"`)

For message events, the payload must be a tuple of `(messages, metadata)`:

- **messages**: Can be either a single message or a list of messages (BaseMessage instances from LangChain)
- **metadata**: A dictionary of metadata (currently not used but required)

The function will:

- Create a `messages` array in the state if it doesn't exist
- Convert messages to plain JSON using LangChain's `message_to_dict`
- Merge messages with the same ID:
  - For AIMessageChunk messages, content is appended
  - For other message types, the entire message is replaced
- Append new messages that don't have matching IDs

Example:

```python
from langchain_core.messages import HumanMessage, AIMessage
from assistant_stream import append_langgraph_event

# Single message
message = HumanMessage(content="Hello", id="msg1")
append_langgraph_event(controller, "default", "message", ([message], {}))

# Multiple messages
messages = [
    HumanMessage(content="Hello", id="msg1"),
    AIMessage(content="Hi there!", id="msg2")
]
append_langgraph_event(controller, "default", "message", (messages, {}))
```

#### Updates Events (`type="updates"`)

For updates events, the payload must be a dictionary with the structure:

```python
{
    "node_name": {
        "channel": new_value,
        ...
    },
    ...
}
```

The function will:

- Update channel values for each node
- Create nodes if they don't exist
- Skip updates to the "messages" channel (as these are handled by message events)

Example:

```python
updates = {
    "agent": {
        "status": "thinking",
        "confidence": 0.95
    },
    "retriever": {
        "documents_found": 5
    }
}
append_langgraph_event(controller, "default", "updates", updates)
```

### Important Notes

1. **State Format**: The state must only contain plain JSON objects (lists, dicts, str, int, bool, None). All LangChain messages are automatically converted to JSON format.

2. **Message Merging**: When a message with an existing ID is appended:
   - If both messages are AI messages (type="ai"), the content is concatenated
   - For all other cases, the entire message is replaced

3. **Error Handling**:
   - Raises `ValueError` if the controller doesn't have a `state` attribute
   - Raises `TypeError` if the payload format is invalid
   - Skips `None` messages silently
   - Ignores unknown event types

4. **Thread Safety**: This function modifies the state directly and is not thread-safe. Ensure proper synchronization if using from multiple threads.

## Example Integration

Here's a complete example of using `append_langgraph_event` with a LangGraph RunController:

```python
from assistant_stream import append_langgraph_event
from langchain_core.messages import HumanMessage, AIMessageChunk

class MyRunController:
    def __init__(self):
        self.state = {}

# Initialize controller
controller = MyRunController()

# Add initial message
user_message = HumanMessage(content="What is the weather?", id="user1")
append_langgraph_event(controller, "main", "message", ([user_message], {}))

# Add AI response chunks
ai_chunk1 = AIMessageChunk(content="The weather", id="ai1")
append_langgraph_event(controller, "main", "message", ([ai_chunk1], {}))

ai_chunk2 = AIMessageChunk(content=" is sunny today.", id="ai1")
append_langgraph_event(controller, "main", "message", ([ai_chunk2], {}))

# Update node states
updates = {
    "weather_agent": {
        "status": "completed",
        "temperature": 72
    }
}
append_langgraph_event(controller, "main", "updates", updates)

# Final state
print(controller.state)
# {
#     "messages": [
#         {"type": "human", "content": "What is the weather?", "id": "user1"},
#         {"type": "ai", "content": "The weather is sunny today.", "id": "ai1"}
#     ],
#     "weather_agent": {
#         "status": "completed",
#         "temperature": 72
#     }
# }
```

## Testing

The integration includes comprehensive unit tests. To run them:

```bash
python -m pytest tests/test_langgraph.py
```

The tests cover:

- Message appending and merging
- Updates handling
- Error cases and edge conditions
- Payload validation
- Message normalization


--- python/assistant-transport-backend-langgraph/main.py ---
#!/usr/bin/env python3
"""
Assistant Transport Backend with LangGraph - FastAPI + assistant-stream + LangGraph server
"""

import os
import asyncio
from typing import Dict, Any, List, Optional, Union, Sequence, Annotated
from contextlib import asynccontextmanager
import uvicorn
import json

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from dotenv import load_dotenv

from assistant_stream.serialization import DataStreamResponse
from assistant_stream import RunController, create_run
from assistant_stream.modules.langgraph import append_langgraph_event, get_tool_call_subgraph_state

from langgraph.graph import StateGraph, END
from langgraph.graph.state import CompiledStateGraph
from langgraph.graph import add_messages
from langgraph.prebuilt import ToolNode

from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, ToolMessage, BaseMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from typing import TypedDict

# Load environment variables
load_dotenv()


class MessagePart(BaseModel):
    """A part of a user message."""
    type: str = Field(..., description="The type of message part")
    text: Optional[str] = Field(None, description="Text content")
    image: Optional[str] = Field(None, description="Image URL or data")


class UserMessage(BaseModel):
    """A user message."""
    role: str = Field(default="user", description="Message role")
    parts: List[MessagePart] = Field(..., description="Message parts")


class AddMessageCommand(BaseModel):
    """Command to add a new message to the conversation."""
    type: str = Field(default="add-message", description="Command type")
    message: UserMessage = Field(..., description="User message")


class AddToolResultCommand(BaseModel):
    """Command to add a tool result to the conversation."""
    type: str = Field(default="add-tool-result", description="Command type")
    toolCallId: str = Field(..., description="ID of the tool call")
    result: Dict[str, Any] = Field(..., description="Tool execution result")


class ChatRequest(BaseModel):
    """Request payload for the chat endpoint."""
    commands: List[Union[AddMessageCommand, AddToolResultCommand]] = Field(
        ..., description="List of commands to execute"
    )
    system: Optional[str] = Field(None, description="System prompt")
    tools: Optional[Dict[str, Any]] = Field(None, description="Available tools")
    runConfig: Optional[Dict[str, Any]] = Field(None, description="Run configuration")
    state: Optional[Dict[str, Any]] = Field(None, description="State")


# Define LangGraph state
class GraphState(TypedDict):
    """State for the conversation graph."""
    messages: Annotated[Sequence[BaseMessage], add_messages]


# Define subagent state
class SubagentState(TypedDict):
    """State for the subagent."""
    messages: Annotated[Sequence[BaseMessage], add_messages]
    task: str
    result: str


# Create the Task tool
@tool
def task_tool(task_description: str) -> str:
    """
    Execute a complex task using a subagent.

    Args:
        task_description: Description of the task to perform

    Returns:
        The result of the task execution
    """
    # This is a placeholder - the actual execution will be handled by the subgraph
    return f"Task '{task_description}' will be executed by the subagent."


# Subagent node for executing tasks
async def subagent_node(state: SubagentState) -> Dict[str, Any]:
    """Subagent that executes the task."""
    messages = state.get("messages", [])
    task = state.get("task", "")

    # Initialize a simpler LLM for the subagent
    llm = ChatOpenAI(
        model="gpt-4o-mini",
        temperature=0.7,
        streaming=True
    )

    # Create a prompt for the subagent
    subagent_messages = [
        SystemMessage(content=f"You are a helpful subagent. Execute this task: {task}"),
        HumanMessage(content=f"Please complete the following task: {task}")
    ]

    # Generate response
    if os.getenv("OPENAI_API_KEY"):
        response = await llm.ainvoke(subagent_messages)
        result = response.content
    else:
        result = f"Mock subagent result for task: {task}"

    return {
        "messages": [AIMessage(content=result)],
        "result": result
    }


def create_subagent_graph() -> CompiledStateGraph:
    """Create the subagent graph."""
    workflow = StateGraph(SubagentState)

    # Add the subagent node
    workflow.add_node("execute_task", subagent_node)

    # Set entry and exit points
    workflow.set_entry_point("execute_task")
    workflow.add_edge("execute_task", END)

    return workflow.compile()


async def agent_node(state: GraphState) -> Dict[str, Any]:
    """Main agent node that can call tools."""
    messages = state.get("messages", [])

    # Initialize the LLM with tool binding
    llm = ChatOpenAI(
        model="gpt-4o-mini",
        temperature=0.7,
        streaming=True,
    )

    # Bind the Task tool to the LLM
    llm_with_tools = llm.bind_tools([task_tool])

    # Check if OpenAI API key is set
    if os.getenv("OPENAI_API_KEY"):
        response = await llm_with_tools.ainvoke(messages)
    else:
        # Mock response with a tool call for testing
        print("⚠️ No OpenAI API key found - using mock response with tool call")
        response = AIMessage(
            content="I'll help you with that task.",
            tool_calls=[{
                "id": "task_001",
                "name": "task_tool",
                "args": {"task_description": "Complete the requested task"}
            }]
        )

    return {"messages": [response]}


def should_call_tools(state: GraphState) -> str:
    """Determine if tools should be called."""
    messages = state.get("messages", [])
    if not messages:
        return "end"

    last_message = messages[-1]
    # Check if the last message has tool calls
    if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
        return "tools"

    return "end"


async def tool_executor_node(state: GraphState) -> Dict[str, Any]:
    """Execute tool calls, including Task tool which spawns subagents."""
    messages = state.get("messages", [])
    if not messages:
        return {"messages": []}

    last_message = messages[-1]
    if not hasattr(last_message, 'tool_calls') or not last_message.tool_calls:
        return {"messages": []}

    # Process each tool call
    tool_messages = []
    for tool_call in last_message.tool_calls:
        if tool_call["name"] == "task_tool":
            # Extract task description
            task_description = tool_call["args"].get("task_description", "")

            # Create and run the subagent graph

            # Initialize subagent state
            subagent_state = {
                "messages": [],
                "task": task_description,
                "result": ""
            }

            # Run the subagent
            final_state = await subagent_graph.ainvoke(subagent_state)

            # Create tool message with the result
            tool_message = ToolMessage(
                content=final_state.get("result", "Task completed"),
                tool_call_id=tool_call["id"],
                artifact={"subgraph_state": final_state}
            )
            tool_messages.append(tool_message)
        else:
            # Handle other tools if any
            tool_message = ToolMessage(
                content=f"Executed tool {tool_call['name']}",
                tool_call_id=tool_call["id"]
            )
            tool_messages.append(tool_message)

    return {"messages": tool_messages}


subagent_graph = create_subagent_graph()

def create_graph() -> CompiledStateGraph:
    """Create and compile the LangGraph with subgraph support."""
    # Create the main workflow
    workflow = StateGraph(GraphState)

    # Add nodes
    workflow.add_node("agent", agent_node)
    workflow.add_node("tools", tool_executor_node)

    # Set entry point
    workflow.set_entry_point("agent")

    # Add conditional edges
    workflow.add_conditional_edges(
        "agent",
        should_call_tools,
        {
            "tools": "tools",
            "end": END
        }
    )

    # After tools, go back to agent for potential follow-up
    workflow.add_edge("tools", "agent")

    # Compile the graph
    return workflow.compile()

graph = create_graph()

@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan manager."""
    print("🚀 Assistant Transport Backend with LangGraph starting up...")
    yield
    print("🛑 Assistant Transport Backend with LangGraph shutting down...")


# Create FastAPI app
app = FastAPI(
    title="Assistant Transport Backend with LangGraph",
    description="A server implementing the assistant-transport protocol with LangGraph and subgraphs",
    version="0.2.0",
    lifespan=lifespan,
)

# Configure CORS
cors_origins = ["*"]  # Allow all origins
app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)


@app.post("/assistant")
async def chat_endpoint(request: ChatRequest):
    """Chat endpoint using LangGraph with streaming and subgraph support."""

    async def run_callback(controller: RunController):
        """Callback function for the run controller."""
        # Initialize controller state if needed
        if controller.state is None:
            controller.state = {}
        if "messages" not in controller.state:
            controller.state["messages"] = []

        input_messages = []

        # Process commands
        for command in request.commands:
            if command.type == "add-message":
                # Extract text from parts
                text_parts = [
                    part.text for part in command.message.parts
                    if part.type == "text" and part.text
                ]
                if text_parts:
                    input_messages.append(HumanMessage(content=" ".join(text_parts)))
            elif command.type == "add-tool-result":
                # Handle tool results
                input_messages.append(ToolMessage(
                    content=str(command.result),
                    tool_call_id=command.toolCallId
                ))

        # Add messages to controller state
        for message in input_messages:
            controller.state["messages"].append(message.model_dump())

        # Create initial state for LangGraph
        input_state = {"messages": input_messages}

        # Stream with subgraph support
        async for namespace, event_type, chunk in graph.astream(
            input_state,
            stream_mode=["messages", "updates"],
            subgraphs=True
        ):
            state = get_tool_call_subgraph_state(
                controller,
                subgraph_node="tools",
                namespace=namespace,
                artifact_field_name="subgraph_state",
                default_state={}
            )
            # Append the event normally
            append_langgraph_event(
                state,
                namespace,
                event_type,
                chunk
            )

    # Create streaming response using assistant-stream
    stream = create_run(run_callback, state=request.state)

    return DataStreamResponse(stream)


@app.get("/health")
async def health_check():
    """Health check endpoint."""
    return {"status": "healthy", "service": "assistant-transport-backend-langgraph"}


def main():
    """Main entry point for running the server."""
    host = os.getenv("HOST", "0.0.0.0")
    port = int(os.getenv("PORT", "8010"))
    debug = os.getenv("DEBUG", "false").lower() == "true"
    log_level = os.getenv("LOG_LEVEL", "info").lower()

    print(f"🌟 Starting Assistant Transport Backend with LangGraph on {host}:{port}")
    print(f"🎯 Debug mode: {debug}")
    print(f"🌍 CORS origins: {cors_origins}")

    uvicorn.run(
        "main:app",
        host=host,
        port=port,
        reload=debug,
        log_level=log_level,
        access_log=True,
    )


if __name__ == "__main__":
    main()

--- python/assistant-transport-backend/main.py ---
#!/usr/bin/env python3
"""
Assistant Transport Backend - Simple FastAPI + assistant-stream server

This server implements the assistant-transport protocol with static responses.
"""

import os
import asyncio
import random
from typing import Dict, Any, List, Optional, Union
from contextlib import asynccontextmanager
import uvicorn 

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from dotenv import load_dotenv

from assistant_stream.serialization import DataStreamResponse
from assistant_stream import RunController, create_run

# Load environment variables
load_dotenv()


class MessagePart(BaseModel):
    """A part of a user message."""
    type: str = Field(..., description="The type of message part")
    text: Optional[str] = Field(None, description="Text content")
    image: Optional[str] = Field(None, description="Image URL or data")


class UserMessage(BaseModel):
    """A user message."""
    role: str = Field(default="user", description="Message role")
    parts: List[MessagePart] = Field(..., description="Message parts")


class AddMessageCommand(BaseModel):
    """Command to add a new message to the conversation."""
    type: str = Field(default="add-message", description="Command type")
    message: UserMessage = Field(..., description="User message")


class AddToolResultCommand(BaseModel):
    """Command to add a tool result to the conversation."""
    type: str = Field(default="add-tool-result", description="Command type")  
    toolCallId: str = Field(..., description="ID of the tool call")
    result: Dict[str, Any] = Field(..., description="Tool execution result")


class AssistantRequest(BaseModel):
    """Request payload for the assistant endpoint."""
    commands: List[Union[AddMessageCommand, AddToolResultCommand]] = Field(
        ..., description="List of commands to execute"
    )
    system: Optional[str] = Field(None, description="System prompt")
    tools: Optional[Dict[str, Any]] = Field(None, description="Available tools")
    # Additional fields that may be sent by the frontend
    runConfig: Optional[Dict[str, Any]] = Field(None, description="Run configuration")
    state: Optional[Dict[str, Any]] = Field(None, description="State")


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan manager."""
    print("🚀 Assistant Transport Backend starting up...")
    yield
    print("🛑 Assistant Transport Backend shutting down...")


# Create FastAPI app
app = FastAPI(
    title="Assistant Transport Backend",
    description="A simple server implementing the assistant-transport protocol with static responses",
    version="0.1.0",
    lifespan=lifespan,
)

# Configure CORS
cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)


@app.post("/assistant")
async def assistant_endpoint(request: AssistantRequest): 
    # Create streaming response using assistant-stream
    async def run_callback(controller: RunController):
        """Callback function for the run controller."""
        try:
            print("run_callback")
            await asyncio.sleep(1)

            if (request.commands[0].type == "add-message"):
                controller.state["messages"].append(request.commands[0].message.model_dump())
            if (request.commands[0].type == "add-tool-result"):
                controller.state["messages"][-1]["parts"][-1]["result"] = request.commands[0].result

            await asyncio.sleep(1)

            controller.state["messages"].append({
                "role": "assistant",
                "parts": [
                    {
                        "type": "text",
                        "text": "Hello22"
                    }
                ]
            })

            controller.state["messages"][-1]["parts"].append({
                "type": "tool-call",
                "toolCallId": "tool_" + str(random.randint(0, 1000000)),
                "toolName": "get_weather",
                "argsText": "",
                "done": False,
            })

            await asyncio.sleep(1)

            controller.state["messages"][-1]["parts"][-1]["argsText"] = "{\"location\": \"SF\""

            await asyncio.sleep(1)

            controller.state["messages"][-1]["parts"][-1]["argsText"] =  controller.state["messages"][-1]["parts"][-1]["argsText"] +    "}"
            controller.state["messages"][-1]["parts"][-1]["done"] = True

            await asyncio.sleep(1)

            controller.state["provider"] = "completed"
            print("run_callback3")
        except Exception as e:
            print(f"❌ Error in stream generation: {e}")
            controller.state["provider"] = "error"
            controller.append_text(f"Error: {str(e)}")
    
    # Create streaming response using assistant-stream
    stream = create_run(run_callback, state=request.state)
    
    return DataStreamResponse(stream)


def main():
    """Main entry point for running the server."""
    host = os.getenv("HOST", "0.0.0.0")
    port = int(os.getenv("PORT", "8000"))
    debug = os.getenv("DEBUG", "false").lower() == "true"
    log_level = os.getenv("LOG_LEVEL", "info").lower()
    
    print(f"🌟 Starting Assistant Transport Backend on {host}:{port}")
    print(f"🎯 Debug mode: {debug}")
    print(f"🌍 CORS origins: {cors_origins}")
    
    uvicorn.run(
        "main:app",
        host=host,
        port=port,
        reload=debug,
        log_level=log_level,
        access_log=True,
    )


if __name__ == "__main__":
    main()

--- python/assistant-stream-hello-world/requirements.txt ---
assistant-stream==0.0.7
fastapi==0.115.0


--- python/assistant-transport-backend-langgraph/requirements.txt ---
# Core dependencies
fastapi>=0.100.0
uvicorn[standard]>=0.20.0
assistant-stream>=0.0.28
pydantic>=2.0.0
python-dotenv>=1.0.0

# LangGraph dependencies
langgraph>=0.2.0
langchain>=0.2.0
langchain-core>=0.2.0
langchain-openai>=0.1.0

# Development dependencies
pytest>=7.0.0
pytest-asyncio>=0.21.0
black>=23.0.0
isort>=5.12.0
mypy>=1.0.0
ruff>=0.1.0

--- python/assistant-transport-backend/requirements.txt ---
# Core dependencies
fastapi>=0.100.0
uvicorn[standard]>=0.20.0  
assistant-stream>=0.0.28
pydantic>=2.0.0
python-dotenv>=1.0.0

# Development dependencies
pytest>=7.0.0
pytest-asyncio>=0.21.0
black>=23.0.0
isort>=5.12.0
mypy>=1.0.0
ruff>=0.1.0
