@artooi/ag-ui-web-component 0.26.0 → 0.26.1

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.
@@ -6,6 +6,15 @@ import type { Tool } from "@ag-ui/core";
6
6
  * `handler` receives the parsed arguments and returns a result that is
7
7
  * JSON-serialised into the AG-UI tool-result message sent back to the agent.
8
8
  */
9
+ /**
10
+ * Draws one call from its arguments alone.
11
+ *
12
+ * Named separately so the replay path can take *this* and never the tool that
13
+ * owns it: a function of this type cannot reach a `handler`, which is what
14
+ * makes "a reload never re-runs a tool's effect" a property of the code rather
15
+ * than a note asking maintainers to be careful.
16
+ */
17
+ export type ChartRenderer = (args: Record<string, unknown>) => Node | null;
9
18
  export interface ClientTool {
10
19
  name: string;
11
20
  description: string;
@@ -33,7 +42,7 @@ export interface ClientTool {
33
42
  *
34
43
  * Return `null` for arguments that say nothing worth drawing.
35
44
  */
36
- render?: (args: Record<string, unknown>) => Node | null;
45
+ render?: ChartRenderer;
37
46
  }
38
47
  /**
39
48
  * Holds the frontend tools a host has declared on an `<ag-ui-chat>` element.
@@ -1 +1 @@
1
- {"version":3,"file":"client_tool_registry.d.ts","sourceRoot":"","sources":["../../src/tools/client_tool_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;GAMG;AACH,qBAAa,kBAAkB;;IAG7B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE/B;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzB;IAED,yCAAyC;IACzC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAM5B;IAED,wDAAwD;IACxD,KAAK,IAAI,IAAI,EAAE,CAMd;CACF"}
1
+ {"version":3,"file":"client_tool_registry.d.ts","sourceRoot":"","sources":["../../src/tools/client_tool_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;AAE3E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;GAMG;AACH,qBAAa,kBAAkB;;IAG7B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE/B;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzB;IAED,yCAAyC;IACzC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAM5B;IAED,wDAAwD;IACxD,KAAK,IAAI,IAAI,EAAE,CAMd;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"chart_block.d.ts","sourceRoot":"","sources":["../../src/ui/chart_block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,2BAA2B;AAC3B,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvE,wBAAwB;AACxB,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAoBD,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AAwOD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,IAAI,CAoDlE"}
1
+ {"version":3,"file":"chart_block.d.ts","sourceRoot":"","sources":["../../src/ui/chart_block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,2BAA2B;AAC3B,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvE,wBAAwB;AACxB,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAoBD,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AA6OD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,IAAI,CAoDlE"}
@@ -1 +1 @@
1
- {"version":3,"file":"chart_spec_from.d.ts","sourceRoot":"","sources":["../../src/ui/chart_spec_from.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA0D7D,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAqC9D"}
1
+ {"version":3,"file":"chart_spec_from.d.ts","sourceRoot":"","sources":["../../src/ui/chart_spec_from.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA6E7D,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAqC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"chart_tool.d.ts","sourceRoot":"","sources":["../../src/ui/chart_tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAInE,wDAAwD;AACxD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAU9C,+BAA+B;AAC/B,wBAAgB,eAAe,IAAI,UAAU,CAqC5C"}
1
+ {"version":3,"file":"chart_tool.d.ts","sourceRoot":"","sources":["../../src/ui/chart_tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAInE,wDAAwD;AACxD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAgB9C,+BAA+B;AAC/B,wBAAgB,eAAe,IAAI,UAAU,CAuC5C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artooi/ag-ui-web-component",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
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",
@@ -24,6 +24,7 @@ import {
24
24
  import { fillTemplate } from "../skills/fill_template.js";
25
25
  import { parseSkills } from "../skills/parse_skills.js";
26
26
  import type { Skill } from "../skills/skill.js";
27
+ import type { ChartRenderer } from "../tools/client_tool_registry.js";
27
28
  import { type ClientTool, ClientToolRegistry } from "../tools/client_tool_registry.js";
28
29
  import { isDestructive } from "../tools/is_destructive.js";
29
30
  import { isNavigates } from "../tools/is_navigates.js";
@@ -42,7 +43,7 @@ import { renderAttachmentChips } from "../ui/attachment_chips.js";
42
43
  import { AttachmentTray } from "../ui/attachment_tray.js";
43
44
  import { renderChart } from "../ui/chart_block.js";
44
45
  import { chartSpecFrom } from "../ui/chart_spec_from.js";
45
- import { createChartTool } from "../ui/chart_tool.js";
46
+ import { CHART_TOOL_NAME, createChartTool } from "../ui/chart_tool.js";
46
47
  import { CheckpointMenu, type CheckpointVerb } from "../ui/checkpoint_menu.js";
47
48
  import { type ConfirmationRequest, requestConfirmation } from "../ui/confirmation_card.js";
48
49
  import { prettifyToolName } from "../ui/prettify_tool_name.js";
@@ -1840,9 +1841,15 @@ export class AgUiChat extends HTMLElement {
1840
1841
  this.#cardElements.set(restored.id, this.#cardFor(restored).element);
1841
1842
  // Only `render` is replayed, never `handler`. A restored transcript
1842
1843
  // redraws what the call drew; it must not re-run what the call *did*.
1843
- const tool = this.#resolveTool(restored.name);
1844
- if (tool !== null) {
1845
- this.#renderToolOutput(tool, restored);
1844
+ // Only the renderer is handed over, never the tool. The guarantee that
1845
+ // a reload cannot re-run a tool's *effect* is worth more than a comment
1846
+ // saying so: this signature cannot reach `handler`, so a later
1847
+ // maintainer adding a "no render? fall back to the handler" convenience
1848
+ // here has to change the type first, which is exactly the moment the
1849
+ // question should be asked.
1850
+ const render = this.#resolveTool(restored.name)?.render;
1851
+ if (render !== undefined) {
1852
+ this.#renderToolOutput(render, restored);
1846
1853
  }
1847
1854
  }
1848
1855
  return;
@@ -2653,7 +2660,9 @@ export class AgUiChat extends HTMLElement {
2653
2660
  const result = await tool.handler(call.args, call.id);
2654
2661
  // Drawn from the arguments rather than the result, so the live path and
2655
2662
  // the replay path render the same thing from the same input.
2656
- this.#renderToolOutput(tool, call);
2663
+ if (tool.render !== undefined) {
2664
+ this.#renderToolOutput(tool.render, call);
2665
+ }
2657
2666
  if (navigates) {
2658
2667
  card.settle(TOOL_CALL_STATUS.DONE, this.#strings.navigating);
2659
2668
  return { content: "", halt: true };
@@ -3053,12 +3062,21 @@ export class AgUiChat extends HTMLElement {
3053
3062
  * arrives is not something to switch on for everybody.
3054
3063
  */
3055
3064
  enableCharts(routes: readonly ("tool" | "activity")[] = ["tool", "activity"]): void {
3065
+ const first = !this.#chartActivity && !this.#toolRegistry.has(CHART_TOOL_NAME);
3056
3066
  if (routes.includes("activity")) {
3057
3067
  this.#chartActivity = true;
3058
3068
  }
3059
3069
  if (routes.includes("tool")) {
3060
3070
  this.registerTool(createChartTool());
3061
3071
  }
3072
+ // Called after the element is connected, the history has already replayed
3073
+ // and every chart in it was skipped -- charts were off at the time. That is
3074
+ // the ordinary way to call this (you have to query the element to call
3075
+ // anything on it), so redrawing rather than documenting an ordering rule is
3076
+ // the only answer that does not make the obvious usage wrong.
3077
+ if (first && this.isConnected) {
3078
+ this.reload();
3079
+ }
3062
3080
  }
3063
3081
 
3064
3082
  /**
@@ -3071,13 +3089,10 @@ export class AgUiChat extends HTMLElement {
3071
3089
  * created inline, in the right place, so anchoring makes *when* the handler
3072
3090
  * runs stop mattering.
3073
3091
  */
3074
- #renderToolOutput(tool: ClientTool, call: AgUiToolCall): void {
3075
- if (tool.render === undefined) {
3076
- return;
3077
- }
3092
+ #renderToolOutput(render: ChartRenderer, call: AgUiToolCall): void {
3078
3093
  let node: Node | null;
3079
3094
  try {
3080
- node = tool.render(call.args);
3095
+ node = render(call.args);
3081
3096
  } catch (error) {
3082
3097
  // `render` is consumer code and this runs inside the history replay, where
3083
3098
  // a throw abandons the loop and takes every later turn of the transcript
@@ -3100,11 +3115,16 @@ export class AgUiChat extends HTMLElement {
3100
3115
  /** Draw, or redraw in place, the chart for one activity message. */
3101
3116
  #drawActivityChart(messageId: string, content: unknown): void {
3102
3117
  const spec = chartSpecFrom(content);
3103
- if (spec === null) {
3104
- return;
3105
- }
3106
- const block = renderChart(spec);
3118
+ const block = spec === null ? null : renderChart(spec);
3107
3119
  if (block === null) {
3120
+ // The server superseded this chart with something undrawable. Leaving the
3121
+ // old one up is the worst available answer: it shows numbers that have
3122
+ // been retracted, reading as current, and a reload then drops the chart
3123
+ // entirely because the *stored* content is the version we could not draw.
3124
+ // Live and reload should agree, and both should say "gone" rather than
3125
+ // one of them lying.
3126
+ this.#activityBlocks.get(messageId)?.remove();
3127
+ this.#activityBlocks.delete(messageId);
3108
3128
  return;
3109
3129
  }
3110
3130
  const existing = this.#activityBlocks.get(messageId);
@@ -7,6 +7,16 @@ import type { Tool } from "@ag-ui/core";
7
7
  * `handler` receives the parsed arguments and returns a result that is
8
8
  * JSON-serialised into the AG-UI tool-result message sent back to the agent.
9
9
  */
10
+ /**
11
+ * Draws one call from its arguments alone.
12
+ *
13
+ * Named separately so the replay path can take *this* and never the tool that
14
+ * owns it: a function of this type cannot reach a `handler`, which is what
15
+ * makes "a reload never re-runs a tool's effect" a property of the code rather
16
+ * than a note asking maintainers to be careful.
17
+ */
18
+ export type ChartRenderer = (args: Record<string, unknown>) => Node | null;
19
+
10
20
  export interface ClientTool {
11
21
  name: string;
12
22
  description: string;
@@ -34,7 +44,7 @@ export interface ClientTool {
34
44
  *
35
45
  * Return `null` for arguments that say nothing worth drawing.
36
46
  */
37
- render?: (args: Record<string, unknown>) => Node | null;
47
+ render?: ChartRenderer;
38
48
  }
39
49
 
40
50
  /**
@@ -174,7 +174,12 @@ function drawStacked(svg: SVGSVGElement, spec: ChartSpec, min: number, max: numb
174
174
  const running = spec.labels.map(() => 0);
175
175
  spec.series.forEach((series, s) => {
176
176
  series.points.forEach((value, i) => {
177
- const from = running[i] as number;
177
+ // `?? 0` rather than a cast: `renderChart` is exported, so a caller can
178
+ // hand it a series carrying more points than there are labels, which
179
+ // `chartSpecFrom` would have refused. The cast that used to be here
180
+ // claimed that could not happen and wrote `y="NaN"` into the DOM when it
181
+ // did.
182
+ const from = running[i] ?? 0;
178
183
  const to = from + value;
179
184
  running[i] = to;
180
185
  const y = scaleY(to, min, max);
@@ -22,6 +22,17 @@ const KINDS: readonly ChartKind[] = ["bar", "line", "pie", "scatter", "stacked"]
22
22
  */
23
23
  const MAX_POINTS = 20_000;
24
24
 
25
+ /**
26
+ * Whether a spec's *labels* are cheap enough to draw.
27
+ *
28
+ * `MAX_POINTS` bounds the data; this bounds the DOM. Every label produces an
29
+ * axis text node whatever the series count, so a spec well inside the point
30
+ * budget can still emit tens of thousands of nodes and block the main thread —
31
+ * again on every reload, since it is in the transcript. Kept separate because
32
+ * the two limits answer different questions and a single number cannot.
33
+ */
34
+ const MAX_LABELS = 2_000;
35
+
25
36
  /**
26
37
  * Largest magnitude a point may carry.
27
38
  *
@@ -59,9 +70,17 @@ function asNumbers(value: unknown): number[] | null {
59
70
  }
60
71
 
61
72
  function asStrings(value: unknown): string[] | null {
62
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
73
+ if (!Array.isArray(value)) {
63
74
  return null;
64
75
  }
76
+ // `every` over an index range rather than `Array.prototype.some`, which skips
77
+ // holes: a sparse array passed the old check and drew a chart with blank axis
78
+ // labels, which reads as a rendering bug rather than bad input.
79
+ for (let i = 0; i < value.length; i += 1) {
80
+ if (typeof value[i] !== "string") {
81
+ return null;
82
+ }
83
+ }
65
84
  return value as string[];
66
85
  }
67
86
 
@@ -94,7 +113,7 @@ export function chartSpecFrom(value: unknown): ChartSpec | null {
94
113
  if (series.length === 0) {
95
114
  return null;
96
115
  }
97
- if (series.length * labels.length > MAX_POINTS) {
116
+ if (series.length * labels.length > MAX_POINTS || labels.length > MAX_LABELS) {
98
117
  return null;
99
118
  }
100
119
 
@@ -20,6 +20,12 @@ function draw(args: Record<string, unknown>): HTMLDivElement | null {
20
20
  return spec === null ? null : renderChart(spec);
21
21
  }
22
22
 
23
+ /** Whether these arguments would produce a chart, without producing one. */
24
+ function drawable(args: Record<string, unknown>): boolean {
25
+ const spec = chartSpecFrom(args);
26
+ return spec !== null && spec.labels.length > 0 && spec.series.length > 0;
27
+ }
28
+
23
29
  const REJECTED =
24
30
  "chart not rendered: expected labels (strings) and series, each with one finite number per label";
25
31
 
@@ -54,11 +60,13 @@ export function createChartTool(): ClientTool {
54
60
  // Says what happened and nothing else; the drawing is `render`'s job. Told
55
61
  // plainly when the arguments are unusable, because the model can fix that
56
62
  // and retry — a silent no-op would leave it believing the chart is on screen.
57
- // Answers on what was actually drawn, not on what validated. A spec can
58
- // pass validation and still have nothing to show -- zero labels matches zero
59
- // points -- and reporting success then would leave the model believing a
60
- // chart is on screen.
61
- handler: (args: Record<string, unknown>) => (draw(args) === null ? REJECTED : "chart rendered"),
63
+ // Answers on what will actually be drawn, not on what validated: a spec can
64
+ // pass validation and still have nothing to show, and reporting success
65
+ // then would leave the model believing a chart is on screen. Asks the
66
+ // question without building the chart, because `render` is about to build
67
+ // the same one a moment later and drawing it twice is pure waste on a spec
68
+ // large enough to matter.
69
+ handler: (args: Record<string, unknown>) => (drawable(args) ? "chart rendered" : REJECTED),
62
70
  render: draw,
63
71
  };
64
72
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.26.0";
1
+ export const VERSION: string = "0.26.1";