Kanzo UI
AI

AI

What knows a model is on the other end — Tasks, where a small model fills an artefact that oracles check; a field the model helps fill; a conversation, with data or without — over the AI SDK, reached through @kanzo-tech/llm, a model in the page. A sibling of @kanzo-tech/ui, never part of it.

@kanzo-tech/ai ships one pattern and the surfaces built on it, one door each:

ForPage
defineTask + runTask + useTaskA Task: a small model fills a small artefact under a schema or a grammar, oracles that are facts check it, and a bounded loop repairs it or asks the person — @kanzo-tech/ai/taskTasks
AssistProvider + AssistA field the model helps fill: ghost text in a Textarea, whole values under an Input, items for a TagsInputAssist
Chat + useAgentChatA conversation, whole: transcript, streaming markdown, reasoning, tool calls, the composerChat
Working, ModelBadge, Checkpoint, ChainOfThought, SourcesA conversation whose turns are the host's, from Chat's parts and these: work under way, a model in the page as it loads, a point to go back to, the steps of the work, the sources a thread rests on — and @kanzo-tech/ui's Question for a decisionA guided chat
useAskChat + AnswerCardA conversation with data: Ask, which answers with a dashboard tile over a relation of the page's DuckDB, and the card each answer is drawn in — @kanzo-tech/ai/dataAsk your data

Chat is the door, and it is the whole conversation wired: five compounds a host used to assemble by hand became one. The parts it draws with — the scrolling transcript, the message, the tool frame, the reasoning fold, the composer — are exported beside it, for a transcript that is not Chat's: an agent's log, a review pane, a guided conversation whose turns are the host's. They were internal until a host needed them one at a time, which is the evidence that reverses wire them for everyone: a part with a second arrangement is a part. The transcript and the composer came out last, when keasy's guided chat drew both by hand — the scroll as an effect that follows every turn, the composer from InputGroup with Enter wired again. The ✨ stays internal, since nothing draws it but Assist, and would come out the same way. The parts lists them.

The model behind all of them is the AI SDK's, asked through @kanzo-tech/llm's two doors — in the page, on WebGPU. The SDK's own names a host imports from ai.

Why it is not in the library

The line is not is it AI. It is does the component know a model exists. A Button does not, and a Collapsible does not — which is why the transcript is built from both and neither moved. Assist takes a model; Chat draws parts whose role is assistant. That is domain knowledge, and the first admission rule keeps the generic vocabulary free of it.

The blunter reason is that the root barrel is a one-way door: a consumer who wants a Button must not pay for a transcript. A separate package makes that structural. It depends on @kanzo-tech/ui; nothing in ui may depend on it.

Installing it

pnpm add @kanzo-tech/ai @kanzo-tech/llm ai @ai-sdk/react

Its other peers, all required:

pnpm add @kanzo-tech/ui @ark-ui/react react react-dom lucide-react

ai and @ai-sdk/react are peers rather than dependencies for the reason @kanzo-tech/llm gives: the host's agent and these components must share one UIMessage, one transport and one useChat, and two copies of the SDK are two type universes that do not assign to each other. @kanzo-tech/ui and Ark are peers because two copies of Ark are two portals and two focus traps. streamdown, which draws the markdown, is a dependency: nothing outside Chat names it.

@kanzo-tech/ai/data is a subpath for the reason /analytics is one in @kanzo-tech/ui: it draws with the analytics and table layers, whose engines are optional peers, and a host that only chats must not install DuckDB to import Chat. Ask your data has its peers.

The stylesheet is one line after @kanzo-tech/ui's — @import "@kanzo-tech/ai/tailwind.css" — and carries sources only: the tokens and variants are ui's.

The middleware pattern

@kanzo-tech/llm holds what the AI SDK lacks and none of what it has: the third party is a required peer, imported by name, and what is ours is the door it does not have — stream and generate, each asking once, and localModel, a model in the page. Three layers:

@kanzo-tech/ai      Assist, Chat, useAgentChat       React; draws
@kanzo-tech/llm     stream, generate, localModel     no React; every model call, once
ai, @ai-sdk/react   the AI SDK: agents, tools, types installed once, imported by name

So a host's AI imports are a few lines:

import { localModel } from "@kanzo-tech/llm/local";
import { ToolLoopAgent, tool } from "ai";
import { Assist, AssistProvider, Chat, useAgentChat } from "@kanzo-tech/ai";

What it does not do

  • It does not choose a model. A model is a LanguageModel the host passes in — localModel, in the page, or the double in a test. Assist asks whatever AssistProvider is given; Chat draws whatever its agent streams.
  • It does not persist anything. The conversation is the AI SDK's chat state, and a field's value is the host's own state.

On this page