@artooi/ag-ui-web-component 0.12.0 → 0.13.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.
@@ -0,0 +1,40 @@
1
+ import type { ClientTool } from "./client_tool_registry.js";
2
+ /**
3
+ * A binding from a named piece of host page state to agent tools.
4
+ *
5
+ * Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
6
+ * generates a read tool and, when `write` is given, a destructive set tool.
7
+ *
8
+ * This is **not** AG-UI shared state. `STATE_SNAPSHOT` / `STATE_DELTA` are
9
+ * protocol events carrying state between agent and client; this is a pair of
10
+ * ordinary client tools the agent calls like any other. The two are unrelated,
11
+ * which is why this no longer carries the word "hook" — the old name read as
12
+ * protocol state sync, which this component does not implement.
13
+ */
14
+ export interface PageState {
15
+ /** Base name; tools become `read_<name>` and `set_<name>`. */
16
+ readonly name: string;
17
+ /** Returns the current state value. */
18
+ readonly read: () => unknown;
19
+ /** Mutates the state from the agent-supplied args. Omit for read-only. */
20
+ readonly write?: (args: Record<string, unknown>) => unknown;
21
+ /** JSON-Schema for the set tool's args. Defaults to an open object. */
22
+ readonly schema?: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Build the tools for a {@link PageState}: always a `read_<name>` (read-only)
26
+ * tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
27
+ */
28
+ export declare function createPageStateTools(binding: PageState): ClientTool[];
29
+ /**
30
+ * @deprecated Renamed to {@link PageState}. The old name read as AG-UI
31
+ * shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this component
32
+ * does not implement. Will be removed in a future major.
33
+ */
34
+ export type StateHook = PageState;
35
+ /**
36
+ * @deprecated Renamed to {@link createPageStateTools}. Will be removed in a
37
+ * future major.
38
+ */
39
+ export declare const createStateHookTools: typeof createPageStateTools;
40
+ //# sourceMappingURL=page_state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page_state.d.ts","sourceRoot":"","sources":["../../src/tools/page_state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,UAAU,EAAE,CA4BrE;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,6BAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artooi/ag-ui-web-component",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Framework-free <ag-ui-chat> Web Component over the AG-UI protocol. Drop-in chat sidebar with a pluggable client-side tool registry, DOM driver primitives, animations, and destructive-action confirmation modal.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/constants.ts CHANGED
@@ -19,6 +19,16 @@ export const SUBMIT_EVENT = "ag-ui-submit";
19
19
  */
20
20
  export const TOGGLE_EVENT = "ag-ui-toggle";
21
21
 
22
+ /**
23
+ * Event dispatched by `<ag-ui-chat>` when AG-UI **shared state** changes — the
24
+ * server streamed a `STATE_SNAPSHOT` / `STATE_DELTA`, or the host assigned
25
+ * {@link AgUiChat.sharedState}. `detail` carries `{ state }`.
26
+ *
27
+ * This is the protocol's own state channel, distinct from `registerPageState`,
28
+ * which exposes host state to the agent as ordinary *tools*.
29
+ */
30
+ export const STATE_EVENT = "ag-ui-state";
31
+
22
32
  /** Roles a chat message can take. */
23
33
  export const MESSAGE_ROLE = {
24
34
  USER: "user",
@@ -2,6 +2,7 @@ import type { Context, Interrupt, Message, Tool } from "@ag-ui/core";
2
2
  import {
3
3
  DEFAULT_ATTACHMENT_MAX_BYTES,
4
4
  MESSAGE_ROLE,
5
+ STATE_EVENT,
5
6
  SUBMIT_EVENT,
6
7
  TOGGLE_EVENT,
7
8
  TOOL_CALL_STATUS,
@@ -17,9 +18,9 @@ import { isDestructive } from "../tools/is_destructive.js";
17
18
  import { isNavigates } from "../tools/is_navigates.js";
18
19
  import { createPageActionTools, type ResolvePageTarget } from "../tools/page_action_tools.js";
19
20
  import { createPageMapContext, type PageMap } from "../tools/page_map.js";
21
+ import { createPageStateTools, type PageState } from "../tools/page_state.js";
20
22
  import { parseToolCatalog } from "../tools/parse_tool_catalog.js";
21
23
  import { createRouteTools, type RouteMap } from "../tools/route_map.js";
22
- import { createStateHookTools, type StateHook } from "../tools/state_hook.js";
23
24
  import {
24
25
  type ApprovalRenderer,
25
26
  type ApprovalRequest,
@@ -73,6 +74,11 @@ export interface SubmitDetail {
73
74
  readonly attachments: readonly AttachmentRef[];
74
75
  }
75
76
 
77
+ /** `detail` shape of the {@link STATE_EVENT} CustomEvent. */
78
+ export interface StateDetail {
79
+ readonly state: Readonly<Record<string, unknown>>;
80
+ }
81
+
76
82
  /** `detail` shape of the {@link TOGGLE_EVENT} CustomEvent. */
77
83
  export interface ToggleDetail {
78
84
  readonly collapsed: boolean;
@@ -158,7 +164,7 @@ export class AgUiChat extends HTMLElement {
158
164
  /**
159
165
  * Per-run frontend tool catalog provider. Defaults to the built-in
160
166
  * `route.*` tools (when a {@link routeMap} is set) plus the tools registered
161
- * via {@link registerTool} / {@link registerStateHook}; override to supply a
167
+ * via {@link registerTool} / {@link registerPageState}; override to supply a
162
168
  * fully custom catalog.
163
169
  */
164
170
  getTools: () => Tool[] = () => [
@@ -322,6 +328,10 @@ export class AgUiChat extends HTMLElement {
322
328
  #runAttachments: readonly AttachmentRef[] = [];
323
329
 
324
330
  #client: AgUiClient | null = null;
331
+ // Seed for the next client. Once one exists it owns the live value (the
332
+ // agent applies STATE_SNAPSHOT / STATE_DELTA into it), so this is only the
333
+ // starting point — `sharedState` reads through to the client when present.
334
+ #sharedState: Record<string, unknown> = {};
325
335
  // Whether an interaction is in flight (first onRunStart → onSettled). Drives
326
336
  // the Send⇄Stop button: `agent.isRunning` is false between frontend-tool
327
337
  // rounds, but the user must still be able to stop there.
@@ -479,13 +489,46 @@ export class AgUiChat extends HTMLElement {
479
489
  this.#toolRegistry.register(tool);
480
490
  }
481
491
 
482
- /** Bind a piece of host state to `read_<name>` / `set_<name>` tools. */
483
- registerStateHook(hook: StateHook): void {
484
- for (const tool of createStateHookTools(hook)) {
492
+ /**
493
+ * AG-UI **shared state** for this conversation — the protocol's own state
494
+ * channel, sent as `RunAgentInput.state` on every run and replaced in place
495
+ * when the server streams `STATE_SNAPSHOT` / `STATE_DELTA`. Assigning seeds
496
+ * the next run; reading returns whatever the agent last applied.
497
+ *
498
+ * Listen for {@link STATE_EVENT} to react to server-driven changes.
499
+ *
500
+ * Not to be confused with {@link registerPageState}, which exposes host
501
+ * state to the agent as ordinary *tools*.
502
+ */
503
+ get sharedState(): Readonly<Record<string, unknown>> {
504
+ return this.#client?.state ?? this.#sharedState;
505
+ }
506
+
507
+ set sharedState(state: Readonly<Record<string, unknown>>) {
508
+ this.#sharedState = { ...state };
509
+ // A client already exists for this conversation — push it through so the
510
+ // next run sends it, rather than silently waiting for a new conversation.
511
+ this.#client?.setState(this.#sharedState);
512
+ }
513
+
514
+ /** Bind a piece of host page state to `read_<name>` / `set_<name>` tools. */
515
+ registerPageState(binding: PageState): void {
516
+ for (const tool of createPageStateTools(binding)) {
485
517
  this.#toolRegistry.register(tool);
486
518
  }
487
519
  }
488
520
 
521
+ /**
522
+ * @deprecated Renamed to {@link registerPageState}. The old name read as
523
+ * AG-UI shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this
524
+ * component does not implement — these are ordinary client tools over host
525
+ * page state. Behaviour is unchanged; this alias will be removed in a future
526
+ * major.
527
+ */
528
+ registerStateHook(binding: PageState): void {
529
+ this.registerPageState(binding);
530
+ }
531
+
489
532
  /** The built-in `route.*` tools, present only when a route map is set. */
490
533
  #routeTools(): ClientTool[] {
491
534
  if (this.routeMap.length === 0) {
@@ -1588,6 +1631,7 @@ export class AgUiChat extends HTMLElement {
1588
1631
  getHeaders: () => this.headers,
1589
1632
  threadId: this.#threadId,
1590
1633
  initialMessages: this.#initialMessages,
1634
+ initialState: this.#sharedState,
1591
1635
  });
1592
1636
  this.#client = new AgUiClient({
1593
1637
  agent,
@@ -1597,12 +1641,25 @@ export class AgUiChat extends HTMLElement {
1597
1641
  executeTool: (call) => this.#executeTool(call),
1598
1642
  resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
1599
1643
  onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
1644
+ onStateChanged: (state) => this.#onSharedStateChanged(state),
1600
1645
  connectionLostMessage: this.#strings.connectionLost,
1601
1646
  });
1602
1647
  }
1603
1648
  return this.#client;
1604
1649
  }
1605
1650
 
1651
+ /** Mirror the agent's applied state and tell the host it moved. */
1652
+ #onSharedStateChanged(state: Readonly<Record<string, unknown>>): void {
1653
+ this.#sharedState = { ...state };
1654
+ this.dispatchEvent(
1655
+ new CustomEvent<StateDetail>(STATE_EVENT, {
1656
+ detail: { state: this.#sharedState },
1657
+ bubbles: true,
1658
+ composed: true,
1659
+ }),
1660
+ );
1661
+ }
1662
+
1606
1663
  /** Whether ``call`` should be gated behind the confirmation card. */
1607
1664
  async #needsConfirmation(call: AgUiToolCall, tool: ClientTool): Promise<boolean> {
1608
1665
  if (this.autoConfirm) {
@@ -131,6 +131,13 @@ export interface AgUiClientConfig extends AgUiRunInputs {
131
131
  * conversation in-memory only.
132
132
  */
133
133
  onPersist?: (messages: readonly Message[]) => void;
134
+ /**
135
+ * Called whenever AG-UI shared state changes — the server streamed a
136
+ * `STATE_SNAPSHOT` / `STATE_DELTA`, or {@link AgUiClient.setState} was
137
+ * called. `@ag-ui/client` owns applying those events; this only forwards
138
+ * the result so a host can react.
139
+ */
140
+ onStateChanged?: (state: Readonly<Record<string, unknown>>) => void;
134
141
  /**
135
142
  * Error text surfaced to {@link AgUiClientHandlers.onError} when a run's
136
143
  * stream closes without a terminal AG-UI event (`RUN_FINISHED`/`RUN_ERROR`) —
@@ -182,6 +189,27 @@ export class AgUiClient {
182
189
  this.#resolveInterrupts = config.resolveInterrupts ?? null;
183
190
  this.#onPersist = config.onPersist ?? (() => {});
184
191
  this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
192
+ const onStateChanged = config.onStateChanged;
193
+ if (onStateChanged !== undefined) {
194
+ // The agent applies STATE_SNAPSHOT / STATE_DELTA itself; subscribing is
195
+ // how we learn the result rather than re-deriving it from the event
196
+ // stream. Lives for the agent's lifetime, which is this client's.
197
+ this.#agent.subscribe({
198
+ onStateChanged: ({ state }) => {
199
+ onStateChanged(state as Record<string, unknown>);
200
+ },
201
+ });
202
+ }
203
+ }
204
+
205
+ /** The current AG-UI shared state, as the agent last applied it. */
206
+ get state(): Readonly<Record<string, unknown>> {
207
+ return this.#agent.state as Record<string, unknown>;
208
+ }
209
+
210
+ /** Replace the shared state; the next run sends it as `RunAgentInput.state`. */
211
+ setState(state: Readonly<Record<string, unknown>>): void {
212
+ this.#agent.setState({ ...state });
185
213
  }
186
214
 
187
215
  /** Whether a run is currently in flight. */
@@ -18,6 +18,12 @@ export interface HttpAgentOptions {
18
18
  threadId?: string;
19
19
  /** Rehydrated history to seed the agent with (durable conversation). */
20
20
  initialMessages?: readonly Message[];
21
+ /**
22
+ * AG-UI shared state to seed the agent with. `@ag-ui/client` sends it as
23
+ * `RunAgentInput.state` on every run and replaces it in place when the
24
+ * server streams `STATE_SNAPSHOT` / `STATE_DELTA`.
25
+ */
26
+ initialState?: Readonly<Record<string, unknown>>;
21
27
  }
22
28
 
23
29
  /**
@@ -31,6 +37,7 @@ export function createHttpAgent(options: HttpAgentOptions): AbstractAgent {
31
37
  return new HttpAgent({
32
38
  url: options.endpoint,
33
39
  headers: options.headers ?? {},
40
+ initialState: { ...(options.initialState ?? {}) },
34
41
  // HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
35
42
  // which would rebind the global `fetch` to the agent instance and trigger
36
43
  // "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export {
4
4
  ELEMENT_TAG,
5
5
  MAX_TOOL_ROUNDS,
6
6
  MESSAGE_ROLE,
7
+ STATE_EVENT,
7
8
  SUBMIT_EVENT,
8
9
  TOGGLE_EVENT,
9
10
  TOOL_CALL_STATUS,
@@ -16,6 +17,7 @@ export {
16
17
  export {
17
18
  AgUiChat,
18
19
  type MessageRole,
20
+ type StateDetail,
19
21
  type SubmitDetail,
20
22
  type ToggleDetail,
21
23
  } from "./core/ag_ui_chat.js";
@@ -93,6 +95,16 @@ export {
93
95
  type ResolvePageTarget,
94
96
  } from "./tools/page_action_tools.js";
95
97
  export { createPageMapContext, type PageMap } from "./tools/page_map.js";
98
+ /**
99
+ * @deprecated Renamed to `createPageStateTools` / `PageState`. The old names
100
+ * read as AG-UI shared-state sync, which this component does not implement.
101
+ */
102
+ export {
103
+ createPageStateTools,
104
+ createStateHookTools,
105
+ type PageState,
106
+ type StateHook,
107
+ } from "./tools/page_state.js";
96
108
  export { parseToolCatalog, type ToolCatalogEntry } from "./tools/parse_tool_catalog.js";
97
109
  export {
98
110
  createRouteTools,
@@ -100,7 +112,6 @@ export {
100
112
  type RouteMap,
101
113
  type RouteWithParams,
102
114
  } from "./tools/route_map.js";
103
- export { createStateHookTools, type StateHook } from "./tools/state_hook.js";
104
115
  export {
105
116
  type ApprovalOptions,
106
117
  type ApprovalRenderer,
@@ -0,0 +1,72 @@
1
+ import { X_DESTRUCTIVE_KEY, X_SUMMARY_KEY } from "../constants.js";
2
+ import type { ClientTool } from "./client_tool_registry.js";
3
+
4
+ /**
5
+ * A binding from a named piece of host page state to agent tools.
6
+ *
7
+ * Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
8
+ * generates a read tool and, when `write` is given, a destructive set tool.
9
+ *
10
+ * This is **not** AG-UI shared state. `STATE_SNAPSHOT` / `STATE_DELTA` are
11
+ * protocol events carrying state between agent and client; this is a pair of
12
+ * ordinary client tools the agent calls like any other. The two are unrelated,
13
+ * which is why this no longer carries the word "hook" — the old name read as
14
+ * protocol state sync, which this component does not implement.
15
+ */
16
+ export interface PageState {
17
+ /** Base name; tools become `read_<name>` and `set_<name>`. */
18
+ readonly name: string;
19
+ /** Returns the current state value. */
20
+ readonly read: () => unknown;
21
+ /** Mutates the state from the agent-supplied args. Omit for read-only. */
22
+ readonly write?: (args: Record<string, unknown>) => unknown;
23
+ /** JSON-Schema for the set tool's args. Defaults to an open object. */
24
+ readonly schema?: Record<string, unknown>;
25
+ }
26
+
27
+ /**
28
+ * Build the tools for a {@link PageState}: always a `read_<name>` (read-only)
29
+ * tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
30
+ */
31
+ export function createPageStateTools(binding: PageState): ClientTool[] {
32
+ const tools: ClientTool[] = [
33
+ {
34
+ name: `read_${binding.name}`,
35
+ description: `Read the "${binding.name}" state.`,
36
+ parameters: {
37
+ type: "object",
38
+ properties: {},
39
+ required: [],
40
+ [X_SUMMARY_KEY]: `Read ${binding.name}`,
41
+ },
42
+ handler: () => binding.read(),
43
+ },
44
+ ];
45
+ const write = binding.write;
46
+ if (write !== undefined) {
47
+ tools.push({
48
+ name: `set_${binding.name}`,
49
+ description: `Update the "${binding.name}" state.`,
50
+ parameters: {
51
+ ...(binding.schema ?? { type: "object" }),
52
+ [X_DESTRUCTIVE_KEY]: true,
53
+ [X_SUMMARY_KEY]: `Update ${binding.name}`,
54
+ },
55
+ handler: (args) => write(args),
56
+ });
57
+ }
58
+ return tools;
59
+ }
60
+
61
+ /**
62
+ * @deprecated Renamed to {@link PageState}. The old name read as AG-UI
63
+ * shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this component
64
+ * does not implement. Will be removed in a future major.
65
+ */
66
+ export type StateHook = PageState;
67
+
68
+ /**
69
+ * @deprecated Renamed to {@link createPageStateTools}. Will be removed in a
70
+ * future major.
71
+ */
72
+ export const createStateHookTools = createPageStateTools;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.12.0";
1
+ export const VERSION: string = "0.13.0";
@@ -1,23 +0,0 @@
1
- import type { ClientTool } from "./client_tool_registry.js";
2
- /**
3
- * A binding from a named piece of host application state to agent tools.
4
- *
5
- * Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
6
- * generates a read tool and, when `write` is given, a destructive set tool.
7
- */
8
- export interface StateHook {
9
- /** Base name; tools become `read_<name>` and `set_<name>`. */
10
- readonly name: string;
11
- /** Returns the current state value. */
12
- readonly read: () => unknown;
13
- /** Mutates the state from the agent-supplied args. Omit for read-only. */
14
- readonly write?: (args: Record<string, unknown>) => unknown;
15
- /** JSON-Schema for the set tool's args. Defaults to an open object. */
16
- readonly schema?: Record<string, unknown>;
17
- }
18
- /**
19
- * Build the tools for a {@link StateHook}: always a `read_<name>` (read-only)
20
- * tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
21
- */
22
- export declare function createStateHookTools(hook: StateHook): ClientTool[];
23
- //# sourceMappingURL=state_hook.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"state_hook.d.ts","sourceRoot":"","sources":["../../src/tools/state_hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,CA4BlE"}
@@ -1,53 +0,0 @@
1
- import { X_DESTRUCTIVE_KEY, X_SUMMARY_KEY } from "../constants.js";
2
- import type { ClientTool } from "./client_tool_registry.js";
3
-
4
- /**
5
- * A binding from a named piece of host application state to agent tools.
6
- *
7
- * Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
8
- * generates a read tool and, when `write` is given, a destructive set tool.
9
- */
10
- export interface StateHook {
11
- /** Base name; tools become `read_<name>` and `set_<name>`. */
12
- readonly name: string;
13
- /** Returns the current state value. */
14
- readonly read: () => unknown;
15
- /** Mutates the state from the agent-supplied args. Omit for read-only. */
16
- readonly write?: (args: Record<string, unknown>) => unknown;
17
- /** JSON-Schema for the set tool's args. Defaults to an open object. */
18
- readonly schema?: Record<string, unknown>;
19
- }
20
-
21
- /**
22
- * Build the tools for a {@link StateHook}: always a `read_<name>` (read-only)
23
- * tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
24
- */
25
- export function createStateHookTools(hook: StateHook): ClientTool[] {
26
- const tools: ClientTool[] = [
27
- {
28
- name: `read_${hook.name}`,
29
- description: `Read the "${hook.name}" state.`,
30
- parameters: {
31
- type: "object",
32
- properties: {},
33
- required: [],
34
- [X_SUMMARY_KEY]: `Read ${hook.name}`,
35
- },
36
- handler: () => hook.read(),
37
- },
38
- ];
39
- const write = hook.write;
40
- if (write !== undefined) {
41
- tools.push({
42
- name: `set_${hook.name}`,
43
- description: `Update the "${hook.name}" state.`,
44
- parameters: {
45
- ...(hook.schema ?? { type: "object" }),
46
- [X_DESTRUCTIVE_KEY]: true,
47
- [X_SUMMARY_KEY]: `Update ${hook.name}`,
48
- },
49
- handler: (args) => write(args),
50
- });
51
- }
52
- return tools;
53
- }