ToolHost
ToolHost bridges tool calls to interactive UI. When a model asks for confirmation, a choice, or form input, ToolHost renders the structured UI in the browser and returns the user's response as a ToolResult.
Concepts
When a model emits a tool call such as confirm-action or fill-form, ToolHost compiles it into a standard SlexExpression and mounts it using the core runtime. The mounted UI uses submit:actions to submit the user's input and settle a Promise.
ToolHost is separate from display-oriented slex fences. Display components render information; ToolHost components collect structured user input and return it programmatically.
Submit
submit:actions reads the fields listed in returnKeys, calls the ToolHost runtime, and resolves the ToolRenderHandle.promise with a ToolResult.
Do not use submit in display fences or component demos. Use button for ordinary interactions; use submit:actions only when the host is waiting for a structured tool result.
Public API
renderToolCall(call, container) → ToolRenderHandle
Compiles a tool call into a SlexExpression, mounts it, and returns a handle.
function renderToolCall(call: ToolCall, container: HTMLElement): ToolRenderHandle;
type ToolRenderHandle = {
promise: Promise<ToolResult>; // Resolves when user submits or ignores
dispose: () => void; // Clean up the rendered UI
};
type ToolResult =
| { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
| { toolCallId?: string; toolName: string; status: "ignored"; value: null };
Usage:
import { renderToolCall } from "slexkit";
const handle = renderToolCall(
{
id: "call_001",
name: "confirm-action",
arguments: {
title: "Delete file?",
description: "This action cannot be undone.",
confirmLabel: "Delete",
requireReason: true,
},
},
document.getElementById("tool-container"),
);
handle.promise.then((result) => {
console.log(result.status); // "submitted" or "ignored"
console.log(result.value); // { confirmed: true, reason: "Obsolete file" }
handle.dispose();
});
registerToolTemplate(name, compiler)
Register a custom tool template. The compiler function receives the tool arguments and returns a SlexExpression.
function registerToolTemplate(name: string, compiler: ToolTemplateCompiler): void;
type ToolTemplateCompiler<TArgs = Record<string, unknown>> = (
args: TArgs,
runtime: ToolRuntime,
call: ToolCall,
) => SlexExpression;
type ToolRuntime = {
submit: (value: Record<string, unknown>) => void;
ignore: () => void;
};
Built-in templates
confirm-action
Yes/no confirmation dialog. Supports an optional reason field.
Arguments (ConfirmActionArguments):
| Field | Type | Default | Description |
|---|---|---|---|
title |
string |
"Confirm action" |
Dialog title |
description |
string |
— | Optional explanation text |
confirmLabel |
string |
"Confirm" |
Submit button label |
ignoreLabel |
string |
"Ignore" |
Cancel button label |
requireReason |
boolean |
false |
Show a reason text input |
reasonLabel |
string |
"Reason" |
Label for the reason input |
reasonPlaceholder |
string |
"Add a reason" |
Placeholder for the reason input |
Result value:
| Key | Type | Description |
|---|---|---|
confirmed |
boolean |
Always true when submitted |
reason |
string |
User's reason text (only if requireReason: true) |
Example:
renderToolCall({
name: "confirm-action",
arguments: {
title: "Delete project?",
description: "This will permanently delete all files.",
confirmLabel: "Delete",
requireReason: true,
},
}, container);
choose-options / option-list
Single or multi-select option list. option-list is an alias for choose-options — behavior is identical.
Arguments (ChooseOptionsArguments / OptionListArguments):
| Field | Type | Default | Description |
|---|---|---|---|
title |
string |
"Choose options" |
Dialog title |
description |
string |
— | Optional explanation text |
options / items |
OptionListItem[] |
[] |
List of selectable items |
multiple |
boolean |
true |
Allow multiple selection |
selected |
string[] |
auto | Pre-selected item IDs |
minSelected |
number |
0 |
Minimum required selections |
maxSelected |
number |
Infinity |
Maximum allowed selections |
submitLabel |
string |
"Submit" |
Submit button label |
ignoreLabel |
string |
"Ignore" |
Cancel button label |
OptionListItem:
| Field | Type | Description |
|---|---|---|
label |
string |
Display text (required) |
id |
string |
Unique identifier (defaults to value or index) |
value |
string |
Submitted value (defaults to id) |
description |
string |
Secondary text shown after label |
selected |
boolean |
Pre-selected state |
disabled |
boolean |
Disabled state |
Rendering:
- When
multiple: true(default): renders checkboxes - When
multiple: false: renders a radio group
Result value:
| Key | Type | Description |
|---|---|---|
selected |
string[] |
IDs of selected items |
Example:
renderToolCall({
name: "choose-options",
arguments: {
title: "Select dependencies",
options: [
{ id: "react", label: "React", description: "UI library" },
{ id: "vue", label: "Vue", description: "Progressive framework" },
{ id: "svelte", label: "Svelte", description: "Compiled framework", selected: true },
],
multiple: true,
minSelected: 1,
maxSelected: 2,
},
}, container);
fill-form
Multi-field form with various field types.
Arguments (FillFormArguments):
| Field | Type | Default | Description |
|---|---|---|---|
title |
string |
"Fill form" |
Form title |
description |
string |
— | Optional explanation text |
fields |
FormField[] |
[] |
Form field definitions |
values |
Record<string, unknown> |
— | Initial field values |
submitLabel |
string |
"Submit" |
Submit button label |
ignoreLabel |
string |
"Ignore" |
Cancel button label |
FormField:
| Field | Type | Default | Description |
|---|---|---|---|
name |
string |
(required) | Field name (used as key in result value) |
type |
FormFieldType |
"text" |
Field type |
label |
string |
name |
Display label |
description |
string |
— | Help text below the field |
placeholder |
string |
— | Input placeholder |
required |
boolean |
false |
Require non-empty value to submit |
disabled |
boolean |
false |
Disable the field |
value |
unknown |
— | Initial value |
options |
Array<{label, value}> |
— | Options for select type |
FormFieldType:
| Type | Renders | Description |
|---|---|---|
text |
<input> |
Free-form text |
number |
<input type="number"> |
Numeric input, value is number | null |
engineering |
<input> with SI prefix parsing |
Accepts values like "10kΩ", "100nF", "1.5MegHz". Parsed with parseEngineeringNumber(). State includes raw, number, valid, prefix, unit, normalized |
select |
<select> |
Dropdown from options array |
checkbox |
Checkbox | Boolean toggle |
switch |
Switch toggle | Boolean toggle (switch style) |
Result value:
An object with a key for each field name, containing the submitted value. For engineering fields, the raw input string is submitted. For checkbox/switch fields, a boolean is submitted. For number fields, a number or null is submitted.
Example:
renderToolCall({
name: "fill-form",
arguments: {
title: "New project",
fields: [
{ name: "name", type: "text", label: "Project name", required: true },
{ name: "framework", type: "select", label: "Framework", options: [
{ label: "React", value: "react" },
{ label: "Svelte", value: "svelte" },
]},
{ name: "resistance", type: "engineering", label: "Target resistance", placeholder: "e.g. 10kΩ" },
{ name: "typescript", type: "checkbox", label: "Use TypeScript", value: true },
],
},
}, container);
Writing a custom template
Implement the ToolTemplateCompiler function and register it:
import { registerToolTemplate, renderToolCall } from "slexkit";
registerToolTemplate("greet-user", (args, runtime, call) => ({
namespace: `tool_greet_${Date.now()}`,
g: {
__slexkitTool: runtime,
name: args.name || "World",
},
layout: {
"card:tool": {
title: "Greeting",
"text:message": { "$content": "'Hello, ' + g.name + '!'" },
"submit:actions": {
returnKeys: ["name"],
submitLabel: args.submitLabel || "OK",
ignoreLabel: args.ignoreLabel || "Cancel",
},
},
},
}));
renderToolCall(
{ name: "greet-user", arguments: { name: "Alice" } },
container,
);
Key patterns
__slexkitTool— Setg.__slexkitTool = runtimeto give expressions access tosubmit()andignore()submit:actions— Always include asubmit:actionscomponent to submit the result. SetreturnKeysto the property names that should be included in the result valuecanSubmit— Add ag.canSubmit()method to control the submit button's disabled state- Unique namespace — Use a timestamp-based namespace to avoid collisions with other tool calls
- Validation — Implement validation logic in
gmethods and wire$disabledon the submit button