@artooi/ag-ui-web-component 0.38.0 → 0.40.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +515 -1
  2. package/README.md +232 -56
  3. package/dist/ag-ui-web-component.bundle.js +370 -139
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +17 -14
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/activity_registry.d.ts +46 -0
  8. package/dist/core/activity_registry.d.ts.map +1 -0
  9. package/dist/core/ag_ui_chat.d.ts +23 -10
  10. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  11. package/dist/core/agui_client.d.ts +28 -0
  12. package/dist/core/agui_client.d.ts.map +1 -1
  13. package/dist/core/client_seed.d.ts +27 -0
  14. package/dist/core/client_seed.d.ts.map +1 -0
  15. package/dist/core/read_max_tool_rounds.d.ts +9 -0
  16. package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
  17. package/dist/core/run_handlers.d.ts +80 -0
  18. package/dist/core/run_handlers.d.ts.map +1 -0
  19. package/dist/core/storage_scope.d.ts +110 -0
  20. package/dist/core/storage_scope.d.ts.map +1 -0
  21. package/dist/core/tool_outcome.d.ts +2 -3
  22. package/dist/core/tool_outcome.d.ts.map +1 -1
  23. package/dist/core/utils.d.ts +46 -0
  24. package/dist/core/utils.d.ts.map +1 -1
  25. package/dist/index.js +16268 -14747
  26. package/dist/index.js.map +4 -4
  27. package/dist/skills/skill_catalog.d.ts +67 -0
  28. package/dist/skills/skill_catalog.d.ts.map +1 -0
  29. package/dist/skills/skill_name_from.d.ts +12 -0
  30. package/dist/skills/skill_name_from.d.ts.map +1 -0
  31. package/dist/tools/tool_catalog.d.ts +79 -0
  32. package/dist/tools/tool_catalog.d.ts.map +1 -0
  33. package/dist/tools/tool_dispatch.d.ts +104 -0
  34. package/dist/tools/tool_dispatch.d.ts.map +1 -0
  35. package/dist/ui/composer/attachment_tray.d.ts.map +1 -1
  36. package/dist/ui/composer/auto_grow.d.ts +10 -0
  37. package/dist/ui/composer/auto_grow.d.ts.map +1 -0
  38. package/dist/ui/composer/composer_attachments.d.ts +57 -0
  39. package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
  40. package/dist/ui/composer/composer_voice.d.ts +48 -0
  41. package/dist/ui/composer/composer_voice.d.ts.map +1 -0
  42. package/dist/ui/composer/voice_input.d.ts.map +1 -1
  43. package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
  44. package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
  45. package/dist/ui/fill_ui_string.d.ts +32 -0
  46. package/dist/ui/fill_ui_string.d.ts.map +1 -0
  47. package/dist/ui/history/conversation_history.d.ts +203 -0
  48. package/dist/ui/history/conversation_history.d.ts.map +1 -0
  49. package/dist/ui/history/relative_time.d.ts.map +1 -1
  50. package/dist/ui/history/thread_drawer.d.ts +0 -14
  51. package/dist/ui/history/thread_drawer.d.ts.map +1 -1
  52. package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -1
  53. package/dist/ui/interrupts/pending_decision.d.ts +23 -0
  54. package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
  55. package/dist/ui/placement/is_collapsible_placement.d.ts +18 -0
  56. package/dist/ui/placement/is_collapsible_placement.d.ts.map +1 -0
  57. package/dist/ui/placement/is_draggable_placement.d.ts +9 -0
  58. package/dist/ui/placement/is_draggable_placement.d.ts.map +1 -0
  59. package/dist/ui/placement/launcher_drag.d.ts +6 -0
  60. package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
  61. package/dist/ui/placement/panel_placement.d.ts +119 -0
  62. package/dist/ui/placement/panel_placement.d.ts.map +1 -0
  63. package/dist/ui/progress/run_announcer.d.ts +41 -0
  64. package/dist/ui/progress/run_announcer.d.ts.map +1 -0
  65. package/dist/ui/progress/subagent_progress.d.ts +58 -0
  66. package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
  67. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  68. package/dist/ui/shell/adopt_styles.d.ts +23 -0
  69. package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
  70. package/dist/ui/shell/glyph_slot.d.ts +7 -0
  71. package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
  72. package/dist/ui/shell/header_button.d.ts +10 -0
  73. package/dist/ui/shell/header_button.d.ts.map +1 -0
  74. package/dist/ui/shell/icon_element.d.ts +12 -0
  75. package/dist/ui/shell/icon_element.d.ts.map +1 -0
  76. package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
  77. package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
  78. package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
  79. package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
  80. package/dist/ui/styles.d.ts +1 -1
  81. package/dist/ui/styles.d.ts.map +1 -1
  82. package/dist/ui/transcript/answer_actions.d.ts +39 -0
  83. package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
  84. package/dist/ui/transcript/answer_stream.d.ts +56 -0
  85. package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
  86. package/dist/ui/transcript/render_or_warn.d.ts +16 -0
  87. package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
  88. package/dist/ui/transcript/starter_chips.d.ts +21 -0
  89. package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
  90. package/dist/ui/transcript/transcript.d.ts +171 -0
  91. package/dist/ui/transcript/transcript.d.ts.map +1 -0
  92. package/dist/ui/ui_strings.d.ts +29 -1
  93. package/dist/ui/ui_strings.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/constants.ts +17 -15
  96. package/src/core/activity_registry.ts +133 -0
  97. package/src/core/ag_ui_chat.ts +945 -4419
  98. package/src/core/agui_client.ts +172 -16
  99. package/src/core/client_seed.ts +27 -0
  100. package/src/core/read_max_tool_rounds.ts +13 -0
  101. package/src/core/remote_conversation_store.ts +1 -1
  102. package/src/core/run_handlers.ts +501 -0
  103. package/src/core/storage_scope.ts +279 -0
  104. package/src/core/tool_outcome.ts +5 -3
  105. package/src/core/utils.ts +109 -0
  106. package/src/skills/skill_catalog.ts +166 -0
  107. package/src/skills/skill_name_from.ts +19 -0
  108. package/src/tools/tool_catalog.ts +354 -0
  109. package/src/tools/tool_dispatch.ts +521 -0
  110. package/src/ui/composer/attachment_tray.ts +6 -5
  111. package/src/ui/composer/auto_grow.ts +12 -0
  112. package/src/ui/composer/composer_attachments.ts +324 -0
  113. package/src/ui/composer/composer_voice.ts +93 -0
  114. package/src/ui/composer/voice_input.ts +4 -4
  115. package/src/ui/excerpts/transcript_quote_offer.ts +198 -0
  116. package/src/ui/fill_ui_string.ts +41 -0
  117. package/src/ui/history/conversation_history.ts +699 -0
  118. package/src/ui/history/relative_time.ts +5 -4
  119. package/src/ui/history/thread_drawer.ts +3 -50
  120. package/src/ui/interrupts/confirmation_card.ts +4 -2
  121. package/src/ui/interrupts/pending_decision.ts +34 -0
  122. package/src/ui/placement/is_collapsible_placement.ts +19 -0
  123. package/src/ui/placement/is_draggable_placement.ts +23 -0
  124. package/src/ui/placement/launcher_drag.ts +104 -89
  125. package/src/ui/placement/panel_placement.ts +1130 -0
  126. package/src/ui/progress/run_announcer.ts +67 -0
  127. package/src/ui/progress/subagent_progress.ts +201 -0
  128. package/src/ui/progress/tool_call_card.ts +2 -0
  129. package/src/ui/shell/adopt_styles.ts +28 -0
  130. package/src/ui/shell/glyph_slot.ts +12 -0
  131. package/src/ui/shell/header_button.ts +21 -0
  132. package/src/ui/shell/icon_element.ts +33 -0
  133. package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
  134. package/src/ui/shell/read_launcher_icon_url.ts +9 -0
  135. package/src/ui/styles.ts +301 -70
  136. package/src/ui/transcript/answer_actions.ts +139 -0
  137. package/src/ui/transcript/answer_stream.ts +123 -0
  138. package/src/ui/transcript/render_or_warn.ts +22 -0
  139. package/src/ui/transcript/starter_chips.ts +42 -0
  140. package/src/ui/transcript/transcript.ts +429 -0
  141. package/src/ui/ui_strings.ts +38 -1
  142. package/src/version.ts +1 -1
@@ -0,0 +1,139 @@
1
+ import { FEEDBACK_EVENT, ICON_RETRY, MESSAGE_ACTIONS } from "../../constants.js";
2
+ import type { FeedbackDetail } from "../../core/events/feedback_detail.js";
3
+ import { commaTokens } from "../../core/utils.js";
4
+ import { copyPayload } from "../excerpts/copy_payload.js";
5
+ import type { UiStrings } from "../ui_strings.js";
6
+ import { attachMessageActions, messageActionBar, messageActionButton } from "./message_actions.js";
7
+
8
+ /** What the answer actions need from the element that owns them. */
9
+ export interface AnswerActionsHost {
10
+ /** The custom element: its `data-message-actions`, and where feedback is dispatched. */
11
+ readonly element: HTMLElement;
12
+ /** The resolved string table. */
13
+ readonly strings: () => UiStrings;
14
+ /** Retry the last turn, as the element's public `retryLastTurn` does. */
15
+ readonly retry: () => void;
16
+ }
17
+
18
+ /**
19
+ * The action row under a finished answer -- copy, retry, feedback -- and the
20
+ * one row currently holding Retry.
21
+ *
22
+ * Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
23
+ * instance.
24
+ */
25
+ export class AnswerActions {
26
+ readonly #host: AnswerActionsHost;
27
+ /**
28
+ * The one action row currently carrying Retry, if any.
29
+ *
30
+ * Retry belongs to the **last** turn only: re-running an older one is
31
+ * branching, and for a page-driving agent editing a past turn is not neutral
32
+ * -- those turns clicked buttons, and re-running turn 3 does not un-save what
33
+ * turn 5 saved. Holding a single owner is what keeps exactly one offer on
34
+ * screen without per-bubble bookkeeping.
35
+ */
36
+ #retryOwner: HTMLElement | null = null;
37
+
38
+ constructor(host: AnswerActionsHost) {
39
+ this.#host = host;
40
+ }
41
+
42
+ /** Forget the row holding Retry, with the transcript it was in. */
43
+ forget(): void {
44
+ this.#retryOwner = null;
45
+ }
46
+
47
+ /**
48
+ * Give a finished assistant bubble its action row, and hand it Retry.
49
+ *
50
+ * Every finished bubble gets copy and feedback -- both are safe on a message
51
+ * of any age. Retry moves to the newest, because it is the only one where
52
+ * re-running answers the same question rather than rewriting history.
53
+ *
54
+ * `data-message-actions` subtracts from that. The row is built only when
55
+ * something survives to go in it: an empty row still takes its margin, still
56
+ * answers to the `message-actions` part, and still reads to a screen reader
57
+ * as a group of actions with none in it.
58
+ */
59
+ attach(bubble: HTMLDivElement, options: { rateable?: boolean } = {}): void {
60
+ const enabled = this.#enabled();
61
+ const copyable = enabled.has(MESSAGE_ACTIONS.COPY);
62
+ // A failed run is copyable -- error text is what people paste into a bug
63
+ // report -- but not rateable: a rating is a statement about an *answer*,
64
+ // and mixing "the connection dropped" into that signal makes the host's
65
+ // feedback data say less than it did before.
66
+ const rateable = options.rateable !== false && enabled.has(MESSAGE_ACTIONS.FEEDBACK);
67
+ if (copyable || rateable) {
68
+ attachMessageActions(bubble, {
69
+ strings: this.#host.strings(),
70
+ // Read at click time, not captured: a bubble rendered from markdown
71
+ // holds its text in the DOM, and that is what the user sees and means
72
+ // to copy. Serialised rather than read off `textContent`, which welds
73
+ // a table into one run of digits and picks up the code blocks' own
74
+ // copy buttons on the way past.
75
+ ...(copyable
76
+ ? {
77
+ text: () => copyPayload(bubble).text,
78
+ html: () => copyPayload(bubble).html,
79
+ }
80
+ : {}),
81
+ ...(rateable
82
+ ? {
83
+ onFeedback: (rating: "up" | "down") => {
84
+ this.#host.element.dispatchEvent(
85
+ new CustomEvent<FeedbackDetail>(FEEDBACK_EVENT, {
86
+ detail: { content: copyPayload(bubble).text, rating },
87
+ bubbles: true,
88
+ composed: true,
89
+ }),
90
+ );
91
+ },
92
+ }
93
+ : {}),
94
+ });
95
+ }
96
+ if (enabled.has(MESSAGE_ACTIONS.RETRY)) {
97
+ this.#moveRetryTo(messageActionBar(bubble, this.#host.strings()));
98
+ }
99
+ }
100
+
101
+ /** Move the Retry button onto `bar`, taking it off whoever held it. */
102
+ #moveRetryTo(bar: HTMLElement): void {
103
+ this.#retryOwner?.querySelector(".message-action--retry")?.remove();
104
+ const retry = messageActionButton("retry", this.#host.strings().retryMessage, ICON_RETRY);
105
+ retry.addEventListener("click", () => {
106
+ this.#host.retry();
107
+ });
108
+ // First in the row: it is the action a reader reaches for when the answer
109
+ // was wrong, which is when they are least inclined to hunt for a control.
110
+ bar.prepend(retry);
111
+ this.#retryOwner = bar;
112
+ }
113
+
114
+ /**
115
+ * Which message actions a finished bubble offers, from
116
+ * `data-message-actions`.
117
+ *
118
+ * **Absent means copy and retry, not all three.** Those two work with nothing
119
+ * wired: copy reads the DOM, retry drives this element. The rating pair does
120
+ * not -- it fires `ag-ui-feedback` and stores nothing by design, because a
121
+ * rating belongs to whatever the host already uses for product signal. With no
122
+ * listener the buttons still latch `aria-pressed`, so a reader is told their
123
+ * rating was taken and a screen reader announces it, while nothing recorded
124
+ * anything. This README has always said two buttons that lead nowhere are
125
+ * worse than none; shipping them by default was that sentence being false.
126
+ *
127
+ * A host with a listener asks for them: `data-message-actions="copy,retry,feedback"`.
128
+ * A value names the survivors, which makes `data-message-actions="false"` --
129
+ * the spelling its sibling `data-quote-selection` uses -- an empty set by
130
+ * falling out of the same rule rather than by a case of its own.
131
+ */
132
+ #enabled(): ReadonlySet<string> {
133
+ const attr = this.#host.element.getAttribute("data-message-actions");
134
+ if (attr === null) {
135
+ return new Set([MESSAGE_ACTIONS.COPY, MESSAGE_ACTIONS.RETRY]);
136
+ }
137
+ return new Set(commaTokens(attr));
138
+ }
139
+ }
@@ -0,0 +1,123 @@
1
+ import { renderMarkdown } from "./render_markdown.js";
2
+
3
+ /** What the streamed answer needs from the element that owns it. */
4
+ export interface AnswerStreamHost {
5
+ /** Append an empty assistant bubble to the transcript, and return it. */
6
+ readonly openBubble: () => HTMLDivElement;
7
+ /** The element's `allowImages`, read per render. */
8
+ readonly allowImages: () => boolean;
9
+ /** Keep the transcript at its foot, if the reader is there. */
10
+ readonly follow: () => void;
11
+ }
12
+
13
+ /**
14
+ * The assistant answer currently streaming: the bubble it streams into, the
15
+ * text the next frame will draw, and how many deltas it arrived in.
16
+ *
17
+ * Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
18
+ * instance.
19
+ */
20
+ export class AnswerStream {
21
+ readonly #host: AnswerStreamHost;
22
+ #streamingBubble: HTMLDivElement | null = null;
23
+ // Text deltas applied to the current streaming bubble. >1 ⇒ the message
24
+ // revealed progressively as it streamed, so the word reveal must not re-animate
25
+ // it; ≤1 ⇒ it arrived at once and the word reveal is appropriate.
26
+ #streamDeltas = 0;
27
+ // The accumulated answer the next render will draw. Deltas overwrite it
28
+ // (each one carries the whole answer), so a frame always draws the latest.
29
+ #streamBuffer = "";
30
+ // The frame that render is queued on, or `null` when nothing is queued —
31
+ // also the flag saying a delta is still undrawn.
32
+ #streamFrame: number | null = null;
33
+
34
+ constructor(host: AnswerStreamHost) {
35
+ this.#host = host;
36
+ }
37
+
38
+ /** Text deltas the current answer has arrived in so far. */
39
+ get deltas(): number {
40
+ return this.#streamDeltas;
41
+ }
42
+
43
+ /**
44
+ * Count one delta received. Counted per delta received, not per render: the
45
+ * word reveal asks whether the answer *arrived* progressively, which
46
+ * coalescing renders must not change the answer to.
47
+ */
48
+ countDelta(): void {
49
+ this.#streamDeltas += 1;
50
+ }
51
+
52
+ /**
53
+ * Queue a render of the answer so far, at most one per frame.
54
+ *
55
+ * Each `TEXT_MESSAGE_CONTENT` event carries the *whole* accumulated answer,
56
+ * and drawing it means marked + DOMPurify over the entire document and a
57
+ * wholesale replacement of the bubble's subtree. Once per token that is
58
+ * quadratic in the answer's length — a long answer is agent-controlled, so
59
+ * an ordinary run becomes a progressively stalling tab — and every rebuild
60
+ * takes any selection or focus inside the bubble with it.
61
+ *
62
+ * A frame is the right grain: it is the fastest anything on screen can
63
+ * change anyway, so a burst of tokens costs one parse and the text still
64
+ * appears to flow rather than in visible chunks.
65
+ */
66
+ queue(buffer: string): void {
67
+ this.#streamBuffer = buffer;
68
+ this.#open();
69
+ if (this.#streamFrame !== null) {
70
+ return;
71
+ }
72
+ this.#streamFrame = requestAnimationFrame(() => {
73
+ this.#streamFrame = null;
74
+ this.into(this.#streamBuffer);
75
+ });
76
+ }
77
+
78
+ /** Render `buffer` into the streaming bubble now, dropping any queued frame. */
79
+ into(buffer: string): HTMLDivElement {
80
+ // A frame still queued would otherwise fire after this and repaint the
81
+ // bubble with whatever the last delta held — behind the buffer just drawn.
82
+ if (this.#streamFrame !== null) {
83
+ cancelAnimationFrame(this.#streamFrame);
84
+ this.#streamFrame = null;
85
+ }
86
+ this.#streamBuffer = buffer;
87
+ const bubble = this.#open();
88
+ bubble.innerHTML = renderMarkdown(buffer, { allowImages: this.#host.allowImages() });
89
+ this.#host.follow();
90
+ return bubble;
91
+ }
92
+
93
+ /**
94
+ * Close the current answer's streaming bubble.
95
+ *
96
+ * Draws a queued render first. A run that ends without a text end — a
97
+ * cancel, an error, a round boundary — leaves the last delta sitting in the
98
+ * queue, and simply dropping the bubble here would strand it: the partial
99
+ * answer the user stopped mid-sentence would lose its final tokens, or be an
100
+ * empty bubble above the stopped note.
101
+ */
102
+ end(): void {
103
+ if (this.#streamFrame !== null) {
104
+ this.into(this.#streamBuffer);
105
+ }
106
+ this.#streamingBubble = null;
107
+ }
108
+
109
+ /**
110
+ * The bubble the current answer streams into, opening it on first sight.
111
+ *
112
+ * Opened the moment a token arrives rather than on the frame that draws it,
113
+ * so the answer's container replaces the pending dots straight away and the
114
+ * turn never shows a gap while the first render waits for a frame.
115
+ */
116
+ #open(): HTMLDivElement {
117
+ if (this.#streamingBubble === null) {
118
+ this.#streamingBubble = this.#host.openBubble();
119
+ this.#streamDeltas = 0;
120
+ }
121
+ return this.#streamingBubble;
122
+ }
123
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Call a consumer's renderer, turning a throw into "nothing drawn".
3
+ *
4
+ * `render` is consumer code -- a host's activity renderer, a tool's `render` --
5
+ * and both call sites run inside the history replay, where a throw abandons the
6
+ * loop and takes every later turn of the transcript with it -- silently, and
7
+ * again on every reload. One thing that fails to draw is worth losing; the rest
8
+ * of the conversation is not. Reported so the failure is findable rather than
9
+ * merely survived.
10
+ *
11
+ * @param render - The consumer's renderer, already bound to its input.
12
+ * @param subject - What was being drawn, for the warning: `tool <name>` or
13
+ * `activity <type>`.
14
+ */
15
+ export function renderOrWarn(render: () => Node | null, subject: string): Node | null {
16
+ try {
17
+ return render();
18
+ } catch (error) {
19
+ console.warn(`ag-ui-chat: render failed for ${subject}`, error);
20
+ return null;
21
+ }
22
+ }
@@ -0,0 +1,42 @@
1
+ import type { UiStrings } from "../ui_strings.js";
2
+ import { renderSuggestionChips } from "./suggestion_chips.js";
3
+
4
+ /**
5
+ * The prompts offered on an empty transcript, from `element`'s `data-starters`.
6
+ *
7
+ * Different from the suggestion chips a run pushes, which are follow-ups to
8
+ * something already said. These answer the blank-page question instead, and
9
+ * they are the host's rather than the model's -- only the host knows what its
10
+ * page is for. Shares the renderer, the count and the length limit, because
11
+ * two rows of prompt chips that behaved differently would be the harder
12
+ * thing to explain.
13
+ *
14
+ * Read once at connect: it is content for a state the widget is in before
15
+ * anything happens, and a host that wants it to change has `slot="empty"`.
16
+ *
17
+ * Parses the attribute itself rather than through the element's JSON attribute
18
+ * reader, because the warning is its own: it says no starters are shown and
19
+ * gives an example of the shape, where the shared one says a built-in default
20
+ * is in use, which for this attribute there is not.
21
+ */
22
+ export function renderStarterChips(
23
+ element: Element,
24
+ strings: UiStrings,
25
+ onPick: (prompt: string) => void,
26
+ ): HTMLElement | null {
27
+ const raw = element.getAttribute("data-starters");
28
+ if (raw === null) {
29
+ return null;
30
+ }
31
+ let parsed: unknown;
32
+ try {
33
+ parsed = JSON.parse(raw);
34
+ } catch {
35
+ console.warn(
36
+ "<ag-ui-chat>: data-starters is not valid JSON, so no starters are shown. " +
37
+ "It takes an array of strings, e.g. data-starters='[\"Summarise this page\"]'.",
38
+ );
39
+ return null;
40
+ }
41
+ return renderSuggestionChips({ prompts: parsed }, strings, onPick);
42
+ }