@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,67 @@
1
+ import { ANNOUNCE_CLEAR_MS } from "../../constants.js";
2
+
3
+ /**
4
+ * The screen-reader-only status region, and the one short thing it says at a
5
+ * time about how a run is going.
6
+ *
7
+ * The transcript cannot do this job. It is rewritten on every animation frame
8
+ * while an answer streams, so as a live region it re-announced the whole
9
+ * answer tens of times per turn -- not merely unhelpful but actively hostile.
10
+ * The published fix for this exact bug (Microsoft's Bot Framework WebChat
11
+ * #3236) is architectural rather than a matter of tuning attributes: demote the
12
+ * visible transcript out of live-region duty and put one synthesised status per
13
+ * event into a separate invisible region. MDN and Scott O'Hara prescribe the
14
+ * same empty-region-then-inject shape.
15
+ *
16
+ * Roughly four calls land per turn -- responding, answered, a card is waiting,
17
+ * stopped or failed -- so the user is told what happened and reads the answer
18
+ * itself by navigating the log, at their own pace, rather than having it
19
+ * shouted at them a token at a time.
20
+ *
21
+ * Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
22
+ * instance. The element places {@link region} in its shadow root.
23
+ */
24
+ export class RunAnnouncer {
25
+ /** The invisible `role="status"` region the statuses are written into. */
26
+ readonly region = document.createElement("div");
27
+ /** Pending clear of {@link region}; see {@link announce} for why it is cleared at all. */
28
+ #timer: ReturnType<typeof setTimeout> | null = null;
29
+
30
+ /** Stamp the region's live-region semantics, before the element places it. */
31
+ mount(): void {
32
+ this.region.className = "sr-only";
33
+ this.region.setAttribute("role", "status");
34
+ this.region.setAttribute("aria-live", "polite");
35
+ // Atomic: each announcement replaces the last and is read whole. Without
36
+ // it a reader may announce only the changed words between two statuses.
37
+ this.region.setAttribute("aria-atomic", "true");
38
+ }
39
+
40
+ /**
41
+ * Say one short thing to a screen reader, without touching the transcript.
42
+ *
43
+ * **The clear is load-bearing, twice.** A reader announces a live region when
44
+ * its content *changes*, so setting the same string twice running -- two turns
45
+ * in a row both starting -- is not a change and is silently not announced.
46
+ * Emptying first makes the next set a change again. It also stops a stale
47
+ * status being read out when a reader later lands on the region.
48
+ */
49
+ announce(message: string): void {
50
+ if (this.#timer !== null) {
51
+ clearTimeout(this.#timer);
52
+ }
53
+ this.region.textContent = message;
54
+ this.#timer = setTimeout(() => {
55
+ this.#timer = null;
56
+ this.region.textContent = "";
57
+ }, ANNOUNCE_CLEAR_MS);
58
+ }
59
+
60
+ /** Cancel a pending clear, so no timer outlives the element's connection. */
61
+ dispose(): void {
62
+ if (this.#timer !== null) {
63
+ clearTimeout(this.#timer);
64
+ this.#timer = null;
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,201 @@
1
+ import { SUBAGENT_PHASE } from "../../constants.js";
2
+ import { fillUiString } from "../fill_ui_string.js";
3
+ import type { UiStrings } from "../ui_strings.js";
4
+ import { SubAgentPanel, type SubAgentPhase, type SubAgentUpdate } from "./subagent_panel.js";
5
+ import { subAgentUpdate } from "./subagent_update.js";
6
+ import type { ToolCallCard } from "./tool_call_card.js";
7
+
8
+ /** What delegated progress needs from the element that owns it. */
9
+ export interface SubAgentProgressHost {
10
+ /** The tool card drawn for a call id, which a delegation's panel hangs off. */
11
+ readonly card: (callId: string) => ToolCallCard | undefined;
12
+ /** The resolved string table. */
13
+ readonly strings: () => UiStrings;
14
+ /** Keep the transcript at its foot, if the reader is there. */
15
+ readonly follow: () => void;
16
+ }
17
+
18
+ /**
19
+ * A delegated sub-agent's progress, hung off the card that delegated: the live
20
+ * panels by delegation, and which delegation each child run belongs to.
21
+ *
22
+ * Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
23
+ * instance.
24
+ */
25
+ export class SubAgentProgress {
26
+ readonly #host: SubAgentProgressHost;
27
+ /**
28
+ * The live delegation panels, keyed by the **parent's** `delegate_task` call
29
+ * id — which is what the wire keys a sub-agent's progress on, so this map and
30
+ * the element's tool cards answer to the same key.
31
+ *
32
+ * Kept beside the cards rather than on them, so a card stays a card: the tool
33
+ * card holds the slot and this holds what went into it, the same division the
34
+ * approval prompt already uses.
35
+ */
36
+ readonly #subagentPanels = new Map<string, SubAgentPanel>();
37
+ /**
38
+ * Which delegation each live `subagentRunId` belongs to.
39
+ *
40
+ * The protocol's closing events -- `SUBAGENT_FINISHED` and `SUBAGENT_ERROR`
41
+ * -- carry the child's run id and nothing else, while everything drawn here
42
+ * is keyed on the parent's `delegate_task` call id. `SUBAGENT_STARTED` is the
43
+ * one event carrying both, so the pairing is recorded there and read back on
44
+ * the close. A close naming a run this never saw open is dropped, which is
45
+ * the same refusal a step for an undrawn card gets.
46
+ */
47
+ readonly #subagentRunDelegations = new Map<string, string>();
48
+
49
+ constructor(host: SubAgentProgressHost) {
50
+ this.#host = host;
51
+ }
52
+
53
+ /**
54
+ * Draw one step of a delegated sub-agent's progress, on the card that
55
+ * delegated.
56
+ *
57
+ * `delegationId` is the parent's own `delegate_task` tool-call id, so the
58
+ * attachment point is a card this element already drew on `TOOL_CALL_START`.
59
+ * That is the whole design: a run that hands work to a sub-agent used to read
60
+ * as a stall -- the card sat at "running…" for the child's entire duration --
61
+ * and the fix is to narrate *into* the thing that was already standing there,
62
+ * rather than to float a second element with the same identity.
63
+ *
64
+ * A progress event for a call this client never drew is dropped. It has no
65
+ * card to attach to, and inventing a floating one is precisely the alternative
66
+ * that was rejected: parent and child interleave in the transcript with
67
+ * nothing marking whose is whose, and the persisted transcript -- which never
68
+ * held the progress at all -- would not match what was on screen.
69
+ *
70
+ * Nothing here writes to the conversation store. `CUSTOM` never enters
71
+ * `agent.messages`, so a reload mid-run leaves the tool card and loses the
72
+ * nested detail, which is the intended behaviour rather than a gap.
73
+ */
74
+ report(value: unknown): void {
75
+ const update = subAgentUpdate(value);
76
+ if (update === null) {
77
+ return;
78
+ }
79
+ this.#apply(update);
80
+ }
81
+
82
+ /** Open a delegation, on `SUBAGENT_STARTED`. */
83
+ start(subagentRunId: string, agent: string, parentToolCallId: string | null): void {
84
+ // A delegation naming no parent call names no card, and a floating
85
+ // panel is exactly what attaching to the card was chosen over.
86
+ if (parentToolCallId === null) {
87
+ return;
88
+ }
89
+ this.#subagentRunDelegations.set(subagentRunId, parentToolCallId);
90
+ this.#apply({
91
+ delegationId: parentToolCallId,
92
+ agent: agent === "" ? null : agent,
93
+ phase: SUBAGENT_PHASE.STARTED,
94
+ status: fillUiString(this.#host.strings().subAgentDelegatedTo, { agent }),
95
+ tool: null,
96
+ });
97
+ }
98
+
99
+ /** Settle a delegation that completed, on `SUBAGENT_FINISHED`. */
100
+ finish(subagentRunId: string): void {
101
+ this.#close(subagentRunId, SUBAGENT_PHASE.FINISHED, null);
102
+ }
103
+
104
+ /** Settle a delegation that failed, on `SUBAGENT_ERROR`. */
105
+ fail(subagentRunId: string, message: string): void {
106
+ // The server's own words, which the contract keeps to the sub-agent's
107
+ // name. Passed through as the status line and set with textContent
108
+ // downstream, never parsed as markup.
109
+ //
110
+ // The message is required by the protocol and can still arrive empty,
111
+ // which would settle the row to a blank line -- a delegation that reads
112
+ // as having said nothing rather than as having failed. The fallback was
113
+ // written and documented in UiStrings and never wired up, so until now
114
+ // the only reader who knew it existed was the one reading the string
115
+ // table.
116
+ this.#close(
117
+ subagentRunId,
118
+ SUBAGENT_PHASE.FAILED,
119
+ message === "" ? this.#host.strings().subAgentFailed : message,
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Forget every delegation. The panels go with the cards they hung off.
125
+ * Nothing restores them: the progress rode the imperative carrier and was
126
+ * never persisted, which is the correct half of that split -- a delegation
127
+ * that was live before this transcript was wiped is not live now.
128
+ */
129
+ clear(): void {
130
+ this.#subagentPanels.clear();
131
+ this.#subagentRunDelegations.clear();
132
+ }
133
+
134
+ /**
135
+ * Settle the delegation a closing lifecycle event names.
136
+ *
137
+ * `status` is the server's text on a failure and `null` on a success, where
138
+ * the wording is this element's own -- the protocol's finish event carries no
139
+ * message, which is the better shape for a localised UI and the reason
140
+ * {@link UiStrings.subAgentFinished} exists.
141
+ *
142
+ * The pairing is deliberately not deleted on close. A panel outlives the
143
+ * delegation it drew, the map is cleared with the transcript alongside the
144
+ * panels, and forgetting the id here would only make a duplicate close draw
145
+ * nothing instead of drawing the same settled row again.
146
+ */
147
+ #close(subagentRunId: string, phase: SubAgentPhase, status: string | null): void {
148
+ const delegationId = this.#subagentRunDelegations.get(subagentRunId);
149
+ if (delegationId === undefined) {
150
+ // A close naming a delegation this never saw open -- the same refusal a
151
+ // step for an undrawn card gets, and the same reason.
152
+ return;
153
+ }
154
+ const agent = this.#subagentPanels.get(delegationId)?.agent ?? null;
155
+ this.#apply({
156
+ delegationId,
157
+ agent,
158
+ phase,
159
+ status: status === null ? this.#finishedLine(agent) : status,
160
+ tool: null,
161
+ });
162
+ }
163
+
164
+ /** The row's line for a delegation that completed, named if its name is known. */
165
+ #finishedLine(agent: string | null): string {
166
+ const strings = this.#host.strings();
167
+ return agent === null
168
+ ? strings.subAgentWorking
169
+ : fillUiString(strings.subAgentFinished, { agent });
170
+ }
171
+
172
+ /**
173
+ * Fold one already-narrowed update into the delegation's panel.
174
+ *
175
+ * The join point of the two carriers, and the reason it is separate from
176
+ * {@link report}: a `CUSTOM` step arrives as `unknown` and has to be vouched
177
+ * for, while a lifecycle event arrives typed off the protocol and has nothing
178
+ * left to check. Both end up here, so the panel has one way in and the phases
179
+ * stay a single state machine regardless of which wire they came from.
180
+ */
181
+ #apply(update: SubAgentUpdate): void {
182
+ const card = this.#host.card(update.delegationId);
183
+ if (card === undefined) {
184
+ return;
185
+ }
186
+ let panel = this.#subagentPanels.get(update.delegationId);
187
+ if (panel === undefined) {
188
+ // Created on whichever phase arrives first rather than only on `started`.
189
+ // The contract says exactly one opens a delegation, and a client that
190
+ // insisted on it would answer a server that dropped one frame by showing
191
+ // nothing at all for the rest of the run.
192
+ panel = new SubAgentPanel(this.#host.strings());
193
+ this.#subagentPanels.set(update.delegationId, panel);
194
+ card.subagentSlot.appendChild(panel.element);
195
+ }
196
+ panel.report(update);
197
+ // The card grew, and the transcript is usually pinned to the foot while a
198
+ // run is in flight.
199
+ this.#host.follow();
200
+ }
201
+ }
@@ -21,6 +21,7 @@ function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
21
21
  [TOOL_CALL_STATUS.DONE]: strings.toolDone,
22
22
  [TOOL_CALL_STATUS.ERROR]: strings.toolError,
23
23
  [TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined,
24
+ [TOOL_CALL_STATUS.INTERRUPTED]: strings.toolInterrupted,
24
25
  };
25
26
  }
26
27
 
@@ -30,6 +31,7 @@ function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
30
31
  [TOOL_CALL_STATUS.DONE]: strings.resultLabel,
31
32
  [TOOL_CALL_STATUS.ERROR]: strings.errorLabel,
32
33
  [TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel,
34
+ [TOOL_CALL_STATUS.INTERRUPTED]: strings.interruptedLabel,
33
35
  };
34
36
  }
35
37
 
@@ -0,0 +1,28 @@
1
+ import { STYLES } from "../styles.js";
2
+
3
+ /**
4
+ * Attach the stylesheet to `root` without an inline `<style>` element.
5
+ *
6
+ * A host with a strict `style-src` and no `'unsafe-inline'` drops an injected
7
+ * `<style>` silently: the component mounts, functions, and renders completely
8
+ * unstyled, with nothing in the console to point at. `adoptedStyleSheets`
9
+ * carries no inline-style origin, so it is unaffected by that policy.
10
+ *
11
+ * The sheet is constructed **per call**, which is once per mounted element,
12
+ * rather than shared at module scope. A shared sheet would additionally avoid
13
+ * re-parsing the stylesheet once per mounted element, which is what
14
+ * `adoptedStyleSheets` is usually reached for -- but a module-level singleton
15
+ * is exactly what this package forbids, and the CSP defect is fixed either way.
16
+ * Per instance is no worse than the `<style>` element it replaces, which also
17
+ * parsed once per mount.
18
+ *
19
+ * No fallback: constructible `CSSStyleSheet` is Chrome 73, Firefox 101 and
20
+ * Safari 16.4, all below this package's declared Safari 17 runtime target. A
21
+ * guard here would be code no supported browser can reach, and the only way
22
+ * to keep it would be to exempt it from the coverage gate.
23
+ */
24
+ export function adoptStyles(root: ShadowRoot): void {
25
+ const sheet = new CSSStyleSheet();
26
+ sheet.replaceSync(STYLES);
27
+ root.adoptedStyleSheets = [sheet];
28
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A `<slot>` a host can project its own mark into, falling back to one of the
3
+ * built-in glyphs. The markup is an author-written constant, never user or
4
+ * server data, so it is assigned directly rather than sanitised.
5
+ */
6
+ export function glyphSlot(slotName: string, className: string, markup: string): HTMLSlotElement {
7
+ const slot = document.createElement("slot");
8
+ slot.name = slotName;
9
+ slot.className = className;
10
+ slot.innerHTML = markup;
11
+ return slot;
12
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Build a header control button: a named slot a host can project markup into,
3
+ * with the built-in glyph as the slot's fallback.
4
+ *
5
+ * The slot is what lets a host replace the mark with its own `<img>` or
6
+ * `<svg>` rather than only restyle it through the `part`; the same
7
+ * slot-with-fallback idiom the header icon uses.
8
+ */
9
+ export function headerButton(modifier: string, label: string, glyph: string): HTMLButtonElement {
10
+ const button = document.createElement("button");
11
+ button.type = "button";
12
+ button.className = `header-btn header-btn--${modifier}`;
13
+ button.setAttribute("part", `header-button ${modifier}-button`);
14
+ button.title = label;
15
+ button.setAttribute("aria-label", label);
16
+ const slot = document.createElement("slot");
17
+ slot.name = `icon-${modifier}`;
18
+ slot.append(document.createTextNode(glyph));
19
+ button.append(slot);
20
+ return button;
21
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * An icon holder wrapping a `<slot>` so a host can project custom markup;
3
+ * with an `<img>` as the slot's fallback when an icon URL is configured, or
4
+ * the given glyph markup when it is not.
5
+ *
6
+ * `iconUrl` is the caller's to read rather than defaulted here, because the
7
+ * three marks answer it differently: the header's is `data-icon-url`, the
8
+ * launcher's falls back to it from an attribute of its own, and the theme
9
+ * toggle never takes an image at all.
10
+ */
11
+ export function iconElement(
12
+ slotName: string,
13
+ part: string,
14
+ fallbackGlyph: string | null,
15
+ iconUrl: string | null,
16
+ ): HTMLSpanElement {
17
+ const holder = document.createElement("span");
18
+ holder.className = "icon-holder";
19
+ holder.setAttribute("part", part);
20
+ const slot = document.createElement("slot");
21
+ slot.name = slotName;
22
+ if (iconUrl !== null) {
23
+ const img = document.createElement("img");
24
+ img.className = "icon-img";
25
+ img.src = iconUrl;
26
+ img.alt = "";
27
+ slot.append(img);
28
+ } else if (fallbackGlyph !== null) {
29
+ slot.innerHTML = fallbackGlyph;
30
+ }
31
+ holder.append(slot);
32
+ return holder;
33
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Whether the launcher's unread badge is shown.
3
+ *
4
+ * The unread badge, unlike every other affordance here, is on by default:
5
+ * a collapsed widget is the one state where an answer can arrive with nothing
6
+ * on screen to say so. `data-unread-badge="false"` turns it off for a host
7
+ * that drives its own chrome from the `ag-ui-unread` event.
8
+ */
9
+ export function isUnreadBadgeEnabled(element: Element): boolean {
10
+ return element.getAttribute("data-unread-badge") !== "false";
11
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The launcher's own image URL. `data-launcher-icon-url` lets the collapsed
3
+ * button carry a different mark from the header's — a product logo reads at
4
+ * 22px in a header bar but rarely at 26px in a circle — and falls back to the
5
+ * header icon so a single `data-icon-url` still feeds both.
6
+ */
7
+ export function readLauncherIconUrl(element: Element): string | null {
8
+ return element.getAttribute("data-launcher-icon-url") ?? element.getAttribute("data-icon-url");
9
+ }