AI SDK adapter
useKitTurn maps useChat messages onto Thinking and Tool call. It imports nothing from the SDK.
# Preview
| Tool part state | ToolCall status |
|---|---|
| input-streaming | PENDING |
| input-available | RUNNING |
| approval-requested | AWAITING |
| approval-responded (approved) | RUNNING |
| approval-responded (denied) | DENIED |
| output-available (preliminary) | RUNNING |
| output-available | DONE |
| output-error | ERROR |
| output-denied | DENIED |
Steps, in part order: one REASONING per reasoning part, one TOOL NAME per tool part, one WRITING for the first text part. A data-kit-step part with { label } names its own step.
Timings are client-observed. AI SDK parts carry no timestamps, so the adapter stamps the clock when it first sees a part and when its state changes. They include network latency. A message loaded from history was never seen live, so it gets no durations at all: the readouts simply do not render.
Tokens come only from message metadata at finish. There is no live token counter, because the client never receives one.
// app/api/chat/route.ts: send usage so TOKENS can render
const result = streamText({ model, messages: convertToModelMessages(messages), tools });
return result.toUIMessageStreamResponse({
messageMetadata: ({ part }) =>
part.type === "finish" ? { usage: part.totalUsage } : undefined,
});# Install
npx shadcn@latest add https://kit.ulasalyesil.com/r/ai-sdk.jsonAdd @import "../styles/hud.css"; to your global CSS, after @import "tailwindcss". Zero dependencies beyond React.
# Usage
import { useKitTurn } from "@/lib/ai-sdk";
const { messages, status } = useChat();
const last = messages.findLast((m) => m.role === "assistant");
const turn = useKitTurn({ message: last, status });
{turn.thinking && <Thinking {...turn.thinking} />}
{turn.tools.map((t) => <ToolCall key={t.toolCallId} {...t} />)}# Props
| Prop | Type | Default | Description |
|---|---|---|---|
| message | UIMessage | undefined | Usually the last assistant message. Structural type, AI SDK v5 to v7. | |
| status | "submitted" | "streaming" | "ready" | "error" | useChat's status. | |
| clock | () => number | performance.now | When each part is first observed and when it changes state. |
| labelFor | (part) => string | undefined | Override step labels. A data-kit-step part names its own step. |
# Presets
[ TOOL: SEARCH ][PENDING]
search pendingstatus="pending"[ TOOL: SEARCH ][RUNNING]
search runningstatus="running"[ TOOL: SEARCH ][AWAITING]
search awaiting approvalstatus="awaiting"[ TOOL: SEARCH ][DONE]
search donestatus="done"[ TOOL: SEARCH ][ERROR]
Request timed out
status="error"[ TOOL: SEARCH ][DENIED]
search deniedstatus="denied"# Agent prompt
Paste into Claude Code, Cursor or any coding agent to wire it in.
Add the —kit AI SDK adapter adapter to this project.
1. Run: npx shadcn@latest add https://kit.ulasalyesil.com/r/ai-sdk.json
2. Make sure the global CSS imports styles/hud.css after tailwindcss.
3. Import: import { useKitTurn } from "@/lib/ai-sdk";
4. Props:
- message: UIMessage | undefined. Usually the last assistant message. Structural type, AI SDK v5 to v7.
- status: "submitted" | "streaming" | "ready" | "error". useChat's status.
- clock: () => number. When each part is first observed and when it changes state.
- labelFor: (part) => string | undefined. Override step labels. A data-kit-step part names its own step.
5. Truth rule: only pass values you actually have. Leave a prop out rather than
passing a placeholder; its readout will not render.
6. With the Vercel AI SDK: npx shadcn@latest add https://kit.ulasalyesil.com/r/ai-sdk.json
then: const turn = useKitTurn({ message: lastAssistant, status })