@artooi/ag-ui-web-component 0.44.0 → 0.46.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 +182 -1
- package/README.md +245 -32
- package/dist/ag-ui-web-component.bundle.js +3696 -2798
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +4 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +28 -8
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/read_flag.d.ts +11 -0
- package/dist/core/read_flag.d.ts.map +1 -0
- package/dist/core/read_json_attribute.d.ts +15 -0
- package/dist/core/read_json_attribute.d.ts.map +1 -0
- package/dist/index.js +5084 -3481
- package/dist/index.js.map +4 -4
- package/dist/tools/tool_catalog.d.ts +5 -0
- package/dist/tools/tool_catalog.d.ts.map +1 -1
- package/dist/tools/tool_dispatch.d.ts.map +1 -1
- package/dist/ui/charts/chart_styles.d.ts +2 -0
- package/dist/ui/charts/chart_styles.d.ts.map +1 -0
- package/dist/ui/composer/attachment_styles.d.ts +2 -0
- package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
- package/dist/ui/composer/build_composer.d.ts +30 -0
- package/dist/ui/composer/build_composer.d.ts.map +1 -0
- package/dist/ui/composer/composer_styles.d.ts +2 -0
- package/dist/ui/composer/composer_styles.d.ts.map +1 -0
- package/dist/ui/composer/skill_styles.d.ts +2 -0
- package/dist/ui/composer/skill_styles.d.ts.map +1 -0
- package/dist/ui/history/history_styles.d.ts +2 -0
- package/dist/ui/history/history_styles.d.ts.map +1 -0
- package/dist/ui/interrupts/approval_styles.d.ts +2 -0
- package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
- package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
- package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
- package/dist/ui/interrupts/question_styles.d.ts +2 -0
- package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
- package/dist/ui/placement/collapse_styles.d.ts +2 -0
- package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
- package/dist/ui/placement/embedded_styles.d.ts +2 -0
- package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
- package/dist/ui/placement/launcher_styles.d.ts +2 -0
- package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
- package/dist/ui/placement/page_styles.d.ts +2 -0
- package/dist/ui/placement/page_styles.d.ts.map +1 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -1
- package/dist/ui/placement/preset_styles.d.ts +2 -0
- package/dist/ui/placement/preset_styles.d.ts.map +1 -0
- package/dist/ui/placement/resize_styles.d.ts +2 -0
- package/dist/ui/placement/resize_styles.d.ts.map +1 -0
- package/dist/ui/placement/sidebar_styles.d.ts +2 -0
- package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
- package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
- package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
- package/dist/ui/placement/surface_for.d.ts +74 -0
- package/dist/ui/placement/surface_for.d.ts.map +1 -0
- package/dist/ui/progress/run_notice_styles.d.ts +2 -0
- package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
- package/dist/ui/progress/thoughts_styles.d.ts +2 -0
- package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
- package/dist/ui/progress/tool_call_card.d.ts +8 -0
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/progress/tool_group_styles.d.ts +2 -0
- package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
- package/dist/ui/progress/tool_step_group.d.ts +47 -0
- package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
- package/dist/ui/progress/tool_step_styles.d.ts +2 -0
- package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
- package/dist/ui/shell/adopt_styles.d.ts +9 -4
- package/dist/ui/shell/adopt_styles.d.ts.map +1 -1
- package/dist/ui/shell/build_header.d.ts +40 -0
- package/dist/ui/shell/build_header.d.ts.map +1 -0
- package/dist/ui/shell/build_launcher.d.ts +20 -0
- package/dist/ui/shell/build_launcher.d.ts.map +1 -0
- package/dist/ui/shell/frame_styles.d.ts +2 -0
- package/dist/ui/shell/frame_styles.d.ts.map +1 -0
- package/dist/ui/shell/theme_styles.d.ts +2 -0
- package/dist/ui/shell/theme_styles.d.ts.map +1 -0
- package/dist/ui/shell/token_styles.d.ts +2 -0
- package/dist/ui/shell/token_styles.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/themes/classic_styles.d.ts +2 -0
- package/dist/ui/themes/classic_styles.d.ts.map +1 -0
- package/dist/ui/themes/classic_tokens.d.ts +2 -0
- package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
- package/dist/ui/themes/code_tokens.d.ts +2 -0
- package/dist/ui/themes/code_tokens.d.ts.map +1 -0
- package/dist/ui/themes/ledger_styles.d.ts +2 -0
- package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
- package/dist/ui/themes/ledger_tokens.d.ts +2 -0
- package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
- package/dist/ui/themes/modern_styles.d.ts +2 -0
- package/dist/ui/themes/modern_styles.d.ts.map +1 -0
- package/dist/ui/themes/quiet_styles.d.ts +2 -0
- package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
- package/dist/ui/themes/scheme_styles.d.ts +2 -0
- package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
- package/dist/ui/themes/soft_styles.d.ts +2 -0
- package/dist/ui/themes/soft_styles.d.ts.map +1 -0
- package/dist/ui/themes/soft_tokens.d.ts +2 -0
- package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
- package/dist/ui/themes/theme_hosts.d.ts +15 -0
- package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
- package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
- package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
- package/dist/ui/transcript/build_transcript.d.ts +21 -0
- package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
- package/dist/ui/transcript/greeting_styles.d.ts +2 -0
- package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
- package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
- package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
- package/dist/ui/transcript/message_action_styles.d.ts +2 -0
- package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
- package/dist/ui/transcript/message_styles.d.ts +2 -0
- package/dist/ui/transcript/message_styles.d.ts.map +1 -0
- package/dist/ui/transcript/transcript.d.ts +16 -1
- package/dist/ui/transcript/transcript.d.ts.map +1 -1
- package/dist/ui/transcript/transcript_styles.d.ts +2 -0
- package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
- package/dist/ui/ui_strings.d.ts +18 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/constants.ts +4 -1
- package/src/core/ag_ui_chat.ts +164 -299
- package/src/core/read_flag.ts +13 -0
- package/src/core/read_json_attribute.ts +30 -0
- package/src/tools/tool_catalog.ts +23 -10
- package/src/tools/tool_dispatch.ts +26 -11
- package/src/ui/charts/chart_block.ts +9 -9
- package/src/ui/charts/chart_styles.ts +63 -0
- package/src/ui/composer/attachment_styles.ts +71 -0
- package/src/ui/composer/build_composer.ts +112 -0
- package/src/ui/composer/composer_styles.ts +174 -0
- package/src/ui/composer/skill_styles.ts +120 -0
- package/src/ui/history/history_styles.ts +484 -0
- package/src/ui/interrupts/approval_styles.ts +52 -0
- package/src/ui/interrupts/confirmation_styles.ts +114 -0
- package/src/ui/interrupts/question_styles.ts +73 -0
- package/src/ui/placement/collapse_styles.ts +137 -0
- package/src/ui/placement/embedded_styles.ts +14 -0
- package/src/ui/placement/launcher_styles.ts +189 -0
- package/src/ui/placement/page_styles.ts +47 -0
- package/src/ui/placement/panel_placement.ts +3 -12
- package/src/ui/placement/preset_styles.ts +33 -0
- package/src/ui/placement/resize_styles.ts +186 -0
- package/src/ui/placement/sidebar_styles.ts +88 -0
- package/src/ui/placement/small_viewport_styles.ts +56 -0
- package/src/ui/placement/surface_for.ts +135 -0
- package/src/ui/progress/run_notice_styles.ts +52 -0
- package/src/ui/progress/thoughts_styles.ts +65 -0
- package/src/ui/progress/tool_call_card.ts +11 -0
- package/src/ui/progress/tool_group_styles.ts +232 -0
- package/src/ui/progress/tool_step_group.ts +269 -0
- package/src/ui/progress/tool_step_styles.ts +472 -0
- package/src/ui/shell/adopt_styles.ts +11 -5
- package/src/ui/shell/build_header.ts +108 -0
- package/src/ui/shell/build_launcher.ts +50 -0
- package/src/ui/shell/frame_styles.ts +119 -0
- package/src/ui/shell/theme_styles.ts +11 -0
- package/src/ui/shell/token_styles.ts +406 -0
- package/src/ui/styles.ts +104 -3774
- package/src/ui/themes/classic_styles.ts +32 -0
- package/src/ui/themes/classic_tokens.ts +50 -0
- package/src/ui/themes/code_tokens.ts +21 -0
- package/src/ui/themes/ledger_styles.ts +168 -0
- package/src/ui/themes/ledger_tokens.ts +32 -0
- package/src/ui/themes/modern_styles.ts +80 -0
- package/src/ui/themes/quiet_styles.ts +115 -0
- package/src/ui/themes/scheme_styles.ts +17 -0
- package/src/ui/themes/soft_styles.ts +148 -0
- package/src/ui/themes/soft_tokens.ts +28 -0
- package/src/ui/themes/theme_hosts.ts +14 -0
- package/src/ui/transcript/attachment_chip_styles.ts +116 -0
- package/src/ui/transcript/build_transcript.ts +67 -0
- package/src/ui/transcript/greeting_styles.ts +171 -0
- package/src/ui/transcript/groups_tool_steps.ts +16 -0
- package/src/ui/transcript/message_action_styles.ts +155 -0
- package/src/ui/transcript/message_styles.ts +262 -0
- package/src/ui/transcript/transcript.ts +109 -3
- package/src/ui/transcript/transcript_styles.ts +140 -0
- package/src/ui/ui_strings.ts +31 -0
- package/src/version.ts +1 -1
- package/dist/ui/placement/is_collapsible_placement.d.ts +0 -18
- package/dist/ui/placement/is_collapsible_placement.d.ts.map +0 -1
- package/dist/ui/placement/is_draggable_placement.d.ts +0 -9
- package/dist/ui/placement/is_draggable_placement.d.ts.map +0 -1
- package/src/ui/placement/is_collapsible_placement.ts +0 -19
- package/src/ui/placement/is_draggable_placement.ts +0 -23
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// The folded tool-step group: one row per run of consecutive tool calls,
|
|
2
|
+
// with the per-call cards one click down.
|
|
3
|
+
|
|
4
|
+
/** The four per-call card modes, as attribute selectors on the host. */
|
|
5
|
+
const CARD_MODES = [
|
|
6
|
+
'[data-tool-display="inline"]',
|
|
7
|
+
'[data-tool-display="minimal"]',
|
|
8
|
+
'[data-tool-display="compact"]',
|
|
9
|
+
'[data-tool-display="full"]',
|
|
10
|
+
].join(", ");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The host in grouped mode: any data-tool-display but the four card modes,
|
|
14
|
+
* which is how the element's toolDisplay getter reads it, so an absent or
|
|
15
|
+
* unknown value groups here as it does there. Wrapped in :where so it weighs
|
|
16
|
+
* what a bare :host weighs, and a theme restating a group rule on its own
|
|
17
|
+
* :host selector still wins at equal weight.
|
|
18
|
+
*/
|
|
19
|
+
const GROUPED = `:host(:where(:not(${CARD_MODES})))`;
|
|
20
|
+
|
|
21
|
+
/** The host in one of the four card modes, where a group drawn earlier is flattened. */
|
|
22
|
+
const CARDS = `:host(:is(${CARD_MODES}))`;
|
|
23
|
+
|
|
24
|
+
export const TOOL_GROUP_STYLES: string = `
|
|
25
|
+
/* ── The folded tool-step group ──────────────────────────────────
|
|
26
|
+
One quiet row per run of consecutive calls, the cards one click down as
|
|
27
|
+
plain lines rather than boxes. Every rule is scoped to grouped mode, so
|
|
28
|
+
none of it reaches a group drawn earlier once the host moves to a card
|
|
29
|
+
mode. */
|
|
30
|
+
${GROUPED} .tool-group {
|
|
31
|
+
align-self: stretch;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
${GROUPED} .tool-group-head {
|
|
38
|
+
appearance: none;
|
|
39
|
+
align-self: flex-start;
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 8px;
|
|
44
|
+
margin: 0 0 0 -6px;
|
|
45
|
+
padding: 4px 6px;
|
|
46
|
+
border: 0;
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
background: transparent;
|
|
49
|
+
color: var(--_muted);
|
|
50
|
+
font: inherit;
|
|
51
|
+
font-size: var(--_text-sm);
|
|
52
|
+
line-height: 1.3;
|
|
53
|
+
text-align: left;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
${GROUPED} .tool-group-head:hover {
|
|
58
|
+
background: var(--_hover);
|
|
59
|
+
color: var(--_fg);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
${GROUPED} .tool-group-head:focus-visible {
|
|
63
|
+
outline: 2px solid var(--_ring);
|
|
64
|
+
outline-offset: 1px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
${GROUPED} .tool-group-label {
|
|
68
|
+
min-width: 0;
|
|
69
|
+
font-weight: 500;
|
|
70
|
+
color: var(--_ink-2);
|
|
71
|
+
overflow-wrap: anywhere;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
${GROUPED} .tool-group-meta {
|
|
75
|
+
min-width: 0;
|
|
76
|
+
color: var(--_muted);
|
|
77
|
+
overflow-wrap: anywhere;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Each note opens with a middle dot, which also parts the first note from
|
|
81
|
+
the label. */
|
|
82
|
+
${GROUPED} .tool-group-note::before {
|
|
83
|
+
content: "\\00B7";
|
|
84
|
+
margin: 0 6px 0 2px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
${GROUPED} .tool-group[data-state="error"] .tool-group-meta {
|
|
88
|
+
color: var(--_danger);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
${GROUPED} .tool-group-icon {
|
|
92
|
+
flex: none;
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
width: 14px;
|
|
95
|
+
height: 14px;
|
|
96
|
+
background: currentColor;
|
|
97
|
+
mask: var(--_ico-check) center / contain no-repeat;
|
|
98
|
+
color: var(--_success);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
${GROUPED} .tool-group[data-state="error"] .tool-group-icon {
|
|
102
|
+
mask-image: var(--_ico-error);
|
|
103
|
+
color: var(--_danger);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
${GROUPED} .tool-group[data-state="running"] .tool-group-icon {
|
|
107
|
+
mask: none;
|
|
108
|
+
background: none;
|
|
109
|
+
border: 2px solid var(--_muted);
|
|
110
|
+
border-top-color: transparent;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Waiting on the user: a steady accent dot rather than a spinner, and the
|
|
116
|
+
count in the accent's ink. */
|
|
117
|
+
${GROUPED} .tool-group[data-state="waiting"] .tool-group-icon {
|
|
118
|
+
mask: none;
|
|
119
|
+
border-radius: 50%;
|
|
120
|
+
transform: scale(0.6);
|
|
121
|
+
background: var(--_accent);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
${GROUPED} .tool-group[data-state="waiting"] .tool-group-meta {
|
|
125
|
+
color: var(--_accent-ink);
|
|
126
|
+
font-weight: 500;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
${GROUPED} .tool-group-chevron {
|
|
130
|
+
flex: none;
|
|
131
|
+
width: 12px;
|
|
132
|
+
height: 12px;
|
|
133
|
+
background: currentColor;
|
|
134
|
+
mask: var(--_ico-chevron) center / contain no-repeat;
|
|
135
|
+
opacity: 0.7;
|
|
136
|
+
transition: transform var(--_motion) var(--_ease);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
${GROUPED} .tool-group[data-open="true"] .tool-group-chevron {
|
|
140
|
+
transform: rotate(90deg);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@media (prefers-reduced-motion: reduce) {
|
|
144
|
+
${GROUPED} .tool-group[data-state="running"] .tool-group-icon {
|
|
145
|
+
animation: none;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
${GROUPED} .tool-group-list {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 2px;
|
|
153
|
+
margin: 2px 0 4px 6px;
|
|
154
|
+
padding-left: 13px;
|
|
155
|
+
border-left: 1px solid var(--_border);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
${GROUPED} .tool-group[data-open="false"] .tool-group-list {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* A card inside the group is one line of the list: icon, name, and its
|
|
163
|
+
Details control at the end, with the arguments and result opening under
|
|
164
|
+
the whole row. */
|
|
165
|
+
${GROUPED} .tool-group .tool-call {
|
|
166
|
+
max-width: 100%;
|
|
167
|
+
align-self: stretch;
|
|
168
|
+
flex-flow: row wrap;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 2px 10px;
|
|
171
|
+
padding: 3px 0;
|
|
172
|
+
border: 0;
|
|
173
|
+
border-radius: 0;
|
|
174
|
+
background: transparent;
|
|
175
|
+
color: var(--_ink-2);
|
|
176
|
+
font-family: inherit;
|
|
177
|
+
font-size: var(--_text-sm);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
${GROUPED} .tool-group .tool-call-head {
|
|
181
|
+
flex: 1 1 auto;
|
|
182
|
+
justify-content: flex-start;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
${GROUPED} .tool-group .tool-call-name {
|
|
186
|
+
flex: 0 1 auto;
|
|
187
|
+
font-weight: 450;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* The repeat ordinal on a step whose label an earlier step has. */
|
|
191
|
+
${GROUPED} .tool-call-repeat {
|
|
192
|
+
color: var(--_muted);
|
|
193
|
+
font-variant-numeric: tabular-nums;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
${GROUPED} .tool-group .tool-call-toggle {
|
|
197
|
+
font-family: inherit;
|
|
198
|
+
font-size: var(--_text-xs);
|
|
199
|
+
color: var(--_muted);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
${GROUPED} .tool-group .tool-call-body,
|
|
203
|
+
${GROUPED} .tool-group .tool-call-approval,
|
|
204
|
+
${GROUPED} .tool-group .tool-call-subagent {
|
|
205
|
+
flex-basis: 100%;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Grouping is decided as each card is built, so a card mode set afterwards
|
|
209
|
+
finds groups already drawn. It flattens them: the group and its list stop
|
|
210
|
+
generating boxes, the row and the repeat ordinals go, and every card lays
|
|
211
|
+
out as a direct child of the answer, exactly as a card built in that mode
|
|
212
|
+
does. */
|
|
213
|
+
${CARDS} .tool-group,
|
|
214
|
+
${CARDS} .tool-group-list {
|
|
215
|
+
display: contents;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
${CARDS} .tool-group-head,
|
|
219
|
+
${CARDS} .tool-call-repeat {
|
|
220
|
+
display: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* Each of these declares its own display, which beats the user-agent rule for
|
|
224
|
+
the hidden attribute, so each needs its own. The host prefix is there for
|
|
225
|
+
weight, not scope: it lifts these over the card-mode rules above that set
|
|
226
|
+
display, which are heavier than a bare class and attribute. */
|
|
227
|
+
:host .tool-group[hidden],
|
|
228
|
+
:host .tool-group-head[hidden],
|
|
229
|
+
:host .tool-group-list[hidden] {
|
|
230
|
+
display: none;
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { SUBAGENT_PHASE, TOOL_CALL_STATUS } from "../../constants.js";
|
|
2
|
+
import { fillUiString } from "../fill_ui_string.js";
|
|
3
|
+
import type { UiStrings } from "../ui_strings.js";
|
|
4
|
+
|
|
5
|
+
/** What a group says about itself, read by the shadow CSS from `data-state`. */
|
|
6
|
+
type GroupState = "waiting" | "running" | "done" | "error";
|
|
7
|
+
|
|
8
|
+
/** The statuses a step is still open in. */
|
|
9
|
+
const OPEN_STATUSES: ReadonlySet<string> = new Set([
|
|
10
|
+
TOOL_CALL_STATUS.PENDING,
|
|
11
|
+
TOOL_CALL_STATUS.DEFERRED,
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A sub-agent panel still working, by the phase it stamps on itself. A panel
|
|
16
|
+
* outlives the delegation it drew, so a settled one does not match.
|
|
17
|
+
*/
|
|
18
|
+
const RUNNING_SUBAGENT = `.tool-call-subagent > .subagent:is(${[
|
|
19
|
+
SUBAGENT_PHASE.STARTED,
|
|
20
|
+
SUBAGENT_PHASE.TOOL_CALL,
|
|
21
|
+
SUBAGENT_PHASE.TOOL_RESULT,
|
|
22
|
+
]
|
|
23
|
+
.map((phase) => `[data-phase="${phase}"]`)
|
|
24
|
+
.join(", ")})`;
|
|
25
|
+
|
|
26
|
+
/** One step of the group: its card, and its label as the row names it. */
|
|
27
|
+
interface Step {
|
|
28
|
+
readonly element: HTMLElement;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The attributes the row is drawn from: each card's `data-status`, its
|
|
35
|
+
* `data-asking` while a question or a confirmation is open on it, and the
|
|
36
|
+
* `data-phase` and `data-agent` a sub-agent panel stamps on itself inside the
|
|
37
|
+
* card that delegated to it. Watched rather than told, so a step settled,
|
|
38
|
+
* asked about or delegated by any path updates the row without the group being
|
|
39
|
+
* called.
|
|
40
|
+
*/
|
|
41
|
+
const WATCHED_ATTRIBUTES: readonly string[] = [
|
|
42
|
+
"data-status",
|
|
43
|
+
"data-asking",
|
|
44
|
+
"data-phase",
|
|
45
|
+
"data-agent",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Consecutive tool calls folded into one quiet row, "Worked through 5 steps",
|
|
50
|
+
* with the per-call cards one click down. Built by the transcript for each run
|
|
51
|
+
* of calls when the element's `toolDisplay` is `grouped`.
|
|
52
|
+
*
|
|
53
|
+
* The group owns no state of its own beyond its nodes and two facts: each
|
|
54
|
+
* step's label, and whether it has already opened itself for the current wait.
|
|
55
|
+
* Everything else is read back off the cards each time one changes.
|
|
56
|
+
*
|
|
57
|
+
* It opens itself while a step waits on the user, because a deferred step
|
|
58
|
+
* carries its approval prompt inside its card and a folded group would hide
|
|
59
|
+
* the very question it is asking. Once per wait, so the user can fold it
|
|
60
|
+
* again. A running sub-agent does not open it: the row names the sub-agent
|
|
61
|
+
* instead, beside the running mark.
|
|
62
|
+
*/
|
|
63
|
+
export class ToolStepGroup {
|
|
64
|
+
readonly element: HTMLDivElement;
|
|
65
|
+
readonly #strings: UiStrings;
|
|
66
|
+
readonly #list: HTMLDivElement;
|
|
67
|
+
readonly #head: HTMLButtonElement;
|
|
68
|
+
readonly #label: HTMLSpanElement;
|
|
69
|
+
readonly #meta: HTMLSpanElement;
|
|
70
|
+
/** The steps, in order. Cards only ever join a group, so this is its list's children. */
|
|
71
|
+
readonly #steps: Step[] = [];
|
|
72
|
+
/** Whether the group has already opened itself for the current wait. */
|
|
73
|
+
#openedForWait = false;
|
|
74
|
+
|
|
75
|
+
constructor(strings: UiStrings) {
|
|
76
|
+
this.#strings = strings;
|
|
77
|
+
this.element = document.createElement("div");
|
|
78
|
+
this.element.className = "tool-group";
|
|
79
|
+
this.element.setAttribute("part", "tool-group");
|
|
80
|
+
this.element.setAttribute("data-open", "false");
|
|
81
|
+
|
|
82
|
+
this.#head = document.createElement("button");
|
|
83
|
+
this.#head.type = "button";
|
|
84
|
+
this.#head.className = "tool-group-head";
|
|
85
|
+
this.#head.setAttribute("part", "tool-group-head");
|
|
86
|
+
this.#head.setAttribute("aria-expanded", "false");
|
|
87
|
+
|
|
88
|
+
const icon = document.createElement("span");
|
|
89
|
+
icon.className = "tool-group-icon";
|
|
90
|
+
icon.setAttribute("part", "tool-group-icon");
|
|
91
|
+
icon.setAttribute("aria-hidden", "true");
|
|
92
|
+
|
|
93
|
+
this.#label = document.createElement("span");
|
|
94
|
+
this.#label.className = "tool-group-label";
|
|
95
|
+
this.#label.setAttribute("part", "tool-group-label");
|
|
96
|
+
|
|
97
|
+
this.#meta = document.createElement("span");
|
|
98
|
+
this.#meta.className = "tool-group-meta";
|
|
99
|
+
this.#meta.setAttribute("part", "tool-group-meta");
|
|
100
|
+
|
|
101
|
+
const chevron = document.createElement("span");
|
|
102
|
+
chevron.className = "tool-group-chevron";
|
|
103
|
+
chevron.setAttribute("aria-hidden", "true");
|
|
104
|
+
|
|
105
|
+
this.#head.append(icon, this.#label, this.#meta, chevron);
|
|
106
|
+
this.#head.addEventListener("click", () => {
|
|
107
|
+
this.setOpen(this.element.getAttribute("data-open") !== "true");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.#list = document.createElement("div");
|
|
111
|
+
this.#list.className = "tool-group-list";
|
|
112
|
+
this.#list.setAttribute("part", "tool-group-list");
|
|
113
|
+
|
|
114
|
+
this.element.append(this.#head, this.#list);
|
|
115
|
+
|
|
116
|
+
new MutationObserver(() => {
|
|
117
|
+
this.#refresh();
|
|
118
|
+
}).observe(this.#list, {
|
|
119
|
+
attributes: true,
|
|
120
|
+
attributeFilter: [...WATCHED_ATTRIBUTES],
|
|
121
|
+
subtree: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Add a card's element as the group's next step, under the label the card
|
|
127
|
+
* was built with.
|
|
128
|
+
*
|
|
129
|
+
* A step whose label an earlier step already has gets a repeat ordinal,
|
|
130
|
+
* "Fill field (2)", in an element of its own beside the card's name. The
|
|
131
|
+
* name itself is never rewritten, so a group flattened back into per-call
|
|
132
|
+
* cards by a card display mode shows exactly the label the card was built
|
|
133
|
+
* with, and the CSS hides the ordinal there.
|
|
134
|
+
*/
|
|
135
|
+
add(card: HTMLElement, name: string): void {
|
|
136
|
+
const seen = this.#steps.filter((step) => step.name === name).length + 1;
|
|
137
|
+
let label = name;
|
|
138
|
+
if (seen > 1) {
|
|
139
|
+
const repeat = document.createElement("span");
|
|
140
|
+
repeat.className = "tool-call-repeat";
|
|
141
|
+
repeat.setAttribute("part", "tool-card-repeat");
|
|
142
|
+
repeat.textContent = fillUiString(this.#strings.toolStepRepeat, { n: seen });
|
|
143
|
+
card.querySelector(".tool-call-name")?.after(repeat);
|
|
144
|
+
label = `${name} ${repeat.textContent}`;
|
|
145
|
+
}
|
|
146
|
+
this.#steps.push({ element: card, name, label });
|
|
147
|
+
this.#list.append(card);
|
|
148
|
+
this.#refresh();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Show or hide the per-step cards. */
|
|
152
|
+
setOpen(open: boolean): void {
|
|
153
|
+
this.element.setAttribute("data-open", String(open));
|
|
154
|
+
this.#head.setAttribute("aria-expanded", String(open));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Open the group now, for the wait that is about to start.
|
|
159
|
+
*
|
|
160
|
+
* The group opens itself from its mutation observer, which runs a tick after
|
|
161
|
+
* the card is marked deferred. The approval prompt is drawn, and takes focus,
|
|
162
|
+
* in the same tick as the mark, while the list is still out of the layout: the
|
|
163
|
+
* focus is dropped and the scroll it would cause never happens. So the caller
|
|
164
|
+
* that is about to draw the prompt opens the group first, and this marks the
|
|
165
|
+
* wait as already answered for, so the observer's later refresh does not open
|
|
166
|
+
* it again over a user who folded it.
|
|
167
|
+
*/
|
|
168
|
+
reveal(): void {
|
|
169
|
+
this.#openedForWait = true;
|
|
170
|
+
this.setOpen(true);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The row's note for a running sub-agent, naming it, or `null` when no step
|
|
175
|
+
* is delegating right now. Only a step that is itself still running counts.
|
|
176
|
+
*/
|
|
177
|
+
#delegation(): string | null {
|
|
178
|
+
for (const { element } of this.#steps) {
|
|
179
|
+
const panel =
|
|
180
|
+
element.getAttribute("data-status") === TOOL_CALL_STATUS.PENDING
|
|
181
|
+
? element.querySelector(RUNNING_SUBAGENT)
|
|
182
|
+
: null;
|
|
183
|
+
if (panel !== null) {
|
|
184
|
+
const agent = panel.getAttribute("data-agent");
|
|
185
|
+
return agent === null
|
|
186
|
+
? this.#strings.subAgentWorking
|
|
187
|
+
: fillUiString(this.#strings.subAgentDelegatedTo, { agent });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#refresh(): void {
|
|
194
|
+
const strings = this.#strings;
|
|
195
|
+
const steps = this.#steps;
|
|
196
|
+
const statuses = steps.map((step) => step.element.getAttribute("data-status") ?? "");
|
|
197
|
+
const open = steps.filter((_, i) => OPEN_STATUSES.has(statuses[i] as string));
|
|
198
|
+
const count = (status: string): number => statuses.filter((s) => s === status).length;
|
|
199
|
+
const failed = count(TOOL_CALL_STATUS.ERROR);
|
|
200
|
+
const declined = count(TOOL_CALL_STATUS.DECLINED);
|
|
201
|
+
const deferred = count(TOOL_CALL_STATUS.DEFERRED);
|
|
202
|
+
// A step is waiting on the user when its approval is pending on the server,
|
|
203
|
+
// or when a question or confirmation is open on it. The second kind keeps
|
|
204
|
+
// its status at running, so it is read off its own flag.
|
|
205
|
+
const asking = steps.filter((step) => step.element.hasAttribute("data-asking")).length;
|
|
206
|
+
const waiting = deferred + asking;
|
|
207
|
+
const total = steps.length;
|
|
208
|
+
|
|
209
|
+
// Opens for a deferred step only. An approval renders inside its card, which
|
|
210
|
+
// a folded group would hide; a question or confirmation renders beside the
|
|
211
|
+
// group, in view, so there is nothing for it to open.
|
|
212
|
+
if (deferred > 0 && !this.#openedForWait) {
|
|
213
|
+
this.#openedForWait = true;
|
|
214
|
+
this.setOpen(true);
|
|
215
|
+
} else if (deferred === 0) {
|
|
216
|
+
this.#openedForWait = false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let state: GroupState = "done";
|
|
220
|
+
if (waiting > 0) {
|
|
221
|
+
state = "waiting";
|
|
222
|
+
} else if (open.length > 0) {
|
|
223
|
+
state = "running";
|
|
224
|
+
} else if (failed > 0) {
|
|
225
|
+
state = "error";
|
|
226
|
+
}
|
|
227
|
+
this.element.setAttribute("data-state", state);
|
|
228
|
+
this.element.setAttribute("data-count", String(total));
|
|
229
|
+
|
|
230
|
+
const only = total === 1 ? steps[0] : undefined;
|
|
231
|
+
if (only !== undefined) {
|
|
232
|
+
this.#label.textContent = only.label;
|
|
233
|
+
} else {
|
|
234
|
+
const template =
|
|
235
|
+
state === "waiting"
|
|
236
|
+
? strings.toolGroupSteps
|
|
237
|
+
: state === "running"
|
|
238
|
+
? strings.toolGroupWorking
|
|
239
|
+
: strings.toolGroupWorked;
|
|
240
|
+
this.#label.textContent = fillUiString(template, { count: total });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const notes: string[] = [];
|
|
244
|
+
const delegation = this.#delegation();
|
|
245
|
+
const current = open[0];
|
|
246
|
+
if (waiting > 0) {
|
|
247
|
+
notes.push(fillUiString(strings.toolGroupWaiting, { count: waiting }));
|
|
248
|
+
} else if (delegation !== null) {
|
|
249
|
+
notes.push(delegation);
|
|
250
|
+
} else if (current !== undefined && total > 1) {
|
|
251
|
+
notes.push(current.label);
|
|
252
|
+
}
|
|
253
|
+
if (failed > 0) {
|
|
254
|
+
notes.push(fillUiString(strings.toolGroupFailed, { count: failed }));
|
|
255
|
+
}
|
|
256
|
+
if (declined > 0) {
|
|
257
|
+
notes.push(fillUiString(strings.toolGroupDeclined, { count: declined }));
|
|
258
|
+
}
|
|
259
|
+
this.#meta.replaceChildren(
|
|
260
|
+
...notes.map((text) => {
|
|
261
|
+
const note = document.createElement("span");
|
|
262
|
+
note.className = "tool-group-note";
|
|
263
|
+
note.textContent = text;
|
|
264
|
+
return note;
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
this.#meta.hidden = notes.length === 0;
|
|
268
|
+
}
|
|
269
|
+
}
|