Showcase

Tool use

Tool-calling agent that browses a mocked product catalog.

toolschat
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, toolsFor } from './_shared/mock-adapter'import { ToolBadge } from './_shared/tool-badge'const TURNS = [  {    toolCalls: [      {        name: 'search_catalog',        args: { query: 'wireless headphones' },        result: { items: [{ id: 'h-42', name: 'Aurora ANC', price: 189 }] },        durationMs: 420,      },    ],    text: 'Aurora ANC ($189) is the best match — active noise cancellation, 30h battery.',  },  {    toolCalls: [      {        name: 'get_inventory',        args: { sku: 'h-42' },        result: { stock: 12, warehouse: 'LAX' },        durationMs: 260,      },    ],    text: '12 units in the LAX warehouse, ships same-day.',  },]export function ToolUseChat() {  const adapter = useMemo(() => createMockAdapter(TURNS), [])  const tools = useMemo(() => toolsFor(TURNS), [])  const chat = useChat({    adapter,    tools,    maxToolIterations: 1,    initialMessages: [      initialAssistant(        "I can browse a product catalog. Ask about an item and I'll call the right tool.",      ),    ],  })  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          .filter((m) => m.role !== 'tool')          .map((m) => (            <div key={m.id} className="flex flex-col gap-1.5">              {m.toolCalls?.map((t) => (                <ToolBadge key={t.id} call={t} />              ))}              {m.content ? <Message message={m} /> : null}            </div>          ))}      </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(),  }}
'use client'import type { ToolCall } from '@agentskit/core'/** * Compact tool-call badge that mirrors the home hero demo: * `✓ name({ args }) Nms` in a green pill when complete, blue with a spinner * while pending. Use inside <Message>{m.toolCalls?.map(...)}</Message>. */export function ToolBadge({ call }: { call: ToolCall }) {  const done = call.status === 'complete' || call.status === 'error'  const error = call.status === 'error'  const args = formatArgs(call.args)  return (    <div      data-ak-tool-badge      className={`inline-flex max-w-full items-start gap-2 rounded-md border px-2.5 py-1 font-mono text-xs ${        error          ? 'border-ak-red/30 bg-ak-red/5 text-ak-red'          : done          ? 'border-ak-green/30 bg-ak-green/5 text-ak-green'          : 'border-ak-blue/30 bg-ak-blue/5 text-ak-blue'      }`}    >      <span className="mt-0.5 shrink-0">        {done ? (error ? '✗' : '✓') : <Spinner />}      </span>      <span className="min-w-0 break-all">        {call.name}        {args ? `(${args})` : '()'}      </span>    </div>  )}function formatArgs(raw: Record<string, unknown> | string | undefined): string {  if (!raw) return ''  const parsed =    typeof raw === 'string'      ? (() => {          try {            return JSON.parse(raw)          } catch {            return raw          }        })()      : raw  if (!parsed || typeof parsed !== 'object') return String(parsed)  const entries = Object.entries(parsed)  if (entries.length === 0) return ''  return entries.map(([k, v]) => `${k}: ${stringify(v)}`).join(', ')}function stringify(value: unknown): string {  if (typeof value === 'string') return `"${value}"`  if (value === null) return 'null'  if (typeof value === 'object') return JSON.stringify(value)  return String(value)}function Spinner() {  return (    <span className="inline-block h-3 w-3 animate-spin rounded-full border-2 border-ak-blue border-t-transparent" />  )}
More examples
See all →