Showcase

Basic chat

Streaming chat with a mock adapter. Zero config, runs in-browser.

streamingchat
Live preview
Source
'use client'import { useMemo } from 'react'import { useChat, ChatContainer, Message, InputBar } from '@agentskit/react'import '@/styles/agentskit-theme.css'import { createMockAdapter, initialAssistant } from './_shared/mock-adapter'const RESPONSES = [  "Great question! Streaming works by sending tokens incrementally. AgentsKit flushes chunks into the UI on each animation frame so the interface stays responsive at high token rates.",  "`useChat` from @agentskit/react wraps state, streaming, abort, and retries. Internally it uses a ChatController from @agentskit/core backed by the adapter's StreamSource.",  "Tokens are atomic units LLMs work with — roughly 3–4 characters. AgentsKit batches chunks and the default Message component renders each incremental update without layout thrash.",  "Context windows are measured in tokens. AgentsKit prunes older messages when you approach the limit so long conversations stay coherent without hitting provider errors.",]export function BasicChat() {  const adapter = useMemo(    () => createMockAdapter(RESPONSES.map((text) => ({ text }))),    [],  )  const chat = useChat({    adapter,    initialMessages: [      initialAssistant(        "Hi! I'm your AI assistant. Ask me anything about streaming, React hooks, or how AgentsKit works under the hood.",      ),    ],  })  return (    <div      data-ak-example      className="flex h-[420px] flex-col overflow-hidden rounded-lg border border-ak-border bg-ak-surface"    >      <ChatContainer className="flex-1 space-y-2 p-4">        {chat.messages.map((m) => (          <Message key={m.id} message={m} />        ))}      </ChatContainer>      <InputBar chat={chat} />    </div>  )}
import type { AdapterFactory, StreamChunk, ToolDefinition } from '@agentskit/core'export type ToolCallEmit = {  name: string  args?: Record<string, unknown>  result?: unknown  durationMs?: number}export type Turn = {  /** Text streamed as the assistant reply. */  text: string  /** Optional tool calls to emit before the text. The runtime executes each via   *  the matching tool stub exposed by `toolsFor(turns)`. */  toolCalls?: ToolCallEmit[]  /** Optional reasoning stream emitted before tool calls / text. */  reasoning?: string}export function createMockAdapter(turns: Turn[], cps = 80): AdapterFactory {  let idx = 0  return {    createSource: () => ({      stream: async function* (): AsyncIterableIterator<StreamChunk> {        const turn = turns[idx % turns.length]        idx += 1        if (turn.reasoning) {          for (const ch of turn.reasoning) {            await sleep(1000 / cps)            yield { type: 'reasoning', content: ch }          }        }        if (turn.toolCalls) {          for (const call of turn.toolCalls) {            yield {              type: 'tool_call',              toolCall: {                id: `call-${Math.random().toString(36).slice(2, 8)}`,                name: call.name,                args: JSON.stringify(call.args ?? {}),              },            }          }        }        for (const ch of turn.text) {          await sleep(1000 / cps)          yield { type: 'text', content: ch }        }        yield { type: 'done' }      },      abort() {},    }),    capabilities: { streaming: true, tools: true },  }}/** * Build a registry of tool stubs whose `execute()` resolves to the mocked * result declared for that tool name in `turns`. When the controller sees a * `tool_call` chunk from the mock adapter it looks up the name here, runs the * stub (with a simulated latency), and emits `tool_result`. */export function toolsFor(turns: Turn[]): ToolDefinition[] {  const byName = new Map<string, ToolCallEmit>()  for (const t of turns) {    for (const c of t.toolCalls ?? []) {      if (!byName.has(c.name)) byName.set(c.name, c)    }  }  return Array.from(byName.values()).map<ToolDefinition>((call) => ({    name: call.name,    description: `Mock ${call.name}`,    schema: {},    async execute() {      if (call.durationMs) await sleep(call.durationMs)      return JSON.stringify(call.result ?? { ok: true })    },  }))}function sleep(ms: number) {  return new Promise<void>((r) => setTimeout(r, ms))}export function initialAssistant(content: string) {  return {    id: 'init',    role: 'assistant' as const,    content,    status: 'complete' as const,    createdAt: new Date(),  }}
More examples
See all →