@threadplane/chat 0.0.50 → 0.0.52

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.
@@ -1,30 +1,250 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, InjectionToken, ViewEncapsulation, viewChild, model, contentChild, untracked, ElementRef, DestroyRef, makeEnvironmentProviders, Injector, runInInjectionContext, ViewContainerRef, SecurityContext } from '@angular/core';
2
+ import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation, viewChild, model, contentChild, untracked, ElementRef, DestroyRef, makeEnvironmentProviders, Injector, runInInjectionContext, ViewContainerRef, SecurityContext } from '@angular/core';
3
3
  import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/common';
4
4
  import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
5
5
  import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
6
6
  export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
7
7
  import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
8
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
+ import { Router, NavigationEnd } from '@angular/router';
10
+ import { filter, map, startWith } from 'rxjs/operators';
9
11
  import { toJSONSchema } from 'zod/v4';
10
12
  import { resolveDynamic, getByPointer, isPathRef, setByPointer, createA2uiMessageParser } from '@threadplane/a2ui';
11
13
  export { isLiteralBoolean, isLiteralNumber, isLiteralString, isPathRef } from '@threadplane/a2ui';
12
14
  import { materialize as materialize$1, createPartialJsonParser } from '@cacheplane/partial-json';
13
15
  import { fromEvent, EMPTY } from 'rxjs';
14
16
 
17
+ // SPDX-License-Identifier: MIT
18
+ /**
19
+ * Structured, classified failure surfaced on `Agent.error`. Extends `Error`, so
20
+ * existing `.message` / `instanceof Error` reads keep working — but adds a
21
+ * machine-readable {@link AgentErrorKind}, a `retryable` flag, an optional HTTP
22
+ * `status`, and the original `cause`.
23
+ *
24
+ * You rarely construct one yourself; adapters normalize raw failures via
25
+ * {@link toAgentError}. Read it off the agent to render legible, cause-specific UI:
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const err = agent.error(); // AgentError | undefined
30
+ * if (err) {
31
+ * console.warn(err.message); // legible, per-kind copy
32
+ * if (err.kind === 'auth') showApiKeyHelp();
33
+ * if (err.retryable) showRetryButton(); // → agent.retry()
34
+ * }
35
+ * ```
36
+ */
37
+ class AgentError extends Error {
38
+ /** The classified failure type. See {@link AgentErrorKind}. */
39
+ kind;
40
+ /** Whether retrying the same request could plausibly succeed:
41
+ * `connection` | `server` (5xx) | `interrupted` → true; `auth` | `aborted` | non-auth `4xx` → false. */
42
+ retryable;
43
+ /** The HTTP status code when the failure came from an HTTP response. */
44
+ status;
45
+ /** The original raw error this was classified from, preserved for debugging/telemetry. */
46
+ cause;
47
+ constructor(init) {
48
+ super(init.message);
49
+ this.name = 'AgentError';
50
+ this.kind = init.kind;
51
+ this.retryable = init.retryable;
52
+ this.status = init.status;
53
+ this.cause = init.cause;
54
+ }
55
+ }
56
+ /**
57
+ * Default, human-facing copy per {@link AgentErrorKind}. Used as the message when
58
+ * a classified error has no better text. Override by mapping `error.kind` to your
59
+ * own strings in a custom error component.
60
+ */
61
+ const AGENT_ERROR_MESSAGES = {
62
+ connection: "Can't reach the server. Check your connection and try again.",
63
+ auth: 'Authentication failed. Check your API key or credentials.',
64
+ server: 'The server ran into an error. You can try again.',
65
+ interrupted: 'The response was interrupted. Try again.',
66
+ aborted: 'Stopped.',
67
+ };
68
+
69
+ // SPDX-License-Identifier: MIT
70
+ /**
71
+ * Whether `raw` represents an abort (a `DOMException`/`Error` named `AbortError`,
72
+ * or an abort-ish message). Shared by the runtime adapters and {@link toAgentError}
73
+ * so a user-requested stop settles to idle instead of surfacing as an error.
74
+ *
75
+ * @param raw Any thrown/rejected value.
76
+ * @returns `true` if it looks like an abort.
77
+ */
78
+ function isAbortError(raw) {
79
+ return raw instanceof Error && (raw.name === 'AbortError' || /\babort/i.test(raw.message));
80
+ }
81
+ /**
82
+ * Reads a numeric status from `raw.status` or `raw.cause.status` only.
83
+ * No text parsing — structured fields only.
84
+ */
85
+ function structuredStatus(raw) {
86
+ const obj = raw;
87
+ const direct = typeof obj?.status === 'number' ? obj.status : undefined;
88
+ const viaCause = typeof obj?.cause?.status === 'number' ? obj.cause.status : undefined;
89
+ if (direct !== undefined)
90
+ return direct;
91
+ if (viaCause !== undefined)
92
+ return viaCause;
93
+ return undefined;
94
+ }
95
+ function isConnectionError(raw) {
96
+ if (!(raw instanceof Error))
97
+ return false;
98
+ return /failed to fetch|networkerror|econnrefused|enotfound|network request failed|load failed/i.test(`${raw.name} ${raw.message}`);
99
+ }
100
+ /**
101
+ * Extracts an HTTP status code from a message string, but ONLY when the token
102
+ * is unambiguously HTTP-shaped:
103
+ * - `HTTP/502`, `HTTP 404`, `HTTP404`
104
+ * - `status: 503`, `status=503`, `code: 404`
105
+ *
106
+ * Bare 3-digit numbers (e.g. model version strings like "gpt-500") are NOT matched.
107
+ */
108
+ function httpStatusFromMessage(raw) {
109
+ const msg = raw instanceof Error ? raw.message : typeof raw === 'string' ? raw : '';
110
+ if (!msg)
111
+ return undefined;
112
+ // "HTTP 500", "HTTP/500", "HTTP500"
113
+ const httpToken = /\bHTTP[ /]?(\d{3})\b/i.exec(msg);
114
+ if (httpToken)
115
+ return Number(httpToken[1]);
116
+ // "status: 503", "status=503", "status 503" (up to 4 non-digit chars between keyword and digits)
117
+ // Also matches "code: 404", "code=404", etc.
118
+ const prefixed = /\b(?:status|code)\b\D{0,4}(\d{3})\b/i.exec(msg);
119
+ if (prefixed)
120
+ return Number(prefixed[1]);
121
+ return undefined;
122
+ }
123
+ function make(kind, retryable, raw, status, message) {
124
+ return new AgentError({ kind, retryable, status, cause: raw, message: message ?? AGENT_ERROR_MESSAGES[kind] });
125
+ }
126
+ function classifyByStatus(status, raw) {
127
+ if (status === 401 || status === 403)
128
+ return make('auth', false, raw, status);
129
+ if (status >= 500)
130
+ return make('server', true, raw, status);
131
+ if (status >= 400)
132
+ return make('server', false, raw, status, `The request was rejected (HTTP ${status}).`);
133
+ // Stray 2xx/3xx from a status field — treat as unknown transient failure, no status.
134
+ return make('server', true, raw, undefined, 'Something went wrong. You can try again.');
135
+ }
136
+ /**
137
+ * Classify any raw error into a structured {@link AgentError}.
138
+ *
139
+ * Resolution order (first match wins): an existing `AgentError` is returned
140
+ * unchanged (idempotent) → a user abort → a structured `status`/`cause.status`
141
+ * → network/connection markers → an HTTP-shaped status in the message → a
142
+ * `server` + retryable fallback. The original error is always preserved on
143
+ * `cause`. Runtime adapters call this before setting `Agent.error`; custom
144
+ * backends can call it too (or throw an `AgentError` directly).
145
+ *
146
+ * @param raw Any thrown/rejected value — an `Error`, a `{ status }` object, a string, etc.
147
+ * @returns The classified {@link AgentError} (kind, retryable, status?, cause).
148
+ * @example
149
+ * ```ts
150
+ * const e = toAgentError(new Error('HTTP 500: Internal Server Error'));
151
+ * e.kind; // 'server'
152
+ * e.retryable; // true
153
+ * e.status; // 500
154
+ * ```
155
+ */
156
+ function toAgentError(raw) {
157
+ if (raw instanceof AgentError)
158
+ return raw;
159
+ if (isAbortError(raw))
160
+ return make('aborted', false, raw);
161
+ // 1. Structured status (authoritative): raw.status or raw.cause.status.
162
+ const structured = structuredStatus(raw);
163
+ if (structured !== undefined)
164
+ return classifyByStatus(structured, raw);
165
+ // 2. Network/connection markers are definitive — before any loose text parsing.
166
+ if (isConnectionError(raw))
167
+ return make('connection', true, raw);
168
+ // 3. Best-effort: only an HTTP-shaped status token in the message counts.
169
+ const httpStatus = httpStatusFromMessage(raw);
170
+ if (httpStatus !== undefined)
171
+ return classifyByStatus(httpStatus, raw);
172
+ // 4. Fallback: unknown failure, assume transient.
173
+ const msg = raw instanceof Error && raw.message ? raw.message : 'Something went wrong. You can try again.';
174
+ return make('server', true, raw, undefined, msg);
175
+ }
176
+
177
+ /**
178
+ * Type guard narrowing a {@link Message} to `role: 'user'`.
179
+ *
180
+ * @param m The message to test.
181
+ * @returns `true` (and narrows `m`) when the message was sent by the user.
182
+ * @example
183
+ * ```ts
184
+ * const userTurns = agent.messages().filter(isUserMessage);
185
+ * ```
186
+ */
15
187
  function isUserMessage(m) {
16
188
  return m.role === 'user';
17
189
  }
190
+ /**
191
+ * Type guard narrowing a {@link Message} to `role: 'assistant'`.
192
+ *
193
+ * @param m The message to test.
194
+ * @returns `true` (and narrows `m`) when the message came from the assistant.
195
+ * @example
196
+ * ```ts
197
+ * const reply = agent.messages().findLast(isAssistantMessage);
198
+ * ```
199
+ */
18
200
  function isAssistantMessage(m) {
19
201
  return m.role === 'assistant';
20
202
  }
203
+ /**
204
+ * Type guard narrowing a {@link Message} to `role: 'tool'` (a tool result turn).
205
+ *
206
+ * @param m The message to test.
207
+ * @returns `true` (and narrows `m`) when the message is a tool result.
208
+ * @example
209
+ * ```ts
210
+ * if (isToolMessage(m)) console.log(m.toolCallId);
211
+ * ```
212
+ */
21
213
  function isToolMessage(m) {
22
214
  return m.role === 'tool';
23
215
  }
216
+ /**
217
+ * Type guard narrowing a {@link Message} to `role: 'system'`.
218
+ *
219
+ * @param m The message to test.
220
+ * @returns `true` (and narrows `m`) when the message is a system message.
221
+ * @example
222
+ * ```ts
223
+ * const visible = agent.messages().filter((m) => !isSystemMessage(m));
224
+ * ```
225
+ */
24
226
  function isSystemMessage(m) {
25
227
  return m.role === 'system';
26
228
  }
27
229
 
230
+ // SPDX-License-Identifier: MIT
231
+ /**
232
+ * Create a typed agent handle.
233
+ *
234
+ * @param debugName Optional name shown in Angular DI error messages.
235
+ * @returns An {@link AgentRef} carrying a state-typed `InjectionToken`.
236
+ * @example
237
+ * ```ts
238
+ * interface TripState { day: number; places: string[]; }
239
+ * export const TRIP = createAgentRef<TripState>('trip');
240
+ * // app.config.ts: provideAgent(TRIP, { assistantId: 'trip' })
241
+ * // component: const agent = injectAgent(TRIP); // LangGraphAgent<TripState>
242
+ * ```
243
+ */
244
+ function createAgentRef(debugName) {
245
+ return { token: new InjectionToken(debugName ?? 'ThreadplaneAgent') };
246
+ }
247
+
28
248
  // SPDX-License-Identifier: MIT
29
249
  class MessageTemplateDirective {
30
250
  chatMessageTemplate = input.required(...(ngDevMode ? [{ debugName: "chatMessageTemplate" }] : []));
@@ -2968,6 +3188,18 @@ const CHAT_TYPING_INDICATOR_STYLES = `
2968
3188
 
2969
3189
  // libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
2970
3190
  // SPDX-License-Identifier: MIT
3191
+ /**
3192
+ * Whether the agent should show a "typing" indicator — it is loading and has
3193
+ * not yet started streaming the assistant's reply.
3194
+ *
3195
+ * @param agent The agent to inspect.
3196
+ * @returns `true` while the agent is awaiting a response but no assistant text
3197
+ * has streamed yet; `false` once tokens arrive or the agent is idle.
3198
+ * @example
3199
+ * ```ts
3200
+ * \@if (isTyping(agent)) { <chat-typing-indicator [agent]="agent" /> }
3201
+ * ```
3202
+ */
2971
3203
  function isTyping(agent) {
2972
3204
  if (!agent.isLoading())
2973
3205
  return false;
@@ -3852,6 +4084,26 @@ const CHAT_ERROR_STYLES = `
3852
4084
  }
3853
4085
  .chat-error__icon { flex-shrink: 0; width: 16px; height: 16px; margin-top: 2px; }
3854
4086
  .chat-error__msg { flex: 1; min-width: 0; word-break: break-word; }
4087
+ .chat-error__retry {
4088
+ flex-shrink: 0;
4089
+ background: transparent;
4090
+ color: var(--ngaf-chat-error-text);
4091
+ border: 1px solid var(--ngaf-chat-error-border);
4092
+ border-radius: var(--ngaf-chat-radius-card);
4093
+ padding: 2px 10px;
4094
+ font-size: var(--ngaf-chat-font-size-sm);
4095
+ cursor: pointer;
4096
+ transition: background 150ms ease, color 150ms ease;
4097
+ white-space: nowrap;
4098
+ }
4099
+ .chat-error__retry:hover {
4100
+ background: var(--ngaf-chat-error-border);
4101
+ color: var(--ngaf-chat-error-text);
4102
+ }
4103
+ .chat-error__retry:focus-visible {
4104
+ outline: 2px solid var(--ngaf-chat-error-border);
4105
+ outline-offset: 2px;
4106
+ }
3855
4107
  `;
3856
4108
 
3857
4109
  // libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
@@ -3867,31 +4119,36 @@ function extractErrorMessage(error) {
3867
4119
  }
3868
4120
  class ChatErrorComponent {
3869
4121
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
3870
- errorMessage = computed(() => extractErrorMessage(this.agent().error()), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
3871
4122
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3872
4123
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatErrorComponent, isStandalone: true, selector: "chat-error", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
3873
- @if (errorMessage(); as msg) {
4124
+ @if (agent().error(); as err) {
3874
4125
  <div class="chat-error" role="alert">
3875
4126
  <svg class="chat-error__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
3876
4127
  <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
3877
4128
  </svg>
3878
- <span class="chat-error__msg">{{ msg }}</span>
4129
+ <span class="chat-error__msg">{{ err.message }}</span>
4130
+ @if (err.retryable) {
4131
+ <button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
4132
+ }
3879
4133
  </div>
3880
4134
  }
3881
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4135
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}.chat-error__retry{flex-shrink:0;background:transparent;color:var(--ngaf-chat-error-text);border:1px solid var(--ngaf-chat-error-border);border-radius:var(--ngaf-chat-radius-card);padding:2px 10px;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}.chat-error__retry:hover{background:var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text)}.chat-error__retry:focus-visible{outline:2px solid var(--ngaf-chat-error-border);outline-offset:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3882
4136
  }
3883
4137
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, decorators: [{
3884
4138
  type: Component,
3885
4139
  args: [{ selector: 'chat-error', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
3886
- @if (errorMessage(); as msg) {
4140
+ @if (agent().error(); as err) {
3887
4141
  <div class="chat-error" role="alert">
3888
4142
  <svg class="chat-error__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
3889
4143
  <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
3890
4144
  </svg>
3891
- <span class="chat-error__msg">{{ msg }}</span>
4145
+ <span class="chat-error__msg">{{ err.message }}</span>
4146
+ @if (err.retryable) {
4147
+ <button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
4148
+ }
3892
4149
  </div>
3893
4150
  }
3894
- `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}\n"] }]
4151
+ `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}.chat-error__retry{flex-shrink:0;background:transparent;color:var(--ngaf-chat-error-text);border:1px solid var(--ngaf-chat-error-border);border-radius:var(--ngaf-chat-radius-card);padding:2px 10px;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}.chat-error__retry:hover{background:var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text)}.chat-error__retry:focus-visible{outline:2px solid var(--ngaf-chat-error-border);outline-offset:2px}\n"] }]
3895
4152
  }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }] } });
3896
4153
 
3897
4154
  // SPDX-License-Identifier: MIT
@@ -3913,6 +4170,18 @@ const CHAT_INTERRUPT_STYLES = `
3913
4170
 
3914
4171
  // libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
3915
4172
  // SPDX-License-Identifier: MIT
4173
+ /**
4174
+ * Read the agent's current human-in-the-loop interrupt, if any.
4175
+ *
4176
+ * @param agent The agent to inspect.
4177
+ * @returns The pending {@link AgentInterrupt}, or `undefined` when the agent is
4178
+ * not currently waiting on an interrupt.
4179
+ * @example
4180
+ * ```ts
4181
+ * const interrupt = getInterrupt(agent);
4182
+ * if (interrupt) agent.resume('approved');
4183
+ * ```
4184
+ */
3916
4185
  function getInterrupt(agent) {
3917
4186
  return agent.interrupt?.();
3918
4187
  }
@@ -4093,6 +4362,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4093
4362
  `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.tcc__name{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-sm, 13px);color:var(--ngaf-chat-text-muted);font-weight:400;padding-left:2px}.tcc__pill{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:9999px;background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted);font-size:10px;font-weight:500;margin-left:6px;line-height:1.4}.tcc__pill svg{width:10px;height:10px}.tcc__pill[data-status=running] svg{animation:tcc-spin .8s linear infinite}@keyframes tcc-spin{to{transform:rotate(360deg)}}.tcc__section{padding:8px 0}.tcc__section+.tcc__section{border-top:1px solid var(--ngaf-chat-separator)}.tcc__section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:0 0 4px}.tcc__section-body{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text);white-space:pre-wrap;overflow-x:auto;margin:0}\n"] }]
4094
4363
  }], propDecorators: { toolCall: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolCall", required: true }] }], defaultCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCollapsed", required: false }] }] } });
4095
4364
 
4365
+ // libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
4366
+ // SPDX-License-Identifier: MIT
4367
+ /**
4368
+ * Returns a CSS style string for a subagent's status badge.
4369
+ * Kept exported for backward compatibility with existing consumers; the
4370
+ * preferred way to style status visually is via the `data-status` attribute
4371
+ * + CSS selectors (see component styles below).
4372
+ */
4373
+ function statusColor(status) {
4374
+ switch (status) {
4375
+ case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
4376
+ case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
4377
+ case 'complete': return 'color: var(--ngaf-chat-success);';
4378
+ case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
4379
+ }
4380
+ }
4381
+ function statusToTraceState(s) {
4382
+ switch (s) {
4383
+ case 'pending': return 'pending';
4384
+ case 'running': return 'running';
4385
+ case 'complete': return 'done';
4386
+ case 'error': return 'error';
4387
+ }
4388
+ }
4389
+ class ChatSubagentCardComponent {
4390
+ subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
4391
+ state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
4392
+ textOf(m) {
4393
+ const c = m.content;
4394
+ return typeof c === 'string' ? c : '';
4395
+ }
4396
+ toolCallsFor(m) {
4397
+ const ids = m.toolCallIds ?? [];
4398
+ if (ids.length === 0)
4399
+ return [];
4400
+ const all = this.subagent().toolCalls?.() ?? [];
4401
+ return ids.map((id) => all.find((tc) => tc.id === id)).filter((tc) => !!tc);
4402
+ }
4403
+ toToolCallInfo(tc) {
4404
+ return { id: tc.id, name: tc.name, args: tc.args, result: tc.result, status: tc.status };
4405
+ }
4406
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4407
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatSubagentCardComponent, isStandalone: true, selector: "chat-subagent-card", inputs: { subagent: { classPropertyName: "subagent", publicName: "subagent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
4408
+ <chat-trace [state]="state()">
4409
+ <span traceLabel>
4410
+ <span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
4411
+ <span class="sac__id">{{ subagent().toolCallId }}</span>
4412
+ <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4413
+ </span>
4414
+ <div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
4415
+ @for (m of subagent().messages(); track m.id) {
4416
+ <div class="sac__msg" [attr.data-role]="m.role">
4417
+ @if (m.reasoning) {
4418
+ <div class="sac__reasoning">{{ m.reasoning }}</div>
4419
+ }
4420
+ @if (textOf(m); as t) {
4421
+ <chat-streaming-md [content]="t" />
4422
+ }
4423
+ @for (tc of toolCallsFor(m); track tc.id) {
4424
+ <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
4425
+ }
4426
+ </div>
4427
+ }
4428
+ </chat-trace>
4429
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4430
+ }
4431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
4432
+ type: Component,
4433
+ args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent, ChatToolCallCardComponent, ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4434
+ <chat-trace [state]="state()">
4435
+ <span traceLabel>
4436
+ <span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
4437
+ <span class="sac__id">{{ subagent().toolCallId }}</span>
4438
+ <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4439
+ </span>
4440
+ <div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
4441
+ @for (m of subagent().messages(); track m.id) {
4442
+ <div class="sac__msg" [attr.data-role]="m.role">
4443
+ @if (m.reasoning) {
4444
+ <div class="sac__reasoning">{{ m.reasoning }}</div>
4445
+ }
4446
+ @if (textOf(m); as t) {
4447
+ <chat-streaming-md [content]="t" />
4448
+ }
4449
+ @for (tc of toolCallsFor(m); track tc.id) {
4450
+ <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
4451
+ }
4452
+ </div>
4453
+ }
4454
+ </chat-trace>
4455
+ `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"] }]
4456
+ }], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
4457
+
4096
4458
  // libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-call-template.directive.ts
4097
4459
  // SPDX-License-Identifier: MIT
4098
4460
  /**
@@ -4221,14 +4583,23 @@ class ChatToolCallsComponent {
4221
4583
  groups = computed(() => {
4222
4584
  const excludeSet = new Set(this.excludeToolNames());
4223
4585
  const calls = this.toolCalls().filter(tc => !excludeSet.has(tc.name));
4586
+ const subs = this.agent().subagents?.() ?? new Map();
4224
4587
  const groupingMode = this.grouping();
4225
4588
  const registry = this.templateRegistry();
4226
4589
  const wildcard = registry.get('*');
4227
4590
  const out = [];
4228
4591
  for (const tc of calls) {
4592
+ // A tool call that spawned a subagent renders as a standalone subagent
4593
+ // card anchored to that call. It never groups with adjacent calls, on
4594
+ // either side: it is its own group and carries a `subagent`, so the next
4595
+ // call can't append to it (a subagent group is never a group target).
4596
+ if (subs.has(tc.id)) {
4597
+ out.push({ name: tc.name, calls: [tc], subagent: subs.get(tc.id) });
4598
+ continue;
4599
+ }
4229
4600
  const tpl = registry.get(tc.name) ?? wildcard;
4230
4601
  const last = out[out.length - 1];
4231
- const sameName = last && last.name === tc.name;
4602
+ const sameName = last && !last.subagent && last.name === tc.name;
4232
4603
  const canGroup = groupingMode === 'auto' && sameName;
4233
4604
  if (canGroup) {
4234
4605
  last.calls.push(tc);
@@ -4262,7 +4633,9 @@ class ChatToolCallsComponent {
4262
4633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4263
4634
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatToolCallsComponent, isStandalone: true, selector: "chat-tool-calls", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, grouping: { classPropertyName: "grouping", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, groupSummary: { classPropertyName: "groupSummary", publicName: "groupSummary", isSignal: true, isRequired: false, transformFunction: null }, excludeToolNames: { classPropertyName: "excludeToolNames", publicName: "excludeToolNames", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "templates", predicate: ChatToolCallTemplateDirective, isSignal: true }], ngImport: i0, template: `
4264
4635
  @for (group of groups(); track $index) {
4265
- @if (group.calls.length > 1 && !group.templateRef) {
4636
+ @if (group.subagent) {
4637
+ <chat-subagent-card [subagent]="group.subagent" />
4638
+ } @else if (group.calls.length > 1 && !group.templateRef) {
4266
4639
  <!-- Default grouped strip -->
4267
4640
  @let expanded = expandedGroups().has($index);
4268
4641
  <div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
@@ -4293,13 +4666,15 @@ class ChatToolCallsComponent {
4293
4666
  }
4294
4667
  }
4295
4668
  }
4296
- `, isInline: true, styles: [":host{display:block;margin-bottom:20px}.ctc__group{border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);margin:0 0 4px}.ctc__group-header{display:flex;align-items:center;gap:.5rem;width:100%;padding:8px 12px;background:transparent;border:0;font:inherit;color:var(--ngaf-chat-text);cursor:pointer;text-align:left}.ctc__group-chevron{width:10px;height:10px;transition:transform .12s ease}.ctc__group[data-expanded=true] .ctc__group-chevron{transform:rotate(90deg)}.ctc__group-body{padding:0 12px 8px;border-top:1px solid var(--ngaf-chat-separator)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4669
+ `, isInline: true, styles: [":host{display:block;margin-bottom:20px}.ctc__group{border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);margin:0 0 4px}.ctc__group-header{display:flex;align-items:center;gap:.5rem;width:100%;padding:8px 12px;background:transparent;border:0;font:inherit;color:var(--ngaf-chat-text);cursor:pointer;text-align:left}.ctc__group-chevron{width:10px;height:10px;transition:transform .12s ease}.ctc__group[data-expanded=true] .ctc__group-chevron{transform:rotate(90deg)}.ctc__group-body{padding:0 12px 8px;border-top:1px solid var(--ngaf-chat-separator)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatSubagentCardComponent, selector: "chat-subagent-card", inputs: ["subagent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4297
4670
  }
4298
4671
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, decorators: [{
4299
4672
  type: Component,
4300
- args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4673
+ args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent, ChatSubagentCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4301
4674
  @for (group of groups(); track $index) {
4302
- @if (group.calls.length > 1 && !group.templateRef) {
4675
+ @if (group.subagent) {
4676
+ <chat-subagent-card [subagent]="group.subagent" />
4677
+ } @else if (group.calls.length > 1 && !group.templateRef) {
4303
4678
  <!-- Default grouped strip -->
4304
4679
  @let expanded = expandedGroups().has($index);
4305
4680
  <div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
@@ -4573,75 +4948,6 @@ function isRecord$2(v) {
4573
4948
  return typeof v === 'object' && v !== null && !Array.isArray(v);
4574
4949
  }
4575
4950
 
4576
- // libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
4577
- // SPDX-License-Identifier: MIT
4578
- /**
4579
- * Returns a CSS style string for a subagent's status badge.
4580
- * Kept exported for backward compatibility with existing consumers; the
4581
- * preferred way to style status visually is via the `data-status` attribute
4582
- * + CSS selectors (see component styles below).
4583
- */
4584
- function statusColor(status) {
4585
- switch (status) {
4586
- case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
4587
- case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
4588
- case 'complete': return 'color: var(--ngaf-chat-success);';
4589
- case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
4590
- }
4591
- }
4592
- function statusToTraceState(s) {
4593
- switch (s) {
4594
- case 'pending': return 'pending';
4595
- case 'running': return 'running';
4596
- case 'complete': return 'done';
4597
- case 'error': return 'error';
4598
- }
4599
- }
4600
- class ChatSubagentCardComponent {
4601
- subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
4602
- state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
4603
- latestMessageContent = computed(() => {
4604
- const messages = this.subagent().messages();
4605
- if (messages.length === 0)
4606
- return '';
4607
- const last = messages[messages.length - 1];
4608
- const c = last.content;
4609
- return typeof c === 'string' ? c : JSON.stringify(c);
4610
- }, ...(ngDevMode ? [{ debugName: "latestMessageContent" }] : []));
4611
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4612
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatSubagentCardComponent, isStandalone: true, selector: "chat-subagent-card", inputs: { subagent: { classPropertyName: "subagent", publicName: "subagent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
4613
- <chat-trace [state]="state()">
4614
- <span traceLabel>
4615
- <span class="sac__name">Subagent</span>
4616
- <span class="sac__id">{{ subagent().toolCallId }}</span>
4617
- <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4618
- </span>
4619
- <div class="sac__count">{{ subagent().messages().length }} message(s)</div>
4620
- @if (subagent().messages().length > 0) {
4621
- <p class="sac__latest-label">Latest message</p>
4622
- <pre class="sac__latest">{{ latestMessageContent() }}</pre>
4623
- }
4624
- </chat-trace>
4625
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__latest-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:8px 0 4px}.sac__latest{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text);white-space:pre-wrap;overflow-x:auto;margin:0}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4626
- }
4627
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
4628
- type: Component,
4629
- args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4630
- <chat-trace [state]="state()">
4631
- <span traceLabel>
4632
- <span class="sac__name">Subagent</span>
4633
- <span class="sac__id">{{ subagent().toolCallId }}</span>
4634
- <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4635
- </span>
4636
- <div class="sac__count">{{ subagent().messages().length }} message(s)</div>
4637
- @if (subagent().messages().length > 0) {
4638
- <p class="sac__latest-label">Latest message</p>
4639
- <pre class="sac__latest">{{ latestMessageContent() }}</pre>
4640
- }
4641
- </chat-trace>
4642
- `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__latest-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:8px 0 4px}.sac__latest{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text);white-space:pre-wrap;overflow-x:auto;margin:0}\n"] }]
4643
- }], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
4644
-
4645
4951
  // libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
4646
4952
  // SPDX-License-Identifier: MIT
4647
4953
  function activeSubagentsFromAgent(agent) {
@@ -6193,10 +6499,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
6193
6499
  class ChatWelcomeSuggestionComponent {
6194
6500
  label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
6195
6501
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
6502
+ /** Optional short description, surfaced as a hover/focus tooltip on the chip. */
6503
+ description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
6196
6504
  selected = output();
6197
6505
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6198
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatWelcomeSuggestionComponent, isStandalone: true, selector: "chat-welcome-suggestion", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: `
6199
- <button type="button" class="chat-welcome-suggestion" (click)="selected.emit(value())">
6506
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatWelcomeSuggestionComponent, isStandalone: true, selector: "chat-welcome-suggestion", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: `
6507
+ <button
6508
+ type="button"
6509
+ class="chat-welcome-suggestion"
6510
+ [attr.title]="description() || null"
6511
+ [attr.aria-description]="description() || null"
6512
+ (click)="selected.emit(value())"
6513
+ >
6200
6514
  <ng-content select="[chatWelcomeSuggestionIcon]" />
6201
6515
  <span class="chat-welcome-suggestion__label">{{ label() }}</span>
6202
6516
  <span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
@@ -6206,13 +6520,19 @@ class ChatWelcomeSuggestionComponent {
6206
6520
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, decorators: [{
6207
6521
  type: Component,
6208
6522
  args: [{ selector: 'chat-welcome-suggestion', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
6209
- <button type="button" class="chat-welcome-suggestion" (click)="selected.emit(value())">
6523
+ <button
6524
+ type="button"
6525
+ class="chat-welcome-suggestion"
6526
+ [attr.title]="description() || null"
6527
+ [attr.aria-description]="description() || null"
6528
+ (click)="selected.emit(value())"
6529
+ >
6210
6530
  <ng-content select="[chatWelcomeSuggestionIcon]" />
6211
6531
  <span class="chat-welcome-suggestion__label">{{ label() }}</span>
6212
6532
  <span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
6213
6533
  </button>
6214
6534
  `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block}.chat-welcome-suggestion{display:inline-flex;align-items:center;gap:.5rem;padding:10px 16px;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:9999px;color:var(--ngaf-chat-text);font-family:inherit;font-size:var(--ngaf-chat-font-size-sm);text-align:center;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .12s ease}.chat-welcome-suggestion:hover{background:var(--ngaf-chat-surface-alt);border-color:var(--ngaf-chat-text-muted)}.chat-welcome-suggestion:active{transform:scale(.98)}.chat-welcome-suggestion:focus-visible{outline:2px solid var(--ngaf-chat-text-muted);outline-offset:2px}.chat-welcome-suggestion__label{white-space:nowrap}.chat-welcome-suggestion__chevron{display:none}\n"] }]
6215
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
6535
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
6216
6536
 
6217
6537
  // libs/chat/src/lib/styles/chat-select.styles.ts
6218
6538
  // SPDX-License-Identifier: MIT
@@ -6268,7 +6588,10 @@ const CHAT_SELECT_STYLES = `
6268
6588
  z-index: 10;
6269
6589
  }
6270
6590
  .chat-select__option {
6271
- display: block;
6591
+ display: flex;
6592
+ flex-direction: column;
6593
+ align-items: flex-start;
6594
+ gap: 2px;
6272
6595
  width: 100%;
6273
6596
  text-align: left;
6274
6597
  border: 0;
@@ -6280,6 +6603,12 @@ const CHAT_SELECT_STYLES = `
6280
6603
  font-size: var(--ngaf-chat-font-size-sm);
6281
6604
  cursor: pointer;
6282
6605
  }
6606
+ .chat-select__option-desc {
6607
+ font-size: var(--ngaf-chat-font-size-xs);
6608
+ color: var(--ngaf-chat-text-muted);
6609
+ line-height: 1.3;
6610
+ white-space: normal;
6611
+ }
6283
6612
  .chat-select__option:hover:not(:disabled),
6284
6613
  .chat-select__option:focus-visible {
6285
6614
  background: var(--ngaf-chat-surface-alt);
@@ -6458,12 +6787,15 @@ class ChatSelectComponent {
6458
6787
  [attr.aria-selected]="opt.value === value()"
6459
6788
  (click)="selectOption(opt)"
6460
6789
  >
6461
- {{ opt.label }}
6790
+ <span class="chat-select__option-label">{{ opt.label }}</span>
6791
+ @if (opt.description) {
6792
+ <span class="chat-select__option-desc">{{ opt.description }}</span>
6793
+ }
6462
6794
  </button>
6463
6795
  }
6464
6796
  </div>
6465
6797
  }
6466
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:block;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6798
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option-desc{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);line-height:1.3;white-space:normal}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6467
6799
  }
6468
6800
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSelectComponent, decorators: [{
6469
6801
  type: Component,
@@ -6501,17 +6833,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
6501
6833
  [attr.aria-selected]="opt.value === value()"
6502
6834
  (click)="selectOption(opt)"
6503
6835
  >
6504
- {{ opt.label }}
6836
+ <span class="chat-select__option-label">{{ opt.label }}</span>
6837
+ @if (opt.description) {
6838
+ <span class="chat-select__option-desc">{{ opt.description }}</span>
6839
+ }
6505
6840
  </button>
6506
6841
  }
6507
6842
  </div>
6508
6843
  }
6509
- `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:block;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"] }]
6844
+ `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option-desc{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);line-height:1.3;white-space:normal}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"] }]
6510
6845
  }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }] } });
6511
6846
 
6512
6847
  // SPDX-License-Identifier: MIT
6513
6848
  const PACKAGE_NAME = '@threadplane/chat';
6514
6849
  const CHAT_CONFIG = new InjectionToken('CHAT_CONFIG');
6850
+ /**
6851
+ * Bootstrap `@threadplane/chat` in an Angular application or standalone
6852
+ * component tree.
6853
+ *
6854
+ * Call this once inside `bootstrapApplication` (or the `providers` array of a
6855
+ * root `ApplicationConfig`). It registers the shared {@link ChatConfig} token
6856
+ * so every chat component in the tree can read the render registry, avatar
6857
+ * label, and assistant display name without explicit prop threading.
6858
+ *
6859
+ * A license check is fired asynchronously on every call (it never throws; a
6860
+ * watermark is shown in non-commercial builds when no valid token is supplied).
6861
+ *
6862
+ * @param config Options bag that controls the chat feature set:
6863
+ * - `renderRegistry` — shared {@link AngularRegistry} wiring tool-view
6864
+ * components to their names; pass the value returned by
6865
+ * `defineAngularRegistry` from `\@threadplane/render`.
6866
+ * - `avatarLabel` — short label shown in the AI avatar bubble (default `"A"`).
6867
+ * - `assistantName` — display name shown above assistant messages
6868
+ * (default `"Assistant"`).
6869
+ * - `license` — signed token from threadplane.ai; omit in development.
6870
+ * @returns An `EnvironmentProviders` value suitable for the `providers` array
6871
+ * of `bootstrapApplication` or `ApplicationConfig`.
6872
+ * @example
6873
+ * ```ts
6874
+ * // main.ts
6875
+ * import { bootstrapApplication } from '@angular/platform-browser';
6876
+ * import { provideChat } from '@threadplane/chat';
6877
+ * import { defineAngularRegistry, provideRender } from '@threadplane/render';
6878
+ * import { DayCardComponent } from './day-card.component';
6879
+ *
6880
+ * const registry = defineAngularRegistry({ day_card: DayCardComponent });
6881
+ *
6882
+ * bootstrapApplication(AppComponent, {
6883
+ * providers: [
6884
+ * provideChat({ renderRegistry: registry, avatarLabel: 'AI' }),
6885
+ * provideRender({ registry }),
6886
+ * ],
6887
+ * });
6888
+ * ```
6889
+ */
6515
6890
  function provideChat(config) {
6516
6891
  void runLicenseCheck({
6517
6892
  package: PACKAGE_NAME,
@@ -6524,6 +6899,65 @@ function provideChat(config) {
6524
6899
  ]);
6525
6900
  }
6526
6901
 
6902
+ // SPDX-License-Identifier: MIT
6903
+ const defaultFromUrl = (url) => {
6904
+ const seg = url.split('?')[0].split('#')[0].split('/').filter(Boolean);
6905
+ return seg.length ? seg[seg.length - 1] : null;
6906
+ };
6907
+ const defaultToCommands = (id) => (id ? ['/', id] : ['/']);
6908
+ /**
6909
+ * Bind an app-owned `activeThreadId` signal to the URL — restore on load, stamp on change,
6910
+ * validate-or-redirect, with a bare URL meaning "no thread" (welcome). URL is the source of
6911
+ * truth; nothing is written to localStorage. Must be called in an injection context.
6912
+ *
6913
+ * @example
6914
+ * ```ts
6915
+ * export const ACTIVE_THREAD = signal<string | null>(null);
6916
+ * // providers: provideAgent({ threadId: ACTIVE_THREAD, onThreadId: id => ACTIVE_THREAD.set(id) })
6917
+ * const threads = inject(LangGraphThreadsAdapter);
6918
+ * injectThreadRouting({ threadId: ACTIVE_THREAD, validate: id => threads.getThread(id).then(Boolean) });
6919
+ * ```
6920
+ */
6921
+ function injectThreadRouting(config) {
6922
+ const router = inject(Router);
6923
+ const fromUrl = config.threadIdFromUrl ?? defaultFromUrl;
6924
+ const toCommands = config.toCommands ?? defaultToCommands;
6925
+ const extras = config.navigationExtras ?? { queryParamsHandling: 'preserve' };
6926
+ const urlThreadId = toSignal(router.events.pipe(filter((e) => e instanceof NavigationEnd), map((e) => fromUrl(e.urlAfterRedirects)), startWith(fromUrl(router.url))), { initialValue: fromUrl(router.url) });
6927
+ // Seed the signal from the URL once.
6928
+ config.threadId.set(urlThreadId());
6929
+ // URL → signal.
6930
+ effect(() => {
6931
+ const urlId = urlThreadId();
6932
+ if (urlId !== untracked(() => config.threadId()))
6933
+ config.threadId.set(urlId);
6934
+ });
6935
+ // signal → URL.
6936
+ effect(() => {
6937
+ const id = config.threadId();
6938
+ const urlId = untracked(() => urlThreadId());
6939
+ if (id !== urlId)
6940
+ void router.navigate(toCommands(id), extras);
6941
+ });
6942
+ // validate stale ids → redirect to bare. Memoize the last id checked so
6943
+ // re-visiting the same thread (A → B → A) doesn't re-hit the backend.
6944
+ if (config.validate) {
6945
+ const validate = config.validate;
6946
+ let lastValidated = null;
6947
+ effect(() => {
6948
+ const id = urlThreadId();
6949
+ if (!id || id === lastValidated)
6950
+ return;
6951
+ lastValidated = id;
6952
+ void validate(id).then((ok) => {
6953
+ if (!ok && untracked(() => urlThreadId()) === id) {
6954
+ void router.navigate(toCommands(null), { ...extras, replaceUrl: true });
6955
+ }
6956
+ });
6957
+ });
6958
+ }
6959
+ }
6960
+
6527
6961
  // SPDX-License-Identifier: MIT
6528
6962
  const CHAT_LIFECYCLE = new InjectionToken('CHAT_LIFECYCLE');
6529
6963
 
@@ -7084,6 +7518,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
7084
7518
  }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: true }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], surfaceFallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceFallback", required: false }] }], events: [{ type: i0.Output, args: ["events"] }], action: [{ type: i0.Output, args: ["action"] }] } });
7085
7519
 
7086
7520
  // SPDX-License-Identifier: MIT
7521
+ /**
7522
+ * Create a {@link ParseTreeStore} — feeds streamed JSON chunks through a
7523
+ * partial-JSON parser and exposes the progressively-materialized spec and
7524
+ * per-element accumulation state as signals, so a generative-UI surface can
7525
+ * render while the spec is still arriving.
7526
+ *
7527
+ * @param parser The partial-JSON parser used to incrementally materialize chunks.
7528
+ * @returns A {@link ParseTreeStore}; call `push(chunk)` as bytes stream in.
7529
+ * @example
7530
+ * ```ts
7531
+ * const store = createParseTreeStore(parser);
7532
+ * store.push('{"type":"Car');
7533
+ * store.spec(); // best-effort Spec | null
7534
+ * ```
7535
+ */
7087
7536
  function createParseTreeStore(parser) {
7088
7537
  const specSignal = signal(null, ...(ngDevMode ? [{ debugName: "specSignal" }] : []));
7089
7538
  const elementStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "elementStatesSignal" }] : []));
@@ -7224,6 +7673,19 @@ function resolveProps(value, dataModel) {
7224
7673
  }
7225
7674
  return value;
7226
7675
  }
7676
+ /**
7677
+ * Create an {@link A2uiSurfaceStore} — the per-conversation store that buffers
7678
+ * streamed A2UI surface updates, tracks each surface's data model + lifecycle
7679
+ * state, and exposes them as signals for rendering. One store backs a chat
7680
+ * thread's A2UI surfaces.
7681
+ *
7682
+ * @returns A fresh, empty {@link A2uiSurfaceStore}.
7683
+ * @example
7684
+ * ```ts
7685
+ * const store = createA2uiSurfaceStore();
7686
+ * const surfaces = store.surfaces; // Signal<Map<string, A2uiSurface>>
7687
+ * ```
7688
+ */
7227
7689
  function createA2uiSurfaceStore() {
7228
7690
  const surfacesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfacesSignal" }] : []));
7229
7691
  const surfaceStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfaceStatesSignal" }] : []));
@@ -7445,6 +7907,19 @@ function trace(...args) {
7445
7907
 
7446
7908
  // SPDX-License-Identifier: MIT
7447
7909
  const A2UI_PREFIX = '---a2ui_JSON---';
7910
+ /**
7911
+ * Create a {@link ContentClassifier} — the streaming accumulator that inspects
7912
+ * an assistant message's content as it arrives and classifies it (markdown vs a
7913
+ * generative-UI/A2UI spec), exposing the parsed result and per-element state as
7914
+ * signals so the renderer can switch modes mid-stream.
7915
+ *
7916
+ * @returns A fresh {@link ContentClassifier}; call `dispose()` when done.
7917
+ * @example
7918
+ * ```ts
7919
+ * const cc = createContentClassifier();
7920
+ * effect(() => console.log(cc.type())); // 'pending' | 'markdown' | 'spec'
7921
+ * ```
7922
+ */
7448
7923
  function createContentClassifier() {
7449
7924
  const typeSignal = signal('pending', ...(ngDevMode ? [{ debugName: "typeSignal" }] : []));
7450
7925
  const markdownSignal = signal('', ...(ngDevMode ? [{ debugName: "markdownSignal" }] : []));
@@ -8895,7 +9370,6 @@ class ChatComponent {
8895
9370
  [handlers]="handlers()"
8896
9371
  (events)="onClientToolEvent($event)"
8897
9372
  />
8898
- <chat-subagents [agent]="agent()" />
8899
9373
  @if (classified.markdown(); as md) {
8900
9374
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8901
9375
  }
@@ -8983,7 +9457,7 @@ class ChatComponent {
8983
9457
  </div>
8984
9458
  </div>
8985
9459
  }
8986
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: ChatSubagentsComponent, selector: "chat-subagents", inputs: ["agent"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9460
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8987
9461
  }
8988
9462
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
8989
9463
  type: Component,
@@ -8992,7 +9466,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8992
9466
  ChatWindowComponent, ChatMessageListComponent, MessageTemplateDirective, ChatMessageComponent,
8993
9467
  ChatInputComponent, ChatTypingIndicatorComponent, ChatErrorComponent,
8994
9468
  ChatThreadListComponent, ChatGenerativeUiComponent,
8995
- ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent, ChatSubagentsComponent, A2uiSurfaceComponent,
9469
+ ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent, A2uiSurfaceComponent,
8996
9470
  ChatMessageActionsComponent, ChatWelcomeComponent, ChatSelectComponent, ChatReasoningComponent,
8997
9471
  ChatScrollBubbleComponent,
8998
9472
  ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
@@ -9065,7 +9539,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
9065
9539
  [handlers]="handlers()"
9066
9540
  (events)="onClientToolEvent($event)"
9067
9541
  />
9068
- <chat-subagents [agent]="agent()" />
9069
9542
  @if (classified.markdown(); as md) {
9070
9543
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
9071
9544
  }
@@ -10822,22 +11295,6 @@ function renderMarkdownToString(content, sanitizer) {
10822
11295
  return plainTextToHtml(content);
10823
11296
  }
10824
11297
 
10825
- // SPDX-License-Identifier: MIT
10826
- /** Chevron down (▼ replacement). 12x12, stroke-based. */
10827
- const ICON_CHEVRON_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.5L6 7.5L9 4.5"/></svg>`;
10828
- /** Chevron up (▲ replacement). 12x12, stroke-based. */
10829
- const ICON_CHEVRON_UP = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5L6 4.5L9 7.5"/></svg>`;
10830
- /** Gear icon (⚙ replacement). 14x14. */
10831
- const ICON_TOOL = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>`;
10832
- /** Warning triangle (⚠ replacement). 18x18. */
10833
- const ICON_WARNING = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`;
10834
- /** Robot/agent icon (replacement). 14x14. */
10835
- const ICON_AGENT = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>`;
10836
- /** Check mark replacement. 12x12. */
10837
- const ICON_CHECK = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6L5 8.5L9.5 3.5"/></svg>`;
10838
- /** Send arrow (for chat input). 16x16. */
10839
- const ICON_SEND = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 12V4M8 4L4 8M8 4L12 8"/></svg>`;
10840
-
10841
11298
  /** Normalize a catalog entry to the `A2uiViewEntry` shape. Bare
10842
11299
  * `Type<unknown>` entries are wrapped as `{ component }`; entries
10843
11300
  * already in the discriminated shape are returned unchanged. */
@@ -11865,6 +12322,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11865
12322
  }], propDecorators: { url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], autoPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlay", required: false }] }], controls: [{ type: i0.Input, args: [{ isSignal: true, alias: "controls", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
11866
12323
 
11867
12324
  // SPDX-License-Identifier: MIT
12325
+ /**
12326
+ * Build the built-in A2UI component catalog — a {@link ViewRegistry} mapping
12327
+ * the standard A2UI element types (Card, Button, TextField, Image, AudioPlayer,
12328
+ * Video, …) to their Angular renderers. Spread it into `provideViews` (with any
12329
+ * of your own views) so an agent's A2UI surface specs render.
12330
+ *
12331
+ * @returns A {@link ViewRegistry} of the standard A2UI components.
12332
+ * @example
12333
+ * ```ts
12334
+ * providers: [provideViews({ ...a2uiBasicCatalog(), MyWidget: MyWidgetComponent })]
12335
+ * ```
12336
+ */
11868
12337
  function a2uiBasicCatalog() {
11869
12338
  return views({
11870
12339
  AudioPlayer: A2uiAudioPlayerComponent,
@@ -11888,29 +12357,140 @@ function a2uiBasicCatalog() {
11888
12357
  });
11889
12358
  }
11890
12359
 
11891
- /** Async function tool — its resolved return value becomes the tool result. */
12360
+ /**
12361
+ * Declare an async function tool the model can call; its resolved return value
12362
+ * becomes the tool result shipped back to the model.
12363
+ *
12364
+ * @param description Natural-language description the model sees.
12365
+ * @param schema Standard Schema (e.g. a Zod object) for the arguments; the
12366
+ * handler's argument type is inferred from it.
12367
+ * @param handler Runs in the browser when the model calls the tool; its return
12368
+ * type `R` is carried on the resulting {@link FunctionToolDef}.
12369
+ * @returns A {@link FunctionToolDef} for inclusion in {@link tools}.
12370
+ * @example
12371
+ * ```ts
12372
+ * const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
12373
+ * const registry = tools({ move_stop: move });
12374
+ * ```
12375
+ */
11892
12376
  function action(description, schema, handler) {
11893
12377
  return { kind: 'function', description, schema, handler };
11894
12378
  }
11895
- /** Render-only component tool — the model fills its props; auto-acknowledged. */
12379
+ /**
12380
+ * Render-only component tool — the model fills the component's props from the
12381
+ * schema's output; the tool call is auto-acknowledged once the component mounts.
12382
+ *
12383
+ * The component's signal inputs are checked against the schema output type
12384
+ * (strict-but-flexible: every schema key must be a declared input with an
12385
+ * assignable type; the component may declare extra inputs the schema doesn't fill).
12386
+ * Author the component with `ViewProps<typeof schema>` as the input type set to
12387
+ * guarantee the shapes stay aligned.
12388
+ *
12389
+ * @param description Natural-language description the model sees.
12390
+ * @param schema Standard Schema defining the props the model must supply.
12391
+ * @param component Angular component whose signal inputs must be compatible with
12392
+ * the schema output. A type-level error is reported here when they diverge.
12393
+ * @returns A {@link ViewToolDef} for inclusion in {@link tools}.
12394
+ * @example
12395
+ * ```ts
12396
+ * const schema = z.object({ label: z.string(), day: z.number() });
12397
+ * type Inputs = ViewProps<typeof schema>; // { label: string; day: number }
12398
+ *
12399
+ * \@Component({ ... })
12400
+ * class DayCardComponent {
12401
+ * label = input.required<string>();
12402
+ * day = input.required<number>();
12403
+ * }
12404
+ *
12405
+ * const dayCard = view('Show a day card', schema, DayCardComponent);
12406
+ * const registry = tools({ day_card: dayCard });
12407
+ * ```
12408
+ */
11896
12409
  function view(description, schema, component) {
11897
- return { kind: 'view', description, schema, component };
12410
+ return { kind: 'view', description, schema, component: component };
11898
12411
  }
11899
- /** Interactive (HITL) component tool — the value it emits becomes the result. */
12412
+ /**
12413
+ * Interactive (human-in-the-loop) component tool — the model fills the
12414
+ * component's props from the schema's output; the value the component emits
12415
+ * back to the framework becomes the tool result sent to the model.
12416
+ *
12417
+ * The component's signal inputs are checked against the schema output type
12418
+ * (strict-but-flexible: every schema key must be a declared input with an
12419
+ * assignable type; the component may declare extra inputs the schema doesn't
12420
+ * fill). Author the component with `ViewProps<typeof schema>` to derive input
12421
+ * prop types directly from the schema.
12422
+ *
12423
+ * @param description Natural-language description the model sees.
12424
+ * @param schema Standard Schema defining the props the model must supply.
12425
+ * @param component Angular component whose signal inputs must be compatible with
12426
+ * the schema output. A type-level error is reported here when they diverge.
12427
+ * @returns An {@link AskToolDef} for inclusion in {@link tools}.
12428
+ * @example
12429
+ * ```ts
12430
+ * const schema = z.object({ question: z.string(), options: z.array(z.string()) });
12431
+ * type Inputs = ViewProps<typeof schema>;
12432
+ *
12433
+ * \@Component({ ... })
12434
+ * class ChoiceCardComponent {
12435
+ * question = input.required<string>();
12436
+ * options = input.required<string[]>();
12437
+ * // Emits the chosen option back to the model.
12438
+ * }
12439
+ *
12440
+ * const choice = ask('Ask the user to choose', schema, ChoiceCardComponent);
12441
+ * const registry = tools({ pick_option: choice });
12442
+ * ```
12443
+ */
11900
12444
  function ask(description, schema, component) {
11901
- return { kind: 'ask', description, schema, component };
12445
+ return { kind: 'ask', description, schema, component: component };
11902
12446
  }
11903
- /** Collect named client tools into a frozen registry (the key is the tool name). */
12447
+ /**
12448
+ * Collect named client tools into a frozen, name-keyed registry.
12449
+ *
12450
+ * The overload is generic over the entire map (`const M`) so that each tool's
12451
+ * precise type ({@link FunctionToolDef}`<S,R>`, {@link ViewToolDef}`<S,C>`, or
12452
+ * {@link AskToolDef}`<S,C>`) and every literal key are preserved in the
12453
+ * {@link ClientToolRegistry} passed to `provideChat`. This lets downstream
12454
+ * consumers look up individual tools without losing generic information.
12455
+ *
12456
+ * @param map An object literal mapping tool names to tool definitions created
12457
+ * by {@link action}, {@link view}, or {@link ask}.
12458
+ * @returns A frozen `Readonly<M>` where `M` is the exact inferred map shape.
12459
+ * @example
12460
+ * ```ts
12461
+ * const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
12462
+ * const dayCard = view('Show a day card', z.object({ label: z.string() }), DayCardComponent);
12463
+ *
12464
+ * const registry = tools({ move_stop: move, day_card: dayCard });
12465
+ * // registry.move_stop is FunctionToolDef<...>
12466
+ * // registry.day_card is ViewToolDef<...>
12467
+ * ```
12468
+ */
11904
12469
  function tools(map) {
11905
12470
  return Object.freeze({ ...map });
11906
12471
  }
11907
12472
 
11908
12473
  // SPDX-License-Identifier: MIT
12474
+ /**
12475
+ * Build an in-memory {@link Agent} for tests and stories — no transport, no
12476
+ * network. Every field is a writable signal so a test can drive UI states
12477
+ * (loading, error, interrupts, tool calls, subagents) deterministically.
12478
+ *
12479
+ * @param opts Initial values for the mock's signals; all optional.
12480
+ * @returns A {@link MockAgent} satisfying the full `Agent` contract.
12481
+ * @example
12482
+ * ```ts
12483
+ * const agent = mockAgent({
12484
+ * messages: [{ id: '1', role: 'assistant', content: 'Hi' }],
12485
+ * isLoading: true,
12486
+ * });
12487
+ * ```
12488
+ */
11909
12489
  function mockAgent(opts = {}) {
11910
12490
  const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
11911
12491
  const status = signal(opts.status ?? 'idle', ...(ngDevMode ? [{ debugName: "status" }] : []));
11912
12492
  const isLoading = signal(opts.isLoading ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
11913
- const error = signal(opts.error ?? null, ...(ngDevMode ? [{ debugName: "error" }] : []));
12493
+ const error = signal(opts.error ?? undefined, ...(ngDevMode ? [{ debugName: "error" }] : []));
11914
12494
  const toolCalls = signal(opts.toolCalls ?? [], ...(ngDevMode ? [{ debugName: "toolCalls" }] : []));
11915
12495
  const state = signal(opts.state ?? {}, ...(ngDevMode ? [{ debugName: "state" }] : []));
11916
12496
  const interrupt = opts.withInterrupt
@@ -11935,6 +12515,7 @@ function mockAgent(opts = {}) {
11935
12515
  events$: opts.events$ ?? EMPTY,
11936
12516
  submit: async (input, submitOpts) => { submitCalls.push({ input, opts: submitOpts }); },
11937
12517
  stop: async () => { stopCount++; },
12518
+ retry: async () => { return; },
11938
12519
  regenerate: async (assistantMessageIndex) => {
11939
12520
  // Truncate messages [N..end] and record the call as a synthetic submit so
11940
12521
  // tests can assert regenerate behavior via the same submitCalls log.
@@ -11956,5 +12537,5 @@ function mockAgent(opts = {}) {
11956
12537
  * Generated bundle index. Do not edit.
11957
12538
  */
11958
12539
 
11959
- export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, CHAT_CONFIG, CHAT_LIFECYCLE, CHAT_MARKDOWN_STYLES, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, ICON_AGENT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CHEVRON_UP, ICON_SEND, ICON_TOOL, ICON_WARNING, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createClientToolsCoordinator, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveJsonSchema, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, getInterrupt, getMessageType, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, surfaceToSpec, toClientToolSpecs, tools, validateArgs, view };
12540
+ export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, AGENT_ERROR_MESSAGES, AgentError, CHAT_CONFIG, CHAT_LIFECYCLE, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createAgentRef, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveJsonSchema, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, getInterrupt, getMessageType, injectThreadRouting, isAbortError, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, toAgentError, toClientToolSpecs, tools, validateArgs, view };
11960
12541
  //# sourceMappingURL=threadplane-chat.mjs.map