@threadplane/chat 0.0.49 → 0.0.51

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,18 +1,179 @@
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, ElementRef, DestroyRef, makeEnvironmentProviders, untracked, Injector, 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
- import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, RenderElementComponent } from '@threadplane/render';
5
+ import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
6
6
  export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
7
- import * as i1 from '@angular/forms';
8
- import { FormsModule } from '@angular/forms';
9
7
  import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
10
- 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';
11
+ import { toJSONSchema } from 'zod/v4';
11
12
  import { resolveDynamic, getByPointer, isPathRef, setByPointer, createA2uiMessageParser } from '@threadplane/a2ui';
12
13
  export { isLiteralBoolean, isLiteralNumber, isLiteralString, isPathRef } from '@threadplane/a2ui';
13
14
  import { materialize as materialize$1, createPartialJsonParser } from '@cacheplane/partial-json';
14
15
  import { fromEvent, EMPTY } from 'rxjs';
15
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
+
16
177
  function isUserMessage(m) {
17
178
  return m.role === 'user';
18
179
  }
@@ -26,6 +187,24 @@ function isSystemMessage(m) {
26
187
  return m.role === 'system';
27
188
  }
28
189
 
190
+ // SPDX-License-Identifier: MIT
191
+ /**
192
+ * Create a typed agent handle.
193
+ *
194
+ * @param debugName Optional name shown in Angular DI error messages.
195
+ * @returns An {@link AgentRef} carrying a state-typed `InjectionToken`.
196
+ * @example
197
+ * ```ts
198
+ * interface TripState { day: number; places: string[]; }
199
+ * export const TRIP = createAgentRef<TripState>('trip');
200
+ * // app.config.ts: provideAgent(TRIP, { assistantId: 'trip' })
201
+ * // component: const agent = injectAgent(TRIP); // LangGraphAgent<TripState>
202
+ * ```
203
+ */
204
+ function createAgentRef(debugName) {
205
+ return { token: new InjectionToken(debugName ?? 'ThreadplaneAgent') };
206
+ }
207
+
29
208
  // SPDX-License-Identifier: MIT
30
209
  class MessageTemplateDirective {
31
210
  chatMessageTemplate = input.required(...(ngDevMode ? [{ debugName: "chatMessageTemplate" }] : []));
@@ -1598,9 +1777,9 @@ const MARKDOWN_VIEW_REGISTRY = new InjectionToken('MARKDOWN_VIEW_REGISTRY');
1598
1777
  * registry. Each child's `type` is looked up in the registry; the resolved
1599
1778
  * component is rendered with `[node]` bound to that child.
1600
1779
  *
1601
- * Identity-preserving: `track $any(child)` keys on the JS reference of the
1602
- * child node. Because @cacheplane/partial-markdown preserves node identity
1603
- * across pushes, unchanged subtrees never re-render.
1780
+ * Position-stable: `track $index` avoids NG0956 re-creation warnings that
1781
+ * occur when the markdown pipeline re-parses content on every stream delta,
1782
+ * producing new child object references even for unchanged nodes.
1604
1783
  */
1605
1784
  class MarkdownChildrenComponent {
1606
1785
  parent = input.required(...(ngDevMode ? [{ debugName: "parent" }] : []));
@@ -1620,7 +1799,7 @@ class MarkdownChildrenComponent {
1620
1799
  }
1621
1800
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownChildrenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1622
1801
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownChildrenComponent, isStandalone: true, selector: "chat-md-children", inputs: { parent: { classPropertyName: "parent", publicName: "parent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1623
- @for (child of children(); track $any(child)) {
1802
+ @for (child of children(); track $index) {
1624
1803
  @let comp = resolve(child);
1625
1804
  @if (comp) {
1626
1805
  <ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
@@ -1636,7 +1815,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1636
1815
  imports: [NgComponentOutlet],
1637
1816
  changeDetection: ChangeDetectionStrategy.OnPush,
1638
1817
  template: `
1639
- @for (child of children(); track $any(child)) {
1818
+ @for (child of children(); track $index) {
1640
1819
  @let comp = resolve(child);
1641
1820
  @if (comp) {
1642
1821
  <ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
@@ -2193,7 +2372,7 @@ class MarkdownTableComponent {
2193
2372
  }
2194
2373
  </thead>
2195
2374
  <tbody>
2196
- @for (row of bodyRows(); track $any(row)) {
2375
+ @for (row of bodyRows(); track $index) {
2197
2376
  <chat-md-table-row [node]="row" />
2198
2377
  }
2199
2378
  </tbody>
@@ -2215,7 +2394,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
2215
2394
  }
2216
2395
  </thead>
2217
2396
  <tbody>
2218
- @for (row of bodyRows(); track $any(row)) {
2397
+ @for (row of bodyRows(); track $index) {
2219
2398
  <chat-md-table-row [node]="row" />
2220
2399
  }
2221
2400
  </tbody>
@@ -2793,9 +2972,19 @@ class ChatInputComponent {
2793
2972
  if (submitted !== null) {
2794
2973
  this.submitted.emit(submitted);
2795
2974
  this.messageText.set('');
2975
+ const el = this.textareaEl()?.nativeElement;
2976
+ if (el)
2977
+ el.value = '';
2796
2978
  requestAnimationFrame(() => this.textareaEl()?.nativeElement.focus());
2797
2979
  }
2798
2980
  }
2981
+ /** Sync the textarea's value into the signal on user input. A direct
2982
+ * [value]/(input) pair is used instead of ngModel: NgModel does not
2983
+ * reliably write a programmatic clear back to the view under zoneless
2984
+ * + OnPush, leaving sent text visible in the composer (audit F1). */
2985
+ onInput(event) {
2986
+ this.messageText.set(event.target.value);
2987
+ }
2799
2988
  /** Abort the current streaming response (if the adapter supports it). */
2800
2989
  onStop() {
2801
2990
  const a = this.agent();
@@ -2826,15 +3015,15 @@ class ChatInputComponent {
2826
3015
  <textarea
2827
3016
  #textareaEl
2828
3017
  class="chat-input__textarea"
2829
- [ngModel]="messageText()"
2830
- (ngModelChange)="messageText.set($event)"
2831
- name="messageText"
3018
+ [value]="messageText()"
3019
+ (input)="onInput($event)"
2832
3020
  [placeholder]="placeholder()"
2833
3021
  (keydown.enter)="onKeydown($any($event))"
2834
3022
  (compositionstart)="composing.set(true)"
2835
3023
  (compositionend)="composing.set(false)"
2836
3024
  (focus)="focused.set(true)"
2837
3025
  (blur)="focused.set(false)"
3026
+ name="messageText"
2838
3027
  rows="1"
2839
3028
  aria-label="Type a message"
2840
3029
  ></textarea>
@@ -2871,11 +3060,11 @@ class ChatInputComponent {
2871
3060
  </div>
2872
3061
  <ng-content select="[chatInputFooter]" />
2873
3062
  </div>
2874
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--ngaf-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--ngaf-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--ngaf-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--ngaf-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--ngaf-chat-text);color:var(--ngaf-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--ngaf-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--ngaf-chat-text-muted);color:var(--ngaf-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3063
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--ngaf-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--ngaf-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--ngaf-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--ngaf-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--ngaf-chat-text);color:var(--ngaf-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--ngaf-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--ngaf-chat-text-muted);color:var(--ngaf-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2875
3064
  }
2876
3065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatInputComponent, decorators: [{
2877
3066
  type: Component,
2878
- args: [{ selector: 'chat-input', standalone: true, imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
3067
+ args: [{ selector: 'chat-input', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2879
3068
  <div class="chat-input__container">
2880
3069
  <ng-content select="[chatInputBanner]" />
2881
3070
  <ng-content select="[chatInputAttachments]" />
@@ -2884,15 +3073,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
2884
3073
  <textarea
2885
3074
  #textareaEl
2886
3075
  class="chat-input__textarea"
2887
- [ngModel]="messageText()"
2888
- (ngModelChange)="messageText.set($event)"
2889
- name="messageText"
3076
+ [value]="messageText()"
3077
+ (input)="onInput($event)"
2890
3078
  [placeholder]="placeholder()"
2891
3079
  (keydown.enter)="onKeydown($any($event))"
2892
3080
  (compositionstart)="composing.set(true)"
2893
3081
  (compositionend)="composing.set(false)"
2894
3082
  (focus)="focused.set(true)"
2895
3083
  (blur)="focused.set(false)"
3084
+ name="messageText"
2896
3085
  rows="1"
2897
3086
  aria-label="Type a message"
2898
3087
  ></textarea>
@@ -3843,6 +4032,26 @@ const CHAT_ERROR_STYLES = `
3843
4032
  }
3844
4033
  .chat-error__icon { flex-shrink: 0; width: 16px; height: 16px; margin-top: 2px; }
3845
4034
  .chat-error__msg { flex: 1; min-width: 0; word-break: break-word; }
4035
+ .chat-error__retry {
4036
+ flex-shrink: 0;
4037
+ background: transparent;
4038
+ color: var(--ngaf-chat-error-text);
4039
+ border: 1px solid var(--ngaf-chat-error-border);
4040
+ border-radius: var(--ngaf-chat-radius-card);
4041
+ padding: 2px 10px;
4042
+ font-size: var(--ngaf-chat-font-size-sm);
4043
+ cursor: pointer;
4044
+ transition: background 150ms ease, color 150ms ease;
4045
+ white-space: nowrap;
4046
+ }
4047
+ .chat-error__retry:hover {
4048
+ background: var(--ngaf-chat-error-border);
4049
+ color: var(--ngaf-chat-error-text);
4050
+ }
4051
+ .chat-error__retry:focus-visible {
4052
+ outline: 2px solid var(--ngaf-chat-error-border);
4053
+ outline-offset: 2px;
4054
+ }
3846
4055
  `;
3847
4056
 
3848
4057
  // libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
@@ -3858,31 +4067,36 @@ function extractErrorMessage(error) {
3858
4067
  }
3859
4068
  class ChatErrorComponent {
3860
4069
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
3861
- errorMessage = computed(() => extractErrorMessage(this.agent().error()), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
3862
4070
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3863
4071
  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: `
3864
- @if (errorMessage(); as msg) {
4072
+ @if (agent().error(); as err) {
3865
4073
  <div class="chat-error" role="alert">
3866
4074
  <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">
3867
4075
  <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"/>
3868
4076
  </svg>
3869
- <span class="chat-error__msg">{{ msg }}</span>
4077
+ <span class="chat-error__msg">{{ err.message }}</span>
4078
+ @if (err.retryable) {
4079
+ <button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
4080
+ }
3870
4081
  </div>
3871
4082
  }
3872
- `, 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 });
4083
+ `, 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 });
3873
4084
  }
3874
4085
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, decorators: [{
3875
4086
  type: Component,
3876
4087
  args: [{ selector: 'chat-error', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
3877
- @if (errorMessage(); as msg) {
4088
+ @if (agent().error(); as err) {
3878
4089
  <div class="chat-error" role="alert">
3879
4090
  <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">
3880
4091
  <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"/>
3881
4092
  </svg>
3882
- <span class="chat-error__msg">{{ msg }}</span>
4093
+ <span class="chat-error__msg">{{ err.message }}</span>
4094
+ @if (err.retryable) {
4095
+ <button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
4096
+ }
3883
4097
  </div>
3884
4098
  }
3885
- `, 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"] }]
4099
+ `, 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"] }]
3886
4100
  }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }] } });
3887
4101
 
3888
4102
  // SPDX-License-Identifier: MIT
@@ -4342,6 +4556,54 @@ const CHAT_GENERATIVE_UI_STYLES = `
4342
4556
  }
4343
4557
  `;
4344
4558
 
4559
+ function isStatePathRef(value) {
4560
+ return (typeof value === 'object' &&
4561
+ value !== null &&
4562
+ !Array.isArray(value) &&
4563
+ typeof value['statePath'] === 'string' &&
4564
+ Object.keys(value).length === 1);
4565
+ }
4566
+ /** Rewrites schema-documented `{ statePath }` prop refs to `$bindState` +
4567
+ * `_bindings`. Returns the input reference unchanged when no rewriting is
4568
+ * needed (keeps downstream memoization intact). */
4569
+ function normalizeJsonRenderSpec(spec) {
4570
+ const sourceElements = (spec.elements ?? {});
4571
+ let specChanged = false;
4572
+ const elements = {};
4573
+ for (const [id, el] of Object.entries(sourceElements)) {
4574
+ const rawProps = el?.props;
4575
+ if (!rawProps) {
4576
+ elements[id] = el;
4577
+ continue;
4578
+ }
4579
+ let elementChanged = false;
4580
+ const props = {};
4581
+ const bindings = {};
4582
+ for (const [key, value] of Object.entries(rawProps)) {
4583
+ if (isStatePathRef(value)) {
4584
+ props[key] = { $bindState: value.statePath };
4585
+ bindings[key] = value.statePath;
4586
+ elementChanged = true;
4587
+ }
4588
+ else {
4589
+ props[key] = value;
4590
+ }
4591
+ }
4592
+ if (elementChanged) {
4593
+ // Merge with any model-emitted `_bindings` (never observed, but cheap
4594
+ // to preserve) — rewritten paths win.
4595
+ const existing = (rawProps['_bindings'] ?? {});
4596
+ props['_bindings'] = { ...existing, ...bindings };
4597
+ elements[id] = { ...el, props };
4598
+ specChanged = true;
4599
+ }
4600
+ else {
4601
+ elements[id] = el;
4602
+ }
4603
+ }
4604
+ return specChanged ? { ...spec, elements } : spec;
4605
+ }
4606
+
4345
4607
  // libs/chat/src/lib/primitives/chat-generative-ui/chat-generative-ui.component.ts
4346
4608
  // SPDX-License-Identifier: MIT
4347
4609
  class ChatGenerativeUiComponent {
@@ -4351,11 +4613,58 @@ class ChatGenerativeUiComponent {
4351
4613
  handlers = input(undefined, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
4352
4614
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
4353
4615
  events = output();
4616
+ /** The bound spec with schema-documented `{ statePath }` prop refs
4617
+ * rewritten to engine-native `{ $bindState }` + `_bindings` so values
4618
+ * resolve against the state store instead of interpolating as
4619
+ * "[object Object]" (F4). */
4620
+ normalizedSpec = computed(() => {
4621
+ const s = this.spec();
4622
+ return s ? normalizeJsonRenderSpec(s) : null;
4623
+ }, ...(ngDevMode ? [{ debugName: "normalizedSpec" }] : []));
4624
+ /** Last value this component seeded per state path. Lets the seeding
4625
+ * effect distinguish "still the value we wrote (possibly a partial
4626
+ * chunk from streaming — safe to overwrite with the newer one)" from
4627
+ * "user edited it via a bound control — leave it alone". */
4628
+ seeded = new Map();
4629
+ constructor() {
4630
+ // Seed `spec.state` (the schema's "initial state model") into an
4631
+ // EXPLICIT consumer-provided store, which is typically EMPTY at first —
4632
+ // without this, statePath/$bindState props would resolve to undefined.
4633
+ // A consumer-provided store intentionally has shared/live semantics
4634
+ // across surfaces: every surface bound to it reads (and writes) the same
4635
+ // state, so the first surface to seed a path wins. When NO store input
4636
+ // is given, this effect is a no-op and render-spec self-seeds its own
4637
+ // per-instance internal store from spec.state, keeping surfaces with
4638
+ // overlapping state keys isolated from each other (a2ui parity).
4639
+ effect(() => {
4640
+ const s = this.spec();
4641
+ const store = this.store();
4642
+ const state = s?.state;
4643
+ if (!state || !store)
4644
+ return;
4645
+ // Untracked: store reads/writes must not become dependencies — the
4646
+ // effect re-runs on spec/store-identity changes only, not on every
4647
+ // write to the (possibly shared) store.
4648
+ untracked(() => {
4649
+ for (const [key, value] of Object.entries(state)) {
4650
+ const path = key.startsWith('/') ? key : `/${key}`;
4651
+ const current = store.get(path);
4652
+ const untouched = current === undefined ||
4653
+ (this.seeded.has(path) && current === this.seeded.get(path));
4654
+ if (untouched) {
4655
+ if (current !== value)
4656
+ store.set(path, value);
4657
+ this.seeded.set(path, value);
4658
+ }
4659
+ }
4660
+ });
4661
+ });
4662
+ }
4354
4663
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4355
4664
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatGenerativeUiComponent, isStandalone: true, selector: "chat-generative-ui", inputs: { spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { events: "events" }, ngImport: i0, template: `
4356
- @if (spec()) {
4665
+ @if (normalizedSpec()) {
4357
4666
  <render-spec
4358
- [spec]="spec()"
4667
+ [spec]="normalizedSpec()"
4359
4668
  [registry]="registry()"
4360
4669
  [store]="store()"
4361
4670
  [handlers]="handlers()"
@@ -4368,9 +4677,9 @@ class ChatGenerativeUiComponent {
4368
4677
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, decorators: [{
4369
4678
  type: Component,
4370
4679
  args: [{ selector: 'chat-generative-ui', standalone: true, imports: [RenderSpecComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4371
- @if (spec()) {
4680
+ @if (normalizedSpec()) {
4372
4681
  <render-spec
4373
- [spec]="spec()"
4682
+ [spec]="normalizedSpec()"
4374
4683
  [registry]="registry()"
4375
4684
  [store]="store()"
4376
4685
  [handlers]="handlers()"
@@ -4379,7 +4688,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4379
4688
  />
4380
4689
  }
4381
4690
  `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block;color:var(--ngaf-chat-text);font-size:var(--ngaf-chat-font-size);line-height:var(--ngaf-chat-line-height)}.chat-generative-ui__error{color:var(--ngaf-chat-error-text);background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm)}\n"] }]
4382
- }], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], events: [{ type: i0.Output, args: ["events"] }] } });
4691
+ }], ctorParameters: () => [], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], events: [{ type: i0.Output, args: ["events"] }] } });
4383
4692
 
4384
4693
  // libs/chat/src/lib/primitives/chat-tool-views/chat-tool-views.component.ts
4385
4694
  // SPDX-License-Identifier: MIT
@@ -4398,6 +4707,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4398
4707
  */
4399
4708
  class ChatToolViewsComponent {
4400
4709
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
4710
+ events = output();
4401
4711
  message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
4402
4712
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
4403
4713
  store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
@@ -4416,7 +4726,7 @@ class ChatToolViewsComponent {
4416
4726
  .map((tc) => ({ id: tc.id, loading: tc.status === 'running', spec: toToolViewSpec(tc) }));
4417
4727
  }, ...(ngDevMode ? [{ debugName: "toolViews" }] : []));
4418
4728
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolViewsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4419
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatToolViewsComponent, isStandalone: true, selector: "chat-tool-views", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4729
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatToolViewsComponent, isStandalone: true, selector: "chat-tool-views", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { events: "events" }, ngImport: i0, template: `
4420
4730
  @for (view of toolViews(); track view.id) {
4421
4731
  <chat-generative-ui
4422
4732
  [spec]="view.spec"
@@ -4424,6 +4734,7 @@ class ChatToolViewsComponent {
4424
4734
  [store]="store()"
4425
4735
  [handlers]="handlers()"
4426
4736
  [loading]="view.loading"
4737
+ (events)="events.emit($event)"
4427
4738
  />
4428
4739
  }
4429
4740
  `, isInline: true, dependencies: [{ kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -4443,11 +4754,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4443
4754
  [store]="store()"
4444
4755
  [handlers]="handlers()"
4445
4756
  [loading]="view.loading"
4757
+ (events)="events.emit($event)"
4446
4758
  />
4447
4759
  }
4448
4760
  `,
4449
4761
  }]
4450
- }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }] } });
4762
+ }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], events: [{ type: i0.Output, args: ["events"] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }] } });
4451
4763
  /** Wraps a tool call into a synthetic single-element render spec. */
4452
4764
  function toToolViewSpec(tc) {
4453
4765
  const args = isRecord$2(tc.args) ? tc.args : {};
@@ -4493,46 +4805,70 @@ function statusToTraceState(s) {
4493
4805
  class ChatSubagentCardComponent {
4494
4806
  subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
4495
4807
  state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
4496
- latestMessageContent = computed(() => {
4497
- const messages = this.subagent().messages();
4498
- if (messages.length === 0)
4499
- return '';
4500
- const last = messages[messages.length - 1];
4501
- const c = last.content;
4502
- return typeof c === 'string' ? c : JSON.stringify(c);
4503
- }, ...(ngDevMode ? [{ debugName: "latestMessageContent" }] : []));
4808
+ textOf(m) {
4809
+ const c = m.content;
4810
+ return typeof c === 'string' ? c : '';
4811
+ }
4812
+ toolCallsFor(m) {
4813
+ const ids = m.toolCallIds ?? [];
4814
+ if (ids.length === 0)
4815
+ return [];
4816
+ const all = this.subagent().toolCalls?.() ?? [];
4817
+ return ids.map((id) => all.find((tc) => tc.id === id)).filter((tc) => !!tc);
4818
+ }
4819
+ toToolCallInfo(tc) {
4820
+ return { id: tc.id, name: tc.name, args: tc.args, result: tc.result, status: tc.status };
4821
+ }
4504
4822
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4505
4823
  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: `
4506
4824
  <chat-trace [state]="state()">
4507
4825
  <span traceLabel>
4508
- <span class="sac__name">Subagent</span>
4826
+ <span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
4509
4827
  <span class="sac__id">{{ subagent().toolCallId }}</span>
4510
4828
  <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4511
4829
  </span>
4512
4830
  <div class="sac__count">{{ subagent().messages().length }} message(s)</div>
4513
- @if (subagent().messages().length > 0) {
4514
- <p class="sac__latest-label">Latest message</p>
4515
- <pre class="sac__latest">{{ latestMessageContent() }}</pre>
4831
+ @for (m of subagent().messages(); track m.id) {
4832
+ <div class="sac__msg" [attr.data-role]="m.role">
4833
+ @if (m.reasoning) {
4834
+ <div class="sac__reasoning">{{ m.reasoning }}</div>
4835
+ }
4836
+ @if (textOf(m); as t) {
4837
+ <chat-streaming-md [content]="t" />
4838
+ }
4839
+ @for (tc of toolCallsFor(m); track tc.id) {
4840
+ <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
4841
+ }
4842
+ </div>
4516
4843
  }
4517
4844
  </chat-trace>
4518
- `, 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 });
4845
+ `, 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 });
4519
4846
  }
4520
4847
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
4521
4848
  type: Component,
4522
- args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4849
+ args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent, ChatToolCallCardComponent, ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4523
4850
  <chat-trace [state]="state()">
4524
4851
  <span traceLabel>
4525
- <span class="sac__name">Subagent</span>
4852
+ <span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
4526
4853
  <span class="sac__id">{{ subagent().toolCallId }}</span>
4527
4854
  <span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
4528
4855
  </span>
4529
4856
  <div class="sac__count">{{ subagent().messages().length }} message(s)</div>
4530
- @if (subagent().messages().length > 0) {
4531
- <p class="sac__latest-label">Latest message</p>
4532
- <pre class="sac__latest">{{ latestMessageContent() }}</pre>
4857
+ @for (m of subagent().messages(); track m.id) {
4858
+ <div class="sac__msg" [attr.data-role]="m.role">
4859
+ @if (m.reasoning) {
4860
+ <div class="sac__reasoning">{{ m.reasoning }}</div>
4861
+ }
4862
+ @if (textOf(m); as t) {
4863
+ <chat-streaming-md [content]="t" />
4864
+ }
4865
+ @for (tc of toolCallsFor(m); track tc.id) {
4866
+ <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
4867
+ }
4868
+ </div>
4533
4869
  }
4534
4870
  </chat-trace>
4535
- `, 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"] }]
4871
+ `, 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"] }]
4536
4872
  }], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
4537
4873
 
4538
4874
  // libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
@@ -6086,10 +6422,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
6086
6422
  class ChatWelcomeSuggestionComponent {
6087
6423
  label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
6088
6424
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
6425
+ /** Optional short description, surfaced as a hover/focus tooltip on the chip. */
6426
+ description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
6089
6427
  selected = output();
6090
6428
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6091
- 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: `
6092
- <button type="button" class="chat-welcome-suggestion" (click)="selected.emit(value())">
6429
+ 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: `
6430
+ <button
6431
+ type="button"
6432
+ class="chat-welcome-suggestion"
6433
+ [attr.title]="description() || null"
6434
+ [attr.aria-description]="description() || null"
6435
+ (click)="selected.emit(value())"
6436
+ >
6093
6437
  <ng-content select="[chatWelcomeSuggestionIcon]" />
6094
6438
  <span class="chat-welcome-suggestion__label">{{ label() }}</span>
6095
6439
  <span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
@@ -6099,13 +6443,19 @@ class ChatWelcomeSuggestionComponent {
6099
6443
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, decorators: [{
6100
6444
  type: Component,
6101
6445
  args: [{ selector: 'chat-welcome-suggestion', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
6102
- <button type="button" class="chat-welcome-suggestion" (click)="selected.emit(value())">
6446
+ <button
6447
+ type="button"
6448
+ class="chat-welcome-suggestion"
6449
+ [attr.title]="description() || null"
6450
+ [attr.aria-description]="description() || null"
6451
+ (click)="selected.emit(value())"
6452
+ >
6103
6453
  <ng-content select="[chatWelcomeSuggestionIcon]" />
6104
6454
  <span class="chat-welcome-suggestion__label">{{ label() }}</span>
6105
6455
  <span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
6106
6456
  </button>
6107
6457
  `, 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"] }]
6108
- }], 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"] }] } });
6458
+ }], 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"] }] } });
6109
6459
 
6110
6460
  // libs/chat/src/lib/styles/chat-select.styles.ts
6111
6461
  // SPDX-License-Identifier: MIT
@@ -6161,7 +6511,10 @@ const CHAT_SELECT_STYLES = `
6161
6511
  z-index: 10;
6162
6512
  }
6163
6513
  .chat-select__option {
6164
- display: block;
6514
+ display: flex;
6515
+ flex-direction: column;
6516
+ align-items: flex-start;
6517
+ gap: 2px;
6165
6518
  width: 100%;
6166
6519
  text-align: left;
6167
6520
  border: 0;
@@ -6173,6 +6526,12 @@ const CHAT_SELECT_STYLES = `
6173
6526
  font-size: var(--ngaf-chat-font-size-sm);
6174
6527
  cursor: pointer;
6175
6528
  }
6529
+ .chat-select__option-desc {
6530
+ font-size: var(--ngaf-chat-font-size-xs);
6531
+ color: var(--ngaf-chat-text-muted);
6532
+ line-height: 1.3;
6533
+ white-space: normal;
6534
+ }
6176
6535
  .chat-select__option:hover:not(:disabled),
6177
6536
  .chat-select__option:focus-visible {
6178
6537
  background: var(--ngaf-chat-surface-alt);
@@ -6351,12 +6710,15 @@ class ChatSelectComponent {
6351
6710
  [attr.aria-selected]="opt.value === value()"
6352
6711
  (click)="selectOption(opt)"
6353
6712
  >
6354
- {{ opt.label }}
6713
+ <span class="chat-select__option-label">{{ opt.label }}</span>
6714
+ @if (opt.description) {
6715
+ <span class="chat-select__option-desc">{{ opt.description }}</span>
6716
+ }
6355
6717
  </button>
6356
6718
  }
6357
6719
  </div>
6358
6720
  }
6359
- `, 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 });
6721
+ `, 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 });
6360
6722
  }
6361
6723
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSelectComponent, decorators: [{
6362
6724
  type: Component,
@@ -6394,17 +6756,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
6394
6756
  [attr.aria-selected]="opt.value === value()"
6395
6757
  (click)="selectOption(opt)"
6396
6758
  >
6397
- {{ opt.label }}
6759
+ <span class="chat-select__option-label">{{ opt.label }}</span>
6760
+ @if (opt.description) {
6761
+ <span class="chat-select__option-desc">{{ opt.description }}</span>
6762
+ }
6398
6763
  </button>
6399
6764
  }
6400
6765
  </div>
6401
6766
  }
6402
- `, 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"] }]
6767
+ `, 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"] }]
6403
6768
  }], 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 }] }] } });
6404
6769
 
6405
6770
  // SPDX-License-Identifier: MIT
6406
6771
  const PACKAGE_NAME = '@threadplane/chat';
6407
6772
  const CHAT_CONFIG = new InjectionToken('CHAT_CONFIG');
6773
+ /**
6774
+ * Bootstrap `@threadplane/chat` in an Angular application or standalone
6775
+ * component tree.
6776
+ *
6777
+ * Call this once inside `bootstrapApplication` (or the `providers` array of a
6778
+ * root `ApplicationConfig`). It registers the shared {@link ChatConfig} token
6779
+ * so every chat component in the tree can read the render registry, avatar
6780
+ * label, and assistant display name without explicit prop threading.
6781
+ *
6782
+ * A license check is fired asynchronously on every call (it never throws; a
6783
+ * watermark is shown in non-commercial builds when no valid token is supplied).
6784
+ *
6785
+ * @param config Options bag that controls the chat feature set:
6786
+ * - `renderRegistry` — shared {@link AngularRegistry} wiring tool-view
6787
+ * components to their names; pass the value returned by
6788
+ * `defineAngularRegistry` from `\@threadplane/render`.
6789
+ * - `avatarLabel` — short label shown in the AI avatar bubble (default `"A"`).
6790
+ * - `assistantName` — display name shown above assistant messages
6791
+ * (default `"Assistant"`).
6792
+ * - `license` — signed token from threadplane.ai; omit in development.
6793
+ * @returns An `EnvironmentProviders` value suitable for the `providers` array
6794
+ * of `bootstrapApplication` or `ApplicationConfig`.
6795
+ * @example
6796
+ * ```ts
6797
+ * // main.ts
6798
+ * import { bootstrapApplication } from '@angular/platform-browser';
6799
+ * import { provideChat } from '@threadplane/chat';
6800
+ * import { defineAngularRegistry, provideRender } from '@threadplane/render';
6801
+ * import { DayCardComponent } from './day-card.component';
6802
+ *
6803
+ * const registry = defineAngularRegistry({ day_card: DayCardComponent });
6804
+ *
6805
+ * bootstrapApplication(AppComponent, {
6806
+ * providers: [
6807
+ * provideChat({ renderRegistry: registry, avatarLabel: 'AI' }),
6808
+ * provideRender({ registry }),
6809
+ * ],
6810
+ * });
6811
+ * ```
6812
+ */
6408
6813
  function provideChat(config) {
6409
6814
  void runLicenseCheck({
6410
6815
  package: PACKAGE_NAME,
@@ -6417,9 +6822,205 @@ function provideChat(config) {
6417
6822
  ]);
6418
6823
  }
6419
6824
 
6825
+ // SPDX-License-Identifier: MIT
6826
+ const defaultFromUrl = (url) => {
6827
+ const seg = url.split('?')[0].split('#')[0].split('/').filter(Boolean);
6828
+ return seg.length ? seg[seg.length - 1] : null;
6829
+ };
6830
+ const defaultToCommands = (id) => (id ? ['/', id] : ['/']);
6831
+ /**
6832
+ * Bind an app-owned `activeThreadId` signal to the URL — restore on load, stamp on change,
6833
+ * validate-or-redirect, with a bare URL meaning "no thread" (welcome). URL is the source of
6834
+ * truth; nothing is written to localStorage. Must be called in an injection context.
6835
+ *
6836
+ * @example
6837
+ * ```ts
6838
+ * export const ACTIVE_THREAD = signal<string | null>(null);
6839
+ * // providers: provideAgent({ threadId: ACTIVE_THREAD, onThreadId: id => ACTIVE_THREAD.set(id) })
6840
+ * const threads = inject(LangGraphThreadsAdapter);
6841
+ * injectThreadRouting({ threadId: ACTIVE_THREAD, validate: id => threads.getThread(id).then(Boolean) });
6842
+ * ```
6843
+ */
6844
+ function injectThreadRouting(config) {
6845
+ const router = inject(Router);
6846
+ const fromUrl = config.threadIdFromUrl ?? defaultFromUrl;
6847
+ const toCommands = config.toCommands ?? defaultToCommands;
6848
+ const extras = config.navigationExtras ?? { queryParamsHandling: 'preserve' };
6849
+ const urlThreadId = toSignal(router.events.pipe(filter((e) => e instanceof NavigationEnd), map((e) => fromUrl(e.urlAfterRedirects)), startWith(fromUrl(router.url))), { initialValue: fromUrl(router.url) });
6850
+ // Seed the signal from the URL once.
6851
+ config.threadId.set(urlThreadId());
6852
+ // URL → signal.
6853
+ effect(() => {
6854
+ const urlId = urlThreadId();
6855
+ if (urlId !== untracked(() => config.threadId()))
6856
+ config.threadId.set(urlId);
6857
+ });
6858
+ // signal → URL.
6859
+ effect(() => {
6860
+ const id = config.threadId();
6861
+ const urlId = untracked(() => urlThreadId());
6862
+ if (id !== urlId)
6863
+ void router.navigate(toCommands(id), extras);
6864
+ });
6865
+ // validate stale ids → redirect to bare. Memoize the last id checked so
6866
+ // re-visiting the same thread (A → B → A) doesn't re-hit the backend.
6867
+ if (config.validate) {
6868
+ const validate = config.validate;
6869
+ let lastValidated = null;
6870
+ effect(() => {
6871
+ const id = urlThreadId();
6872
+ if (!id || id === lastValidated)
6873
+ return;
6874
+ lastValidated = id;
6875
+ void validate(id).then((ok) => {
6876
+ if (!ok && untracked(() => urlThreadId()) === id) {
6877
+ void router.navigate(toCommands(null), { ...extras, replaceUrl: true });
6878
+ }
6879
+ });
6880
+ });
6881
+ }
6882
+ }
6883
+
6420
6884
  // SPDX-License-Identifier: MIT
6421
6885
  const CHAT_LIFECYCLE = new InjectionToken('CHAT_LIFECYCLE');
6422
6886
 
6887
+ // SPDX-License-Identifier: MIT
6888
+ /**
6889
+ * Convert a Standard Schema to a JSON Schema for the model's `parameters`.
6890
+ * Uses Zod's converter; throws a clear error for non-Zod validators (callers
6891
+ * should supply a Zod schema — see the client-tools docs).
6892
+ */
6893
+ function deriveJsonSchema(toolName, schema) {
6894
+ try {
6895
+ return toJSONSchema(schema);
6896
+ }
6897
+ catch (err) {
6898
+ throw new Error(`client tool "${toolName}": could not derive a JSON Schema from its schema. ` +
6899
+ `Use a Zod schema (recommended) or an already-JSON-Schema-compatible validator. ` +
6900
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
6901
+ }
6902
+ }
6903
+
6904
+ /** Validate raw model args against a Standard Schema. */
6905
+ async function validateArgs(schema, args) {
6906
+ const res = await schema['~standard'].validate(args);
6907
+ if (res.issues) {
6908
+ return { ok: false, error: res.issues.map((i) => i.message).join('; ') };
6909
+ }
6910
+ // Cast rather than rely on discriminant narrowing: the cockpit example apps
6911
+ // compile this source with `strictNullChecks: false`, where the
6912
+ // `issues?: undefined` discriminant doesn't narrow `res` to the success type.
6913
+ return { ok: true, value: res.value };
6914
+ }
6915
+ /** Validate args, run the handler, and normalize the outcome to a ClientToolResult. */
6916
+ async function executeFunctionTool(def, rawArgs) {
6917
+ const v = await validateArgs(def.schema, rawArgs);
6918
+ if (!v.ok)
6919
+ return { ok: false, error: `invalid arguments: ${v.error}` };
6920
+ try {
6921
+ const value = await def.handler(v.value);
6922
+ return { ok: true, value };
6923
+ }
6924
+ catch (err) {
6925
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
6926
+ }
6927
+ }
6928
+
6929
+ // SPDX-License-Identifier: MIT
6930
+ /**
6931
+ * Watches the agent's pending client tool calls and auto-runs FUNCTION tools,
6932
+ * resolving each with its result. View/ask (component) tools are handled by the
6933
+ * rendering layer, not here. No-op if the agent lacks the clientTools
6934
+ * capability. MUST be called in an injection context (sets up an effect).
6935
+ */
6936
+ function startClientToolExecutor(agent, registry) {
6937
+ const cap = agent.clientTools;
6938
+ if (!cap)
6939
+ return;
6940
+ const inFlight = new Set();
6941
+ effect(() => {
6942
+ for (const tc of cap.pending()) {
6943
+ const def = registry[tc.name];
6944
+ if (!def || def.kind !== 'function')
6945
+ continue; // non-function handled elsewhere
6946
+ // NB: do NOT skip on `tc.status === 'complete'`. A client tool call is
6947
+ // marked 'complete' once its args finish streaming, yet it still has no
6948
+ // result and needs the browser to execute it. `pending` already excludes
6949
+ // calls that have a result or were resolved; `inFlight` prevents a
6950
+ // double-dispatch within a render cycle.
6951
+ if (inFlight.has(tc.id))
6952
+ continue;
6953
+ inFlight.add(tc.id);
6954
+ void executeFunctionTool(def, tc.args).then((result) => {
6955
+ cap.resolve(tc.id, result);
6956
+ inFlight.delete(tc.id);
6957
+ });
6958
+ }
6959
+ });
6960
+ }
6961
+
6962
+ // SPDX-License-Identifier: MIT
6963
+ /** Build the catalog spec list shipped to the model. */
6964
+ function toClientToolSpecs(registry) {
6965
+ return Object.entries(registry).map(([name, def]) => ({
6966
+ name,
6967
+ description: def.description,
6968
+ parameters: deriveJsonSchema(name, def.schema),
6969
+ }));
6970
+ }
6971
+ /** Map each view/ask tool to a RenderViewEntry that carries its schema, so the
6972
+ * render lib can gate the real component's mount on schema-readiness (showing
6973
+ * the fallback skeleton while a streaming tool call's args are still
6974
+ * incomplete) instead of mounting a required-input component too early. */
6975
+ function viewComponents(registry) {
6976
+ const out = {};
6977
+ for (const [name, def] of Object.entries(registry)) {
6978
+ if (def.kind === 'view' || def.kind === 'ask') {
6979
+ out[name] = { component: def.component, schema: def.schema };
6980
+ }
6981
+ }
6982
+ return out;
6983
+ }
6984
+ function createClientToolsCoordinator(registry) {
6985
+ const viewRegistry = views(viewComponents(registry));
6986
+ const ackedViews = new Set();
6987
+ return {
6988
+ viewRegistry,
6989
+ connect(agent) {
6990
+ const cap = agent.clientTools;
6991
+ if (!cap)
6992
+ return;
6993
+ cap.setCatalog(toClientToolSpecs(registry));
6994
+ startClientToolExecutor(agent, registry); // function tools
6995
+ // Auto-ack `view` tools: they render but produce no user value.
6996
+ effect(() => {
6997
+ for (const tc of cap.pending()) {
6998
+ const def = registry[tc.name];
6999
+ if (!def || def.kind !== 'view')
7000
+ continue;
7001
+ if (ackedViews.has(tc.id))
7002
+ continue;
7003
+ ackedViews.add(tc.id);
7004
+ cap.resolve(tc.id, { ok: true, value: { shown: true } });
7005
+ }
7006
+ });
7007
+ },
7008
+ handleRenderEvent(agent, event) {
7009
+ if (event.type !== 'result')
7010
+ return;
7011
+ const cap = agent.clientTools;
7012
+ if (!cap)
7013
+ return;
7014
+ // elementKey is the tool NAME in the tool-view spec; resolve the pending `ask`
7015
+ // call for that name with the component's emitted value.
7016
+ const name = event.elementKey;
7017
+ const pending = cap.pending().find((tc) => tc.name === name && registry[tc.name]?.kind === 'ask');
7018
+ if (pending)
7019
+ cap.resolve(pending.id, { ok: true, value: event.value });
7020
+ },
7021
+ };
7022
+ }
7023
+
6423
7024
  const RESERVED_PROP_KEYS = new Set(['child', 'children', 'action', 'tabItems', 'entryPointChild', 'contentChild']);
6424
7025
  /** Pull the (single) component-type key + its props from a v1 ComponentDef wrapper. */
6425
7026
  function unwrapComponentDef(def) {
@@ -6485,9 +7086,8 @@ function surfaceToSpec(surface) {
6485
7086
  // snapshot taken at conversion time and never reflects user
6486
7087
  // input writes back into the store. The `_bindings` map below
6487
7088
  // tells the catalog component which prop names map to which
6488
- // paths so its emit() callback can write back via the
6489
- // a2ui:datamodel:<path>:<value> magic-string protocol that
6490
- // render-element's emitFn intercepts.
7089
+ // paths so its injectRenderHost().set(path, value) call can
7090
+ // write the typed value back to the render state store.
6491
7091
  const path = value.path;
6492
7092
  bindings[key] = path;
6493
7093
  resolvedProps[key] = { $bindState: path };
@@ -7926,6 +8526,14 @@ function isPinned(scrollHeight, scrollTop, clientHeight, tolerance = 150) {
7926
8526
  class ChatComponent {
7927
8527
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
7928
8528
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
8529
+ /**
8530
+ * Client-declared tools (`view`/`ask`/`function`) the model may call. When
8531
+ * provided, a coordinator ships their catalog to the agent, runs `function`
8532
+ * tools in the browser, and renders/resolves `view`/`ask` tools through the
8533
+ * same tool-views pipeline as `views`. Additive — leave undefined for the
8534
+ * classic server-tools-only experience.
8535
+ */
8536
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
7929
8537
  store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
7930
8538
  handlers = input({}, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
7931
8539
  threads = input([], ...(ngDevMode ? [{ debugName: "threads" }] : []));
@@ -7982,18 +8590,45 @@ class ChatComponent {
7982
8590
  const explicit = this.store();
7983
8591
  if (explicit)
7984
8592
  return explicit;
7985
- if (this.views())
8593
+ // A render store is needed whenever there's any view registry to render
8594
+ // through — user-supplied `views()` OR client-tool view/ask components.
8595
+ if (this.effectiveViews())
7986
8596
  return this._internalStore;
7987
8597
  return undefined;
7988
8598
  }, ...(ngDevMode ? [{ debugName: "resolvedStore" }] : []));
8599
+ /**
8600
+ * Lazily-built client-tools coordinator, memoized on the `clientTools`
8601
+ * registry input. Undefined when no client tools are declared. The
8602
+ * coordinator owns the catalog/executor wiring and the view/ask render
8603
+ * registry; the composition merges and connects it below.
8604
+ */
8605
+ coordinator = computed(() => {
8606
+ const reg = this.clientTools();
8607
+ return reg ? createClientToolsCoordinator(reg) : undefined;
8608
+ }, ...(ngDevMode ? [{ debugName: "coordinator" }] : []));
8609
+ /**
8610
+ * The view registry actually used for rendering tool-views and for
8611
+ * excluding view-backed tool names from default tool-call cards. Merges
8612
+ * the coordinator's `view`/`ask` components (keyed by tool name) into the
8613
+ * user-supplied `views()` so client-declared component tools render through
8614
+ * the same pipeline. Falls back to `views()` when no client tools exist.
8615
+ */
8616
+ effectiveViews = computed(() => {
8617
+ const base = this.views();
8618
+ const coord = this.coordinator();
8619
+ if (!coord)
8620
+ return base;
8621
+ return base ? withViews(base, coord.viewRegistry) : coord.viewRegistry;
8622
+ }, ...(ngDevMode ? [{ debugName: "effectiveViews" }] : []));
7989
8623
  renderRegistry = computed(() => {
7990
8624
  const v = this.views();
7991
8625
  return v ? toRenderRegistry(v) : undefined;
7992
8626
  }, ...(ngDevMode ? [{ debugName: "renderRegistry" }] : []));
7993
- /** Tool names that have a registered view (keys of the `views` registry).
7994
- * These render as inline tool-views and are excluded from the default
7995
- * tool-call card so they don't render twice. */
7996
- viewToolNames = computed(() => Object.keys(this.views() ?? {}), ...(ngDevMode ? [{ debugName: "viewToolNames" }] : []));
8627
+ /** Tool names that have a registered view (keys of the effective view
8628
+ * registry, including client-declared view/ask tools). These render as
8629
+ * inline tool-views and are excluded from the default tool-call card so
8630
+ * they don't render twice. */
8631
+ viewToolNames = computed(() => Object.keys(this.effectiveViews() ?? {}), ...(ngDevMode ? [{ debugName: "viewToolNames" }] : []));
7997
8632
  /** Union of GenUI dispatcher tool names and registered view tool names. */
7998
8633
  excludedToolNames = computed(() => [
7999
8634
  ...this.genuiToolNames(),
@@ -8040,6 +8675,7 @@ class ChatComponent {
8040
8675
  }
8041
8676
  classifiers = new Map();
8042
8677
  destroyRef = inject(DestroyRef);
8678
+ injector = inject(Injector);
8043
8679
  // Resolved against the component's own `providers` in normal use. The fallback
8044
8680
  // is for tests that construct ChatComponent via `new` inside a bare injection
8045
8681
  // context (no element injector, so component-level providers are skipped).
@@ -8261,6 +8897,35 @@ class ChatComponent {
8261
8897
  }
8262
8898
  this.partialEventsLastIndex = events.length;
8263
8899
  });
8900
+ // Connect the client-tools coordinator to the agent. connect() ships the
8901
+ // tool catalog, starts the function-tool executor, and installs the view
8902
+ // auto-ack effect. Those installs create effect()s of their own, which
8903
+ // Angular forbids from *within* a running effect (NG0602). So this effect
8904
+ // only detects readiness (both coordinator + agent present) and defers the
8905
+ // actual connect() to a microtask that runs OUTSIDE the reactive context,
8906
+ // re-entering the component's injection context so the coordinator's
8907
+ // effects bind to this component's lifecycle. `connected` guards against
8908
+ // re-running per coordinator identity (a new clientTools registry yields a
8909
+ // new coordinator and re-connects; the old one's effects are abandoned).
8910
+ let connected;
8911
+ effect(() => {
8912
+ const coord = this.coordinator();
8913
+ let agentRef;
8914
+ try {
8915
+ agentRef = this.agent();
8916
+ }
8917
+ catch {
8918
+ return;
8919
+ }
8920
+ if (!coord || !agentRef)
8921
+ return;
8922
+ if (connected === coord)
8923
+ return;
8924
+ connected = coord;
8925
+ queueMicrotask(() => {
8926
+ runInInjectionContext(this.injector, () => coord.connect(agentRef));
8927
+ });
8928
+ });
8264
8929
  effect(() => {
8265
8930
  // janitor: drop classifiers for messages no longer in the agent's list
8266
8931
  let liveIds;
@@ -8478,6 +9143,25 @@ class ChatComponent {
8478
9143
  onSpecEvent(event, messageIndex) {
8479
9144
  this.renderEvent.emit({ messageIndex, event });
8480
9145
  }
9146
+ /**
9147
+ * Forwards a render event bubbled up from a `<chat-tool-views>` component
9148
+ * (a client-declared `view`/`ask` tool's rendered UI) to the client-tools
9149
+ * coordinator. The coordinator resolves the matching pending `ask` tool call
9150
+ * when the event is a `result`. No-op when no client tools are wired.
9151
+ */
9152
+ onClientToolEvent(event) {
9153
+ const coord = this.coordinator();
9154
+ if (!coord)
9155
+ return;
9156
+ let agentRef;
9157
+ try {
9158
+ agentRef = this.agent();
9159
+ }
9160
+ catch {
9161
+ return;
9162
+ }
9163
+ coord.handleRenderEvent(agentRef, event);
9164
+ }
8481
9165
  onA2uiAction(message) {
8482
9166
  void this.agent().submit({ message: JSON.stringify(message) });
8483
9167
  }
@@ -8498,7 +9182,7 @@ class ChatComponent {
8498
9182
  this.messageCopy.emit({ messageIndex: idx, content });
8499
9183
  }
8500
9184
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8501
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatComponent, isStandalone: true, selector: "chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, activeThreadId: { classPropertyName: "activeThreadId", publicName: "activeThreadId", isSignal: true, isRequired: false, transformFunction: null }, welcomeDisabled: { classPropertyName: "welcomeDisabled", publicName: "welcomeDisabled", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, modelPickerPlaceholder: { classPropertyName: "modelPickerPlaceholder", publicName: "modelPickerPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, genuiToolNames: { classPropertyName: "genuiToolNames", publicName: "genuiToolNames", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", threadSelected: "threadSelected", renderEvent: "renderEvent", regenerate: "regenerate", rate: "rate", messageCopy: "messageCopy" }, providers: [
9185
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatComponent, isStandalone: true, selector: "chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, clientTools: { classPropertyName: "clientTools", publicName: "clientTools", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, activeThreadId: { classPropertyName: "activeThreadId", publicName: "activeThreadId", isSignal: true, isRequired: false, transformFunction: null }, welcomeDisabled: { classPropertyName: "welcomeDisabled", publicName: "welcomeDisabled", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, modelPickerPlaceholder: { classPropertyName: "modelPickerPlaceholder", publicName: "modelPickerPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, genuiToolNames: { classPropertyName: "genuiToolNames", publicName: "genuiToolNames", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", threadSelected: "threadSelected", renderEvent: "renderEvent", regenerate: "regenerate", rate: "rate", messageCopy: "messageCopy" }, providers: [
8502
9186
  { provide: CHAT_LIFECYCLE, useFactory: createChatLifecycle },
8503
9187
  ], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
8504
9188
  @if (showWelcome()) {
@@ -8563,19 +9247,26 @@ class ChatComponent {
8563
9247
  <chat-tool-views
8564
9248
  [agent]="agent()"
8565
9249
  [message]="message"
8566
- [views]="views()"
9250
+ [views]="effectiveViews()"
8567
9251
  [store]="resolvedStore()"
8568
9252
  [handlers]="handlers()"
9253
+ (events)="onClientToolEvent($event)"
8569
9254
  />
8570
9255
  <chat-subagents [agent]="agent()" />
8571
9256
  @if (classified.markdown(); as md) {
8572
9257
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8573
9258
  }
8574
9259
  @if (classified.spec(); as spec) {
9260
+ <!-- Pass ONLY the explicit consumer store (may be
9261
+ undefined) — never the conversation-wide internal
9262
+ fallback. Without a consumer store, render-spec
9263
+ self-seeds a per-instance store from spec.state so
9264
+ same-key dashboards across messages stay isolated
9265
+ (a2ui parity). -->
8575
9266
  <chat-generative-ui
8576
9267
  [spec]="spec"
8577
9268
  [registry]="renderRegistry()"
8578
- [store]="resolvedStore()"
9269
+ [store]="store()"
8579
9270
  [handlers]="handlers()"
8580
9271
  [loading]="agent().isLoading()"
8581
9272
  (events)="onSpecEvent($event, i)"
@@ -8649,7 +9340,7 @@ class ChatComponent {
8649
9340
  </div>
8650
9341
  </div>
8651
9342
  }
8652
- `, 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"] }, { 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 });
9343
+ `, 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 });
8653
9344
  }
8654
9345
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
8655
9346
  type: Component,
@@ -8726,19 +9417,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8726
9417
  <chat-tool-views
8727
9418
  [agent]="agent()"
8728
9419
  [message]="message"
8729
- [views]="views()"
9420
+ [views]="effectiveViews()"
8730
9421
  [store]="resolvedStore()"
8731
9422
  [handlers]="handlers()"
9423
+ (events)="onClientToolEvent($event)"
8732
9424
  />
8733
9425
  <chat-subagents [agent]="agent()" />
8734
9426
  @if (classified.markdown(); as md) {
8735
9427
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8736
9428
  }
8737
9429
  @if (classified.spec(); as spec) {
9430
+ <!-- Pass ONLY the explicit consumer store (may be
9431
+ undefined) — never the conversation-wide internal
9432
+ fallback. Without a consumer store, render-spec
9433
+ self-seeds a per-instance store from spec.state so
9434
+ same-key dashboards across messages stay isolated
9435
+ (a2ui parity). -->
8738
9436
  <chat-generative-ui
8739
9437
  [spec]="spec"
8740
9438
  [registry]="renderRegistry()"
8741
- [store]="resolvedStore()"
9439
+ [store]="store()"
8742
9440
  [handlers]="handlers()"
8743
9441
  [loading]="agent().isLoading()"
8744
9442
  (events)="onSpecEvent($event, i)"
@@ -8813,7 +9511,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8813
9511
  </div>
8814
9512
  }
8815
9513
  `, 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"] }]
8816
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], welcomeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeDisabled", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], modelPickerPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPickerPlaceholder", required: false }] }], genuiToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "genuiToolNames", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], renderEvent: [{ type: i0.Output, args: ["renderEvent"] }], regenerate: [{ type: i0.Output, args: ["regenerate"] }], rate: [{ type: i0.Output, args: ["rate"] }], messageCopy: [{ type: i0.Output, args: ["messageCopy"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
9514
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], clientTools: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientTools", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], welcomeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeDisabled", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], modelPickerPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPickerPlaceholder", required: false }] }], genuiToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "genuiToolNames", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], renderEvent: [{ type: i0.Output, args: ["renderEvent"] }], regenerate: [{ type: i0.Output, args: ["regenerate"] }], rate: [{ type: i0.Output, args: ["rate"] }], messageCopy: [{ type: i0.Output, args: ["messageCopy"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
8817
9515
 
8818
9516
  // libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts
8819
9517
  // SPDX-License-Identifier: MIT
@@ -8823,6 +9521,8 @@ class ChatPopupComponent {
8823
9521
  * messages classified as A2UI parse correctly but never mount a
8824
9522
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8825
9523
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9524
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9525
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8826
9526
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8827
9527
  * renders in the chat-input chrome. */
8828
9528
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8877,7 +9577,7 @@ class ChatPopupComponent {
8877
9577
  openWindow() { this.open.set(true); }
8878
9578
  closeWindow() { this.open.set(false); }
8879
9579
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8880
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatPopupComponent, isStandalone: true, selector: "chat-popup", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, ngImport: i0, template: `
9580
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatPopupComponent, isStandalone: true, selector: "chat-popup", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, clientTools: { classPropertyName: "clientTools", publicName: "clientTools", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, ngImport: i0, template: `
8881
9581
  <div class="chat-popup__launcher">
8882
9582
  <chat-launcher-button (clicked)="toggle()" />
8883
9583
  </div>
@@ -8888,6 +9588,7 @@ class ChatPopupComponent {
8888
9588
  <chat
8889
9589
  [agent]="agent()"
8890
9590
  [views]="views()"
9591
+ [clientTools]="clientTools()"
8891
9592
  [modelOptions]="modelOptions()"
8892
9593
  [showModelPicker]="showModelPicker()"
8893
9594
  [selectedModel]="selectedModel()"
@@ -8897,7 +9598,7 @@ class ChatPopupComponent {
8897
9598
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
8898
9599
  </chat>
8899
9600
  </div>
8900
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--ngaf-chat-bg);border:1px solid var(--ngaf-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9601
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--ngaf-chat-bg);border:1px solid var(--ngaf-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8901
9602
  }
8902
9603
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, decorators: [{
8903
9604
  type: Component,
@@ -8912,6 +9613,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8912
9613
  <chat
8913
9614
  [agent]="agent()"
8914
9615
  [views]="views()"
9616
+ [clientTools]="clientTools()"
8915
9617
  [modelOptions]="modelOptions()"
8916
9618
  [showModelPicker]="showModelPicker()"
8917
9619
  [selectedModel]="selectedModel()"
@@ -8922,7 +9624,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8922
9624
  </chat>
8923
9625
  </div>
8924
9626
  `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--ngaf-chat-bg);border:1px solid var(--ngaf-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}\n"] }]
8925
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }] } });
9627
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], clientTools: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientTools", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }] } });
8926
9628
 
8927
9629
  // libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts
8928
9630
  // SPDX-License-Identifier: MIT
@@ -8932,6 +9634,8 @@ class ChatSidebarComponent {
8932
9634
  * messages classified as A2UI parse correctly but never mount a
8933
9635
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8934
9636
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9637
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9638
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8935
9639
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8936
9640
  * renders in the chat-input chrome. */
8937
9641
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8985,7 +9689,7 @@ class ChatSidebarComponent {
8985
9689
  openWindow() { this.open.set(true); }
8986
9690
  closeWindow() { this.open.set(false); }
8987
9691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8988
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatSidebarComponent, isStandalone: true, selector: "chat-sidebar", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, pushContent: { classPropertyName: "pushContent", publicName: "pushContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, host: { properties: { "attr.data-push": "pushContent() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"true\" : \"false\"" } }, ngImport: i0, template: `
9692
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatSidebarComponent, isStandalone: true, selector: "chat-sidebar", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, clientTools: { classPropertyName: "clientTools", publicName: "clientTools", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, pushContent: { classPropertyName: "pushContent", publicName: "pushContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, host: { properties: { "attr.data-push": "pushContent() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"true\" : \"false\"" } }, ngImport: i0, template: `
8989
9693
  <div class="chat-sidebar__content"><ng-content /></div>
8990
9694
  <div class="chat-sidebar__launcher">
8991
9695
  <chat-launcher-button (clicked)="toggle()" />
@@ -9002,6 +9706,7 @@ class ChatSidebarComponent {
9002
9706
  <chat
9003
9707
  [agent]="agent()"
9004
9708
  [views]="views()"
9709
+ [clientTools]="clientTools()"
9005
9710
  [modelOptions]="modelOptions()"
9006
9711
  [showModelPicker]="showModelPicker()"
9007
9712
  [selectedModel]="selectedModel()"
@@ -9011,7 +9716,7 @@ class ChatSidebarComponent {
9011
9716
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
9012
9717
  </chat>
9013
9718
  </aside>
9014
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-sidebar__content{transition:margin-right .3s ease;min-height:100vh}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:28rem}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--ngaf-chat-debug-claim-bottom, 0);width:28rem;background:var(--ngaf-chat-bg);border-left:1px solid var(--ngaf-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--ngaf-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--ngaf-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--ngaf-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9719
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-sidebar__content{transition:margin-right .3s ease;min-height:100vh}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:28rem}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--ngaf-chat-debug-claim-bottom, 0);width:28rem;background:var(--ngaf-chat-bg);border-left:1px solid var(--ngaf-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--ngaf-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--ngaf-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--ngaf-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9015
9720
  }
9016
9721
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, decorators: [{
9017
9722
  type: Component,
@@ -9035,6 +9740,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
9035
9740
  <chat
9036
9741
  [agent]="agent()"
9037
9742
  [views]="views()"
9743
+ [clientTools]="clientTools()"
9038
9744
  [modelOptions]="modelOptions()"
9039
9745
  [showModelPicker]="showModelPicker()"
9040
9746
  [selectedModel]="selectedModel()"
@@ -9045,7 +9751,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
9045
9751
  </chat>
9046
9752
  </aside>
9047
9753
  `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-sidebar__content{transition:margin-right .3s ease;min-height:100vh}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:28rem}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--ngaf-chat-debug-claim-bottom, 0);width:28rem;background:var(--ngaf-chat-bg);border-left:1px solid var(--ngaf-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--ngaf-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--ngaf-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--ngaf-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"] }]
9048
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], pushContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pushContent", required: false }] }] } });
9754
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], clientTools: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientTools", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], pushContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pushContent", required: false }] }] } });
9049
9755
 
9050
9756
  // libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
9051
9757
  // SPDX-License-Identifier: MIT
@@ -10615,24 +11321,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10615
11321
  `, styles: [".a2ui-card{display:flex;flex-direction:column;gap:var(--a2ui-spacing-2);border-radius:var(--a2ui-shape-medium);border:1px solid var(--a2ui-outline);background:var(--a2ui-surface);padding:var(--a2ui-spacing-4);box-shadow:var(--a2ui-elevation-1)}\n"] }]
10616
11322
  }], propDecorators: { childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: true }] }], 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 }] }] } });
10617
11323
 
10618
- // SPDX-License-Identifier: MIT
10619
- /** Emits a data model binding event if the prop has a binding path. */
10620
- function emitBinding(emit, bindings, prop, value) {
11324
+ /** Writes a typed value to the render state store if the prop has a binding path. */
11325
+ function emitBinding(host, bindings, prop, value) {
10621
11326
  const path = bindings?.[prop];
10622
11327
  if (path) {
10623
- emit(`a2ui:datamodel:${path}:${value}`);
11328
+ host.set(path, value);
10624
11329
  }
10625
11330
  }
10626
11331
 
10627
11332
  // SPDX-License-Identifier: MIT
10628
11333
  class A2uiCheckBoxComponent {
11334
+ host = injectRenderHost();
10629
11335
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10630
11336
  /** v1 canonical prop: boolean checked state. */
10631
11337
  value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
10632
11338
  /** Pre-v1 alias retained for back-compat. */
10633
11339
  checked = input(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
10634
11340
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10635
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10636
11341
  // Framework inputs required by the render harness.
10637
11342
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10638
11343
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10645,14 +11350,14 @@ class A2uiCheckBoxComponent {
10645
11350
  // `value`; pre-v1 used `checked`.
10646
11351
  const bound = this._bindings();
10647
11352
  if (bound['value']) {
10648
- emitBinding(this.emit(), bound, 'value', val);
11353
+ emitBinding(this.host, bound, 'value', val);
10649
11354
  }
10650
11355
  else {
10651
- emitBinding(this.emit(), bound, 'checked', val);
11356
+ emitBinding(this.host, bound, 'checked', val);
10652
11357
  }
10653
11358
  }
10654
11359
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiCheckBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10655
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: A2uiCheckBoxComponent, isStandalone: true, selector: "a2ui-check-box", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11360
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: A2uiCheckBoxComponent, isStandalone: true, selector: "a2ui-check-box", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
10656
11361
  <label class="a2ui-cb">
10657
11362
  <input type="checkbox" [checked]="effectiveValue()" (change)="onChange($event)" class="a2ui-cb__input" />
10658
11363
  {{ label() }}
@@ -10667,7 +11372,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10667
11372
  {{ label() }}
10668
11373
  </label>
10669
11374
  `, styles: [".a2ui-cb{display:flex;align-items:center;gap:var(--a2ui-spacing-2);font-size:var(--a2ui-typography-body-size);cursor:pointer}.a2ui-cb__input{width:16px;height:16px;border-radius:var(--a2ui-shape-extra-small);cursor:pointer;accent-color:var(--a2ui-primary)}\n"] }]
10670
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11375
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
10671
11376
 
10672
11377
  // SPDX-License-Identifier: MIT
10673
11378
  const ALIGN_MAP = {
@@ -10710,6 +11415,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10710
11415
  class A2uiDateTimeInputComponent {
10711
11416
  static _idCounter = 0;
10712
11417
  _inputId = `a2ui-date-time-input-${++A2uiDateTimeInputComponent._idCounter}`;
11418
+ host = injectRenderHost();
10713
11419
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10714
11420
  /** v1 prop: value (resolved DynamicString). */
10715
11421
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -10718,7 +11424,6 @@ class A2uiDateTimeInputComponent {
10718
11424
  /** v1 prop: enableTime — include time portion. */
10719
11425
  enableTime = input(false, ...(ngDevMode ? [{ debugName: "enableTime" }] : []));
10720
11426
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10721
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10722
11427
  // Framework inputs required by the render harness.
10723
11428
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10724
11429
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10736,10 +11441,10 @@ class A2uiDateTimeInputComponent {
10736
11441
  }, ...(ngDevMode ? [{ debugName: "htmlInputType" }] : []));
10737
11442
  onChange(event) {
10738
11443
  const val = event.target.value;
10739
- emitBinding(this.emit(), this._bindings(), 'value', val);
11444
+ emitBinding(this.host, this._bindings(), 'value', val);
10740
11445
  }
10741
11446
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiDateTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10742
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiDateTimeInputComponent, isStandalone: true, selector: "a2ui-date-time-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, enableDate: { classPropertyName: "enableDate", publicName: "enableDate", isSignal: true, isRequired: false, transformFunction: null }, enableTime: { classPropertyName: "enableTime", publicName: "enableTime", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11447
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiDateTimeInputComponent, isStandalone: true, selector: "a2ui-date-time-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, enableDate: { classPropertyName: "enableDate", publicName: "enableDate", isSignal: true, isRequired: false, transformFunction: null }, enableTime: { classPropertyName: "enableTime", publicName: "enableTime", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
10743
11448
  <div class="a2ui-dti">
10744
11449
  @if (label()) {
10745
11450
  <label [htmlFor]="_inputId" class="a2ui-dti__label">{{ label() }}</label>
@@ -10770,7 +11475,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10770
11475
  />
10771
11476
  </div>
10772
11477
  `, styles: [".a2ui-dti{display:flex;flex-direction:column;gap:var(--a2ui-spacing-1)}.a2ui-dti__label{font-size:var(--a2ui-typography-label-size);font-weight:var(--a2ui-typography-label-weight);color:var(--a2ui-label)}.a2ui-dti__input{padding:var(--a2ui-spacing-2) var(--a2ui-spacing-3);font-size:var(--a2ui-typography-body-size);border-radius:var(--a2ui-shape-small);background:var(--a2ui-input-bg);color:var(--a2ui-on-surface);border:1px solid var(--a2ui-outline);outline:none;transition:border-color var(--a2ui-motion-duration-short) var(--a2ui-motion-easing-standard)}.a2ui-dti__input:focus{outline:var(--a2ui-focus-ring-width) solid var(--a2ui-focus-ring-color);outline-offset:2px;border-color:var(--a2ui-primary)}\n"] }]
10773
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], enableDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableDate", required: false }] }], enableTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTime", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11478
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], enableDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableDate", required: false }] }], enableTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTime", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
10774
11479
 
10775
11480
  // SPDX-License-Identifier: MIT
10776
11481
  class A2uiDividerComponent {
@@ -11061,6 +11766,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11061
11766
 
11062
11767
  // SPDX-License-Identifier: MIT
11063
11768
  class A2uiMultipleChoiceComponent {
11769
+ host = injectRenderHost();
11064
11770
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11065
11771
  /** Resolved current selections from surface-to-spec. Normalized in
11066
11772
  * `selectionsArray` because LLMs sometimes seed the data model with a
@@ -11080,7 +11786,6 @@ class A2uiMultipleChoiceComponent {
11080
11786
  /** When ≤ 1 — render as single-select <select>; otherwise multi-select checkboxes. */
11081
11787
  maxAllowedSelections = input(1, ...(ngDevMode ? [{ debugName: "maxAllowedSelections" }] : []));
11082
11788
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11083
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11084
11789
  // Framework inputs required by the render harness.
11085
11790
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11086
11791
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11092,7 +11797,7 @@ class A2uiMultipleChoiceComponent {
11092
11797
  }
11093
11798
  onSelectChange(event) {
11094
11799
  const val = event.target.value;
11095
- emitBinding(this.emit(), this._bindings(), 'selections', val);
11800
+ emitBinding(this.host, this._bindings(), 'selections', val);
11096
11801
  }
11097
11802
  onCheckChange(value, event) {
11098
11803
  const checked = event.target.checked;
@@ -11104,11 +11809,11 @@ class A2uiMultipleChoiceComponent {
11104
11809
  else if (!checked && idx !== -1) {
11105
11810
  current.splice(idx, 1);
11106
11811
  }
11107
- // Emit the updated selections array as a JSON string so emitBinding can convey it.
11108
- emitBinding(this.emit(), this._bindings(), 'selections', JSON.stringify(current));
11812
+ // Pass the updated selections array directly (typed value, no JSON stringification needed).
11813
+ emitBinding(this.host, this._bindings(), 'selections', current);
11109
11814
  }
11110
11815
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiMultipleChoiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11111
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiMultipleChoiceComponent, isStandalone: true, selector: "a2ui-multiple-choice", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxAllowedSelections: { classPropertyName: "maxAllowedSelections", publicName: "maxAllowedSelections", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11816
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiMultipleChoiceComponent, isStandalone: true, selector: "a2ui-multiple-choice", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxAllowedSelections: { classPropertyName: "maxAllowedSelections", publicName: "maxAllowedSelections", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11112
11817
  <div class="a2ui-mc">
11113
11818
  @if (label()) {
11114
11819
  <span class="a2ui-mc__label">{{ label() }}</span>
@@ -11173,7 +11878,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11173
11878
  }
11174
11879
  </div>
11175
11880
  `, styles: [".a2ui-mc{display:flex;flex-direction:column;gap:var(--a2ui-spacing-1)}.a2ui-mc__label{font-size:var(--a2ui-typography-label-size);font-weight:var(--a2ui-typography-label-weight);color:var(--a2ui-label)}.a2ui-mc__select{padding:var(--a2ui-spacing-2) var(--a2ui-spacing-3);font-size:var(--a2ui-typography-body-size);border-radius:var(--a2ui-shape-small);background:var(--a2ui-input-bg);color:var(--a2ui-on-surface);border:1px solid var(--a2ui-outline);outline:none;transition:border-color var(--a2ui-motion-duration-short) var(--a2ui-motion-easing-standard)}.a2ui-mc__select:focus{outline:var(--a2ui-focus-ring-width) solid var(--a2ui-focus-ring-color);outline-offset:2px;border-color:var(--a2ui-primary)}.a2ui-mc__checks{display:flex;flex-direction:column;gap:var(--a2ui-spacing-2)}.a2ui-mc__check-row{display:flex;align-items:center;gap:var(--a2ui-spacing-2);font-size:var(--a2ui-typography-body-size);cursor:pointer}.a2ui-mc__checkbox{width:16px;height:16px;border-radius:var(--a2ui-shape-extra-small);cursor:pointer;accent-color:var(--a2ui-primary)}\n"] }]
11176
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], selections: [{ type: i0.Input, args: [{ isSignal: true, alias: "selections", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], maxAllowedSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxAllowedSelections", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11881
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], selections: [{ type: i0.Input, args: [{ isSignal: true, alias: "selections", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], maxAllowedSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxAllowedSelections", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11177
11882
 
11178
11883
  // SPDX-License-Identifier: MIT
11179
11884
  const ROW_ALIGN_MAP = {
@@ -11231,6 +11936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11231
11936
  class A2uiSliderComponent {
11232
11937
  static _idCounter = 0;
11233
11938
  _inputId = `a2ui-slider-${++A2uiSliderComponent._idCounter}`;
11939
+ host = injectRenderHost();
11234
11940
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11235
11941
  /** v1 prop: value (resolved DynamicNumber). */
11236
11942
  value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -11240,7 +11946,6 @@ class A2uiSliderComponent {
11240
11946
  maxValue = input(100, ...(ngDevMode ? [{ debugName: "maxValue" }] : []));
11241
11947
  step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
11242
11948
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11243
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11244
11949
  // Framework inputs required by the render harness.
11245
11950
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11246
11951
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11248,10 +11953,10 @@ class A2uiSliderComponent {
11248
11953
  spec = input(undefined, ...(ngDevMode ? [{ debugName: "spec" }] : []));
11249
11954
  onInput(event) {
11250
11955
  const val = Number(event.target.value);
11251
- emitBinding(this.emit(), this._bindings(), 'value', val);
11956
+ emitBinding(this.host, this._bindings(), 'value', val);
11252
11957
  }
11253
11958
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11254
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiSliderComponent, isStandalone: true, selector: "a2ui-slider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11959
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiSliderComponent, isStandalone: true, selector: "a2ui-slider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11255
11960
  <div class="a2ui-slider">
11256
11961
  @if (label()) {
11257
11962
  <label [htmlFor]="_inputId" class="a2ui-slider__label">{{ label() }}: {{ value() }}</label>
@@ -11288,7 +11993,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11288
11993
  />
11289
11994
  </div>
11290
11995
  `, styles: [".a2ui-slider{display:flex;flex-direction:column;gap:var(--a2ui-spacing-1)}.a2ui-slider__label{font-size:var(--a2ui-typography-label-size);font-weight:var(--a2ui-typography-label-weight);color:var(--a2ui-label)}.a2ui-slider__input{width:100%;cursor:pointer;accent-color:var(--a2ui-primary)}\n"] }]
11291
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], minValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "minValue", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11996
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], minValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "minValue", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11292
11997
 
11293
11998
  // SPDX-License-Identifier: MIT
11294
11999
  class A2uiTabsComponent {
@@ -11395,6 +12100,7 @@ const TYPE_MAP = {
11395
12100
  class A2uiTextFieldComponent {
11396
12101
  static _idCounter = 0;
11397
12102
  _inputId = `a2ui-text-field-${++A2uiTextFieldComponent._idCounter}`;
12103
+ host = injectRenderHost();
11398
12104
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11399
12105
  /** v1 prop: text (resolved string value). */
11400
12106
  text = input('', ...(ngDevMode ? [{ debugName: "text" }] : []));
@@ -11404,7 +12110,6 @@ class A2uiTextFieldComponent {
11404
12110
  textFieldType = input('shortText', ...(ngDevMode ? [{ debugName: "textFieldType" }] : []));
11405
12111
  validationRegexp = input('', ...(ngDevMode ? [{ debugName: "validationRegexp" }] : []));
11406
12112
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11407
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11408
12113
  // Framework inputs required by the render harness.
11409
12114
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11410
12115
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11416,14 +12121,14 @@ class A2uiTextFieldComponent {
11416
12121
  // Emit on 'text' binding (v1 prop name); also try 'value' for compat.
11417
12122
  const bound = this._bindings();
11418
12123
  if (bound['text']) {
11419
- emitBinding(this.emit(), bound, 'text', val);
12124
+ emitBinding(this.host, bound, 'text', val);
11420
12125
  }
11421
12126
  else {
11422
- emitBinding(this.emit(), bound, 'value', val);
12127
+ emitBinding(this.host, bound, 'value', val);
11423
12128
  }
11424
12129
  }
11425
12130
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiTextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11426
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiTextFieldComponent, isStandalone: true, selector: "a2ui-text-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, textFieldType: { classPropertyName: "textFieldType", publicName: "textFieldType", isSignal: true, isRequired: false, transformFunction: null }, validationRegexp: { classPropertyName: "validationRegexp", publicName: "validationRegexp", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
12131
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiTextFieldComponent, isStandalone: true, selector: "a2ui-text-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, textFieldType: { classPropertyName: "textFieldType", publicName: "textFieldType", isSignal: true, isRequired: false, transformFunction: null }, validationRegexp: { classPropertyName: "validationRegexp", publicName: "validationRegexp", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
11427
12132
  <div class="a2ui-tf">
11428
12133
  @if (label()) {
11429
12134
  <label [htmlFor]="_inputId" class="a2ui-tf__label">{{ label() }}</label>
@@ -11480,7 +12185,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11480
12185
  }
11481
12186
  </div>
11482
12187
  `, styles: [".a2ui-tf{display:flex;flex-direction:column;gap:var(--a2ui-spacing-1)}.a2ui-tf__label{font-size:var(--a2ui-typography-label-size);font-weight:var(--a2ui-typography-label-weight);color:var(--a2ui-label)}.a2ui-tf__input{padding:var(--a2ui-spacing-2) var(--a2ui-spacing-3);font-size:var(--a2ui-typography-body-size);border-radius:var(--a2ui-shape-small);background:var(--a2ui-input-bg);color:var(--a2ui-on-surface);border:1px solid var(--a2ui-outline);outline:none;transition:border-color var(--a2ui-motion-duration-short) var(--a2ui-motion-easing-standard);resize:vertical}.a2ui-tf__input:focus{outline:var(--a2ui-focus-ring-width) solid var(--a2ui-focus-ring-color);outline-offset:2px;border-color:var(--a2ui-primary)}\n"] }]
11483
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], textFieldType: [{ type: i0.Input, args: [{ isSignal: true, alias: "textFieldType", required: false }] }], validationRegexp: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationRegexp", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
12188
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], textFieldType: [{ type: i0.Input, args: [{ isSignal: true, alias: "textFieldType", required: false }] }], validationRegexp: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationRegexp", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", 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 }] }] } });
11484
12189
 
11485
12190
  // SPDX-License-Identifier: MIT
11486
12191
  class A2uiVideoComponent {
@@ -11540,12 +12245,125 @@ function a2uiBasicCatalog() {
11540
12245
  });
11541
12246
  }
11542
12247
 
12248
+ /**
12249
+ * Declare an async function tool the model can call; its resolved return value
12250
+ * becomes the tool result shipped back to the model.
12251
+ *
12252
+ * @param description Natural-language description the model sees.
12253
+ * @param schema Standard Schema (e.g. a Zod object) for the arguments; the
12254
+ * handler's argument type is inferred from it.
12255
+ * @param handler Runs in the browser when the model calls the tool; its return
12256
+ * type `R` is carried on the resulting {@link FunctionToolDef}.
12257
+ * @returns A {@link FunctionToolDef} for inclusion in {@link tools}.
12258
+ * @example
12259
+ * ```ts
12260
+ * const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
12261
+ * const registry = tools({ move_stop: move });
12262
+ * ```
12263
+ */
12264
+ function action(description, schema, handler) {
12265
+ return { kind: 'function', description, schema, handler };
12266
+ }
12267
+ /**
12268
+ * Render-only component tool — the model fills the component's props from the
12269
+ * schema's output; the tool call is auto-acknowledged once the component mounts.
12270
+ *
12271
+ * The component's signal inputs are checked against the schema output type
12272
+ * (strict-but-flexible: every schema key must be a declared input with an
12273
+ * assignable type; the component may declare extra inputs the schema doesn't fill).
12274
+ * Author the component with `ViewProps<typeof schema>` as the input type set to
12275
+ * guarantee the shapes stay aligned.
12276
+ *
12277
+ * @param description Natural-language description the model sees.
12278
+ * @param schema Standard Schema defining the props the model must supply.
12279
+ * @param component Angular component whose signal inputs must be compatible with
12280
+ * the schema output. A type-level error is reported here when they diverge.
12281
+ * @returns A {@link ViewToolDef} for inclusion in {@link tools}.
12282
+ * @example
12283
+ * ```ts
12284
+ * const schema = z.object({ label: z.string(), day: z.number() });
12285
+ * type Inputs = ViewProps<typeof schema>; // { label: string; day: number }
12286
+ *
12287
+ * \@Component({ ... })
12288
+ * class DayCardComponent {
12289
+ * label = input.required<string>();
12290
+ * day = input.required<number>();
12291
+ * }
12292
+ *
12293
+ * const dayCard = view('Show a day card', schema, DayCardComponent);
12294
+ * const registry = tools({ day_card: dayCard });
12295
+ * ```
12296
+ */
12297
+ function view(description, schema, component) {
12298
+ return { kind: 'view', description, schema, component: component };
12299
+ }
12300
+ /**
12301
+ * Interactive (human-in-the-loop) component tool — the model fills the
12302
+ * component's props from the schema's output; the value the component emits
12303
+ * back to the framework becomes the tool result sent to the model.
12304
+ *
12305
+ * The component's signal inputs are checked against the schema output type
12306
+ * (strict-but-flexible: every schema key must be a declared input with an
12307
+ * assignable type; the component may declare extra inputs the schema doesn't
12308
+ * fill). Author the component with `ViewProps<typeof schema>` to derive input
12309
+ * prop types directly from the schema.
12310
+ *
12311
+ * @param description Natural-language description the model sees.
12312
+ * @param schema Standard Schema defining the props the model must supply.
12313
+ * @param component Angular component whose signal inputs must be compatible with
12314
+ * the schema output. A type-level error is reported here when they diverge.
12315
+ * @returns An {@link AskToolDef} for inclusion in {@link tools}.
12316
+ * @example
12317
+ * ```ts
12318
+ * const schema = z.object({ question: z.string(), options: z.array(z.string()) });
12319
+ * type Inputs = ViewProps<typeof schema>;
12320
+ *
12321
+ * \@Component({ ... })
12322
+ * class ChoiceCardComponent {
12323
+ * question = input.required<string>();
12324
+ * options = input.required<string[]>();
12325
+ * // Emits the chosen option back to the model.
12326
+ * }
12327
+ *
12328
+ * const choice = ask('Ask the user to choose', schema, ChoiceCardComponent);
12329
+ * const registry = tools({ pick_option: choice });
12330
+ * ```
12331
+ */
12332
+ function ask(description, schema, component) {
12333
+ return { kind: 'ask', description, schema, component: component };
12334
+ }
12335
+ /**
12336
+ * Collect named client tools into a frozen, name-keyed registry.
12337
+ *
12338
+ * The overload is generic over the entire map (`const M`) so that each tool's
12339
+ * precise type ({@link FunctionToolDef}`<S,R>`, {@link ViewToolDef}`<S,C>`, or
12340
+ * {@link AskToolDef}`<S,C>`) and every literal key are preserved in the
12341
+ * {@link ClientToolRegistry} passed to `provideChat`. This lets downstream
12342
+ * consumers look up individual tools without losing generic information.
12343
+ *
12344
+ * @param map An object literal mapping tool names to tool definitions created
12345
+ * by {@link action}, {@link view}, or {@link ask}.
12346
+ * @returns A frozen `Readonly<M>` where `M` is the exact inferred map shape.
12347
+ * @example
12348
+ * ```ts
12349
+ * const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
12350
+ * const dayCard = view('Show a day card', z.object({ label: z.string() }), DayCardComponent);
12351
+ *
12352
+ * const registry = tools({ move_stop: move, day_card: dayCard });
12353
+ * // registry.move_stop is FunctionToolDef<...>
12354
+ * // registry.day_card is ViewToolDef<...>
12355
+ * ```
12356
+ */
12357
+ function tools(map) {
12358
+ return Object.freeze({ ...map });
12359
+ }
12360
+
11543
12361
  // SPDX-License-Identifier: MIT
11544
12362
  function mockAgent(opts = {}) {
11545
12363
  const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
11546
12364
  const status = signal(opts.status ?? 'idle', ...(ngDevMode ? [{ debugName: "status" }] : []));
11547
12365
  const isLoading = signal(opts.isLoading ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
11548
- const error = signal(opts.error ?? null, ...(ngDevMode ? [{ debugName: "error" }] : []));
12366
+ const error = signal(opts.error ?? undefined, ...(ngDevMode ? [{ debugName: "error" }] : []));
11549
12367
  const toolCalls = signal(opts.toolCalls ?? [], ...(ngDevMode ? [{ debugName: "toolCalls" }] : []));
11550
12368
  const state = signal(opts.state ?? {}, ...(ngDevMode ? [{ debugName: "state" }] : []));
11551
12369
  const interrupt = opts.withInterrupt
@@ -11570,6 +12388,7 @@ function mockAgent(opts = {}) {
11570
12388
  events$: opts.events$ ?? EMPTY,
11571
12389
  submit: async (input, submitOpts) => { submitCalls.push({ input, opts: submitOpts }); },
11572
12390
  stop: async () => { stopCount++; },
12391
+ retry: async () => { return; },
11573
12392
  regenerate: async (assistantMessageIndex) => {
11574
12393
  // Truncate messages [N..end] and record the call as a synthetic submit so
11575
12394
  // tests can assert regenerate behavior via the same submitCalls log.
@@ -11591,5 +12410,5 @@ function mockAgent(opts = {}) {
11591
12410
  * Generated bundle index. Do not edit.
11592
12411
  */
11593
12412
 
11594
- 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, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createContentClassifier, createParseTreeStore, createPartialArgsBridge, emitBinding, extractErrorMessage, formatDuration, getInterrupt, getMessageType, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, statusColor, submitMessage, surfaceToSpec };
12413
+ 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, 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, createAgentRef, createClientToolsCoordinator, 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, surfaceToSpec, toAgentError, toClientToolSpecs, tools, validateArgs, view };
11595
12414
  //# sourceMappingURL=threadplane-chat.mjs.map