Provider fanout
Same prompt across openai, anthropic, gemini, ollama. Compare quality + cost.
'use client'import { useMemo, useRef, useState } from 'react'import { motion } from 'motion/react'import { createAdapter } from '@agentskit/adapters/createAdapter'import type { AdapterFactory } from '@agentskit/core'type Provider = { id: string label: string model: string cps: number costPerKtok: number reply: string}const PROVIDERS: Provider[] = [ { id: 'anthropic', label: 'anthropic', model: 'claude-sonnet-4-6', cps: 95, costPerKtok: 0.015, reply: 'Three principles: clear types, explicit error paths, observable side-effects.', }, { id: 'openai', label: 'openai', model: 'gpt-4o', cps: 80, costPerKtok: 0.01, reply: 'Pick boring tech, keep state at the edges, write tests where it hurts.', }, { id: 'gemini', label: 'gemini', model: 'gemini-2.5-pro', cps: 110, costPerKtok: 0.005, reply: 'Favor simplicity. Measure before optimizing. Ship small, ship often.', }, { id: 'ollama', label: 'ollama (local)', model: 'llama3', cps: 40, costPerKtok: 0, reply: 'Reduce coupling. Make the change easy, then make the easy change.', },]function makeAdapter(p: Provider): AdapterFactory { return createAdapter({ async send() { const enc = new TextEncoder() return new ReadableStream<Uint8Array>({ async start(ctrl) { for (const ch of p.reply) { await new Promise(r => setTimeout(r, 1000 / p.cps)) ctrl.enqueue(enc.encode(JSON.stringify({ type: 'text', content: ch }) + '\n')) } ctrl.enqueue(enc.encode(JSON.stringify({ type: 'done' }) + '\n')) ctrl.close() }, }) }, async *parse(stream) { const reader = stream.getReader() const dec = new TextDecoder() let buf = '' while (true) { const { done, value } = await reader.read() if (done) break buf += dec.decode(value, { stream: true }) const lines = buf.split('\n') buf = lines.pop() ?? '' for (const line of lines) { if (line.trim()) yield JSON.parse(line) } } }, })}type RaceState = { text: string; ms: number; done: boolean; tokens: number }const EMPTY: RaceState = { text: '', ms: 0, done: false, tokens: 0 }export function ProviderFanout() { const adapters = useMemo( () => Object.fromEntries(PROVIDERS.map(p => [p.id, makeAdapter(p)] as const)), [], ) const [prompt, setPrompt] = useState('Three principles for writing maintainable code.') const [running, setRunning] = useState(false) const [state, setState] = useState<Record<string, RaceState>>( () => Object.fromEntries(PROVIDERS.map(p => [p.id, { ...EMPTY }])), ) const startRef = useRef(0) const run = async () => { setRunning(true) startRef.current = performance.now() setState(Object.fromEntries(PROVIDERS.map(p => [p.id, { ...EMPTY }]))) await Promise.all( PROVIDERS.map(async p => { const src = adapters[p.id].createSource({ messages: [{ role: 'user', content: prompt }] as any }) for await (const ch of src.stream()) { if (ch.type === 'text' && 'content' in ch) { setState(prev => ({ ...prev, [p.id]: { ...prev[p.id], text: prev[p.id].text + (ch.content as string), tokens: prev[p.id].tokens + 1, ms: Math.round(performance.now() - startRef.current), }, })) } if (ch.type === 'done') { setState(prev => ({ ...prev, [p.id]: { ...prev[p.id], done: true } })) } } }), ) setRunning(false) } const max = Math.max(1, ...Object.values(state).map(s => s.ms)) return ( <div data-ak-example className="flex flex-col gap-3 rounded-lg border border-ak-border bg-ak-surface p-4"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-ak-graphite">@agentskit/adapters · createAdapter · fan-out same prompt</span> <span className="text-ak-graphite">{PROVIDERS.length} providers</span> </div> <div className="flex gap-2"> <input value={prompt} onChange={e => setPrompt(e.target.value)} className="flex-1 rounded-md border border-ak-border bg-ak-midnight px-3 py-1.5 font-mono text-xs text-ak-foam outline-none focus:border-ak-blue" /> <button type="button" onClick={run} disabled={running} className="rounded-md bg-ak-blue/20 px-3 py-1.5 font-mono text-xs text-ak-blue disabled:opacity-50" > {running ? 'racing…' : '▶ fan out'} </button> </div> <div className="grid grid-cols-1 gap-2 md:grid-cols-2"> {PROVIDERS.map(p => { const s = state[p.id] const cost = ((s.tokens / 1000) * p.costPerKtok).toFixed(5) return ( <div key={p.id} className="rounded-md border border-ak-border bg-ak-midnight p-3"> <div className="mb-2 flex items-center justify-between font-mono text-[11px]"> <div className="flex items-center gap-2"> <span className={`h-2 w-2 rounded-full ${s.done ? 'bg-ak-green' : running ? 'animate-pulse bg-[#f0b429]' : 'bg-ak-border'}`} /> <span className="text-ak-foam">{p.label}</span> <span className="text-ak-graphite">{p.model}</span> </div> <div className="flex gap-2 text-ak-graphite"> <span>{s.ms}ms</span> <span className="text-ak-green">${cost}</span> </div> </div> <div className="mb-2 h-1 overflow-hidden rounded bg-ak-surface"> <motion.div initial={false} animate={{ width: `${(s.ms / max) * 100}%` }} className={`h-full ${s.done ? 'bg-ak-green' : 'bg-ak-blue'}`} /> </div> <div className="min-h-[60px] font-mono text-[11px] text-ak-foam"> {s.text} {!s.done && running && ( <span className="ml-0.5 inline-block h-3 w-[2px] translate-y-0.5 animate-pulse bg-ak-blue" /> )} </div> </div> ) })} </div> <div className="font-mono text-[10px] text-ak-graphite"> each adapter built via createAdapter — same prompt, parallel streams, real per-provider latency + cost </div> </div> )}Basic chat
Streaming chat with a mock adapter. Zero config, runs in-browser.
streamingchatTool use
Tool-calling agent that browses a mocked product catalog.
toolschatRAG
Retrieval-augmented chat with inline source citations.
ragchatCode assistant
Code-aware chat with syntax-highlighted output.
codechatMarkdown chat
Rich Markdown rendering in assistant responses.
markdownchatSupport bot
Chat with escalation, memory, and confirmation gates.
supportmemorytoolsMulti-agent
Planner + worker + reviewer topology.
multi-agentMulti-model
Switch providers on the fly in a single conversation.
multi-modelchatAgent actions
Streaming UI with live tool-call visualization.
toolsstreamingshadcn/ui chat
AgentsKit styled with shadcn/ui tokens.
chatdesign-systemMaterial UI chat
AgentsKit styled with MUI components.
chatdesign-systemSandbox runner
Agent-emitted code runs isolated in E2B / WebContainer.
sandboxtoolscodeObservability trace
Live span tree with tokens, latency, cost. Exports to LangSmith / OTEL.
observabilityproductionPersistent memory
Cross-session recall with sqlite, redis, or lancedb backends.
memorypersistenceEval suite
Run regression tests, track accuracy / latency / cost in CI.
evalproductionciSlack integration
Agent posts to Slack via @agentskit/tools.
integrationstoolsInk terminal
Same controller, rendered in your terminal via Ink.
inkcliterminalSkill swap
Hot-swap personas mid-conversation: researcher, critic, planner.
skillspromptsRuntime ReAct
Standalone agent runtime — no UI required. ReAct loop with tools + memory.
runtimemulti-agentRAG with citations
Retrieve top-k chunks with scores and inline cite refs.
ragcitations