Documents
migration
migration
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Mar 17, 2026

If you already have an assistant-ui web app, most of your code transfers directly. The runtime core is shared between @assistant-ui/react and @assistant-ui/react-ink via @assistant-ui/core — only the UI layer changes.

What stays the same#

  • Runtime setupuseLocalRuntime, ChatModelAdapter, and all runtime options work identically.
  • AI SDK integration@assistant-ui/react-ai-sdk works with React Ink. Your runtime setup transfers directly.
  • Tool definitionsuseAssistantTool, makeAssistantTool, and tool UI renderers use the same API.
  • State hooksuseAuiState, useAui, and selector patterns are the same.
  • Backend code — Your API routes, streaming endpoints, and server-side logic need zero changes.

What changes#

Web (@assistant-ui/react)Terminal (@assistant-ui/react-ink)
AssistantRuntimeProviderAssistantRuntimeProvider (same name, from @assistant-ui/react-ink)
DOM primitives (div, button, input)Ink primitives (Box, Text, TextInput)
CSS / Tailwind stylingInk's flexbox + ANSI colors
@assistant-ui/ui (shadcn components)Build your own terminal UI with primitives
@assistant-ui/react-markdown@assistant-ui/react-ink-markdown (ANSI-rendered markdown)

Step-by-step#

Install the React Ink package#

npm install @assistant-ui/react-ink ink react

Keep your runtime hook#

Your useLocalRuntime setup works as-is — no changes needed:

// This file is identical to your web version
import { useLocalRuntime, type ChatModelAdapter } from "@assistant-ui/react-ink";

export function useAppRuntime(adapter: ChatModelAdapter) {
  return useLocalRuntime(adapter);
}
If you use a monorepo, you can share the adapter between web and terminal projects directly.

Replace the provider#

// Before (web)
// import { AssistantRuntimeProvider } from "@assistant-ui/react";

// After (Terminal) — same name, different package
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";

export function App() {
  const runtime = useAppRuntime(adapter);
  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your terminal UI */}
    </AssistantRuntimeProvider>
  );
}

Rebuild the UI layer#

This is the main migration effort. Web components don't render in the terminal. Replace them with Ink primitives:

// Web — DOM-based
// import { Thread } from "@/components/assistant-ui/thread";

// Terminal — use Ink primitives
import {
  ThreadPrimitive,
  ComposerPrimitive,
  MessagePrimitive,
} from "@assistant-ui/react-ink";
import { Box } from "ink";

function MyMessage() {
  return (
    <Box marginBottom={1}>
      <MessagePrimitive.Parts />
    </Box>
  );
}

function ChatScreen() {
  return (
    <ThreadPrimitive.Root>
      <ThreadPrimitive.Messages>
        {() => <MyMessage />}
      </ThreadPrimitive.Messages>
      <Box borderStyle="round" borderColor="gray" paddingX={1}>
        <ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
      </Box>
    </ThreadPrimitive.Root>
  );
}

See the Primitives reference for the full list of available components.

Monorepo code sharing#

In a monorepo, you can share everything except the UI layer:

packages/
  shared/
    hooks/ ← useAppRuntime with ChatModelAdapter (shared)
    tools/ ← tool definitions (shared)
  web/
    components/ ← DOM-based UI
  terminal/
    components/ ← Ink-based terminal UI

The runtime hook, tool definitions, and backend code are platform-agnostic. Only the UI components need separate implementations for web and terminal.