@slexkit/mcp 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -58
- package/dist/data/llms-components.txt +6 -21
- package/dist/data/llms-full.txt +7465 -6858
- package/dist/data/llms-runtime.txt +1704 -1433
- package/dist/data/llms-toolhost.txt +290 -284
- package/dist/data/llms.txt +19 -13
- package/dist/data/slexkit-ai-manifest.json +318 -297
- package/dist/data/standard/slex-capabilities.catalog.json +413 -0
- package/dist/data/standard/slex-component-catalog.json +4475 -0
- package/dist/data/standard/slex-conformance.json +248 -0
- package/dist/data/standard/slex-expression.schema.json +2116 -0
- package/dist/data/standard/slex-logic-profile.json +130 -0
- package/dist/data/standard/slex-standard-manifest.json +37 -0
- package/dist/index.js +852 -40
- package/dist/index.js.map +5 -5
- package/package.json +47 -47
|
@@ -6,290 +6,296 @@ ToolHost is the structured-input path for confirmations, choices, and forms. Do
|
|
|
6
6
|
|
|
7
7
|
Raw Markdown: /docs/reference/toolhost.md
|
|
8
8
|
|
|
9
|
-
---
|
|
10
|
-
title: ToolHost
|
|
11
|
-
category: Reference
|
|
12
|
-
status: ready
|
|
13
|
-
order: 70
|
|
14
|
-
summary: "Structured user-input UI for confirmations, choices, forms,
|
|
15
|
-
slexkitRenderMode: component
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# ToolHost
|
|
19
|
-
|
|
20
|
-
ToolHost bridges
|
|
21
|
-
|
|
22
|
-
## Concepts
|
|
23
|
-
|
|
24
|
-
When
|
|
25
|
-
|
|
26
|
-
ToolHost is **separate from display-oriented `slex` fences**. Display components render information; ToolHost components collect structured user input and return it programmatically.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
type
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
110
|
-
| `
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
| `
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
|
154
|
-
|
|
155
|
-
| `
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
| `
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
|
215
|
-
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
|
223
|
-
|
|
224
|
-
| `
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
{ name: "
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
9
|
+
---
|
|
10
|
+
title: ToolHost
|
|
11
|
+
category: Reference
|
|
12
|
+
status: ready
|
|
13
|
+
order: 70
|
|
14
|
+
summary: "Structured user-input UI for confirmations, choices, forms, and templates."
|
|
15
|
+
slexkitRenderMode: component
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# ToolHost
|
|
19
|
+
|
|
20
|
+
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`.
|
|
21
|
+
|
|
22
|
+
## Concepts
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
ToolHost is **separate from display-oriented `slex` fences**. Display components render information; ToolHost components collect structured user input and return it programmatically.
|
|
27
|
+
|
|
28
|
+
### Submit
|
|
29
|
+
|
|
30
|
+
`submit:actions` reads the fields listed in `returnKeys`, calls the ToolHost runtime, and resolves the `ToolRenderHandle.promise` with a `ToolResult`.
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
## Public API
|
|
35
|
+
|
|
36
|
+
### `renderToolCall(call, container) → ToolRenderHandle`
|
|
37
|
+
|
|
38
|
+
Compiles a tool call into a SlexExpression, mounts it, and returns a handle.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
function renderToolCall(call: ToolCall, container: HTMLElement): ToolRenderHandle;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
type ToolRenderHandle = {
|
|
46
|
+
promise: Promise<ToolResult>; // Resolves when user submits or ignores
|
|
47
|
+
dispose: () => void; // Clean up the rendered UI
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ToolResult =
|
|
51
|
+
| { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
|
|
52
|
+
| { toolCallId?: string; toolName: string; status: "ignored"; value: null };
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Usage:**
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { renderToolCall } from "slexkit";
|
|
59
|
+
|
|
60
|
+
const handle = renderToolCall(
|
|
61
|
+
{
|
|
62
|
+
id: "call_001",
|
|
63
|
+
name: "confirm-action",
|
|
64
|
+
arguments: {
|
|
65
|
+
title: "Delete file?",
|
|
66
|
+
description: "This action cannot be undone.",
|
|
67
|
+
confirmLabel: "Delete",
|
|
68
|
+
requireReason: true,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
document.getElementById("tool-container"),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
handle.promise.then((result) => {
|
|
75
|
+
console.log(result.status); // "submitted" or "ignored"
|
|
76
|
+
console.log(result.value); // { confirmed: true, reason: "Obsolete file" }
|
|
77
|
+
handle.dispose();
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `registerToolTemplate(name, compiler)`
|
|
82
|
+
|
|
83
|
+
Register a custom tool template. The compiler function receives the tool arguments and returns a `SlexExpression`.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
function registerToolTemplate(name: string, compiler: ToolTemplateCompiler): void;
|
|
87
|
+
|
|
88
|
+
type ToolTemplateCompiler<TArgs = Record<string, unknown>> = (
|
|
89
|
+
args: TArgs,
|
|
90
|
+
runtime: ToolRuntime,
|
|
91
|
+
call: ToolCall,
|
|
92
|
+
) => SlexExpression;
|
|
93
|
+
|
|
94
|
+
type ToolRuntime = {
|
|
95
|
+
submit: (value: Record<string, unknown>) => void;
|
|
96
|
+
ignore: () => void;
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Built-in templates
|
|
101
|
+
|
|
102
|
+
### `confirm-action`
|
|
103
|
+
|
|
104
|
+
Yes/no confirmation dialog. Supports an optional reason field.
|
|
105
|
+
|
|
106
|
+
**Arguments (`ConfirmActionArguments`):**
|
|
107
|
+
|
|
108
|
+
| Field | Type | Default | Description |
|
|
109
|
+
|-------|------|---------|-------------|
|
|
110
|
+
| `title` | `string` | `"Confirm action"` | Dialog title |
|
|
111
|
+
| `description` | `string` | — | Optional explanation text |
|
|
112
|
+
| `confirmLabel` | `string` | `"Confirm"` | Submit button label |
|
|
113
|
+
| `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
|
|
114
|
+
| `requireReason` | `boolean` | `false` | Show a reason text input |
|
|
115
|
+
| `reasonLabel` | `string` | `"Reason"` | Label for the reason input |
|
|
116
|
+
| `reasonPlaceholder` | `string` | `"Add a reason"` | Placeholder for the reason input |
|
|
117
|
+
|
|
118
|
+
**Result value:**
|
|
119
|
+
|
|
120
|
+
| Key | Type | Description |
|
|
121
|
+
|-----|------|-------------|
|
|
122
|
+
| `confirmed` | `boolean` | Always `true` when submitted |
|
|
123
|
+
| `reason` | `string` | User's reason text (only if `requireReason: true`) |
|
|
124
|
+
|
|
125
|
+
**Example:**
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
renderToolCall({
|
|
129
|
+
name: "confirm-action",
|
|
130
|
+
arguments: {
|
|
131
|
+
title: "Delete project?",
|
|
132
|
+
description: "This will permanently delete all files.",
|
|
133
|
+
confirmLabel: "Delete",
|
|
134
|
+
requireReason: true,
|
|
135
|
+
},
|
|
136
|
+
}, container);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `choose-options` / `option-list`
|
|
140
|
+
|
|
141
|
+
Single or multi-select option list. `option-list` is an alias for `choose-options` — behavior is identical.
|
|
142
|
+
|
|
143
|
+
**Arguments (`ChooseOptionsArguments` / `OptionListArguments`):**
|
|
144
|
+
|
|
145
|
+
| Field | Type | Default | Description |
|
|
146
|
+
|-------|------|---------|-------------|
|
|
147
|
+
| `title` | `string` | `"Choose options"` | Dialog title |
|
|
148
|
+
| `description` | `string` | — | Optional explanation text |
|
|
149
|
+
| `options` / `items` | `OptionListItem[]` | `[]` | List of selectable items |
|
|
150
|
+
| `multiple` | `boolean` | `true` | Allow multiple selection |
|
|
151
|
+
| `selected` | `string[]` | auto | Pre-selected item IDs |
|
|
152
|
+
| `minSelected` | `number` | `0` | Minimum required selections |
|
|
153
|
+
| `maxSelected` | `number` | `Infinity` | Maximum allowed selections |
|
|
154
|
+
| `submitLabel` | `string` | `"Submit"` | Submit button label |
|
|
155
|
+
| `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
|
|
156
|
+
|
|
157
|
+
**OptionListItem:**
|
|
158
|
+
|
|
159
|
+
| Field | Type | Description |
|
|
160
|
+
|-------|------|-------------|
|
|
161
|
+
| `label` | `string` | Display text (required) |
|
|
162
|
+
| `id` | `string` | Unique identifier (defaults to `value` or index) |
|
|
163
|
+
| `value` | `string` | Submitted value (defaults to `id`) |
|
|
164
|
+
| `description` | `string` | Secondary text shown after label |
|
|
165
|
+
| `selected` | `boolean` | Pre-selected state |
|
|
166
|
+
| `disabled` | `boolean` | Disabled state |
|
|
167
|
+
|
|
168
|
+
**Rendering:**
|
|
169
|
+
- When `multiple: true` (default): renders **checkboxes**
|
|
170
|
+
- When `multiple: false`: renders a **radio group**
|
|
171
|
+
|
|
172
|
+
**Result value:**
|
|
173
|
+
|
|
174
|
+
| Key | Type | Description |
|
|
175
|
+
|-----|------|-------------|
|
|
176
|
+
| `selected` | `string[]` | IDs of selected items |
|
|
177
|
+
|
|
178
|
+
**Example:**
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
renderToolCall({
|
|
182
|
+
name: "choose-options",
|
|
183
|
+
arguments: {
|
|
184
|
+
title: "Select dependencies",
|
|
185
|
+
options: [
|
|
186
|
+
{ id: "react", label: "React", description: "UI library" },
|
|
187
|
+
{ id: "vue", label: "Vue", description: "Progressive framework" },
|
|
188
|
+
{ id: "svelte", label: "Svelte", description: "Compiled framework", selected: true },
|
|
189
|
+
],
|
|
190
|
+
multiple: true,
|
|
191
|
+
minSelected: 1,
|
|
192
|
+
maxSelected: 2,
|
|
193
|
+
},
|
|
194
|
+
}, container);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `fill-form`
|
|
198
|
+
|
|
199
|
+
Multi-field form with various field types.
|
|
200
|
+
|
|
201
|
+
**Arguments (`FillFormArguments`):**
|
|
202
|
+
|
|
203
|
+
| Field | Type | Default | Description |
|
|
204
|
+
|-------|------|---------|-------------|
|
|
205
|
+
| `title` | `string` | `"Fill form"` | Form title |
|
|
206
|
+
| `description` | `string` | — | Optional explanation text |
|
|
207
|
+
| `fields` | `FormField[]` | `[]` | Form field definitions |
|
|
208
|
+
| `values` | `Record<string, unknown>` | — | Initial field values |
|
|
209
|
+
| `submitLabel` | `string` | `"Submit"` | Submit button label |
|
|
210
|
+
| `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
|
|
211
|
+
|
|
212
|
+
**FormField:**
|
|
213
|
+
|
|
214
|
+
| Field | Type | Default | Description |
|
|
215
|
+
|-------|------|---------|-------------|
|
|
216
|
+
| `name` | `string` | (required) | Field name (used as key in result value) |
|
|
217
|
+
| `type` | `FormFieldType` | `"text"` | Field type |
|
|
218
|
+
| `label` | `string` | `name` | Display label |
|
|
219
|
+
| `description` | `string` | — | Help text below the field |
|
|
220
|
+
| `placeholder` | `string` | — | Input placeholder |
|
|
221
|
+
| `required` | `boolean` | `false` | Require non-empty value to submit |
|
|
222
|
+
| `disabled` | `boolean` | `false` | Disable the field |
|
|
223
|
+
| `value` | `unknown` | — | Initial value |
|
|
224
|
+
| `options` | `Array<{label, value}>` | — | Options for `select` type |
|
|
225
|
+
|
|
226
|
+
**FormFieldType:**
|
|
227
|
+
|
|
228
|
+
| Type | Renders | Description |
|
|
229
|
+
|------|---------|-------------|
|
|
230
|
+
| `text` | `<input>` | Free-form text |
|
|
231
|
+
| `number` | `<input type="number">` | Numeric input, value is `number \| null` |
|
|
232
|
+
| `engineering` | `<input>` with SI prefix parsing | Accepts values like `"10kΩ"`, `"100nF"`, `"1.5MegHz"`. Parsed with `parseEngineeringNumber()`. State includes `raw`, `number`, `valid`, `prefix`, `unit`, `normalized` |
|
|
233
|
+
| `select` | `<select>` | Dropdown from `options` array |
|
|
234
|
+
| `checkbox` | Checkbox | Boolean toggle |
|
|
235
|
+
| `switch` | Switch toggle | Boolean toggle (switch style) |
|
|
236
|
+
|
|
237
|
+
**Result value:**
|
|
238
|
+
|
|
239
|
+
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.
|
|
240
|
+
|
|
241
|
+
**Example:**
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
renderToolCall({
|
|
245
|
+
name: "fill-form",
|
|
246
|
+
arguments: {
|
|
247
|
+
title: "New project",
|
|
248
|
+
fields: [
|
|
249
|
+
{ name: "name", type: "text", label: "Project name", required: true },
|
|
250
|
+
{ name: "framework", type: "select", label: "Framework", options: [
|
|
251
|
+
{ label: "React", value: "react" },
|
|
252
|
+
{ label: "Svelte", value: "svelte" },
|
|
253
|
+
]},
|
|
254
|
+
{ name: "resistance", type: "engineering", label: "Target resistance", placeholder: "e.g. 10kΩ" },
|
|
255
|
+
{ name: "typescript", type: "checkbox", label: "Use TypeScript", value: true },
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
}, container);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Writing a custom template
|
|
262
|
+
|
|
263
|
+
Implement the `ToolTemplateCompiler` function and register it:
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
import { registerToolTemplate, renderToolCall } from "slexkit";
|
|
267
|
+
|
|
268
|
+
registerToolTemplate("greet-user", (args, runtime, call) => ({
|
|
269
|
+
namespace: `tool_greet_${Date.now()}`,
|
|
270
|
+
g: {
|
|
271
|
+
__slexkitTool: runtime,
|
|
272
|
+
name: args.name || "World",
|
|
273
|
+
},
|
|
274
|
+
layout: {
|
|
275
|
+
"card:tool": {
|
|
276
|
+
title: "Greeting",
|
|
277
|
+
"text:message": { "$content": "'Hello, ' + g.name + '!'" },
|
|
278
|
+
"submit:actions": {
|
|
279
|
+
returnKeys: ["name"],
|
|
280
|
+
submitLabel: args.submitLabel || "OK",
|
|
281
|
+
ignoreLabel: args.ignoreLabel || "Cancel",
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
renderToolCall(
|
|
288
|
+
{ name: "greet-user", arguments: { name: "Alice" } },
|
|
289
|
+
container,
|
|
290
|
+
);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Key patterns
|
|
294
|
+
|
|
295
|
+
1. **`__slexkitTool`** — Set `g.__slexkitTool = runtime` to give expressions access to `submit()` and `ignore()`
|
|
296
|
+
2. **`submit:actions`** — Always include a `submit:actions` component to submit the result. Set `returnKeys` to the property names that should be included in the result value
|
|
297
|
+
3. **`canSubmit`** — Add a `g.canSubmit()` method to control the submit button's disabled state
|
|
298
|
+
4. **Unique namespace** — Use a timestamp-based namespace to avoid collisions with other tool calls
|
|
293
299
|
5. **Validation** — Implement validation logic in `g` methods and wire `$disabled` on the submit button
|
|
294
300
|
|
|
295
301
|
---
|