@artooi/ag-ui-web-component 0.12.0 → 0.14.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.
@@ -79,6 +79,12 @@ export interface AgUiClientHandlers {
79
79
  * their result itself — so this is the channel for server-executed output.
80
80
  */
81
81
  onToolResult(toolCallId: string, content: string): void;
82
+ /**
83
+ * Fired for AG-UI activity events — ambient notices about what the *run* did,
84
+ * as opposed to work the agent asked for. `django-ag-ui` emits one with
85
+ * `activityType: "compaction"` when it condensed the history.
86
+ */
87
+ onActivity(activityType: string, content: unknown): void;
82
88
  /** Fired when a reasoning model starts emitting its chain-of-thought. */
83
89
  onReasoningStart(): void;
84
90
  /** Fired on every reasoning token; ``buffer`` is the full reasoning text so far. */
@@ -131,6 +137,13 @@ export interface AgUiClientConfig extends AgUiRunInputs {
131
137
  * conversation in-memory only.
132
138
  */
133
139
  onPersist?: (messages: readonly Message[]) => void;
140
+ /**
141
+ * Called whenever AG-UI shared state changes — the server streamed a
142
+ * `STATE_SNAPSHOT` / `STATE_DELTA`, or {@link AgUiClient.setState} was
143
+ * called. `@ag-ui/client` owns applying those events; this only forwards
144
+ * the result so a host can react.
145
+ */
146
+ onStateChanged?: (state: Readonly<Record<string, unknown>>) => void;
134
147
  /**
135
148
  * Error text surfaced to {@link AgUiClientHandlers.onError} when a run's
136
149
  * stream closes without a terminal AG-UI event (`RUN_FINISHED`/`RUN_ERROR`) —
@@ -182,6 +195,27 @@ export class AgUiClient {
182
195
  this.#resolveInterrupts = config.resolveInterrupts ?? null;
183
196
  this.#onPersist = config.onPersist ?? (() => {});
184
197
  this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
198
+ const onStateChanged = config.onStateChanged;
199
+ if (onStateChanged !== undefined) {
200
+ // The agent applies STATE_SNAPSHOT / STATE_DELTA itself; subscribing is
201
+ // how we learn the result rather than re-deriving it from the event
202
+ // stream. Lives for the agent's lifetime, which is this client's.
203
+ this.#agent.subscribe({
204
+ onStateChanged: ({ state }) => {
205
+ onStateChanged(state as Record<string, unknown>);
206
+ },
207
+ });
208
+ }
209
+ }
210
+
211
+ /** The current AG-UI shared state, as the agent last applied it. */
212
+ get state(): Readonly<Record<string, unknown>> {
213
+ return this.#agent.state as Record<string, unknown>;
214
+ }
215
+
216
+ /** Replace the shared state; the next run sends it as `RunAgentInput.state`. */
217
+ setState(state: Readonly<Record<string, unknown>>): void {
218
+ this.#agent.setState({ ...state });
185
219
  }
186
220
 
187
221
  /** Whether a run is currently in flight. */
@@ -385,6 +419,9 @@ export class AgUiClient {
385
419
  onToolCallResultEvent({ event }) {
386
420
  h.onToolResult(event.toolCallId, event.content);
387
421
  },
422
+ onActivitySnapshotEvent({ event }) {
423
+ h.onActivity(event.activityType, event.content);
424
+ },
388
425
  // Reasoning. `@ag-ui/client` already maps the deprecated
389
426
  // THINKING_* events onto these REASONING_* callbacks, so handling the
390
427
  // reasoning family alone covers both protocol versions.
@@ -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
@@ -1,9 +1,12 @@
1
1
  // Public surface re-exports. Per CLAUDE.md, this is the only re-export point.
2
2
 
3
3
  export {
4
+ COMPACTION_ACTIVITY_TYPE,
4
5
  ELEMENT_TAG,
6
+ LOAD_CAPABILITY_TOOL,
5
7
  MAX_TOOL_ROUNDS,
6
8
  MESSAGE_ROLE,
9
+ STATE_EVENT,
7
10
  SUBMIT_EVENT,
8
11
  TOGGLE_EVENT,
9
12
  TOOL_CALL_STATUS,
@@ -16,6 +19,7 @@ export {
16
19
  export {
17
20
  AgUiChat,
18
21
  type MessageRole,
22
+ type StateDetail,
19
23
  type SubmitDetail,
20
24
  type ToggleDetail,
21
25
  } from "./core/ag_ui_chat.js";
@@ -93,6 +97,16 @@ export {
93
97
  type ResolvePageTarget,
94
98
  } from "./tools/page_action_tools.js";
95
99
  export { createPageMapContext, type PageMap } from "./tools/page_map.js";
100
+ /**
101
+ * @deprecated Renamed to `createPageStateTools` / `PageState`. The old names
102
+ * read as AG-UI shared-state sync, which this component does not implement.
103
+ */
104
+ export {
105
+ createPageStateTools,
106
+ createStateHookTools,
107
+ type PageState,
108
+ type StateHook,
109
+ } from "./tools/page_state.js";
96
110
  export { parseToolCatalog, type ToolCatalogEntry } from "./tools/parse_tool_catalog.js";
97
111
  export {
98
112
  createRouteTools,
@@ -100,7 +114,6 @@ export {
100
114
  type RouteMap,
101
115
  type RouteWithParams,
102
116
  } from "./tools/route_map.js";
103
- export { createStateHookTools, type StateHook } from "./tools/state_hook.js";
104
117
  export {
105
118
  type ApprovalOptions,
106
119
  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;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A muted one-line notice about something the *run* did, rendered inline in the
3
+ * transcript between turns.
4
+ *
5
+ * Distinct from a tool card (which reports work the agent asked for and is
6
+ * settleable) and from an error (which is a failure). A notice is ambient: the
7
+ * agent condensed earlier turns, or loaded a skill. It never settles, never
8
+ * takes an action, and carries no controls — so it stays visually quiet and out
9
+ * of the way of the conversation it annotates.
10
+ */
11
+ export function renderRunNotice(icon: string, text: string, kind: string): HTMLDivElement {
12
+ const notice = document.createElement("div");
13
+ notice.className = `run-notice run-notice--${kind}`;
14
+ notice.setAttribute("part", `run-notice run-notice-${kind}`);
15
+ // A status role, not an alert: this is informational and must not interrupt a
16
+ // screen reader mid-sentence. Polite announcements land after the current
17
+ // utterance, which is right for an annotation about turns already spoken.
18
+ notice.setAttribute("role", "status");
19
+
20
+ const glyph = document.createElement("span");
21
+ glyph.className = "run-notice-icon";
22
+ glyph.setAttribute("part", "run-notice-icon");
23
+ glyph.textContent = icon;
24
+ // Decorative: the adjacent text already says what happened, and a screen
25
+ // reader announcing the emoji's name would just add noise.
26
+ glyph.setAttribute("aria-hidden", "true");
27
+
28
+ const label = document.createElement("span");
29
+ label.className = "run-notice-text";
30
+ label.setAttribute("part", "run-notice-text");
31
+ label.textContent = text;
32
+
33
+ notice.append(glyph, label);
34
+ return notice;
35
+ }
package/src/ui/styles.ts CHANGED
@@ -886,6 +886,32 @@ export const STYLES = `
886
886
  margin-top: 6px;
887
887
  }
888
888
 
889
+ .run-notice {
890
+ display: inline-flex;
891
+ align-items: center;
892
+ gap: 6px;
893
+ align-self: flex-start;
894
+ max-width: 100%;
895
+ margin: 2px 0;
896
+ padding: 3px 10px;
897
+ border: 1px dashed var(--ag-ui-border);
898
+ border-radius: 999px;
899
+ background: transparent;
900
+ color: var(--ag-ui-muted);
901
+ font-size: 0.8em;
902
+ line-height: 1.4;
903
+ }
904
+
905
+ .run-notice-icon {
906
+ flex: none;
907
+ opacity: 0.75;
908
+ }
909
+
910
+ .run-notice-text {
911
+ min-width: 0;
912
+ overflow-wrap: anywhere;
913
+ }
914
+
889
915
  .attachment-chip {
890
916
  display: inline-flex;
891
917
  align-items: center;
@@ -49,6 +49,10 @@ export interface UiStrings {
49
49
  navigating: string;
50
50
  /** Missing-placeholder skill hint. Tokens: `{title}`, `{fields}`. */
51
51
  skillNeeds: string;
52
+ /** Notice shown when the agent condensed earlier turns. Token: `{count}`. */
53
+ historyCompacted: string;
54
+ /** Notice shown when the agent loads an agent skill. Token: `{name}`. */
55
+ usingSkill: string;
52
56
 
53
57
  // ── Composer ────────────────────────────────────────────────────────────────
54
58
  /** `aria-label` of the message textarea. */
@@ -195,6 +199,8 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
195
199
  noResult: "No result returned.",
196
200
  declinedAction: "User declined the action.",
197
201
  navigating: "Navigating…",
202
+ historyCompacted: "Earlier turns condensed to fit the context window ({count} removed)",
203
+ usingSkill: "Using skill {name}",
198
204
  skillNeeds: "“{title}” needs: {fields}",
199
205
 
200
206
  message: "Message",
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.12.0";
1
+ export const VERSION: string = "0.14.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
- }