Tool calls & reasoning
Agents think before they act and invoke tools to get things done. reasoning and tool parts in a message’s parts array bring both inline — one part per tool call, updated in place as the agent streams its output.
How it works
Section titled “How it works”Both tool and reasoning are parts inside the parts array of the ChatMessage object you pass to <kai-chat> or <kai-message>. Update the matching part in place (immutably) as the agent streams. Since the message carries arrays, you assign it in JavaScript rather than as an HTML attribute.
import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-chat');
const chat = document.getElementById('chat');const aId = crypto.randomUUID();const callId = crypto.randomUUID();
// Append an empty assistant message the moment the agent startschat.messages = [ ...chat.messages, { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: prompt }] }, { id: aId, role: 'assistant', parts: [ { type: 'reasoning', text: '', label: 'Agent reasoning' }, { type: 'tool', tool: { type: 'search_web', state: 'input-streaming', // tool call incoming — inputs not yet complete toolCallId: callId, }, }, ], },];chat.loading = true;
// As the stream arrives, replace the matching part with a new array reference// to drive re-renders. Each state transition reassigns chat.messages.
// Input fully received → update state to 'input-available'chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, parts: m.parts.map((p) => p.type === 'tool' ? { ...p, tool: { ...p.tool, state: 'input-available', input: { query: 'AI/UI docs' } } } : p, ), } : m,);
// Tool executed successfully → state becomes 'output-available'chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, parts: m.parts.map((p) => p.type === 'tool' ? { ...p, tool: { ...p.tool, state: 'output-available', output: { results: ['…'] } } } : p, ), } : m,);
// Tool failed → state becomes 'output-error'// chat.messages = chat.messages.map((m) =>// m.id === aId// ? { ...m, parts: m.parts.map((p) => p.type === 'tool' ? { ...p, tool: { ...p.tool, state: 'output-error', errorText: 'Rate limit exceeded' } } : p) }// : m// );
// Stream the final reply into a text part, appended after reasoning + toollet answer = '';for await (const token of streamFromYourModel(prompt)) { answer += token; chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, parts: [...m.parts.filter((p) => p.type !== 'text'), { type: 'text', text: answer }] } : m, );}chat.loading = false;The ergonomic path is createAssistantStream from @kitn.ai/ui/state: its upsertTool(toolCallId, patch) and appendReasoning(delta) handle the part-lookup and new-reference plumbing above for you.
Tool lifecycle — four states:
state | Rendered as | When to set it |
|---|---|---|
input-streaming | Spinning loader, “Processing” badge | Tool call chunk received; input is still arriving |
input-available | Settings icon, “Ready” badge | Input complete; tool is executing |
output-available | Check icon, “Completed” badge | Tool returned successfully |
output-error | X icon, “Error” badge + errorText | Tool threw or returned an error |
reasoning is a { type: 'reasoning', text, label? } part — append tokens to text as they stream in. The block auto-expands while reasoning is in progress if you use <kai-reasoning streaming> directly; via <kai-chat> or <kai-message> the block is collapsible once complete.
<kai-thinking-bar> is the pre-reasoning status bar — show it before the first reasoning token arrives. It fires kai-stop when the user clicks “Answer now” (requires stoppable):
<kai-thinking-bar text="Thinking…" stoppable stop-label="Answer now"></kai-thinking-bar>
<script type="module"> document.querySelector('kai-thinking-bar').addEventListener('kai-stop', () => { abortController.abort(); });</script>Next steps
Section titled “Next steps”- Drop-in chat — the full
kai-submitstreaming loop. kai-messagereference — completeChatMessageshape,actionsReveal,proseSize.kai-toolreference —toolproperty shape,openflag, state tokens.kai-reasoningreference —streamingauto-expand,kai-open-change.kai-thinking-barreference —text,stoppable,kai-stop.- Compose your own shell — lay out the thread and composer without
<kai-chat>.