@artooi/ag-ui-web-component 0.25.2 → 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.
- package/CHANGELOG.md +92 -1
- package/README.md +89 -0
- package/dist/ag-ui-web-component.bundle.js +73 -29
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +19 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +12 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +726 -165
- package/dist/index.js.map +4 -4
- package/dist/tools/client_tool_registry.d.ts +33 -1
- package/dist/tools/client_tool_registry.d.ts.map +1 -1
- package/dist/ui/chart_block.d.ts +39 -0
- package/dist/ui/chart_block.d.ts.map +1 -0
- package/dist/ui/chart_spec_from.d.ts +12 -0
- package/dist/ui/chart_spec_from.d.ts.map +1 -0
- package/dist/ui/chart_tool.d.ts +15 -0
- package/dist/ui/chart_tool.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +11 -0
- package/src/core/ag_ui_chat.ts +165 -3
- package/src/core/agui_client.ts +51 -3
- package/src/index.ts +8 -0
- package/src/tools/client_tool_registry.ts +34 -1
- package/src/ui/chart_block.ts +354 -0
- package/src/ui/chart_spec_from.ts +125 -0
- package/src/ui/chart_tool.ts +72 -0
- package/src/ui/styles.ts +44 -0
- package/src/version.ts +1 -1
package/src/core/ag_ui_chat.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Context, Interrupt, Message, Tool } from "@ag-ui/core";
|
|
2
2
|
import {
|
|
3
3
|
ATTACHMENT_EVENT,
|
|
4
|
+
CHART_ACTIVITY_TYPE,
|
|
4
5
|
COMPACTION_ACTIVITY_TYPE,
|
|
5
6
|
DEFAULT_ATTACHMENT_MAX_BYTES,
|
|
6
7
|
ICON_ATTACH,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
import { fillTemplate } from "../skills/fill_template.js";
|
|
24
25
|
import { parseSkills } from "../skills/parse_skills.js";
|
|
25
26
|
import type { Skill } from "../skills/skill.js";
|
|
27
|
+
import type { ChartRenderer } from "../tools/client_tool_registry.js";
|
|
26
28
|
import { type ClientTool, ClientToolRegistry } from "../tools/client_tool_registry.js";
|
|
27
29
|
import { isDestructive } from "../tools/is_destructive.js";
|
|
28
30
|
import { isNavigates } from "../tools/is_navigates.js";
|
|
@@ -39,6 +41,9 @@ import {
|
|
|
39
41
|
import { attachCopyButtons } from "../ui/attach_copy_buttons.js";
|
|
40
42
|
import { renderAttachmentChips } from "../ui/attachment_chips.js";
|
|
41
43
|
import { AttachmentTray } from "../ui/attachment_tray.js";
|
|
44
|
+
import { renderChart } from "../ui/chart_block.js";
|
|
45
|
+
import { chartSpecFrom } from "../ui/chart_spec_from.js";
|
|
46
|
+
import { CHART_TOOL_NAME, createChartTool } from "../ui/chart_tool.js";
|
|
42
47
|
import { CheckpointMenu, type CheckpointVerb } from "../ui/checkpoint_menu.js";
|
|
43
48
|
import { type ConfirmationRequest, requestConfirmation } from "../ui/confirmation_card.js";
|
|
44
49
|
import { prettifyToolName } from "../ui/prettify_tool_name.js";
|
|
@@ -392,6 +397,15 @@ export class AgUiChat extends HTMLElement {
|
|
|
392
397
|
* (`TOOL_CALL_RESULT`), so the post-run executeTool sweep doesn't overwrite
|
|
393
398
|
* the real output with the generic "executed on the server" fallback.
|
|
394
399
|
*/
|
|
400
|
+
/** Whether a server-pushed chart activity is drawn. Off unless asked for. */
|
|
401
|
+
#chartActivity = false;
|
|
402
|
+
|
|
403
|
+
/** Card elements by call id, so a rendering handler can find its own card. */
|
|
404
|
+
readonly #cardElements = new Map<string, HTMLElement>();
|
|
405
|
+
|
|
406
|
+
/** Chart blocks by activity message id, so an update redraws in place. */
|
|
407
|
+
readonly #activityBlocks = new Map<string, HTMLElement>();
|
|
408
|
+
|
|
395
409
|
readonly #serverSettled = new Set<string>();
|
|
396
410
|
/**
|
|
397
411
|
* Tool calls made during the current interaction, in the order they started,
|
|
@@ -1662,6 +1676,8 @@ export class AgUiChat extends HTMLElement {
|
|
|
1662
1676
|
this.#hidePending();
|
|
1663
1677
|
this.#toolCards.clear();
|
|
1664
1678
|
this.#serverSettled.clear();
|
|
1679
|
+
this.#cardElements.clear();
|
|
1680
|
+
this.#activityBlocks.clear();
|
|
1665
1681
|
this.#initialMessages = [];
|
|
1666
1682
|
this.#attachTray?.clear();
|
|
1667
1683
|
// Keep the empty-state region; everything else clears.
|
|
@@ -1822,7 +1838,29 @@ export class AgUiChat extends HTMLElement {
|
|
|
1822
1838
|
if (this.#noticeIfSkillLoad(restored)) {
|
|
1823
1839
|
continue;
|
|
1824
1840
|
}
|
|
1825
|
-
this.#cardFor(restored);
|
|
1841
|
+
this.#cardElements.set(restored.id, this.#cardFor(restored).element);
|
|
1842
|
+
// Only `render` is replayed, never `handler`. A restored transcript
|
|
1843
|
+
// redraws what the call drew; it must not re-run what the call *did*.
|
|
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);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (message.role === "activity") {
|
|
1858
|
+
// The client materialises a pushed activity as a message of its own, so a
|
|
1859
|
+
// chart's data is in the transcript already and survives a reload. Only
|
|
1860
|
+
// the drawing had to be put back.
|
|
1861
|
+
const activity = message as unknown as { activityType?: unknown; content?: unknown };
|
|
1862
|
+
if (activity.activityType === CHART_ACTIVITY_TYPE && this.#chartActivity) {
|
|
1863
|
+
this.#drawActivityChart(message.id, activity.content);
|
|
1826
1864
|
}
|
|
1827
1865
|
return;
|
|
1828
1866
|
}
|
|
@@ -2539,6 +2577,10 @@ export class AgUiChat extends HTMLElement {
|
|
|
2539
2577
|
}
|
|
2540
2578
|
const card = this.#cardFor(call);
|
|
2541
2579
|
this.#toolCards.delete(call.id);
|
|
2580
|
+
// Kept after the card leaves `#toolCards`: a tool that renders into the
|
|
2581
|
+
// transcript places itself against its own card, and by the time it runs the
|
|
2582
|
+
// card is no longer reachable by id.
|
|
2583
|
+
this.#cardElements.set(call.id, card.element);
|
|
2542
2584
|
const tool = this.#resolveTool(call.name);
|
|
2543
2585
|
if (tool === null) {
|
|
2544
2586
|
// Not a client tool. A server-side tool's real output arrives via
|
|
@@ -2613,7 +2655,14 @@ export class AgUiChat extends HTMLElement {
|
|
|
2613
2655
|
this.conversationStore.saveCheckpoint(this.#threadId, { toolCallId: call.id });
|
|
2614
2656
|
}
|
|
2615
2657
|
try {
|
|
2616
|
-
|
|
2658
|
+
// The call id lets a handler that renders into the transcript find its
|
|
2659
|
+
// own card; handlers that only act on the page ignore it.
|
|
2660
|
+
const result = await tool.handler(call.args, call.id);
|
|
2661
|
+
// Drawn from the arguments rather than the result, so the live path and
|
|
2662
|
+
// the replay path render the same thing from the same input.
|
|
2663
|
+
if (tool.render !== undefined) {
|
|
2664
|
+
this.#renderToolOutput(tool.render, call);
|
|
2665
|
+
}
|
|
2617
2666
|
if (navigates) {
|
|
2618
2667
|
card.settle(TOOL_CALL_STATUS.DONE, this.#strings.navigating);
|
|
2619
2668
|
return { content: "", halt: true };
|
|
@@ -2775,7 +2824,13 @@ export class AgUiChat extends HTMLElement {
|
|
|
2775
2824
|
this.#runTools.push({ id: call.id, name: call.name });
|
|
2776
2825
|
this.#cardFor(call);
|
|
2777
2826
|
},
|
|
2778
|
-
onActivity: (activityType, content) => {
|
|
2827
|
+
onActivity: (activityType, content, messageId) => {
|
|
2828
|
+
if (activityType === CHART_ACTIVITY_TYPE) {
|
|
2829
|
+
if (this.#chartActivity) {
|
|
2830
|
+
this.#drawActivityChart(messageId, content);
|
|
2831
|
+
}
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2779
2834
|
if (activityType !== COMPACTION_ACTIVITY_TYPE) {
|
|
2780
2835
|
return;
|
|
2781
2836
|
}
|
|
@@ -2811,6 +2866,11 @@ export class AgUiChat extends HTMLElement {
|
|
|
2811
2866
|
// that shipped in the same release is what makes showing them safe now.
|
|
2812
2867
|
this.#showPending();
|
|
2813
2868
|
},
|
|
2869
|
+
onActivityChanged: (messageId, activityType, content) => {
|
|
2870
|
+
if (activityType === CHART_ACTIVITY_TYPE && this.#chartActivity) {
|
|
2871
|
+
this.#drawActivityChart(messageId, content);
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2814
2874
|
onRunEnd: () => {
|
|
2815
2875
|
// Per-round end; the button stays on Stop until the whole interaction
|
|
2816
2876
|
// settles — the user must be able to cancel between tool rounds.
|
|
@@ -2983,6 +3043,108 @@ export class AgUiChat extends HTMLElement {
|
|
|
2983
3043
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
2984
3044
|
}
|
|
2985
3045
|
|
|
3046
|
+
/**
|
|
3047
|
+
* Turn on chart rendering, by whichever route this consumer wants.
|
|
3048
|
+
*
|
|
3049
|
+
* Both routes converge on one renderer deliberately. Built apart they become
|
|
3050
|
+
* two chart implementations with two sets of bugs, and the choice between them
|
|
3051
|
+
* is about *where the data lives* rather than how a bar should look:
|
|
3052
|
+
*
|
|
3053
|
+
* - `"tool"` registers the built-in `render_chart`. The agent decides a chart
|
|
3054
|
+
* helps and calls it, so the numbers are in its context and it can discuss
|
|
3055
|
+
* them afterwards. Costs one model round, and works over any transport.
|
|
3056
|
+
* - `"activity"` draws a server-pushed `ACTIVITY_SNAPSHOT` of type `chart`.
|
|
3057
|
+
* No round trip, and the data never enters the model's context at all —
|
|
3058
|
+
* which is what makes it the one for a large or sensitive dataset. Only this
|
|
3059
|
+
* route can update a chart in place as the server computes.
|
|
3060
|
+
*
|
|
3061
|
+
* Off unless asked for, both of them: a component that renders whatever
|
|
3062
|
+
* arrives is not something to switch on for everybody.
|
|
3063
|
+
*/
|
|
3064
|
+
enableCharts(routes: readonly ("tool" | "activity")[] = ["tool", "activity"]): void {
|
|
3065
|
+
const first = !this.#chartActivity && !this.#toolRegistry.has(CHART_TOOL_NAME);
|
|
3066
|
+
if (routes.includes("activity")) {
|
|
3067
|
+
this.#chartActivity = true;
|
|
3068
|
+
}
|
|
3069
|
+
if (routes.includes("tool")) {
|
|
3070
|
+
this.registerTool(createChartTool());
|
|
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
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
/**
|
|
3083
|
+
* Place a tool's rendered node against its own card.
|
|
3084
|
+
*
|
|
3085
|
+
* Anchored rather than appended because a client tool's handler does not run
|
|
3086
|
+
* until the round is over: appending would put the node after everything the
|
|
3087
|
+
* model said next, visibly detached from the call that produced it, and in a
|
|
3088
|
+
* different order than the same transcript takes on reload. The card was
|
|
3089
|
+
* created inline, in the right place, so anchoring makes *when* the handler
|
|
3090
|
+
* runs stop mattering.
|
|
3091
|
+
*/
|
|
3092
|
+
#renderToolOutput(render: ChartRenderer, call: AgUiToolCall): void {
|
|
3093
|
+
let node: Node | null;
|
|
3094
|
+
try {
|
|
3095
|
+
node = render(call.args);
|
|
3096
|
+
} catch (error) {
|
|
3097
|
+
// `render` is consumer code and this runs inside the history replay, where
|
|
3098
|
+
// a throw abandons the loop and takes every later turn of the transcript
|
|
3099
|
+
// with it -- silently, and again on every reload. A chart that fails to
|
|
3100
|
+
// draw is worth losing; the rest of the conversation is not. Reported so
|
|
3101
|
+
// the failure is findable rather than merely survived.
|
|
3102
|
+
console.warn(`ag-ui-chat: render failed for tool ${call.name}`, error);
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (node === null) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
// `after` rather than an insert-or-append branch: both callers set the card
|
|
3109
|
+
// element immediately before calling, and a parentless anchor makes `after`
|
|
3110
|
+
// a no-op, so the alternative would be a branch nothing can reach.
|
|
3111
|
+
this.#cardElements.get(call.id)?.after(node);
|
|
3112
|
+
this.#afterTranscriptGrew();
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
/** Draw, or redraw in place, the chart for one activity message. */
|
|
3116
|
+
#drawActivityChart(messageId: string, content: unknown): void {
|
|
3117
|
+
const spec = chartSpecFrom(content);
|
|
3118
|
+
const block = spec === null ? null : renderChart(spec);
|
|
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);
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
const existing = this.#activityBlocks.get(messageId);
|
|
3131
|
+
if (existing === undefined) {
|
|
3132
|
+
this.#ensureGroup().appendChild(block);
|
|
3133
|
+
} else {
|
|
3134
|
+
// Replaced rather than appended: a server redrawing a chart under the same
|
|
3135
|
+
// id means *this chart changed*, and a second copy below the first would
|
|
3136
|
+
// read as two measurements instead of one that moved.
|
|
3137
|
+
existing.replaceWith(block);
|
|
3138
|
+
}
|
|
3139
|
+
this.#activityBlocks.set(messageId, block);
|
|
3140
|
+
this.#afterTranscriptGrew();
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
#afterTranscriptGrew(): void {
|
|
3144
|
+
this.#updateEmptyState();
|
|
3145
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
2986
3148
|
#cardFor(call: AgUiToolCall): ToolCallCard {
|
|
2987
3149
|
const existing = this.#toolCards.get(call.id);
|
|
2988
3150
|
if (existing !== undefined) {
|
package/src/core/agui_client.ts
CHANGED
|
@@ -83,7 +83,18 @@ export interface AgUiClientHandlers {
|
|
|
83
83
|
* as opposed to work the agent asked for. `django-ag-ui` emits one with
|
|
84
84
|
* `activityType: "compaction"` when it condensed the history.
|
|
85
85
|
*/
|
|
86
|
-
onActivity(activityType: string, content: unknown): void;
|
|
86
|
+
onActivity(activityType: string, content: unknown, messageId: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* An activity's content changed in place — a snapshot re-sent under the same
|
|
89
|
+
* `messageId` with `replace`, or an `ACTIVITY_DELTA` whose JSON patch
|
|
90
|
+
* `@ag-ui/client` has already applied.
|
|
91
|
+
*
|
|
92
|
+
* Reported after the client has updated its own message, so `content` is the
|
|
93
|
+
* result rather than the instruction. That is the whole reason this is a
|
|
94
|
+
* separate callback: the raw delta event fires *before* the patch lands, and
|
|
95
|
+
* a subscriber acting on it would redraw from stale content.
|
|
96
|
+
*/
|
|
97
|
+
onActivityChanged(messageId: string, activityType: string, content: unknown): void;
|
|
87
98
|
/** Fired when a reasoning model starts emitting its chain-of-thought. */
|
|
88
99
|
onReasoningStart(): void;
|
|
89
100
|
/** Fired on every reasoning token; ``buffer`` is the full reasoning text so far. */
|
|
@@ -397,6 +408,9 @@ export class AgUiClient {
|
|
|
397
408
|
#buildSubscriber(pending: AgUiToolCall[], runState: RunState): AgentSubscriber {
|
|
398
409
|
const h = this.#handlers;
|
|
399
410
|
const closed = this.#closedMessageIds;
|
|
411
|
+
// Charts whose patch has been dispatched but not yet applied. Scoped to the
|
|
412
|
+
// subscriber, so it cannot outlive the run that created it.
|
|
413
|
+
const pendingDeltas = new Set<string>();
|
|
400
414
|
return {
|
|
401
415
|
onRunInitialized() {
|
|
402
416
|
h.onRunStart();
|
|
@@ -434,8 +448,42 @@ export class AgUiClient {
|
|
|
434
448
|
onToolCallResultEvent({ event }) {
|
|
435
449
|
h.onToolResult(event.toolCallId, event.content);
|
|
436
450
|
},
|
|
437
|
-
onActivitySnapshotEvent({ event }) {
|
|
438
|
-
|
|
451
|
+
onActivitySnapshotEvent({ event, messages }) {
|
|
452
|
+
// A snapshot for an id already in the list is a replacement, not a new
|
|
453
|
+
// activity: the client has swapped its content in place, and a second
|
|
454
|
+
// append would leave the superseded one on screen.
|
|
455
|
+
const known = messages.some(
|
|
456
|
+
(message) => message.id === event.messageId && message.role === "activity",
|
|
457
|
+
);
|
|
458
|
+
if (known) {
|
|
459
|
+
h.onActivityChanged(event.messageId, event.activityType, event.content);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
h.onActivity(event.activityType, event.content, event.messageId);
|
|
463
|
+
},
|
|
464
|
+
// Deliberately does *not* read the message here. `@ag-ui/client`
|
|
465
|
+
// dispatches this subscriber **before** applying the patch, so the
|
|
466
|
+
// message still holds its previous content: redrawing from it would leave
|
|
467
|
+
// the chart one revision behind for the life of the run, and disagreeing
|
|
468
|
+
// with what a reload shows. Note which chart moved and read the result on
|
|
469
|
+
// the change that follows.
|
|
470
|
+
onActivityDeltaEvent({ event }) {
|
|
471
|
+
pendingDeltas.add(event.messageId);
|
|
472
|
+
},
|
|
473
|
+
// Emitted after the client has written the patched messages, which is the
|
|
474
|
+
// first moment the result exists. Only the ids marked above are looked at,
|
|
475
|
+
// so an ordinary text delta does not walk the transcript.
|
|
476
|
+
onMessagesChanged({ messages }) {
|
|
477
|
+
if (pendingDeltas.size === 0) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
for (const id of pendingDeltas) {
|
|
481
|
+
const message = messages.find((entry) => entry.id === id);
|
|
482
|
+
if (message !== undefined && message.role === "activity") {
|
|
483
|
+
h.onActivityChanged(id, message.activityType, message.content);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
pendingDeltas.clear();
|
|
439
487
|
},
|
|
440
488
|
// `@ag-ui/client` maps the deprecated THINKING_* events onto these
|
|
441
489
|
// REASONING_* callbacks, so the reasoning family alone covers both
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export {
|
|
4
4
|
ATTACHMENT_EVENT,
|
|
5
|
+
CHART_ACTIVITY_TYPE,
|
|
5
6
|
COMPACTION_ACTIVITY_TYPE,
|
|
6
7
|
ELEMENT_TAG,
|
|
7
8
|
LOAD_CAPABILITY_TOOL,
|
|
@@ -129,6 +130,13 @@ export {
|
|
|
129
130
|
type ApprovalRequest,
|
|
130
131
|
requestApproval,
|
|
131
132
|
} from "./ui/approval_card.js";
|
|
133
|
+
// Charts. `CHART_ACTIVITY_TYPE` above is the wire name a server sets on an
|
|
134
|
+
// ACTIVITY_SNAPSHOT; these are the shape it carries and the renderer itself,
|
|
135
|
+
// for a host building its own visual on the same seam.
|
|
136
|
+
export type { ChartKind, ChartSeries, ChartSpec } from "./ui/chart_block.js";
|
|
137
|
+
export { renderChart } from "./ui/chart_block.js";
|
|
138
|
+
export { chartSpecFrom } from "./ui/chart_spec_from.js";
|
|
139
|
+
export { CHART_TOOL_NAME } from "./ui/chart_tool.js";
|
|
132
140
|
export { CheckpointMenu, type CheckpointVerb } from "./ui/checkpoint_menu.js";
|
|
133
141
|
export {
|
|
134
142
|
type ConfirmationOptions,
|
|
@@ -7,11 +7,44 @@ 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;
|
|
13
23
|
parameters: Record<string, unknown>;
|
|
14
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Run the tool. `callId` identifies the call being executed, for a handler
|
|
26
|
+
* that renders into the transcript and needs to place itself against its own
|
|
27
|
+
* card; handlers that only act on the page ignore it.
|
|
28
|
+
*/
|
|
29
|
+
handler: (args: Record<string, unknown>, callId?: string) => unknown | Promise<unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Draw this call, from its arguments alone.
|
|
32
|
+
*
|
|
33
|
+
* Optional, and **the only half that is replayed**. A restored transcript
|
|
34
|
+
* redraws by calling `render`; it never calls {@link handler}. That is what
|
|
35
|
+
* keeps a replayable tool apart from an effectful one *structurally* rather
|
|
36
|
+
* than by promise: the restore path holds no reference to the half that acts,
|
|
37
|
+
* so re-running `fill_field` on every reload is not a mistake anyone can make.
|
|
38
|
+
*
|
|
39
|
+
* The contract this must keep, because it runs again on every restore:
|
|
40
|
+
*
|
|
41
|
+
* - a pure function of `args` — no host state, no network, no clock;
|
|
42
|
+
* - deterministic, so a reload reproduces what was there before;
|
|
43
|
+
* - free of effects outside the node it returns, which the component places.
|
|
44
|
+
*
|
|
45
|
+
* Return `null` for arguments that say nothing worth drawing.
|
|
46
|
+
*/
|
|
47
|
+
render?: ChartRenderer;
|
|
15
48
|
}
|
|
16
49
|
|
|
17
50
|
/**
|