@adia-ai/web-modules 0.8.28 → 0.8.30
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 +21 -0
- package/chat/README.md +54 -0
- package/chat/chat-shell/chat-shell.js +19 -0
- package/chat/chat-surfaces/chat-surfaces.d.ts +19 -0
- package/chat/chat-surfaces/chat-surfaces.js +160 -15
- package/dist/chat/chat-shell.min.js +1 -1
- package/dist/everything.min.js +82 -82
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-modules
|
|
2
2
|
|
|
3
|
+
## [0.8.30] — 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`chatShell.setStreamedText(text)`** — replaces the streaming assistant message's text outright, the counterpart to `appendChunk` for a producer that revises what it already streamed.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **A rewritten or withheld turn now leaves the screen agreeing with the transcript (gh#665).** `wireAgentEvents` treated every `text` event as an append, so an `@adia-ai/agent` output guardrail's re-snapshot (`{ text: '', snapshot: <rewritten> }`) appended nothing and the ORIGINAL text stayed on screen while the session recorded the rewrite — a redaction that redacted nothing visible. An empty delta is now a replace from `snapshot` (an empty snapshot clears the bubble), which is safe for any text event since the snapshot is always the authoritative text.
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
- **`billing/` touched in this release window** (1 file(s), e.g. `invoice-history/invoice-history.class.js`) — carried by the entries above.
|
|
13
|
+
- **`chat/` touched in this release window** (5 file(s), e.g. `chat/README.md`) — carried by the entries above.
|
|
14
|
+
- **`dist/` bundles rebuilt** in this cut's window (2 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
15
|
+
|
|
16
|
+
## [0.8.29] — 2026-08-06
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **chat-surfaces renders plan-envelope chrome** — `wireAgentEvents` gains a `step` case rendering the turn's surface manifest and "what changed" synthesis as feed chrome (workflow:'plan'), riding the existing AgentEvent `step` kind — no event-vocabulary change (gh#648, PR #654).
|
|
20
|
+
|
|
21
|
+
### Maintenance
|
|
22
|
+
- **`dist/` bundles rebuilt** in this cut's window (1 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
23
|
+
|
|
3
24
|
## [0.8.28] — 2026-08-05
|
|
4
25
|
|
|
5
26
|
### Fixed
|
package/chat/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# `web-modules/chat`
|
|
2
|
+
|
|
3
|
+
The chat cluster: `<chat-shell>` (behavior-only orchestrator) composing
|
|
4
|
+
`<chat-header>` / `<chat-thread>` / `<chat-composer>` / `<chat-status>` /
|
|
5
|
+
`<chat-empty>`, plus `chat-surfaces.js` — the L3 seam that renders an
|
|
6
|
+
`@adia-ai/agent` `AgentEvent` stream (or a raw `@adia-ai/llm` chunk stream)
|
|
7
|
+
into the thread. See `chat-shell/chat-shell.js`'s header comment for the
|
|
8
|
+
bespoke structure vocabulary and the two operating modes (built-in LLM
|
|
9
|
+
streaming vs. external-drive).
|
|
10
|
+
|
|
11
|
+
## `wireAgentEvents` — AgentEvent → rendering (gh#667)
|
|
12
|
+
|
|
13
|
+
`wireAgentEvents(chatShell, registry, { events, statusEl? })` folds ONE
|
|
14
|
+
`AsyncIterable<AgentEvent>` (`@adia-ai/agent`'s stream contract — see
|
|
15
|
+
[`packages/agent/README.md`](../../agent/README.md#events)) into the thread.
|
|
16
|
+
It never touches session state — `reduce(session, event)` is the caller's
|
|
17
|
+
job, the same event object just also flows through this function to paint.
|
|
18
|
+
|
|
19
|
+
| Event | Rendering |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `text` | Appended to the streaming bubble (`chatShell.appendChunk`); an EMPTY delta REPLACES from `snapshot` (`chatShell.setStreamedText`) — the guardrail-rewrite case (see `@adia-ai/agent`'s README, "Output rewrite"). |
|
|
22
|
+
| `thinking` | `<agent-reasoning-ui>.addThought(text)` — folded into the turn's collapsed-by-default reasoning viewer. |
|
|
23
|
+
| `tool_use` | `<agent-reasoning-ui>.addStep({id, label})` (or `updateStep` if `tool_input_delta` already opened the step) — a running step in the reasoning viewer's timeline. |
|
|
24
|
+
| `tool_input_delta` | Cheap upsert on the running step's `label` (`updateStep`, or `addStep` if this is the first fragment) — progress only, nothing executes off a partial. |
|
|
25
|
+
| `tool_result` | `updateStep(id, {outcomes:[output]})` + `completeStep(id)`, or `failStep(id, output)` when `isError` — the step's status icon flips to done/error and the (truncated) output becomes the step's collapsible outcome. |
|
|
26
|
+
| `step` (`workflow: 'plan'`) | Feed-chrome prose via `describePlanManifest` / `describeTurnSynthesis` (gh#648) — unchanged, NOT routed through the reasoning viewer (a plan manifest/synthesis line is thread narration, not a pipeline step). |
|
|
27
|
+
| `step` (any other workflow) | One-shot attempt marker — `@adia-ai/agent`'s workflow loop emits this AFTER the step already ran, so it lands as an immediately-settled `addStep` + `completeStep`/`failStep` in the reasoning viewer. |
|
|
28
|
+
| `guardrail` | `addThought('Guardrail "<name>" <blocked\|rewrote> <target> — <reason>')` — a subtle note inside the (collapsed) reasoning viewer, never a thread interruption. |
|
|
29
|
+
| `progress` | `statusEl.textContent` from the code-owned `PROGRESS_LABELS` table (CHAT-HARNESS law 3 — never the raw `stage` string, never model text). |
|
|
30
|
+
| `surface` | Routed to the surface-host `registry` (WCH-4 / gh#599) — a generative-UI envelope line, mounted/updated/closed via `createSurfaceRegistry`. |
|
|
31
|
+
| `memory` | Render-silent — a session-state write, not a thread event. |
|
|
32
|
+
| `trace` | Render-silent by default — tracing is devtools' surface (a future inspector panel reads the `trace` event / `@adia-ai/agent`'s `trace` sink), never the thread. |
|
|
33
|
+
| `error` | `reasoning.fail(message)` (if a reasoning viewer is open) + an error bubble + `stopStreaming()`. |
|
|
34
|
+
| `done` | `reasoning.finish()` (or `.fail('Blocked by guardrail')` when `stopReason === 'guardrail_denied'`) + `stopStreaming()`. |
|
|
35
|
+
|
|
36
|
+
**Why `<agent-reasoning-ui>`, not a new primitive:** the primitive audit
|
|
37
|
+
(adia-author §0) found `agent-reasoning-ui` already built for exactly this
|
|
38
|
+
shape — "Agent inner monologue + pipeline viewer with steps, thoughts,
|
|
39
|
+
plans, and iterations," composing `<timeline-ui>` for step rows with a
|
|
40
|
+
collapsible per-step `outcomes` list (the "collapsible tool-result body"
|
|
41
|
+
requirement) and a `collapsed`/status-icon summary row (the "thinking
|
|
42
|
+
affordance" requirement). One viewer, mounted lazily above the streamed
|
|
43
|
+
answer on the first reasoning-worthy event per turn, covers tool activity,
|
|
44
|
+
workflow-step markers, thinking, and guardrail notes — no chip primitive,
|
|
45
|
+
no dedicated step-marker element, and no second collapsible region were
|
|
46
|
+
minted. `<chat-status>` (candidate considered) stays scoped to the header's
|
|
47
|
+
connection/streaming indicator — it is a single, module-scoped element, not
|
|
48
|
+
shaped for a per-event marker stream.
|
|
49
|
+
|
|
50
|
+
**Demo:** `playgrounds/agent-chat/app/agent-chat.contents.js` — the *"Ask
|
|
51
|
+
about a component"* prompt path drives a real `createAgent()` loop with a
|
|
52
|
+
keyless deterministic client (`simulatedClient()`, used whenever
|
|
53
|
+
`npm run proxy` isn't up) through `tool_use` → `tool_result` → final text,
|
|
54
|
+
rendered live via this module's `wireAgentEvents`.
|
|
@@ -184,6 +184,25 @@ class ChatShell extends UIElement {
|
|
|
184
184
|
this.#scrollToBottom();
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Replace the streaming assistant message's text outright, instead of
|
|
189
|
+
* appending to it. The counterpart to `appendChunk` for the case where a
|
|
190
|
+
* producer revises what it already streamed — an `@adia-ai/agent` output
|
|
191
|
+
* guardrail rewriting or withholding the turn's text is the real one.
|
|
192
|
+
* Appending cannot express that: what is on screen has to come off.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} text — the full replacement text ('' clears the bubble).
|
|
195
|
+
*/
|
|
196
|
+
setStreamedText(text) {
|
|
197
|
+
const last = this.#messages[this.#messages.length - 1];
|
|
198
|
+
if (!last || last.role !== 'assistant') return;
|
|
199
|
+
last.content = text;
|
|
200
|
+
|
|
201
|
+
const contentEl = this.#messagesEl?.querySelector('[data-role]:last-child [data-content]');
|
|
202
|
+
if (contentEl) contentEl.textContent = text;
|
|
203
|
+
this.#scrollToBottom();
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
deleteMessage(id) {
|
|
188
207
|
const idx = this.#messages.findIndex(m => m.id === id);
|
|
189
208
|
if (idx === -1) return;
|
|
@@ -39,9 +39,28 @@ export function createSurfaceRegistry(opts: {
|
|
|
39
39
|
/** CHAT-HARNESS law 3 — code-owned progress labels, never model text. */
|
|
40
40
|
export const PROGRESS_LABELS: Record<string, string>;
|
|
41
41
|
|
|
42
|
+
/** Plan-envelope feed chrome (gh#648) — code-owned sentence around a
|
|
43
|
+
* `PlanEnvelope`'s per-surface rationale. Ruling 3: feed chrome, never a canvas. */
|
|
44
|
+
export function describePlanManifest(plan: {
|
|
45
|
+
turnId: string;
|
|
46
|
+
surfaces: { surfaceId: string; rationale: string }[];
|
|
47
|
+
}): string;
|
|
48
|
+
|
|
49
|
+
/** Plan-envelope feed chrome (gh#648) — code-owned sentence around a
|
|
50
|
+
* `TurnSynthesis`'s new/updated/closed surface lists. */
|
|
51
|
+
export function describeTurnSynthesis(synthesis: {
|
|
52
|
+
turnId: string;
|
|
53
|
+
new: string[];
|
|
54
|
+
updated: string[];
|
|
55
|
+
closed: string[];
|
|
56
|
+
}): string;
|
|
57
|
+
|
|
42
58
|
export function wireAgentEvents(
|
|
43
59
|
chatShell: Element & {
|
|
44
60
|
appendChunk?(text: string): void;
|
|
61
|
+
/** Replace the streaming text outright — used when a text event carries
|
|
62
|
+
* an empty delta, which is a producer revising what it streamed. */
|
|
63
|
+
setStreamedText?(text: string): void;
|
|
45
64
|
appendMessage?(msg: { role: string; content?: string; render?: boolean }): Element;
|
|
46
65
|
startStreaming?(): void;
|
|
47
66
|
stopStreaming?(): void;
|
|
@@ -24,7 +24,22 @@
|
|
|
24
24
|
* specifier. The HOST (a demo page, a consumer app) builds the renderer
|
|
25
25
|
* (`createRenderer` + its own `WidgetAdapter`) and hands it in; this module
|
|
26
26
|
* only ever calls the five methods `Renderer` exposes.
|
|
27
|
+
*
|
|
28
|
+
* `@adia-ai/web-components` primitives ARE a declared dependency edge
|
|
29
|
+
* (unlike `@genui/*`), so `wireAgentEvents` imports `<agent-reasoning-ui>`
|
|
30
|
+
* directly (gh#667) — the "agent inner monologue + pipeline viewer with
|
|
31
|
+
* steps, thoughts, plans, and iterations" primitive already built for
|
|
32
|
+
* exactly this shape (primitive audit, adia-author §0 — no new primitive
|
|
33
|
+
* was minted). One reasoning viewer is lazily mounted per assistant turn
|
|
34
|
+
* and renders `thinking` (→ `addThought`), `tool_use`/`tool_input_delta`/
|
|
35
|
+
* `tool_result` (→ `addStep`/`updateStep`/`completeStep`/`failStep`, with
|
|
36
|
+
* the tool output riding the step's collapsible `outcomes`), non-'plan'
|
|
37
|
+
* `step` events (→ the same step API — one-shot attempt markers), and
|
|
38
|
+
* `guardrail` (→ `addThought`, a subtle note). `memory` and `trace` stay
|
|
39
|
+
* render-silent (trace is devtools' surface, not the thread's — see the
|
|
40
|
+
* event → rendering table in `packages/web-modules/chat/README.md`).
|
|
27
41
|
*/
|
|
42
|
+
import '../../../web-components/components/agent-reasoning/agent-reasoning.js';
|
|
28
43
|
|
|
29
44
|
const CLOSED_ANNOTATION_TEXT = 'Closed.';
|
|
30
45
|
const PROTOCOL_VERSION = 'v1.0';
|
|
@@ -163,6 +178,38 @@ export const PROGRESS_LABELS = {
|
|
|
163
178
|
done: 'Done',
|
|
164
179
|
};
|
|
165
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Plan-envelope feed chrome (gh#648, docs/ORCHESTRATION.md §Plan envelope
|
|
183
|
+
* ruling 3 — "what changed" is feed chrome, never a canvas). These render
|
|
184
|
+
* from the `{ type: 'step', workflow: 'plan', step: 'manifest'|'synthesis' }`
|
|
185
|
+
* AgentEvent — the EXISTING `step` kind (`@adia-ai/agent`'s `AgentEvent`
|
|
186
|
+
* union already carries `workflow`/`step`/`data`), so no closed-vocabulary
|
|
187
|
+
* `type` amendment is needed to carry this: `workflow: 'plan'` is just a
|
|
188
|
+
* new value inside an already-open string field, the same way
|
|
189
|
+
* `packages/agent/src/workflow.ts`'s own step events use `workflow` for a
|
|
190
|
+
* different named workflow.
|
|
191
|
+
*
|
|
192
|
+
* Code-owned text (CHAT-HARNESS law 3's spirit, applied to `step` the way
|
|
193
|
+
* PROGRESS_LABELS applies it to `progress`): the rationale strings inside
|
|
194
|
+
* a PlanEnvelope ARE planner-authored copy (not raw model text — R-O10's
|
|
195
|
+
* `rationale` is a structured field, one line per surface), so they render
|
|
196
|
+
* verbatim; the surrounding sentence is code-owned.
|
|
197
|
+
*/
|
|
198
|
+
export function describePlanManifest(plan) {
|
|
199
|
+
const n = plan.surfaces.length;
|
|
200
|
+
const list = plan.surfaces.map((s) => `${s.surfaceId} — ${s.rationale}`).join('; ');
|
|
201
|
+
return `Planning ${n} surface${n === 1 ? '' : 's'}: ${list}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const SYNTHESIS_LABELS = { new: 'New', updated: 'Updated', closed: 'Closed' };
|
|
205
|
+
|
|
206
|
+
export function describeTurnSynthesis(synthesis) {
|
|
207
|
+
const parts = ['new', 'updated', 'closed']
|
|
208
|
+
.filter((key) => synthesis[key].length > 0)
|
|
209
|
+
.map((key) => `${SYNTHESIS_LABELS[key]}: ${synthesis[key].join(', ')}`);
|
|
210
|
+
return parts.length ? `What changed — ${parts.join(' · ')}` : 'What changed — nothing this turn';
|
|
211
|
+
}
|
|
212
|
+
|
|
166
213
|
/**
|
|
167
214
|
* Consumes ONE `AsyncIterable<AgentEvent>` for the turn already opened on
|
|
168
215
|
* `chatShell` (the caller has already appended the user message and an
|
|
@@ -188,10 +235,40 @@ export async function wireAgentEvents(chatShell, registry, opts) {
|
|
|
188
235
|
return thread?.querySelector('[data-role="assistant"]:last-child [data-bubble]') ?? null;
|
|
189
236
|
};
|
|
190
237
|
|
|
238
|
+
// One <agent-reasoning-ui> per turn (gh#667) — lazily mounted above the
|
|
239
|
+
// streamed answer on the first reasoning-worthy event (thinking, tool
|
|
240
|
+
// activity, a non-plan step, or a guardrail note), and re-used for the
|
|
241
|
+
// rest of the turn so tool steps/thoughts accumulate in ONE collapsible
|
|
242
|
+
// region instead of one element per event. `null` until then; a fresh
|
|
243
|
+
// `wireAgentEvents` call (one per turn, per this function's own contract
|
|
244
|
+
// above) starts a fresh viewer.
|
|
245
|
+
let reasoning = null;
|
|
246
|
+
const ensureReasoning = () => {
|
|
247
|
+
if (reasoning) return reasoning;
|
|
248
|
+
const bubble = currentBubble();
|
|
249
|
+
if (!bubble) return null;
|
|
250
|
+
reasoning = document.createElement('agent-reasoning-ui');
|
|
251
|
+
// Collapsed BY DEFAULT (gh#667 scope — thinking is a collapsed-by-default
|
|
252
|
+
// affordance, not a start-expanded one; the primitive's own default is
|
|
253
|
+
// expanded, so this is set explicitly).
|
|
254
|
+
reasoning.collapsed = true;
|
|
255
|
+
// Above the streamed text — the "showed its work, then answered" order.
|
|
256
|
+
bubble.insertBefore(reasoning, bubble.firstChild);
|
|
257
|
+
return reasoning;
|
|
258
|
+
};
|
|
259
|
+
const findStepEntry = (id) => reasoning?.entries?.find((e) => e.kind === 'step' && e.id === id);
|
|
260
|
+
|
|
191
261
|
for await (const event of events) {
|
|
192
262
|
switch (event.type) {
|
|
193
263
|
case 'text':
|
|
194
|
-
|
|
264
|
+
// An EMPTY delta is a replace, not a no-op: the snapshot is the
|
|
265
|
+
// authoritative text, and a producer sends this shape when it
|
|
266
|
+
// revises what it already streamed (an @adia-ai/agent output
|
|
267
|
+
// guardrail rewriting or withholding the turn). Resetting from the
|
|
268
|
+
// snapshot is safe for any text event — appending is only the
|
|
269
|
+
// cheaper path for the ordinary incremental case.
|
|
270
|
+
if (event.text === '') chatShell.setStreamedText?.(event.snapshot ?? '');
|
|
271
|
+
else chatShell.appendChunk?.(event.text);
|
|
195
272
|
break;
|
|
196
273
|
|
|
197
274
|
case 'progress': {
|
|
@@ -206,30 +283,98 @@ export async function wireAgentEvents(chatShell, registry, opts) {
|
|
|
206
283
|
break;
|
|
207
284
|
}
|
|
208
285
|
|
|
209
|
-
case '
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
286
|
+
case 'step': {
|
|
287
|
+
// Plan-envelope chrome (gh#648, ruling 1 — harness-level progress,
|
|
288
|
+
// never wire; ruling 3 — feed chrome, never a canvas) keeps its own
|
|
289
|
+
// rendering — a manifest/synthesis summary is feed prose, not a
|
|
290
|
+
// pipeline step. Every OTHER workflow's step is a one-shot attempt
|
|
291
|
+
// marker (`@adia-ai/agent`'s workflow.ts emits it once, after the
|
|
292
|
+
// step already ran) — rendered as an immediately-settled step in
|
|
293
|
+
// the turn's reasoning viewer (gh#667).
|
|
294
|
+
if (event.workflow === 'plan' && event.step === 'manifest') {
|
|
295
|
+
chatShell.appendMessage?.({ role: 'assistant', content: describePlanManifest(event.data), render: true });
|
|
296
|
+
chatShell.appendMessage?.({ role: 'assistant', content: '' });
|
|
297
|
+
} else if (event.workflow === 'plan' && event.step === 'synthesis') {
|
|
298
|
+
chatShell.appendMessage?.({ role: 'assistant', content: describeTurnSynthesis(event.data), render: true });
|
|
299
|
+
chatShell.appendMessage?.({ role: 'assistant', content: '' });
|
|
300
|
+
} else {
|
|
301
|
+
const r = ensureReasoning();
|
|
302
|
+
if (r) {
|
|
303
|
+
const id = `${event.workflow}:${event.step}:${event.data?.round ?? 1}`;
|
|
304
|
+
const label = `${event.workflow}: ${event.step}`;
|
|
305
|
+
r.addStep({ id, label });
|
|
306
|
+
if (event.data?.accepted) r.completeStep(id);
|
|
307
|
+
else r.failStep(id, event.data?.reason ?? 'fell through');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'tool_use': {
|
|
314
|
+
const r = ensureReasoning();
|
|
315
|
+
if (r) {
|
|
316
|
+
const label = `${event.name}(${JSON.stringify(event.input)})`;
|
|
317
|
+
if (findStepEntry(event.id)) r.updateStep(event.id, { label });
|
|
318
|
+
else r.addStep({ id: event.id, label });
|
|
319
|
+
}
|
|
216
320
|
break;
|
|
321
|
+
}
|
|
217
322
|
|
|
218
|
-
case '
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
323
|
+
case 'tool_input_delta': {
|
|
324
|
+
// Partial args — render progress on the running chip only; never
|
|
325
|
+
// executes off it (README's own rule — the matching tool_use is
|
|
326
|
+
// the call). Cheap upsert since addStep/updateStep already exist.
|
|
327
|
+
const r = ensureReasoning();
|
|
328
|
+
if (r) {
|
|
329
|
+
const label = `${event.name}(${event.partial})`;
|
|
330
|
+
if (findStepEntry(event.id)) r.updateStep(event.id, { label });
|
|
331
|
+
else r.addStep({ id: event.id, label });
|
|
332
|
+
}
|
|
225
333
|
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case 'tool_result': {
|
|
337
|
+
const r = ensureReasoning();
|
|
338
|
+
if (r) {
|
|
339
|
+
const outcome = String(event.output).slice(0, 300);
|
|
340
|
+
if (event.isError) {
|
|
341
|
+
r.failStep(event.id, outcome);
|
|
342
|
+
} else {
|
|
343
|
+
r.updateStep(event.id, { outcomes: [outcome] });
|
|
344
|
+
r.completeStep(event.id);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
case 'guardrail': {
|
|
351
|
+
const r = ensureReasoning();
|
|
352
|
+
const verb = event.action === 'deny' ? 'blocked' : 'rewrote';
|
|
353
|
+
const target = event.toolName ? `tool call "${event.toolName}"` : 'the response';
|
|
354
|
+
r?.addThought?.(`Guardrail "${event.name}" ${verb} ${target}${event.reason ? ` — ${event.reason}` : ''}`);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
case 'thinking': {
|
|
359
|
+
const r = ensureReasoning();
|
|
360
|
+
r?.addThought?.(event.text);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 'memory' and 'trace' stay render-silent (see this module's header
|
|
365
|
+
// comment + the chat README's event table) — they fall to `default`.
|
|
226
366
|
|
|
227
367
|
case 'error':
|
|
368
|
+
reasoning?.fail?.(event.error?.message ?? String(event.error));
|
|
228
369
|
chatShell.appendMessage?.({ role: 'error', content: event.error?.message ?? String(event.error) });
|
|
229
370
|
chatShell.stopStreaming?.();
|
|
230
371
|
break;
|
|
231
372
|
|
|
232
373
|
case 'done':
|
|
374
|
+
if (reasoning) {
|
|
375
|
+
if (event.stopReason === 'guardrail_denied') reasoning.fail('Blocked by guardrail');
|
|
376
|
+
else reasoning.finish();
|
|
377
|
+
}
|
|
233
378
|
chatShell.stopStreaming?.();
|
|
234
379
|
break;
|
|
235
380
|
|
|
@@ -47,4 +47,4 @@ var RS=Object.defineProperty;var ee=(i,e,t)=>()=>{if(t)throw t[0];try{return i&&
|
|
|
47
47
|
`):t}async#f(){if(this.#o||this.#e)return;let e=this.#i,t=Zu(this.language);this.#o=!0;try{let n=await Promise.resolve().then(()=>(Au(),Xu)),r=null;try{r=(await n.importWithTimeout(n.languages[t],`lang-${t}`))?.extension??null}catch(s){this.dispatchEvent(new CustomEvent("language-load-error",{bubbles:!0,detail:{phase:"language",language:t,error:s}}))}if(e!==this.#i||!this.isConnected)return;this.#O(n,r,t)}catch(n){this.dispatchEvent(new CustomEvent("language-load-error",{bubbles:!0,detail:{phase:"core",error:n}})),console.warn("[code-ui] CodeMirror failed to load; staying on static fallback.",n)}finally{this.#o=!1}}#O(e,t,n){let{EditorState:r,EditorView:s,Compartment:o,lineNumbers:l,placeholder:a,syntaxHighlighting:h,history:c,historyKeymap:f,defaultKeymap:u,indentWithTab:d,keymap:O,adiaBaseTheme:m,adiaHighlightStyle:g,lintGutter:b,jsonLinter:S}=e,y=this.querySelector(":scope > pre"),C=y?y.querySelector("code")?.textContent??"":"",x=this.text||C||"";this.#l=x;let k=document.createElement("div");k.setAttribute("data-cm-mount",""),y?y.replaceWith(k):this.appendChild(k);let w=[m,h(g)];this.editable&&w.push(c(),O.of([{key:"Mod-s",run:E=>(this.#g(E),!0)},...u,...f,d]),s.updateListener.of(E=>{if(!E.docChanged)return;let j=E.state.doc.toString();this.#d(j),this.dispatchEvent(new CustomEvent("input",{bubbles:!0,detail:{value:j}}))}),s.domEventHandlers({focus:(E,j)=>(this.#n=j.state.doc.toString(),!1),blur:(E,j)=>{let N=j.state.doc.toString();return this.#n!==null&&N!==this.#n&&this.dispatchEvent(new CustomEvent("change",{bubbles:!0,detail:{value:N}})),this.#n=null,!1}})),this.#s=new o,w.push(this.#s.of(this.#u(s,r))),t&&w.push(t),this.lineNumbers&&w.push(l()),this.placeholder&&w.push(a(this.placeholder)),this.editable&&n==="json"&&w.push(S,b()),this.#e=new s({parent:k,state:r.create({doc:x,extensions:w})}),this.editable&&this.#d(x)}#u(e,t){return this.editable&&!this.disabled&&!this.readonly?[]:[e.editable.of(!1),t.readOnly.of(!0)]}async#p(){if(!this.#e||!this.#s)return;let e=await Promise.resolve().then(()=>(Au(),Xu));this.#e.dispatch({effects:this.#s.reconfigure(this.#u(e.EditorView,e.EditorState))})}get form(){return this.internals.form}get labels(){return this.internals.labels}get validity(){return this.internals.validity}get validationMessage(){return this.internals.validationMessage}get willValidate(){return this.internals.willValidate}checkValidity(){return this.internals.checkValidity()}reportValidity(){return this.internals.reportValidity()}#d(e){this.editable&&(e=e??this.value??"",this.internals.setFormValue(e),this.#m(e))}#m(e){return e=e??"",this.required&&!e.trim()?(this.internals.setValidity({valueMissing:!0},this.getAttribute("data-msg-required")||"This field is required.",this),this.setAttribute("aria-invalid","true"),!1):(this.internals.setValidity({}),this.removeAttribute("aria-invalid"),!0)}formResetCallback(){this.#e?this.#e.dispatch({changes:{from:0,to:this.#e.state.doc.length,insert:this.#l}}):this.text=this.#l}formDisabledCallback(e){this.disabled=e}formStateRestoreCallback(e,t){typeof e=="string"&&(this.#e?this.#e.dispatch({changes:{from:0,to:this.#e.state.doc.length,insert:e}}):this.text=e)}#g(e){this.dispatchEvent(new CustomEvent("save",{bubbles:!0,cancelable:!0,detail:{value:e.state.doc.toString()}}))}get value(){return this.#e?this.#e.state.doc.toString():this.querySelector(":scope > pre > code")?.textContent??this.text??""}set value(e){this.text=String(e??"")}#b(){let e=this.#e?this.#e.state.doc.toString():this.querySelector(":scope > pre > code")?.textContent??"";e&&navigator.clipboard.writeText(e).then(()=>{let t=this.querySelector(':scope > header [slot="copy"]');if(!t)return;let n=t.textContent;t.textContent="Copied!",this.#r!=null&&clearTimeout(this.#r),this.#r=setTimeout(()=>{this.#r=null,t.textContent=n},1500)})}render(){let e=this.querySelector(':scope > header [slot="label"]');if(e&&this.language&&(e.textContent=this.language),this.#p(),!this.inline&&this.text!=="")if(this.#e){if(this.editable&&this.#e.hasFocus)return;let t=this.#e.state.doc.toString();t!==this.text&&this.#e.dispatch({changes:{from:0,to:t.length,insert:this.text}})}else{let t=this.querySelector(":scope > pre > code");t&&t.textContent!==this.text&&(t.textContent=this.text)}}};is("code-ui",es);function Ru(i){return i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}var Dv=0,jl=class extends an{static properties={streaming:{type:Boolean,default:!1,reflect:!0},provider:{type:String,default:"",reflect:!0},model:{type:String,default:"",reflect:!0},system:{type:String,default:"",reflect:!0},proxyUrl:{type:String,default:"",attribute:"proxy-url",reflect:!0},thinking:{type:Boolean,default:!1,reflect:!0}};static template=()=>null;#t=[];#r=null;#e=null;#i=null;#o=null;#n=null;#l="";get messages(){return[...this.#t]}get conversation(){return this.#t.filter(e=>e.role==="user"||e.role==="assistant").map(e=>e.html?{role:e.role,content:e.content,html:!0}:{role:e.role,content:e.content})}set conversation(e){this.clear();for(let t of e)this.appendMessage({role:t.role,content:t.content,render:!t.html,html:!!t.html})}set apiKey(e){this.#l=e}connected(){this.#e=this.querySelector("chat-thread"),this.#i=this.querySelector("chat-composer"),this.#o=this.querySelector("chat-empty"),this.#n=this.querySelector("chat-status"),this.#i?.addEventListener("composer-submit",this.#a)}disconnected(){this.#i?.removeEventListener("composer-submit",this.#a),this.abort()}#s(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:t}))}#a=e=>{if(this.streaming)return;let{text:t,model:n}=e.detail||{};t&&(this.#i.clear(),this.#s("submit",{text:t,model:n}),(this.proxyUrl||this.#l)&&this.send(t,n?{model:n}:{}))};appendMessage({role:e,content:t="",render:n=!1,html:r=!1}){let s=`msg_${++Dv}`;this.#t.push({id:s,role:e,content:t,html:r});let o=document.createElement("div");if(o.setAttribute("data-role",e),o.setAttribute("data-id",s),e==="user")o.innerHTML=`<div data-bubble>${Ru(t)}</div>`;else if(e==="assistant"){let l=n||r,a=r?t:n&&t?ql(t):Ru(t);o.innerHTML=`
|
|
48
48
|
<span data-avatar>AI</span>
|
|
49
49
|
<div data-bubble><div data-content>${a}</div>${l?"":"<span data-cursor></span>"}</div>
|
|
50
|
-
`}else e==="error"&&(o.innerHTML=`<icon-ui name="warning"></icon-ui><span>${Ru(t)}</span>`);return this.#e?.appendChild(o),this.#h(),this.#s("message",{id:s,role:e,content:t}),o}appendChunk(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content+=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&n.insertAdjacentText("beforeend",e),this.#h()}deleteMessage(e){let t=this.#t.findIndex(n=>n.id===e);t!==-1&&(this.#t.splice(t,1),this.#e?.querySelector(`[data-id="${e}"]`)?.remove())}clear(){if(this.#t.length=0,this.#e){let e=this.#o;this.#e.innerHTML="",e&&this.#e.appendChild(e)}this.#s("clear")}startStreaming(){this.streaming=!0,this.#i&&(this.#i.disabled=!0),this.#n&&(this.#n.textContent="Typing..."),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!0)}stopStreaming(){this.streaming=!1,this.#i&&(this.#i.disabled=!1),this.#n&&(this.#n.textContent=""),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!1),this.#e?.querySelector("[data-role]:last-child [data-cursor]")?.remove();let e=this.#t[this.#t.length-1];if(e?.role==="assistant"&&e.content&&!e.html){let t=this.#e?.querySelector("[data-role]:last-child [data-content]");t&&(t.innerHTML=ql(e.content),this.#c(t))}this.#i?.focus()}abort(){this.#r&&(this.#r.abort(),this.#r=null,this.#s("abort")),this.streaming&&this.stopStreaming()}async send(e,t={}){let n=t.model||this.model||this.#i?.model;if(!n)throw new Error("No model specified");this.appendMessage({role:"user",content:e}),this.appendMessage({role:"assistant",content:""}),this.startStreaming(),this.#r=new AbortController;try{let r={provider:this.provider||void 0,apiKey:this.#l||void 0,model:n,system:this.system||void 0,proxyUrl:this.proxyUrl||void 0,thinking:this.thinking||void 0,messages:this.conversation.slice(0,-1),signal:this.#r.signal,...t},s;for(let o of["../../../llm/index.js","../../llm/index.js"])try{({streamChat:s}=await import(new URL(o,import.meta.url).href));break}catch{}if(!s){this.appendMessage({role:"error",content:"@adia-ai/llm is not available \u2014 in a fresh checkout run the packages/llm build (see gh#527/gh#541)."}),this.#s("error",{error:new Error("llm build artifact missing")});return}for await(let o of s(r))o.type==="text"?(this.appendChunk(o.text),this.#s("chunk",{text:o.text,snapshot:o.snapshot})):o.type==="thinking"?this.#s("thinking",{text:o.text}):o.type==="done"?this.#s("done",{text:o.text,usage:o.usage,stopReason:o.stopReason}):o.type==="error"&&(this.appendMessage({role:"error",content:o.error.message}),this.#s("error",{error:o.error}))}catch(r){r.name!=="AbortError"&&(this.appendMessage({role:"error",content:r.message}),this.#s("error",{error:r}))}this.#r=null,this.stopStreaming()}export(){return{messages:this.#t.map(e=>({role:e.role,content:e.content})),model:this.model,system:this.system}}import(e){e.model&&(this.model=e.model),e.system&&(this.system=e.system),e.messages&&(this.conversation=e.messages)}#h(){let e=this.#e;e&&requestAnimationFrame(()=>{e.scrollTop=e.scrollHeight})}#c(e){for(let t of e.querySelectorAll("pre")){let n=t.querySelector("code");if(!n)continue;let r=n.getAttribute("data-lang")||"",s=document.createElement("code-ui");r&&s.setAttribute("language",r),s.textContent=n.textContent,t.replaceWith(s)}}};is("chat-shell",jl);export{jl as ChatShell};
|
|
50
|
+
`}else e==="error"&&(o.innerHTML=`<icon-ui name="warning"></icon-ui><span>${Ru(t)}</span>`);return this.#e?.appendChild(o),this.#h(),this.#s("message",{id:s,role:e,content:t}),o}appendChunk(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content+=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&n.insertAdjacentText("beforeend",e),this.#h()}setStreamedText(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&(n.textContent=e),this.#h()}deleteMessage(e){let t=this.#t.findIndex(n=>n.id===e);t!==-1&&(this.#t.splice(t,1),this.#e?.querySelector(`[data-id="${e}"]`)?.remove())}clear(){if(this.#t.length=0,this.#e){let e=this.#o;this.#e.innerHTML="",e&&this.#e.appendChild(e)}this.#s("clear")}startStreaming(){this.streaming=!0,this.#i&&(this.#i.disabled=!0),this.#n&&(this.#n.textContent="Typing..."),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!0)}stopStreaming(){this.streaming=!1,this.#i&&(this.#i.disabled=!1),this.#n&&(this.#n.textContent=""),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!1),this.#e?.querySelector("[data-role]:last-child [data-cursor]")?.remove();let e=this.#t[this.#t.length-1];if(e?.role==="assistant"&&e.content&&!e.html){let t=this.#e?.querySelector("[data-role]:last-child [data-content]");t&&(t.innerHTML=ql(e.content),this.#c(t))}this.#i?.focus()}abort(){this.#r&&(this.#r.abort(),this.#r=null,this.#s("abort")),this.streaming&&this.stopStreaming()}async send(e,t={}){let n=t.model||this.model||this.#i?.model;if(!n)throw new Error("No model specified");this.appendMessage({role:"user",content:e}),this.appendMessage({role:"assistant",content:""}),this.startStreaming(),this.#r=new AbortController;try{let r={provider:this.provider||void 0,apiKey:this.#l||void 0,model:n,system:this.system||void 0,proxyUrl:this.proxyUrl||void 0,thinking:this.thinking||void 0,messages:this.conversation.slice(0,-1),signal:this.#r.signal,...t},s;for(let o of["../../../llm/index.js","../../llm/index.js"])try{({streamChat:s}=await import(new URL(o,import.meta.url).href));break}catch{}if(!s){this.appendMessage({role:"error",content:"@adia-ai/llm is not available \u2014 in a fresh checkout run the packages/llm build (see gh#527/gh#541)."}),this.#s("error",{error:new Error("llm build artifact missing")});return}for await(let o of s(r))o.type==="text"?(this.appendChunk(o.text),this.#s("chunk",{text:o.text,snapshot:o.snapshot})):o.type==="thinking"?this.#s("thinking",{text:o.text}):o.type==="done"?this.#s("done",{text:o.text,usage:o.usage,stopReason:o.stopReason}):o.type==="error"&&(this.appendMessage({role:"error",content:o.error.message}),this.#s("error",{error:o.error}))}catch(r){r.name!=="AbortError"&&(this.appendMessage({role:"error",content:r.message}),this.#s("error",{error:r}))}this.#r=null,this.stopStreaming()}export(){return{messages:this.#t.map(e=>({role:e.role,content:e.content})),model:this.model,system:this.system}}import(e){e.model&&(this.model=e.model),e.system&&(this.system=e.system),e.messages&&(this.conversation=e.messages)}#h(){let e=this.#e;e&&requestAnimationFrame(()=>{e.scrollTop=e.scrollHeight})}#c(e){for(let t of e.querySelectorAll("pre")){let n=t.querySelector("code");if(!n)continue;let r=n.getAttribute("data-lang")||"",s=document.createElement("code-ui");r&&s.setAttribute("language",r),s.textContent=n.textContent,t.replaceWith(s)}}};is("chat-shell",jl);export{jl as ChatShell};
|