@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.
- package/CHANGELOG.md +88 -1
- package/README.md +75 -5
- package/dist/ag-ui-web-component.bundle.js +49 -23
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +29 -4
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +17 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +6 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +220 -39
- package/dist/index.js.map +4 -4
- package/dist/tools/page_state.d.ts +40 -0
- package/dist/tools/page_state.d.ts.map +1 -0
- package/dist/ui/run_notice.d.ts +12 -0
- package/dist/ui/run_notice.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +4 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +28 -0
- package/src/core/ag_ui_chat.ts +154 -7
- package/src/core/agui_client.ts +37 -0
- package/src/core/create_http_agent.ts +7 -0
- package/src/index.ts +14 -1
- package/src/tools/page_state.ts +72 -0
- package/src/ui/run_notice.ts +35 -0
- package/src/ui/styles.ts +26 -0
- package/src/ui/ui_strings.ts +6 -0
- package/src/version.ts +1 -1
- package/dist/tools/state_hook.d.ts +0 -23
- package/dist/tools/state_hook.d.ts.map +0 -1
- package/src/tools/state_hook.ts +0 -53
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ClientTool } from "./client_tool_registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* A binding from a named piece of host page 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
|
+
* This is **not** AG-UI shared state. `STATE_SNAPSHOT` / `STATE_DELTA` are
|
|
9
|
+
* protocol events carrying state between agent and client; this is a pair of
|
|
10
|
+
* ordinary client tools the agent calls like any other. The two are unrelated,
|
|
11
|
+
* which is why this no longer carries the word "hook" — the old name read as
|
|
12
|
+
* protocol state sync, which this component does not implement.
|
|
13
|
+
*/
|
|
14
|
+
export interface PageState {
|
|
15
|
+
/** Base name; tools become `read_<name>` and `set_<name>`. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** Returns the current state value. */
|
|
18
|
+
readonly read: () => unknown;
|
|
19
|
+
/** Mutates the state from the agent-supplied args. Omit for read-only. */
|
|
20
|
+
readonly write?: (args: Record<string, unknown>) => unknown;
|
|
21
|
+
/** JSON-Schema for the set tool's args. Defaults to an open object. */
|
|
22
|
+
readonly schema?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build the tools for a {@link PageState}: always a `read_<name>` (read-only)
|
|
26
|
+
* tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createPageStateTools(binding: PageState): ClientTool[];
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Renamed to {@link PageState}. The old name read as AG-UI
|
|
31
|
+
* shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this component
|
|
32
|
+
* does not implement. Will be removed in a future major.
|
|
33
|
+
*/
|
|
34
|
+
export type StateHook = PageState;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Renamed to {@link createPageStateTools}. Will be removed in a
|
|
37
|
+
* future major.
|
|
38
|
+
*/
|
|
39
|
+
export declare const createStateHookTools: typeof createPageStateTools;
|
|
40
|
+
//# sourceMappingURL=page_state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page_state.d.ts","sourceRoot":"","sources":["../../src/tools/page_state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;;;;;;;GAWG;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,OAAO,EAAE,SAAS,GAAG,UAAU,EAAE,CA4BrE;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,6BAAuB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
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 declare function renderRunNotice(icon: string, text: string, kind: string): HTMLDivElement;
|
|
12
|
+
//# sourceMappingURL=run_notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run_notice.d.ts","sourceRoot":"","sources":["../../src/ui/run_notice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAwBxF"}
|
package/dist/ui/styles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const STYLES = "\n:host {\n /* Colors */\n --ag-ui-bg: #ffffff;\n --ag-ui-fg: #1a1a2e;\n --ag-ui-accent: #4f46e5;\n --ag-ui-user-bg: #4f46e5;\n --ag-ui-user-fg: #ffffff;\n --ag-ui-assistant-bg: #f1f1f6;\n --ag-ui-input-bg: var(--ag-ui-bg);\n --ag-ui-tool-bg: var(--ag-ui-assistant-bg);\n --ag-ui-tool-fg: var(--ag-ui-accent);\n --ag-ui-header-bg: var(--ag-ui-accent);\n --ag-ui-header-fg: #ffffff;\n --ag-ui-border: #e2e2ec;\n --ag-ui-radius: 12px;\n\n /* Status accents for tool-call cards. */\n --ag-ui-success: #15803d;\n --ag-ui-danger: #b91c1c;\n --ag-ui-muted: #6b7280;\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --ag-ui-tool-icon-done: \"\u2713\";\n --ag-ui-tool-icon-error: \"\u2715\";\n --ag-ui-tool-icon-declined: \"\u2298\";\n --ag-ui-tool-spin-duration: 0.7s;\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --ag-ui-well-bg: transparent;\n --ag-ui-well-border: var(--ag-ui-border);\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --ag-ui-shadow: 0 12px 32px rgba(20, 20, 50, 0.18);\n --ag-ui-font: inherit;\n --ag-ui-font-size: 14px;\n --ag-ui-code-font: ui-monospace, \"SF Mono\", Menlo, monospace;\n\n /* Spacing \u2014 the density preset overrides these. */\n --ag-ui-space: 10px;\n --ag-ui-pad: 16px;\n --ag-ui-msg-pad: 8px 12px;\n --ag-ui-msg-radius: 14px;\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --ag-ui-position: fixed;\n --ag-ui-z-index: 2147483000;\n --ag-ui-width: 380px;\n --ag-ui-height: 560px;\n --ag-ui-inset: auto 24px 24px auto;\n --ag-ui-max-width: calc(100vw - 48px);\n --ag-ui-max-height: calc(100vh - 48px);\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --ag-ui-content-max-width: 820px;\n\n position: var(--ag-ui-position);\n inset: var(--ag-ui-inset);\n z-index: var(--ag-ui-z-index);\n width: var(--ag-ui-width);\n max-width: var(--ag-ui-max-width);\n height: var(--ag-ui-height);\n max-height: var(--ag-ui-max-height);\n display: flex;\n font-family: var(--ag-ui-font);\n font-size: var(--ag-ui-font-size);\n color: var(--ag-ui-fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --ag-ui-bg: #0d1117;\n --ag-ui-fg: #c9d1d9;\n --ag-ui-accent: #3fb950;\n --ag-ui-user-bg: #238636;\n --ag-ui-assistant-bg: #161b22;\n --ag-ui-header-bg: #010409;\n --ag-ui-header-fg: #c9d1d9;\n --ag-ui-border: #30363d;\n --ag-ui-muted: #8b949e;\n --ag-ui-font: var(--ag-ui-code-font);\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --ag-ui-font-size: 13px;\n --ag-ui-space: 6px;\n --ag-ui-pad: 10px;\n --ag-ui-msg-pad: 5px 9px;\n --ag-ui-msg-radius: 10px;\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --ag-ui-inset: auto auto 24px 24px;\n}\n\n:host([placement=\"side\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"full\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n/* Page: full-bleed background with a centred reading column. Unlike\n \"full\" (edge-to-edge, left-aligned messages) the content sits in a column\n capped at --ag-ui-content-max-width. The column is produced by symmetric auto\n padding on the scroll area + composer (no per-row wrapper), so user pills\n still right-align and the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--ag-ui-pad), calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer (skill chips, the\n /-command palette, the missing-placeholder hint, the upload tray) line up\n with the column too \u2014 chips are padding-based, the palette/hint/tray are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Sidebar: a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n --ag-ui-rail-width: 52px;\n transition: width 0.28s ease;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --ag-ui-inset: 0 auto 0 0;\n}\n\n:host([placement=\"sidebar\"]) .chat {\n transition: transform 0.28s ease;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width, hide the panel, and\n reveal the rail. Higher specificity than the generic collapse rules, so it\n wins regardless of source order. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--ag-ui-rail-width);\n height: 100vh;\n max-height: 100vh;\n bottom: 0;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n display: none;\n}\n\n/* The rail is a sibling of the panel (so it survives the panel being hidden);\n shown only for a collapsed sidebar. */\n.rail {\n display: none;\n border: none;\n font: inherit;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .rail {\n display: flex;\n position: absolute;\n inset: 0;\n align-items: flex-start;\n justify-content: center;\n padding-top: 16px;\n border: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n cursor: pointer;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --ag-ui-position: static;\n --ag-ui-width: 100%;\n --ag-ui-height: 100%;\n --ag-ui-max-width: 100%;\n --ag-ui-max-height: 100%;\n --ag-ui-shadow: none;\n --ag-ui-z-index: auto;\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder: a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--ag-ui-icon-size, 22px);\n height: var(--ag-ui-icon-size, 22px);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--ag-ui-icon-radius, 4px);\n}\n\n.header-controls {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.header-btn {\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 4px 7px;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* Collapsed: keep just the header bar \u2014 hide the whole body, and let the host\n shrink to the header. (A host can also fully hide the element itself.) */\n:host([collapsed]) {\n height: auto;\n max-height: none;\n}\n\n:host([collapsed]) .messages,\n:host([collapsed]) .input-row,\n:host([collapsed]) .skill-chips,\n:host([collapsed]) .skill-palette,\n:host([collapsed]) .skill-hint {\n display: none;\n}\n\n/* Edge-docked placements pin top *and* bottom, which would otherwise keep the\n panel full-height when collapsed; unpin the bottom so it shrinks to the\n header. (Floating/bottom-left pin only the bottom, so they already shrink.) */\n:host([collapsed][placement=\"side\"]),\n:host([collapsed][placement=\"full\"]) {\n bottom: auto;\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n padding: var(--ag-ui-pad);\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n}\n\n/* Empty-state region (slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--ag-ui-muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the layout is today's flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--ag-ui-pad);\n background: var(--ag-ui-well-bg);\n border: 1px solid var(--ag-ui-well-border);\n border-radius: var(--ag-ui-radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--ag-ui-msg-pad);\n border-radius: var(--ag-ui-msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--ag-ui-user-bg);\n color: var(--ag-ui-user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--ag-ui-assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--ag-ui-accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--ag-ui-border);\n color: var(--ag-ui-muted);\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--ag-ui-assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ag-ui-muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--ag-ui-muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--ag-ui-muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: \"\u25BE \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: \"\u25B8 \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--ag-ui-border);\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--ag-ui-tool-bg);\n border: 1px solid var(--ag-ui-border);\n color: var(--ag-ui-tool-fg);\n}\n\n.tool-call-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n word-break: break-word;\n}\n\n/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--ag-ui-muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--ag-ui-tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-done);\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-error);\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-declined);\n color: var(--ag-ui-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode: the lightest card \u2014 drop the box chrome so the\n status row reads as one line of the answer; the result toggle still expands\n below it. */\n.tool-call[data-display=\"inline\"] {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--ag-ui-muted);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--ag-ui-muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--ag-ui-fg);\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--ag-ui-accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: \"\u25B8 \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: \"\u25BE \";\n}\n\n.input-row {\n display: flex;\n gap: 8px;\n padding: 12px;\n border-top: 1px solid var(--ag-ui-border);\n}\n\n.input {\n flex: 1;\n resize: none;\n background: var(--ag-ui-input-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 10px;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.input:focus {\n border-color: var(--ag-ui-accent);\n}\n\n.send {\n border: none;\n border-radius: 8px;\n padding: 0 16px;\n background: var(--ag-ui-accent);\n color: #ffffff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight. */\n.send[data-state=\"running\"] {\n background: var(--ag-ui-muted);\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The \uD83D\uDCCE picker button sits left of the input; hidden until data-attachments-url. */\n.attach-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.attach-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.attach-input {\n display: none;\n}\n\n/* The \uD83C\uDFA4 mic button; shown only once #wireVoice mounts it. */\n.voice-slot {\n display: contents;\n}\n\n.voice-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.voice-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n border-color: var(--ag-ui-danger);\n background: var(--ag-ui-danger);\n color: #ffffff;\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px 0;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n background: var(--ag-ui-assistant-bg);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--ag-ui-danger);\n color: var(--ag-ui-danger);\n}\n\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 14ch;\n}\n\n.attachment-chip-size {\n color: var(--ag-ui-muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--ag-ui-danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--ag-ui-border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--ag-ui-accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--ag-ui-accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--ag-ui-muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--ag-ui-assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */\n.approval {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.approval[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.approval-body {\n font-weight: 600;\n}\n\n.approval-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.approval-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.approval-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.approval-btn--approve {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */\n.question {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.question[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.question-body {\n font-weight: 600;\n}\n\n.question-options {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.question-choice {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n\n.question-input {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 10px;\n font: inherit;\n color: var(--ag-ui-fg);\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n}\n\n.question-input:disabled {\n opacity: 0.6;\n}\n\n.question-actions {\n display: flex;\n justify-content: flex-end;\n}\n\n.question-btn {\n border: 1px solid var(--ag-ui-accent);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n.question-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--ag-ui-assistant-bg);\n color: var(--ag-ui-fg);\n}\n\n.skill-chip:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--ag-ui-fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--ag-ui-assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--ag-ui-muted);\n}\n\n.skill-hint {\n margin: 8px 12px 0;\n font-size: 0.85em;\n color: var(--ag-ui-danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n}\n\n.drawer[hidden] {\n display: none;\n}\n\n.checkpoints {\n position: absolute;\n inset-block-start: 3rem;\n inset-inline: 0.75rem;\n z-index: 6;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.5rem;\n border: 1px solid var(--agui-border, #d7d7dc);\n border-radius: 0.5rem;\n background: var(--agui-surface, #fff);\n box-shadow: 0 6px 24px rgb(0 0 0 / 12%);\n max-height: 60%;\n overflow-y: auto;\n}\n\n.checkpoints[hidden] {\n display: none;\n}\n\n.checkpoints-title {\n font-size: 0.75rem;\n font-weight: 600;\n opacity: 0.7;\n}\n\n.checkpoints-empty {\n padding: 0.5rem 0.25rem;\n font-size: 0.8125rem;\n opacity: 0.7;\n}\n\n.checkpoint-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.25rem;\n border-radius: 0.375rem;\n}\n\n.checkpoint-row:hover {\n background: var(--agui-hover, #f3f3f5);\n}\n\n.checkpoint-label {\n flex: 1;\n font-size: 0.8125rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.checkpoint-branch {\n font-size: 0.6875rem;\n padding: 0 0.375rem;\n border-radius: 999px;\n background: var(--agui-hover, #f3f3f5);\n opacity: 0.8;\n}\n\n.checkpoint-action {\n font: inherit;\n font-size: 0.75rem;\n cursor: pointer;\n padding: 0.125rem 0.5rem;\n border: 1px solid var(--agui-border, #d7d7dc);\n border-radius: 0.375rem;\n background: transparent;\n color: inherit;\n}\n\n.checkpoint-action:hover {\n background: var(--agui-hover, #f3f3f5);\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--ag-ui-bg);\n border-right: 1px solid var(--ag-ui-border);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ag-ui-space);\n padding: var(--ag-ui-pad);\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-title {\n font-weight: 600;\n}\n\n.drawer-new {\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n background: var(--ag-ui-bg);\n color: var(--ag-ui-accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.drawer-empty {\n padding: var(--ag-ui-pad);\n font-size: 0.9em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-row--active {\n background: var(--ag-ui-assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--ag-ui-muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--ag-ui-muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-accent);\n border-radius: 6px;\n background: var(--ag-ui-input-bg);\n color: var(--ag-ui-fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--ag-ui-danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--ag-ui-danger);\n color: #ffffff;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
|
|
1
|
+
export declare const STYLES = "\n:host {\n /* Colors */\n --ag-ui-bg: #ffffff;\n --ag-ui-fg: #1a1a2e;\n --ag-ui-accent: #4f46e5;\n --ag-ui-user-bg: #4f46e5;\n --ag-ui-user-fg: #ffffff;\n --ag-ui-assistant-bg: #f1f1f6;\n --ag-ui-input-bg: var(--ag-ui-bg);\n --ag-ui-tool-bg: var(--ag-ui-assistant-bg);\n --ag-ui-tool-fg: var(--ag-ui-accent);\n --ag-ui-header-bg: var(--ag-ui-accent);\n --ag-ui-header-fg: #ffffff;\n --ag-ui-border: #e2e2ec;\n --ag-ui-radius: 12px;\n\n /* Status accents for tool-call cards. */\n --ag-ui-success: #15803d;\n --ag-ui-danger: #b91c1c;\n --ag-ui-muted: #6b7280;\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --ag-ui-tool-icon-done: \"\u2713\";\n --ag-ui-tool-icon-error: \"\u2715\";\n --ag-ui-tool-icon-declined: \"\u2298\";\n --ag-ui-tool-spin-duration: 0.7s;\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --ag-ui-well-bg: transparent;\n --ag-ui-well-border: var(--ag-ui-border);\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --ag-ui-shadow: 0 12px 32px rgba(20, 20, 50, 0.18);\n --ag-ui-font: inherit;\n --ag-ui-font-size: 14px;\n --ag-ui-code-font: ui-monospace, \"SF Mono\", Menlo, monospace;\n\n /* Spacing \u2014 the density preset overrides these. */\n --ag-ui-space: 10px;\n --ag-ui-pad: 16px;\n --ag-ui-msg-pad: 8px 12px;\n --ag-ui-msg-radius: 14px;\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --ag-ui-position: fixed;\n --ag-ui-z-index: 2147483000;\n --ag-ui-width: 380px;\n --ag-ui-height: 560px;\n --ag-ui-inset: auto 24px 24px auto;\n --ag-ui-max-width: calc(100vw - 48px);\n --ag-ui-max-height: calc(100vh - 48px);\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --ag-ui-content-max-width: 820px;\n\n position: var(--ag-ui-position);\n inset: var(--ag-ui-inset);\n z-index: var(--ag-ui-z-index);\n width: var(--ag-ui-width);\n max-width: var(--ag-ui-max-width);\n height: var(--ag-ui-height);\n max-height: var(--ag-ui-max-height);\n display: flex;\n font-family: var(--ag-ui-font);\n font-size: var(--ag-ui-font-size);\n color: var(--ag-ui-fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --ag-ui-bg: #0d1117;\n --ag-ui-fg: #c9d1d9;\n --ag-ui-accent: #3fb950;\n --ag-ui-user-bg: #238636;\n --ag-ui-assistant-bg: #161b22;\n --ag-ui-header-bg: #010409;\n --ag-ui-header-fg: #c9d1d9;\n --ag-ui-border: #30363d;\n --ag-ui-muted: #8b949e;\n --ag-ui-font: var(--ag-ui-code-font);\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --ag-ui-font-size: 13px;\n --ag-ui-space: 6px;\n --ag-ui-pad: 10px;\n --ag-ui-msg-pad: 5px 9px;\n --ag-ui-msg-radius: 10px;\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --ag-ui-inset: auto auto 24px 24px;\n}\n\n:host([placement=\"side\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"full\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n/* Page: full-bleed background with a centred reading column. Unlike\n \"full\" (edge-to-edge, left-aligned messages) the content sits in a column\n capped at --ag-ui-content-max-width. The column is produced by symmetric auto\n padding on the scroll area + composer (no per-row wrapper), so user pills\n still right-align and the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--ag-ui-pad), calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer (skill chips, the\n /-command palette, the missing-placeholder hint, the upload tray) line up\n with the column too \u2014 chips are padding-based, the palette/hint/tray are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Sidebar: a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n --ag-ui-rail-width: 52px;\n transition: width 0.28s ease;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --ag-ui-inset: 0 auto 0 0;\n}\n\n:host([placement=\"sidebar\"]) .chat {\n transition: transform 0.28s ease;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width, hide the panel, and\n reveal the rail. Higher specificity than the generic collapse rules, so it\n wins regardless of source order. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--ag-ui-rail-width);\n height: 100vh;\n max-height: 100vh;\n bottom: 0;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n display: none;\n}\n\n/* The rail is a sibling of the panel (so it survives the panel being hidden);\n shown only for a collapsed sidebar. */\n.rail {\n display: none;\n border: none;\n font: inherit;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .rail {\n display: flex;\n position: absolute;\n inset: 0;\n align-items: flex-start;\n justify-content: center;\n padding-top: 16px;\n border: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n cursor: pointer;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --ag-ui-position: static;\n --ag-ui-width: 100%;\n --ag-ui-height: 100%;\n --ag-ui-max-width: 100%;\n --ag-ui-max-height: 100%;\n --ag-ui-shadow: none;\n --ag-ui-z-index: auto;\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder: a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--ag-ui-icon-size, 22px);\n height: var(--ag-ui-icon-size, 22px);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--ag-ui-icon-radius, 4px);\n}\n\n.header-controls {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.header-btn {\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 4px 7px;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* Collapsed: keep just the header bar \u2014 hide the whole body, and let the host\n shrink to the header. (A host can also fully hide the element itself.) */\n:host([collapsed]) {\n height: auto;\n max-height: none;\n}\n\n:host([collapsed]) .messages,\n:host([collapsed]) .input-row,\n:host([collapsed]) .skill-chips,\n:host([collapsed]) .skill-palette,\n:host([collapsed]) .skill-hint {\n display: none;\n}\n\n/* Edge-docked placements pin top *and* bottom, which would otherwise keep the\n panel full-height when collapsed; unpin the bottom so it shrinks to the\n header. (Floating/bottom-left pin only the bottom, so they already shrink.) */\n:host([collapsed][placement=\"side\"]),\n:host([collapsed][placement=\"full\"]) {\n bottom: auto;\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n padding: var(--ag-ui-pad);\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n}\n\n/* Empty-state region (slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--ag-ui-muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the layout is today's flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--ag-ui-pad);\n background: var(--ag-ui-well-bg);\n border: 1px solid var(--ag-ui-well-border);\n border-radius: var(--ag-ui-radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--ag-ui-msg-pad);\n border-radius: var(--ag-ui-msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--ag-ui-user-bg);\n color: var(--ag-ui-user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--ag-ui-assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--ag-ui-accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--ag-ui-border);\n color: var(--ag-ui-muted);\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--ag-ui-assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ag-ui-muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--ag-ui-muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--ag-ui-muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: \"\u25BE \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: \"\u25B8 \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--ag-ui-border);\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--ag-ui-tool-bg);\n border: 1px solid var(--ag-ui-border);\n color: var(--ag-ui-tool-fg);\n}\n\n.tool-call-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n word-break: break-word;\n}\n\n/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--ag-ui-muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--ag-ui-tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-done);\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-error);\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-declined);\n color: var(--ag-ui-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode: the lightest card \u2014 drop the box chrome so the\n status row reads as one line of the answer; the result toggle still expands\n below it. */\n.tool-call[data-display=\"inline\"] {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--ag-ui-muted);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--ag-ui-muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--ag-ui-fg);\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--ag-ui-accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: \"\u25B8 \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: \"\u25BE \";\n}\n\n.input-row {\n display: flex;\n gap: 8px;\n padding: 12px;\n border-top: 1px solid var(--ag-ui-border);\n}\n\n.input {\n flex: 1;\n resize: none;\n background: var(--ag-ui-input-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 10px;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.input:focus {\n border-color: var(--ag-ui-accent);\n}\n\n.send {\n border: none;\n border-radius: 8px;\n padding: 0 16px;\n background: var(--ag-ui-accent);\n color: #ffffff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight. */\n.send[data-state=\"running\"] {\n background: var(--ag-ui-muted);\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The \uD83D\uDCCE picker button sits left of the input; hidden until data-attachments-url. */\n.attach-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.attach-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.attach-input {\n display: none;\n}\n\n/* The \uD83C\uDFA4 mic button; shown only once #wireVoice mounts it. */\n.voice-slot {\n display: contents;\n}\n\n.voice-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.voice-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n border-color: var(--ag-ui-danger);\n background: var(--ag-ui-danger);\n color: #ffffff;\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px 0;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.run-notice {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n align-self: flex-start;\n max-width: 100%;\n margin: 2px 0;\n padding: 3px 10px;\n border: 1px dashed var(--ag-ui-border);\n border-radius: 999px;\n background: transparent;\n color: var(--ag-ui-muted);\n font-size: 0.8em;\n line-height: 1.4;\n}\n\n.run-notice-icon {\n flex: none;\n opacity: 0.75;\n}\n\n.run-notice-text {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n background: var(--ag-ui-assistant-bg);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--ag-ui-danger);\n color: var(--ag-ui-danger);\n}\n\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 14ch;\n}\n\n.attachment-chip-size {\n color: var(--ag-ui-muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--ag-ui-danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--ag-ui-border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--ag-ui-accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--ag-ui-accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--ag-ui-muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--ag-ui-assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */\n.approval {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.approval[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.approval-body {\n font-weight: 600;\n}\n\n.approval-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.approval-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.approval-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.approval-btn--approve {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */\n.question {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.question[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.question-body {\n font-weight: 600;\n}\n\n.question-options {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.question-choice {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n\n.question-input {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 10px;\n font: inherit;\n color: var(--ag-ui-fg);\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n}\n\n.question-input:disabled {\n opacity: 0.6;\n}\n\n.question-actions {\n display: flex;\n justify-content: flex-end;\n}\n\n.question-btn {\n border: 1px solid var(--ag-ui-accent);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n.question-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--ag-ui-assistant-bg);\n color: var(--ag-ui-fg);\n}\n\n.skill-chip:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--ag-ui-fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--ag-ui-assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--ag-ui-muted);\n}\n\n.skill-hint {\n margin: 8px 12px 0;\n font-size: 0.85em;\n color: var(--ag-ui-danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n}\n\n.drawer[hidden] {\n display: none;\n}\n\n.checkpoints {\n position: absolute;\n inset-block-start: 3rem;\n inset-inline: 0.75rem;\n z-index: 6;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.5rem;\n border: 1px solid var(--agui-border, #d7d7dc);\n border-radius: 0.5rem;\n background: var(--agui-surface, #fff);\n box-shadow: 0 6px 24px rgb(0 0 0 / 12%);\n max-height: 60%;\n overflow-y: auto;\n}\n\n.checkpoints[hidden] {\n display: none;\n}\n\n.checkpoints-title {\n font-size: 0.75rem;\n font-weight: 600;\n opacity: 0.7;\n}\n\n.checkpoints-empty {\n padding: 0.5rem 0.25rem;\n font-size: 0.8125rem;\n opacity: 0.7;\n}\n\n.checkpoint-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.25rem;\n border-radius: 0.375rem;\n}\n\n.checkpoint-row:hover {\n background: var(--agui-hover, #f3f3f5);\n}\n\n.checkpoint-label {\n flex: 1;\n font-size: 0.8125rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.checkpoint-branch {\n font-size: 0.6875rem;\n padding: 0 0.375rem;\n border-radius: 999px;\n background: var(--agui-hover, #f3f3f5);\n opacity: 0.8;\n}\n\n.checkpoint-action {\n font: inherit;\n font-size: 0.75rem;\n cursor: pointer;\n padding: 0.125rem 0.5rem;\n border: 1px solid var(--agui-border, #d7d7dc);\n border-radius: 0.375rem;\n background: transparent;\n color: inherit;\n}\n\n.checkpoint-action:hover {\n background: var(--agui-hover, #f3f3f5);\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--ag-ui-bg);\n border-right: 1px solid var(--ag-ui-border);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ag-ui-space);\n padding: var(--ag-ui-pad);\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-title {\n font-weight: 600;\n}\n\n.drawer-new {\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n background: var(--ag-ui-bg);\n color: var(--ag-ui-accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.drawer-empty {\n padding: var(--ag-ui-pad);\n font-size: 0.9em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-row--active {\n background: var(--ag-ui-assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--ag-ui-muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--ag-ui-muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-accent);\n border-radius: 6px;\n background: var(--ag-ui-input-bg);\n color: var(--ag-ui-fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--ag-ui-danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--ag-ui-danger);\n color: #ffffff;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
|
|
2
2
|
//# sourceMappingURL=styles.d.ts.map
|
package/dist/ui/styles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,42mCA49ClB,CAAC"}
|
package/dist/ui/ui_strings.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export interface UiStrings {
|
|
|
46
46
|
navigating: string;
|
|
47
47
|
/** Missing-placeholder skill hint. Tokens: `{title}`, `{fields}`. */
|
|
48
48
|
skillNeeds: string;
|
|
49
|
+
/** Notice shown when the agent condensed earlier turns. Token: `{count}`. */
|
|
50
|
+
historyCompacted: string;
|
|
51
|
+
/** Notice shown when the agent loads an agent skill. Token: `{name}`. */
|
|
52
|
+
usingSkill: string;
|
|
49
53
|
/** `aria-label` of the message textarea. */
|
|
50
54
|
message: string;
|
|
51
55
|
/** Placeholder of the message textarea. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui_strings.d.ts","sourceRoot":"","sources":["../../src/ui/ui_strings.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ui_strings.d.ts","sourceRoot":"","sources":["../../src/ui/ui_strings.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artooi/ag-ui-web-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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",
|
package/src/constants.ts
CHANGED
|
@@ -19,6 +19,16 @@ export const SUBMIT_EVENT = "ag-ui-submit";
|
|
|
19
19
|
*/
|
|
20
20
|
export const TOGGLE_EVENT = "ag-ui-toggle";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Event dispatched by `<ag-ui-chat>` when AG-UI **shared state** changes — the
|
|
24
|
+
* server streamed a `STATE_SNAPSHOT` / `STATE_DELTA`, or the host assigned
|
|
25
|
+
* {@link AgUiChat.sharedState}. `detail` carries `{ state }`.
|
|
26
|
+
*
|
|
27
|
+
* This is the protocol's own state channel, distinct from `registerPageState`,
|
|
28
|
+
* which exposes host state to the agent as ordinary *tools*.
|
|
29
|
+
*/
|
|
30
|
+
export const STATE_EVENT = "ag-ui-state";
|
|
31
|
+
|
|
22
32
|
/** Roles a chat message can take. */
|
|
23
33
|
export const MESSAGE_ROLE = {
|
|
24
34
|
USER: "user",
|
|
@@ -107,3 +117,21 @@ export const TOOL_DISPLAY = {
|
|
|
107
117
|
COMPACT: "compact",
|
|
108
118
|
FULL: "full",
|
|
109
119
|
} as const;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* `activityType` of the AG-UI `ACTIVITY_SNAPSHOT` event `django-ag-ui` emits
|
|
123
|
+
* when a compaction capability trimmed the message history. Its `content`
|
|
124
|
+
* carries `{ removed, before, after }`.
|
|
125
|
+
*/
|
|
126
|
+
export const COMPACTION_ACTIVITY_TYPE = "compaction";
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Pydantic-AI's built-in tool the model calls to load a *deferred* capability,
|
|
130
|
+
* with `{ id }` naming it. Agent skills are deferred capabilities whose id is
|
|
131
|
+
* the skill name, so a call to this tool is how "the agent picked skill X"
|
|
132
|
+
* reaches the client — there is no separate event for it.
|
|
133
|
+
*
|
|
134
|
+
* Not to be confused with the host-provided {@link Skill} catalog, which is a
|
|
135
|
+
* *human* affordance (a prompt the user launches from the `/`-palette).
|
|
136
|
+
*/
|
|
137
|
+
export const LOAD_CAPABILITY_TOOL = "load_capability";
|
package/src/core/ag_ui_chat.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Context, Interrupt, Message, Tool } from "@ag-ui/core";
|
|
2
2
|
import {
|
|
3
|
+
COMPACTION_ACTIVITY_TYPE,
|
|
3
4
|
DEFAULT_ATTACHMENT_MAX_BYTES,
|
|
5
|
+
LOAD_CAPABILITY_TOOL,
|
|
4
6
|
MESSAGE_ROLE,
|
|
7
|
+
STATE_EVENT,
|
|
5
8
|
SUBMIT_EVENT,
|
|
6
9
|
TOGGLE_EVENT,
|
|
7
10
|
TOOL_CALL_STATUS,
|
|
@@ -17,9 +20,9 @@ import { isDestructive } from "../tools/is_destructive.js";
|
|
|
17
20
|
import { isNavigates } from "../tools/is_navigates.js";
|
|
18
21
|
import { createPageActionTools, type ResolvePageTarget } from "../tools/page_action_tools.js";
|
|
19
22
|
import { createPageMapContext, type PageMap } from "../tools/page_map.js";
|
|
23
|
+
import { createPageStateTools, type PageState } from "../tools/page_state.js";
|
|
20
24
|
import { parseToolCatalog } from "../tools/parse_tool_catalog.js";
|
|
21
25
|
import { createRouteTools, type RouteMap } from "../tools/route_map.js";
|
|
22
|
-
import { createStateHookTools, type StateHook } from "../tools/state_hook.js";
|
|
23
26
|
import {
|
|
24
27
|
type ApprovalRenderer,
|
|
25
28
|
type ApprovalRequest,
|
|
@@ -37,6 +40,7 @@ import {
|
|
|
37
40
|
} from "../ui/question_card.js";
|
|
38
41
|
import { renderMarkdown } from "../ui/render_markdown.js";
|
|
39
42
|
import { wrapWords } from "../ui/reveal_words.js";
|
|
43
|
+
import { renderRunNotice } from "../ui/run_notice.js";
|
|
40
44
|
import { SkillsMenu } from "../ui/skills_menu.js";
|
|
41
45
|
import { STYLES } from "../ui/styles.js";
|
|
42
46
|
import { ThoughtsBlock } from "../ui/thoughts_block.js";
|
|
@@ -73,6 +77,11 @@ export interface SubmitDetail {
|
|
|
73
77
|
readonly attachments: readonly AttachmentRef[];
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
/** `detail` shape of the {@link STATE_EVENT} CustomEvent. */
|
|
81
|
+
export interface StateDetail {
|
|
82
|
+
readonly state: Readonly<Record<string, unknown>>;
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
/** `detail` shape of the {@link TOGGLE_EVENT} CustomEvent. */
|
|
77
86
|
export interface ToggleDetail {
|
|
78
87
|
readonly collapsed: boolean;
|
|
@@ -158,7 +167,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
158
167
|
/**
|
|
159
168
|
* Per-run frontend tool catalog provider. Defaults to the built-in
|
|
160
169
|
* `route.*` tools (when a {@link routeMap} is set) plus the tools registered
|
|
161
|
-
* via {@link registerTool} / {@link
|
|
170
|
+
* via {@link registerTool} / {@link registerPageState}; override to supply a
|
|
162
171
|
* fully custom catalog.
|
|
163
172
|
*/
|
|
164
173
|
getTools: () => Tool[] = () => [
|
|
@@ -322,6 +331,10 @@ export class AgUiChat extends HTMLElement {
|
|
|
322
331
|
#runAttachments: readonly AttachmentRef[] = [];
|
|
323
332
|
|
|
324
333
|
#client: AgUiClient | null = null;
|
|
334
|
+
// Seed for the next client. Once one exists it owns the live value (the
|
|
335
|
+
// agent applies STATE_SNAPSHOT / STATE_DELTA into it), so this is only the
|
|
336
|
+
// starting point — `sharedState` reads through to the client when present.
|
|
337
|
+
#sharedState: Record<string, unknown> = {};
|
|
325
338
|
// Whether an interaction is in flight (first onRunStart → onSettled). Drives
|
|
326
339
|
// the Send⇄Stop button: `agent.isRunning` is false between frontend-tool
|
|
327
340
|
// rounds, but the user must still be able to stop there.
|
|
@@ -479,13 +492,46 @@ export class AgUiChat extends HTMLElement {
|
|
|
479
492
|
this.#toolRegistry.register(tool);
|
|
480
493
|
}
|
|
481
494
|
|
|
482
|
-
/**
|
|
483
|
-
|
|
484
|
-
|
|
495
|
+
/**
|
|
496
|
+
* AG-UI **shared state** for this conversation — the protocol's own state
|
|
497
|
+
* channel, sent as `RunAgentInput.state` on every run and replaced in place
|
|
498
|
+
* when the server streams `STATE_SNAPSHOT` / `STATE_DELTA`. Assigning seeds
|
|
499
|
+
* the next run; reading returns whatever the agent last applied.
|
|
500
|
+
*
|
|
501
|
+
* Listen for {@link STATE_EVENT} to react to server-driven changes.
|
|
502
|
+
*
|
|
503
|
+
* Not to be confused with {@link registerPageState}, which exposes host
|
|
504
|
+
* state to the agent as ordinary *tools*.
|
|
505
|
+
*/
|
|
506
|
+
get sharedState(): Readonly<Record<string, unknown>> {
|
|
507
|
+
return this.#client?.state ?? this.#sharedState;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
set sharedState(state: Readonly<Record<string, unknown>>) {
|
|
511
|
+
this.#sharedState = { ...state };
|
|
512
|
+
// A client already exists for this conversation — push it through so the
|
|
513
|
+
// next run sends it, rather than silently waiting for a new conversation.
|
|
514
|
+
this.#client?.setState(this.#sharedState);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Bind a piece of host page state to `read_<name>` / `set_<name>` tools. */
|
|
518
|
+
registerPageState(binding: PageState): void {
|
|
519
|
+
for (const tool of createPageStateTools(binding)) {
|
|
485
520
|
this.#toolRegistry.register(tool);
|
|
486
521
|
}
|
|
487
522
|
}
|
|
488
523
|
|
|
524
|
+
/**
|
|
525
|
+
* @deprecated Renamed to {@link registerPageState}. The old name read as
|
|
526
|
+
* AG-UI shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this
|
|
527
|
+
* component does not implement — these are ordinary client tools over host
|
|
528
|
+
* page state. Behaviour is unchanged; this alias will be removed in a future
|
|
529
|
+
* major.
|
|
530
|
+
*/
|
|
531
|
+
registerStateHook(binding: PageState): void {
|
|
532
|
+
this.registerPageState(binding);
|
|
533
|
+
}
|
|
534
|
+
|
|
489
535
|
/** The built-in `route.*` tools, present only when a route map is set. */
|
|
490
536
|
#routeTools(): ClientTool[] {
|
|
491
537
|
if (this.routeMap.length === 0) {
|
|
@@ -1168,11 +1214,18 @@ export class AgUiChat extends HTMLElement {
|
|
|
1168
1214
|
const toolCalls = message.toolCalls;
|
|
1169
1215
|
if (toolCalls !== undefined) {
|
|
1170
1216
|
for (const call of toolCalls) {
|
|
1171
|
-
|
|
1217
|
+
const restored = {
|
|
1172
1218
|
id: call.id,
|
|
1173
1219
|
name: call.function.name,
|
|
1174
1220
|
args: this.#parseArgs(call.function.arguments),
|
|
1175
|
-
}
|
|
1221
|
+
};
|
|
1222
|
+
// Restored history goes through the same interception as the live
|
|
1223
|
+
// stream — otherwise a reload resurrects the raw `load_capability`
|
|
1224
|
+
// card the live path deliberately replaced.
|
|
1225
|
+
if (this.#noticeIfSkillLoad(restored)) {
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
this.#cardFor(restored);
|
|
1176
1229
|
}
|
|
1177
1230
|
}
|
|
1178
1231
|
return;
|
|
@@ -1588,6 +1641,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
1588
1641
|
getHeaders: () => this.headers,
|
|
1589
1642
|
threadId: this.#threadId,
|
|
1590
1643
|
initialMessages: this.#initialMessages,
|
|
1644
|
+
initialState: this.#sharedState,
|
|
1591
1645
|
});
|
|
1592
1646
|
this.#client = new AgUiClient({
|
|
1593
1647
|
agent,
|
|
@@ -1597,12 +1651,25 @@ export class AgUiChat extends HTMLElement {
|
|
|
1597
1651
|
executeTool: (call) => this.#executeTool(call),
|
|
1598
1652
|
resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
|
|
1599
1653
|
onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
|
|
1654
|
+
onStateChanged: (state) => this.#onSharedStateChanged(state),
|
|
1600
1655
|
connectionLostMessage: this.#strings.connectionLost,
|
|
1601
1656
|
});
|
|
1602
1657
|
}
|
|
1603
1658
|
return this.#client;
|
|
1604
1659
|
}
|
|
1605
1660
|
|
|
1661
|
+
/** Mirror the agent's applied state and tell the host it moved. */
|
|
1662
|
+
#onSharedStateChanged(state: Readonly<Record<string, unknown>>): void {
|
|
1663
|
+
this.#sharedState = { ...state };
|
|
1664
|
+
this.dispatchEvent(
|
|
1665
|
+
new CustomEvent<StateDetail>(STATE_EVENT, {
|
|
1666
|
+
detail: { state: this.#sharedState },
|
|
1667
|
+
bubbles: true,
|
|
1668
|
+
composed: true,
|
|
1669
|
+
}),
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1606
1673
|
/** Whether ``call`` should be gated behind the confirmation card. */
|
|
1607
1674
|
async #needsConfirmation(call: AgUiToolCall, tool: ClientTool): Promise<boolean> {
|
|
1608
1675
|
if (this.autoConfirm) {
|
|
@@ -1615,6 +1682,12 @@ export class AgUiChat extends HTMLElement {
|
|
|
1615
1682
|
}
|
|
1616
1683
|
|
|
1617
1684
|
async #executeTool(call: AgUiToolCall): Promise<ToolExecution | null> {
|
|
1685
|
+
// A skill load already rendered as a notice on the stream; it is never a
|
|
1686
|
+
// client tool and its result is pydantic-ai's business, so it must not
|
|
1687
|
+
// acquire a card here on the way to the no-result fallback below.
|
|
1688
|
+
if (skillNameFrom(call) !== null) {
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1618
1691
|
const card = this.#cardFor(call);
|
|
1619
1692
|
this.#toolCards.delete(call.id);
|
|
1620
1693
|
const tool = this.#resolveTool(call.name);
|
|
@@ -1780,8 +1853,30 @@ export class AgUiChat extends HTMLElement {
|
|
|
1780
1853
|
},
|
|
1781
1854
|
onToolCall: (call) => {
|
|
1782
1855
|
this.#hidePending();
|
|
1856
|
+
// A skill activation is an ordinary `load_capability` tool call — the
|
|
1857
|
+
// deferred-capability mechanism pydantic-ai already uses — so it arrives
|
|
1858
|
+
// here rather than on a channel of its own. Render it as a notice and
|
|
1859
|
+
// *return*: falling through would show a raw tool card beside the chip,
|
|
1860
|
+
// which is worse than the card alone.
|
|
1861
|
+
if (this.#noticeIfSkillLoad(call)) {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1783
1864
|
this.#cardFor(call);
|
|
1784
1865
|
},
|
|
1866
|
+
onActivity: (activityType, content) => {
|
|
1867
|
+
if (activityType !== COMPACTION_ACTIVITY_TYPE) {
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
const removed = compactionRemoved(content);
|
|
1871
|
+
if (removed === null) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
this.#appendNotice(
|
|
1875
|
+
"🗜",
|
|
1876
|
+
this.#strings.historyCompacted.replace("{count}", String(removed)),
|
|
1877
|
+
"compaction",
|
|
1878
|
+
);
|
|
1879
|
+
},
|
|
1785
1880
|
onToolResult: (toolCallId, content) => {
|
|
1786
1881
|
const card = this.#toolCards.get(toolCallId);
|
|
1787
1882
|
if (card === undefined) {
|
|
@@ -1912,6 +2007,34 @@ export class AgUiChat extends HTMLElement {
|
|
|
1912
2007
|
* {@link AgUiClientHandlers.onToolCall} creates the card (pending) during the
|
|
1913
2008
|
* run; {@link #executeTool} later retrieves the same card to settle it.
|
|
1914
2009
|
*/
|
|
2010
|
+
/**
|
|
2011
|
+
* Append an ambient run notice to the current group.
|
|
2012
|
+
*
|
|
2013
|
+
* Goes through {@link #ensureGroup} like a tool card so it lands *inside* the
|
|
2014
|
+
* assistant turn it annotates rather than floating between turns, and shares
|
|
2015
|
+
* the same pending-hide / scroll behaviour.
|
|
2016
|
+
*/
|
|
2017
|
+
/**
|
|
2018
|
+
* Render a skill notice for a `load_capability` call; ``true`` when handled.
|
|
2019
|
+
*
|
|
2020
|
+
* Shared by the live stream and history replay so the transcript looks the
|
|
2021
|
+
* same before and after a reload.
|
|
2022
|
+
*/
|
|
2023
|
+
#noticeIfSkillLoad(call: AgUiToolCall): boolean {
|
|
2024
|
+
const skill = skillNameFrom(call);
|
|
2025
|
+
if (skill === null) {
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
this.#appendNotice("✨", this.#strings.usingSkill.replace("{name}", skill), "skill");
|
|
2029
|
+
return true;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
#appendNotice(icon: string, text: string, kind: string): void {
|
|
2033
|
+
this.#ensureGroup().appendChild(renderRunNotice(icon, text, kind));
|
|
2034
|
+
this.#updateEmptyState();
|
|
2035
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1915
2038
|
#cardFor(call: AgUiToolCall): ToolCallCard {
|
|
1916
2039
|
const existing = this.#toolCards.get(call.id);
|
|
1917
2040
|
if (existing !== undefined) {
|
|
@@ -1935,3 +2058,27 @@ export class AgUiChat extends HTMLElement {
|
|
|
1935
2058
|
return card;
|
|
1936
2059
|
}
|
|
1937
2060
|
}
|
|
2061
|
+
|
|
2062
|
+
/**
|
|
2063
|
+
* The skill name a `load_capability` call activated, or `null` when the call is
|
|
2064
|
+
* something else.
|
|
2065
|
+
*
|
|
2066
|
+
* Every deferred capability loads through this one tool, so the id is only a
|
|
2067
|
+
* *skill* name when the project wired agent skills — a project deferring some
|
|
2068
|
+
* other capability would surface its id here too. That is acceptable for a
|
|
2069
|
+
* muted notice and preferable to inventing a parallel signal: the id is exactly
|
|
2070
|
+
* what the model selected, which is what the notice reports.
|
|
2071
|
+
*/
|
|
2072
|
+
function skillNameFrom(call: AgUiToolCall): string | null {
|
|
2073
|
+
if (call.name !== LOAD_CAPABILITY_TOOL) {
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
2076
|
+
const id = (call.args as { id?: unknown } | null | undefined)?.id;
|
|
2077
|
+
return typeof id === "string" && id !== "" ? id : null;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
/** The `removed` count from a compaction activity payload, or `null` if absent. */
|
|
2081
|
+
function compactionRemoved(content: unknown): number | null {
|
|
2082
|
+
const removed = (content as { removed?: unknown } | null | undefined)?.removed;
|
|
2083
|
+
return typeof removed === "number" ? removed : null;
|
|
2084
|
+
}
|