Tool calls & reasoning
Agents think before they act and invoke tools to get things done. The tools and reasoning fields on ChatMessage bring both inline — one object per tool call, updated in place as the agent streams its output.
How it works
Section titled “How it works”Both tools and reasoning live directly on the ChatMessage object you pass to <kai-chat> or <kai-message>. Update the message in place 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';
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', content: prompt }, { id: aId, role: 'assistant', content: '', reasoning: { text: '', label: 'Agent reasoning' }, tools: [ { type: 'search_web', state: 'input-streaming', // tool call incoming — inputs not yet complete toolCallId: callId, }, ], },];chat.loading = true;
// As the stream arrives, mutate the message array with new array references// to drive re-renders. Each state transition is a new array assigned to chat.messages.
// Input fully received → update state to 'input-available'chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, tools: [{ ...m.tools[0], state: 'input-available', input: { query: 'kitn-chat docs' } }], } : m,);
// Tool executed successfully → state becomes 'output-available'chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, tools: [{ ...m.tools[0], state: 'output-available', output: { results: ['…'] } }], } : m,);
// Tool failed → state becomes 'output-error'// chat.messages = chat.messages.map((m) =>// m.id === aId// ? { ...m, tools: [{ ...m.tools[0], state: 'output-error', errorText: 'Rate limit exceeded' }] }// : m// );
// Stream the final reply into contentfor await (const token of streamFromYourModel(prompt)) { chat.messages = chat.messages.map((m) => m.id === aId ? { ...m, content: m.content + token } : m, );}chat.loading = false;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 single { text, label? } object — 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>.