@threadplane/chat 0.0.47 → 0.0.50

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,13 +1,12 @@
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 { input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, InjectionToken, ViewEncapsulation, viewChild, model, contentChild, untracked, ElementRef, DestroyRef, makeEnvironmentProviders, Injector, runInInjectionContext, ViewContainerRef, SecurityContext } from '@angular/core';
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';
6
- export { VIEW_REGISTRY, provideViews, toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
7
- import * as i1 from '@angular/forms';
8
- import { FormsModule } from '@angular/forms';
5
+ import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
6
+ export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
9
7
  import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
10
8
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
+ import { toJSONSchema } from 'zod/v4';
11
10
  import { resolveDynamic, getByPointer, isPathRef, setByPointer, createA2uiMessageParser } from '@threadplane/a2ui';
12
11
  export { isLiteralBoolean, isLiteralNumber, isLiteralString, isPathRef } from '@threadplane/a2ui';
13
12
  import { materialize as materialize$1, createPartialJsonParser } from '@cacheplane/partial-json';
@@ -1598,9 +1597,9 @@ const MARKDOWN_VIEW_REGISTRY = new InjectionToken('MARKDOWN_VIEW_REGISTRY');
1598
1597
  * registry. Each child's `type` is looked up in the registry; the resolved
1599
1598
  * component is rendered with `[node]` bound to that child.
1600
1599
  *
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.
1600
+ * Position-stable: `track $index` avoids NG0956 re-creation warnings that
1601
+ * occur when the markdown pipeline re-parses content on every stream delta,
1602
+ * producing new child object references even for unchanged nodes.
1604
1603
  */
1605
1604
  class MarkdownChildrenComponent {
1606
1605
  parent = input.required(...(ngDevMode ? [{ debugName: "parent" }] : []));
@@ -1620,7 +1619,7 @@ class MarkdownChildrenComponent {
1620
1619
  }
1621
1620
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownChildrenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1622
1621
  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)) {
1622
+ @for (child of children(); track $index) {
1624
1623
  @let comp = resolve(child);
1625
1624
  @if (comp) {
1626
1625
  <ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
@@ -1636,7 +1635,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1636
1635
  imports: [NgComponentOutlet],
1637
1636
  changeDetection: ChangeDetectionStrategy.OnPush,
1638
1637
  template: `
1639
- @for (child of children(); track $any(child)) {
1638
+ @for (child of children(); track $index) {
1640
1639
  @let comp = resolve(child);
1641
1640
  @if (comp) {
1642
1641
  <ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
@@ -2193,7 +2192,7 @@ class MarkdownTableComponent {
2193
2192
  }
2194
2193
  </thead>
2195
2194
  <tbody>
2196
- @for (row of bodyRows(); track $any(row)) {
2195
+ @for (row of bodyRows(); track $index) {
2197
2196
  <chat-md-table-row [node]="row" />
2198
2197
  }
2199
2198
  </tbody>
@@ -2215,7 +2214,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
2215
2214
  }
2216
2215
  </thead>
2217
2216
  <tbody>
2218
- @for (row of bodyRows(); track $any(row)) {
2217
+ @for (row of bodyRows(); track $index) {
2219
2218
  <chat-md-table-row [node]="row" />
2220
2219
  }
2221
2220
  </tbody>
@@ -2793,9 +2792,19 @@ class ChatInputComponent {
2793
2792
  if (submitted !== null) {
2794
2793
  this.submitted.emit(submitted);
2795
2794
  this.messageText.set('');
2795
+ const el = this.textareaEl()?.nativeElement;
2796
+ if (el)
2797
+ el.value = '';
2796
2798
  requestAnimationFrame(() => this.textareaEl()?.nativeElement.focus());
2797
2799
  }
2798
2800
  }
2801
+ /** Sync the textarea's value into the signal on user input. A direct
2802
+ * [value]/(input) pair is used instead of ngModel: NgModel does not
2803
+ * reliably write a programmatic clear back to the view under zoneless
2804
+ * + OnPush, leaving sent text visible in the composer (audit F1). */
2805
+ onInput(event) {
2806
+ this.messageText.set(event.target.value);
2807
+ }
2799
2808
  /** Abort the current streaming response (if the adapter supports it). */
2800
2809
  onStop() {
2801
2810
  const a = this.agent();
@@ -2826,15 +2835,15 @@ class ChatInputComponent {
2826
2835
  <textarea
2827
2836
  #textareaEl
2828
2837
  class="chat-input__textarea"
2829
- [ngModel]="messageText()"
2830
- (ngModelChange)="messageText.set($event)"
2831
- name="messageText"
2838
+ [value]="messageText()"
2839
+ (input)="onInput($event)"
2832
2840
  [placeholder]="placeholder()"
2833
2841
  (keydown.enter)="onKeydown($any($event))"
2834
2842
  (compositionstart)="composing.set(true)"
2835
2843
  (compositionend)="composing.set(false)"
2836
2844
  (focus)="focused.set(true)"
2837
2845
  (blur)="focused.set(false)"
2846
+ name="messageText"
2838
2847
  rows="1"
2839
2848
  aria-label="Type a message"
2840
2849
  ></textarea>
@@ -2871,11 +2880,11 @@ class ChatInputComponent {
2871
2880
  </div>
2872
2881
  <ng-content select="[chatInputFooter]" />
2873
2882
  </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 });
2883
+ `, 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
2884
  }
2876
2885
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatInputComponent, decorators: [{
2877
2886
  type: Component,
2878
- args: [{ selector: 'chat-input', standalone: true, imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2887
+ args: [{ selector: 'chat-input', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2879
2888
  <div class="chat-input__container">
2880
2889
  <ng-content select="[chatInputBanner]" />
2881
2890
  <ng-content select="[chatInputAttachments]" />
@@ -2884,15 +2893,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
2884
2893
  <textarea
2885
2894
  #textareaEl
2886
2895
  class="chat-input__textarea"
2887
- [ngModel]="messageText()"
2888
- (ngModelChange)="messageText.set($event)"
2889
- name="messageText"
2896
+ [value]="messageText()"
2897
+ (input)="onInput($event)"
2890
2898
  [placeholder]="placeholder()"
2891
2899
  (keydown.enter)="onKeydown($any($event))"
2892
2900
  (compositionstart)="composing.set(true)"
2893
2901
  (compositionend)="composing.set(false)"
2894
2902
  (focus)="focused.set(true)"
2895
2903
  (blur)="focused.set(false)"
2904
+ name="messageText"
2896
2905
  rows="1"
2897
2906
  aria-label="Type a message"
2898
2907
  ></textarea>
@@ -4152,6 +4161,39 @@ function pluralize(word, count) {
4152
4161
  return count === 1 ? word : `${word}s`;
4153
4162
  }
4154
4163
 
4164
+ /**
4165
+ * Resolves the tool calls that belong to a specific message, mirroring the
4166
+ * per-message scoping the chat lib uses to avoid every assistant bubble
4167
+ * re-rendering the whole thread's tool-call list.
4168
+ *
4169
+ * - No message context → the agent's global tool-call list.
4170
+ * - Assistant message with `toolCallIds` (LangGraph) → those ids, in order.
4171
+ * - Assistant message with `tool_use` content blocks (Anthropic) → those ids.
4172
+ * - Assistant message with no linkage → [] (this message emitted no calls).
4173
+ * - Any non-assistant message → the global list (callers filter as needed).
4174
+ *
4175
+ * MUST be called inside a reactive context (computed/effect) so the
4176
+ * `agent.toolCalls()` signal read registers a dependency.
4177
+ */
4178
+ function resolveMessageToolCalls(agent, message) {
4179
+ const all = agent.toolCalls();
4180
+ if (message && message.role === 'assistant') {
4181
+ if (message.toolCallIds && message.toolCallIds.length > 0) {
4182
+ return message.toolCallIds
4183
+ .map((id) => all.find((tc) => tc.id === id))
4184
+ .filter((x) => !!x);
4185
+ }
4186
+ if (Array.isArray(message.content)) {
4187
+ const blocks = message.content.filter((b) => b.type === 'tool_use');
4188
+ return blocks
4189
+ .map((b) => all.find((tc) => tc.id === b.id))
4190
+ .filter((x) => !!x);
4191
+ }
4192
+ return [];
4193
+ }
4194
+ return all;
4195
+ }
4196
+
4155
4197
  // libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
4156
4198
  // SPDX-License-Identifier: MIT
4157
4199
  class ChatToolCallsComponent {
@@ -4175,30 +4217,7 @@ class ChatToolCallsComponent {
4175
4217
  }
4176
4218
  return map;
4177
4219
  }, ...(ngDevMode ? [{ debugName: "templateRegistry" }] : []));
4178
- toolCalls = computed(() => {
4179
- const msg = this.message();
4180
- if (msg && msg.role === 'assistant') {
4181
- const all = this.agent().toolCalls();
4182
- // Prefer adapter-provided `toolCallIds` (LangGraph populates it from
4183
- // raw `tool_calls`). This is the per-message scope that prevents
4184
- // every AI bubble from re-rendering the entire thread's tool-call
4185
- // list when content is a flat string (the LangGraph adapter's
4186
- // default — extractTextContent reduces complex content to string).
4187
- if (msg.toolCallIds && msg.toolCallIds.length > 0) {
4188
- return msg.toolCallIds.map(id => all.find(tc => tc.id === id)).filter((x) => !!x);
4189
- }
4190
- // Anthropic-style content-block path: extract tool_use IDs inline.
4191
- if (Array.isArray(msg.content)) {
4192
- const blocks = msg.content.filter((b) => b.type === 'tool_use');
4193
- return blocks.map(b => all.find(tc => tc.id === b.id)).filter((x) => !!x);
4194
- }
4195
- // Assistant message provided but no tool-call linkage available:
4196
- // this message didn't emit any tool calls. Return empty.
4197
- return [];
4198
- }
4199
- // No message context at all (e.g. agent-level pinning) → global list.
4200
- return this.agent().toolCalls();
4201
- }, ...(ngDevMode ? [{ debugName: "toolCalls" }] : []));
4220
+ toolCalls = computed(() => resolveMessageToolCalls(this.agent(), this.message()), ...(ngDevMode ? [{ debugName: "toolCalls" }] : []));
4202
4221
  groups = computed(() => {
4203
4222
  const excludeSet = new Set(this.excludeToolNames());
4204
4223
  const calls = this.toolCalls().filter(tc => !excludeSet.has(tc.name));
@@ -4314,6 +4333,246 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4314
4333
  `, styles: [":host{display:block;margin-bottom:20px}.ctc__group{border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);margin:0 0 4px}.ctc__group-header{display:flex;align-items:center;gap:.5rem;width:100%;padding:8px 12px;background:transparent;border:0;font:inherit;color:var(--ngaf-chat-text);cursor:pointer;text-align:left}.ctc__group-chevron{width:10px;height:10px;transition:transform .12s ease}.ctc__group[data-expanded=true] .ctc__group-chevron{transform:rotate(90deg)}.ctc__group-body{padding:0 12px 8px;border-top:1px solid var(--ngaf-chat-separator)}\n"] }]
4315
4334
  }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], grouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouping", required: false }] }], groupSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupSummary", required: false }] }], excludeToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeToolNames", required: false }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ChatToolCallTemplateDirective), { isSignal: true }] }] } });
4316
4335
 
4336
+ // SPDX-License-Identifier: MIT
4337
+ const CHAT_GENERATIVE_UI_STYLES = `
4338
+ :host {
4339
+ display: block;
4340
+ color: var(--ngaf-chat-text);
4341
+ font-size: var(--ngaf-chat-font-size);
4342
+ line-height: var(--ngaf-chat-line-height);
4343
+ }
4344
+ .chat-generative-ui__error {
4345
+ color: var(--ngaf-chat-error-text);
4346
+ background: var(--ngaf-chat-error-bg);
4347
+ border: 1px solid var(--ngaf-chat-error-border);
4348
+ border-radius: var(--ngaf-chat-radius-card);
4349
+ padding: 8px 12px;
4350
+ font-size: var(--ngaf-chat-font-size-sm);
4351
+ }
4352
+ `;
4353
+
4354
+ function isStatePathRef(value) {
4355
+ return (typeof value === 'object' &&
4356
+ value !== null &&
4357
+ !Array.isArray(value) &&
4358
+ typeof value['statePath'] === 'string' &&
4359
+ Object.keys(value).length === 1);
4360
+ }
4361
+ /** Rewrites schema-documented `{ statePath }` prop refs to `$bindState` +
4362
+ * `_bindings`. Returns the input reference unchanged when no rewriting is
4363
+ * needed (keeps downstream memoization intact). */
4364
+ function normalizeJsonRenderSpec(spec) {
4365
+ const sourceElements = (spec.elements ?? {});
4366
+ let specChanged = false;
4367
+ const elements = {};
4368
+ for (const [id, el] of Object.entries(sourceElements)) {
4369
+ const rawProps = el?.props;
4370
+ if (!rawProps) {
4371
+ elements[id] = el;
4372
+ continue;
4373
+ }
4374
+ let elementChanged = false;
4375
+ const props = {};
4376
+ const bindings = {};
4377
+ for (const [key, value] of Object.entries(rawProps)) {
4378
+ if (isStatePathRef(value)) {
4379
+ props[key] = { $bindState: value.statePath };
4380
+ bindings[key] = value.statePath;
4381
+ elementChanged = true;
4382
+ }
4383
+ else {
4384
+ props[key] = value;
4385
+ }
4386
+ }
4387
+ if (elementChanged) {
4388
+ // Merge with any model-emitted `_bindings` (never observed, but cheap
4389
+ // to preserve) — rewritten paths win.
4390
+ const existing = (rawProps['_bindings'] ?? {});
4391
+ props['_bindings'] = { ...existing, ...bindings };
4392
+ elements[id] = { ...el, props };
4393
+ specChanged = true;
4394
+ }
4395
+ else {
4396
+ elements[id] = el;
4397
+ }
4398
+ }
4399
+ return specChanged ? { ...spec, elements } : spec;
4400
+ }
4401
+
4402
+ // libs/chat/src/lib/primitives/chat-generative-ui/chat-generative-ui.component.ts
4403
+ // SPDX-License-Identifier: MIT
4404
+ class ChatGenerativeUiComponent {
4405
+ spec = input(null, ...(ngDevMode ? [{ debugName: "spec" }] : []));
4406
+ registry = input(undefined, ...(ngDevMode ? [{ debugName: "registry" }] : []));
4407
+ store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
4408
+ handlers = input(undefined, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
4409
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
4410
+ events = output();
4411
+ /** The bound spec with schema-documented `{ statePath }` prop refs
4412
+ * rewritten to engine-native `{ $bindState }` + `_bindings` so values
4413
+ * resolve against the state store instead of interpolating as
4414
+ * "[object Object]" (F4). */
4415
+ normalizedSpec = computed(() => {
4416
+ const s = this.spec();
4417
+ return s ? normalizeJsonRenderSpec(s) : null;
4418
+ }, ...(ngDevMode ? [{ debugName: "normalizedSpec" }] : []));
4419
+ /** Last value this component seeded per state path. Lets the seeding
4420
+ * effect distinguish "still the value we wrote (possibly a partial
4421
+ * chunk from streaming — safe to overwrite with the newer one)" from
4422
+ * "user edited it via a bound control — leave it alone". */
4423
+ seeded = new Map();
4424
+ constructor() {
4425
+ // Seed `spec.state` (the schema's "initial state model") into an
4426
+ // EXPLICIT consumer-provided store, which is typically EMPTY at first —
4427
+ // without this, statePath/$bindState props would resolve to undefined.
4428
+ // A consumer-provided store intentionally has shared/live semantics
4429
+ // across surfaces: every surface bound to it reads (and writes) the same
4430
+ // state, so the first surface to seed a path wins. When NO store input
4431
+ // is given, this effect is a no-op and render-spec self-seeds its own
4432
+ // per-instance internal store from spec.state, keeping surfaces with
4433
+ // overlapping state keys isolated from each other (a2ui parity).
4434
+ effect(() => {
4435
+ const s = this.spec();
4436
+ const store = this.store();
4437
+ const state = s?.state;
4438
+ if (!state || !store)
4439
+ return;
4440
+ // Untracked: store reads/writes must not become dependencies — the
4441
+ // effect re-runs on spec/store-identity changes only, not on every
4442
+ // write to the (possibly shared) store.
4443
+ untracked(() => {
4444
+ for (const [key, value] of Object.entries(state)) {
4445
+ const path = key.startsWith('/') ? key : `/${key}`;
4446
+ const current = store.get(path);
4447
+ const untouched = current === undefined ||
4448
+ (this.seeded.has(path) && current === this.seeded.get(path));
4449
+ if (untouched) {
4450
+ if (current !== value)
4451
+ store.set(path, value);
4452
+ this.seeded.set(path, value);
4453
+ }
4454
+ }
4455
+ });
4456
+ });
4457
+ }
4458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4459
+ 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: `
4460
+ @if (normalizedSpec()) {
4461
+ <render-spec
4462
+ [spec]="normalizedSpec()"
4463
+ [registry]="registry()"
4464
+ [store]="store()"
4465
+ [handlers]="handlers()"
4466
+ [loading]="loading()"
4467
+ (events)="events.emit($event)"
4468
+ />
4469
+ }
4470
+ `, isInline: true, 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"], dependencies: [{ kind: "component", type: RenderSpecComponent, selector: "render-spec", inputs: ["spec", "registry", "store", "functions", "handlers", "loading"], outputs: ["events"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4471
+ }
4472
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, decorators: [{
4473
+ type: Component,
4474
+ args: [{ selector: 'chat-generative-ui', standalone: true, imports: [RenderSpecComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4475
+ @if (normalizedSpec()) {
4476
+ <render-spec
4477
+ [spec]="normalizedSpec()"
4478
+ [registry]="registry()"
4479
+ [store]="store()"
4480
+ [handlers]="handlers()"
4481
+ [loading]="loading()"
4482
+ (events)="events.emit($event)"
4483
+ />
4484
+ }
4485
+ `, 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"] }]
4486
+ }], 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"] }] } });
4487
+
4488
+ // libs/chat/src/lib/primitives/chat-tool-views/chat-tool-views.component.ts
4489
+ // SPDX-License-Identifier: MIT
4490
+ /**
4491
+ * Renders a frontend component for a tool call by reusing the chat
4492
+ * composition's `views` registry. A tool call whose `name` matches a
4493
+ * registry key is bridged into a synthetic one-element render spec
4494
+ * (`{ root: name, elements: { [name]: { type: name, props } } }`) and
4495
+ * rendered through the existing render-spec pipeline.
4496
+ *
4497
+ * Props merge the live `args` (present while the call streams) with the
4498
+ * `result` (on completion) and always include `status`, so a view
4499
+ * component can show its own loading/empty/error states. `RenderElement`
4500
+ * filters props down to the component's declared inputs, so extra keys
4501
+ * (and a `status` a component chooses not to declare) are harmless.
4502
+ */
4503
+ class ChatToolViewsComponent {
4504
+ agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
4505
+ events = output();
4506
+ message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
4507
+ views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
4508
+ store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
4509
+ handlers = input({}, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
4510
+ registry = computed(() => {
4511
+ const v = this.views();
4512
+ return v ? toRenderRegistry(v) : undefined;
4513
+ }, ...(ngDevMode ? [{ debugName: "registry" }] : []));
4514
+ toolViews = computed(() => {
4515
+ const v = this.views();
4516
+ if (!v)
4517
+ return [];
4518
+ const names = new Set(Object.keys(v));
4519
+ return resolveMessageToolCalls(this.agent(), this.message())
4520
+ .filter((tc) => names.has(tc.name))
4521
+ .map((tc) => ({ id: tc.id, loading: tc.status === 'running', spec: toToolViewSpec(tc) }));
4522
+ }, ...(ngDevMode ? [{ debugName: "toolViews" }] : []));
4523
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolViewsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4524
+ 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: `
4525
+ @for (view of toolViews(); track view.id) {
4526
+ <chat-generative-ui
4527
+ [spec]="view.spec"
4528
+ [registry]="registry()"
4529
+ [store]="store()"
4530
+ [handlers]="handlers()"
4531
+ [loading]="view.loading"
4532
+ (events)="events.emit($event)"
4533
+ />
4534
+ }
4535
+ `, isInline: true, dependencies: [{ kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4536
+ }
4537
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolViewsComponent, decorators: [{
4538
+ type: Component,
4539
+ args: [{
4540
+ selector: 'chat-tool-views',
4541
+ standalone: true,
4542
+ imports: [ChatGenerativeUiComponent],
4543
+ changeDetection: ChangeDetectionStrategy.OnPush,
4544
+ template: `
4545
+ @for (view of toolViews(); track view.id) {
4546
+ <chat-generative-ui
4547
+ [spec]="view.spec"
4548
+ [registry]="registry()"
4549
+ [store]="store()"
4550
+ [handlers]="handlers()"
4551
+ [loading]="view.loading"
4552
+ (events)="events.emit($event)"
4553
+ />
4554
+ }
4555
+ `,
4556
+ }]
4557
+ }], 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 }] }] } });
4558
+ /** Wraps a tool call into a synthetic single-element render spec. */
4559
+ function toToolViewSpec(tc) {
4560
+ const args = isRecord$2(tc.args) ? tc.args : {};
4561
+ const result = isRecord$2(tc.result) ? tc.result : {};
4562
+ return {
4563
+ root: tc.name,
4564
+ elements: {
4565
+ [tc.name]: {
4566
+ type: tc.name,
4567
+ props: { ...args, ...result, status: tc.status },
4568
+ },
4569
+ },
4570
+ };
4571
+ }
4572
+ function isRecord$2(v) {
4573
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
4574
+ }
4575
+
4317
4576
  // libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
4318
4577
  // SPDX-License-Identifier: MIT
4319
4578
  /**
@@ -5783,63 +6042,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
5783
6042
  }]
5784
6043
  }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], checkpointSelected: [{ type: i0.Output, args: ["checkpointSelected"] }], templateRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
5785
6044
 
5786
- // SPDX-License-Identifier: MIT
5787
- const CHAT_GENERATIVE_UI_STYLES = `
5788
- :host {
5789
- display: block;
5790
- color: var(--ngaf-chat-text);
5791
- font-size: var(--ngaf-chat-font-size);
5792
- line-height: var(--ngaf-chat-line-height);
5793
- }
5794
- .chat-generative-ui__error {
5795
- color: var(--ngaf-chat-error-text);
5796
- background: var(--ngaf-chat-error-bg);
5797
- border: 1px solid var(--ngaf-chat-error-border);
5798
- border-radius: var(--ngaf-chat-radius-card);
5799
- padding: 8px 12px;
5800
- font-size: var(--ngaf-chat-font-size-sm);
5801
- }
5802
- `;
5803
-
5804
- // libs/chat/src/lib/primitives/chat-generative-ui/chat-generative-ui.component.ts
5805
- // SPDX-License-Identifier: MIT
5806
- class ChatGenerativeUiComponent {
5807
- spec = input(null, ...(ngDevMode ? [{ debugName: "spec" }] : []));
5808
- registry = input(undefined, ...(ngDevMode ? [{ debugName: "registry" }] : []));
5809
- store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
5810
- handlers = input(undefined, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
5811
- loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
5812
- events = output();
5813
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5814
- 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: `
5815
- @if (spec()) {
5816
- <render-spec
5817
- [spec]="spec()"
5818
- [registry]="registry()"
5819
- [store]="store()"
5820
- [handlers]="handlers()"
5821
- [loading]="loading()"
5822
- (events)="events.emit($event)"
5823
- />
5824
- }
5825
- `, isInline: true, 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"], dependencies: [{ kind: "component", type: RenderSpecComponent, selector: "render-spec", inputs: ["spec", "registry", "store", "functions", "handlers", "loading"], outputs: ["events"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5826
- }
5827
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, decorators: [{
5828
- type: Component,
5829
- args: [{ selector: 'chat-generative-ui', standalone: true, imports: [RenderSpecComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
5830
- @if (spec()) {
5831
- <render-spec
5832
- [spec]="spec()"
5833
- [registry]="registry()"
5834
- [store]="store()"
5835
- [handlers]="handlers()"
5836
- [loading]="loading()"
5837
- (events)="events.emit($event)"
5838
- />
5839
- }
5840
- `, 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"] }]
5841
- }], 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"] }] } });
5842
-
5843
6045
  // libs/chat/src/lib/styles/chat-welcome.styles.ts
5844
6046
  // SPDX-License-Identifier: MIT
5845
6047
  const CHAT_WELCOME_STYLES = `
@@ -6325,6 +6527,143 @@ function provideChat(config) {
6325
6527
  // SPDX-License-Identifier: MIT
6326
6528
  const CHAT_LIFECYCLE = new InjectionToken('CHAT_LIFECYCLE');
6327
6529
 
6530
+ // SPDX-License-Identifier: MIT
6531
+ /**
6532
+ * Convert a Standard Schema to a JSON Schema for the model's `parameters`.
6533
+ * Uses Zod's converter; throws a clear error for non-Zod validators (callers
6534
+ * should supply a Zod schema — see the client-tools docs).
6535
+ */
6536
+ function deriveJsonSchema(toolName, schema) {
6537
+ try {
6538
+ return toJSONSchema(schema);
6539
+ }
6540
+ catch (err) {
6541
+ throw new Error(`client tool "${toolName}": could not derive a JSON Schema from its schema. ` +
6542
+ `Use a Zod schema (recommended) or an already-JSON-Schema-compatible validator. ` +
6543
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
6544
+ }
6545
+ }
6546
+
6547
+ /** Validate raw model args against a Standard Schema. */
6548
+ async function validateArgs(schema, args) {
6549
+ const res = await schema['~standard'].validate(args);
6550
+ if (res.issues) {
6551
+ return { ok: false, error: res.issues.map((i) => i.message).join('; ') };
6552
+ }
6553
+ // Cast rather than rely on discriminant narrowing: the cockpit example apps
6554
+ // compile this source with `strictNullChecks: false`, where the
6555
+ // `issues?: undefined` discriminant doesn't narrow `res` to the success type.
6556
+ return { ok: true, value: res.value };
6557
+ }
6558
+ /** Validate args, run the handler, and normalize the outcome to a ClientToolResult. */
6559
+ async function executeFunctionTool(def, rawArgs) {
6560
+ const v = await validateArgs(def.schema, rawArgs);
6561
+ if (!v.ok)
6562
+ return { ok: false, error: `invalid arguments: ${v.error}` };
6563
+ try {
6564
+ const value = await def.handler(v.value);
6565
+ return { ok: true, value };
6566
+ }
6567
+ catch (err) {
6568
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
6569
+ }
6570
+ }
6571
+
6572
+ // SPDX-License-Identifier: MIT
6573
+ /**
6574
+ * Watches the agent's pending client tool calls and auto-runs FUNCTION tools,
6575
+ * resolving each with its result. View/ask (component) tools are handled by the
6576
+ * rendering layer, not here. No-op if the agent lacks the clientTools
6577
+ * capability. MUST be called in an injection context (sets up an effect).
6578
+ */
6579
+ function startClientToolExecutor(agent, registry) {
6580
+ const cap = agent.clientTools;
6581
+ if (!cap)
6582
+ return;
6583
+ const inFlight = new Set();
6584
+ effect(() => {
6585
+ for (const tc of cap.pending()) {
6586
+ const def = registry[tc.name];
6587
+ if (!def || def.kind !== 'function')
6588
+ continue; // non-function handled elsewhere
6589
+ // NB: do NOT skip on `tc.status === 'complete'`. A client tool call is
6590
+ // marked 'complete' once its args finish streaming, yet it still has no
6591
+ // result and needs the browser to execute it. `pending` already excludes
6592
+ // calls that have a result or were resolved; `inFlight` prevents a
6593
+ // double-dispatch within a render cycle.
6594
+ if (inFlight.has(tc.id))
6595
+ continue;
6596
+ inFlight.add(tc.id);
6597
+ void executeFunctionTool(def, tc.args).then((result) => {
6598
+ cap.resolve(tc.id, result);
6599
+ inFlight.delete(tc.id);
6600
+ });
6601
+ }
6602
+ });
6603
+ }
6604
+
6605
+ // SPDX-License-Identifier: MIT
6606
+ /** Build the catalog spec list shipped to the model. */
6607
+ function toClientToolSpecs(registry) {
6608
+ return Object.entries(registry).map(([name, def]) => ({
6609
+ name,
6610
+ description: def.description,
6611
+ parameters: deriveJsonSchema(name, def.schema),
6612
+ }));
6613
+ }
6614
+ /** Map each view/ask tool to a RenderViewEntry that carries its schema, so the
6615
+ * render lib can gate the real component's mount on schema-readiness (showing
6616
+ * the fallback skeleton while a streaming tool call's args are still
6617
+ * incomplete) instead of mounting a required-input component too early. */
6618
+ function viewComponents(registry) {
6619
+ const out = {};
6620
+ for (const [name, def] of Object.entries(registry)) {
6621
+ if (def.kind === 'view' || def.kind === 'ask') {
6622
+ out[name] = { component: def.component, schema: def.schema };
6623
+ }
6624
+ }
6625
+ return out;
6626
+ }
6627
+ function createClientToolsCoordinator(registry) {
6628
+ const viewRegistry = views(viewComponents(registry));
6629
+ const ackedViews = new Set();
6630
+ return {
6631
+ viewRegistry,
6632
+ connect(agent) {
6633
+ const cap = agent.clientTools;
6634
+ if (!cap)
6635
+ return;
6636
+ cap.setCatalog(toClientToolSpecs(registry));
6637
+ startClientToolExecutor(agent, registry); // function tools
6638
+ // Auto-ack `view` tools: they render but produce no user value.
6639
+ effect(() => {
6640
+ for (const tc of cap.pending()) {
6641
+ const def = registry[tc.name];
6642
+ if (!def || def.kind !== 'view')
6643
+ continue;
6644
+ if (ackedViews.has(tc.id))
6645
+ continue;
6646
+ ackedViews.add(tc.id);
6647
+ cap.resolve(tc.id, { ok: true, value: { shown: true } });
6648
+ }
6649
+ });
6650
+ },
6651
+ handleRenderEvent(agent, event) {
6652
+ if (event.type !== 'result')
6653
+ return;
6654
+ const cap = agent.clientTools;
6655
+ if (!cap)
6656
+ return;
6657
+ // elementKey is the tool NAME in the tool-view spec; resolve the pending `ask`
6658
+ // call for that name with the component's emitted value.
6659
+ const name = event.elementKey;
6660
+ const pending = cap.pending().find((tc) => tc.name === name && registry[tc.name]?.kind === 'ask');
6661
+ if (pending)
6662
+ cap.resolve(pending.id, { ok: true, value: event.value });
6663
+ },
6664
+ };
6665
+ }
6666
+
6328
6667
  const RESERVED_PROP_KEYS = new Set(['child', 'children', 'action', 'tabItems', 'entryPointChild', 'contentChild']);
6329
6668
  /** Pull the (single) component-type key + its props from a v1 ComponentDef wrapper. */
6330
6669
  function unwrapComponentDef(def) {
@@ -6390,9 +6729,8 @@ function surfaceToSpec(surface) {
6390
6729
  // snapshot taken at conversion time and never reflects user
6391
6730
  // input writes back into the store. The `_bindings` map below
6392
6731
  // tells the catalog component which prop names map to which
6393
- // paths so its emit() callback can write back via the
6394
- // a2ui:datamodel:<path>:<value> magic-string protocol that
6395
- // render-element's emitFn intercepts.
6732
+ // paths so its injectRenderHost().set(path, value) call can
6733
+ // write the typed value back to the render state store.
6396
6734
  const path = value.path;
6397
6735
  bindings[key] = path;
6398
6736
  resolvedProps[key] = { $bindState: path };
@@ -7695,7 +8033,7 @@ function isStructurallyComplete(env) {
7695
8033
  * message, so the chat composition can render "Search flights" instead
7696
8034
  * of a raw `{"version":"v1","action":...}` JSON dump as a user bubble.
7697
8035
  *
7698
- * Per the A2UI v0.9 spec, action messages flow on the client → agent
8036
+ * Per the A2UI spec, action messages flow on the client → agent
7699
8037
  * return channel and are framed as typed events (closer to tool calls
7700
8038
  * than user utterances). The spec is silent on chat-bubble rendering;
7701
8039
  * Google's "A2UI in Practice" article and the Stream Chat reference
@@ -7831,6 +8169,14 @@ function isPinned(scrollHeight, scrollTop, clientHeight, tolerance = 150) {
7831
8169
  class ChatComponent {
7832
8170
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
7833
8171
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
8172
+ /**
8173
+ * Client-declared tools (`view`/`ask`/`function`) the model may call. When
8174
+ * provided, a coordinator ships their catalog to the agent, runs `function`
8175
+ * tools in the browser, and renders/resolves `view`/`ask` tools through the
8176
+ * same tool-views pipeline as `views`. Additive — leave undefined for the
8177
+ * classic server-tools-only experience.
8178
+ */
8179
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
7834
8180
  store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
7835
8181
  handlers = input({}, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
7836
8182
  threads = input([], ...(ngDevMode ? [{ debugName: "threads" }] : []));
@@ -7887,14 +8233,50 @@ class ChatComponent {
7887
8233
  const explicit = this.store();
7888
8234
  if (explicit)
7889
8235
  return explicit;
7890
- if (this.views())
8236
+ // A render store is needed whenever there's any view registry to render
8237
+ // through — user-supplied `views()` OR client-tool view/ask components.
8238
+ if (this.effectiveViews())
7891
8239
  return this._internalStore;
7892
8240
  return undefined;
7893
8241
  }, ...(ngDevMode ? [{ debugName: "resolvedStore" }] : []));
8242
+ /**
8243
+ * Lazily-built client-tools coordinator, memoized on the `clientTools`
8244
+ * registry input. Undefined when no client tools are declared. The
8245
+ * coordinator owns the catalog/executor wiring and the view/ask render
8246
+ * registry; the composition merges and connects it below.
8247
+ */
8248
+ coordinator = computed(() => {
8249
+ const reg = this.clientTools();
8250
+ return reg ? createClientToolsCoordinator(reg) : undefined;
8251
+ }, ...(ngDevMode ? [{ debugName: "coordinator" }] : []));
8252
+ /**
8253
+ * The view registry actually used for rendering tool-views and for
8254
+ * excluding view-backed tool names from default tool-call cards. Merges
8255
+ * the coordinator's `view`/`ask` components (keyed by tool name) into the
8256
+ * user-supplied `views()` so client-declared component tools render through
8257
+ * the same pipeline. Falls back to `views()` when no client tools exist.
8258
+ */
8259
+ effectiveViews = computed(() => {
8260
+ const base = this.views();
8261
+ const coord = this.coordinator();
8262
+ if (!coord)
8263
+ return base;
8264
+ return base ? withViews(base, coord.viewRegistry) : coord.viewRegistry;
8265
+ }, ...(ngDevMode ? [{ debugName: "effectiveViews" }] : []));
7894
8266
  renderRegistry = computed(() => {
7895
8267
  const v = this.views();
7896
8268
  return v ? toRenderRegistry(v) : undefined;
7897
8269
  }, ...(ngDevMode ? [{ debugName: "renderRegistry" }] : []));
8270
+ /** Tool names that have a registered view (keys of the effective view
8271
+ * registry, including client-declared view/ask tools). These render as
8272
+ * inline tool-views and are excluded from the default tool-call card so
8273
+ * they don't render twice. */
8274
+ viewToolNames = computed(() => Object.keys(this.effectiveViews() ?? {}), ...(ngDevMode ? [{ debugName: "viewToolNames" }] : []));
8275
+ /** Union of GenUI dispatcher tool names and registered view tool names. */
8276
+ excludedToolNames = computed(() => [
8277
+ ...this.genuiToolNames(),
8278
+ ...this.viewToolNames(),
8279
+ ], ...(ngDevMode ? [{ debugName: "excludedToolNames" }] : []));
7898
8280
  messageContent = messageContent;
7899
8281
  /**
7900
8282
  * Renderable content for a human-role message bubble. Most human
@@ -7903,7 +8285,7 @@ class ChatComponent {
7903
8285
  * on a rendered surface) flow through the same submit channel and
7904
8286
  * land in the message stream as a HumanMessage whose content is a
7905
8287
  * JSON-serialized `A2uiActionMessage`. Showing the raw JSON as if
7906
- * the user typed it leaks the protocol; per the A2UI v0.9 spec
8288
+ * the user typed it leaks the protocol; per the A2UI spec
7907
8289
  * those events resemble tool calls more than user utterances.
7908
8290
  *
7909
8291
  * `a2uiActionLabel` returns a short human-readable label for
@@ -7936,6 +8318,7 @@ class ChatComponent {
7936
8318
  }
7937
8319
  classifiers = new Map();
7938
8320
  destroyRef = inject(DestroyRef);
8321
+ injector = inject(Injector);
7939
8322
  // Resolved against the component's own `providers` in normal use. The fallback
7940
8323
  // is for tests that construct ChatComponent via `new` inside a bare injection
7941
8324
  // context (no element injector, so component-level providers are skipped).
@@ -8015,6 +8398,38 @@ class ChatComponent {
8015
8398
  store.update(event.data);
8016
8399
  });
8017
8400
  });
8401
+ // Sync the agent STATE signal into the render store. AG-UI delivers
8402
+ // backend data as agent shared state (STATE_SNAPSHOT/STATE_DELTA); other
8403
+ // adapters expose graph state via the same `state()` signal. The render
8404
+ // store keys are JSON pointers, so map each top-level state key `k` to
8405
+ // `/k`. Exclude `messages` (carried in the snapshot but irrelevant to the
8406
+ // render store and large). Fires for any adapter whose `state()` carries
8407
+ // non-message keys; no-op when no render store is active. Coexists with
8408
+ // the events$ `state_update` path above (both merge into the same store).
8409
+ effect(() => {
8410
+ let agentRef;
8411
+ try {
8412
+ agentRef = this.agent();
8413
+ }
8414
+ catch {
8415
+ return;
8416
+ }
8417
+ const stateFn = agentRef.state;
8418
+ if (typeof stateFn !== 'function')
8419
+ return;
8420
+ const state = stateFn.call(agentRef);
8421
+ const store = this.resolvedStore();
8422
+ if (!store || state == null || typeof state !== 'object' || Array.isArray(state))
8423
+ return;
8424
+ const updates = {};
8425
+ for (const [k, v] of Object.entries(state)) {
8426
+ if (k === 'messages')
8427
+ continue;
8428
+ updates['/' + k] = v;
8429
+ }
8430
+ if (Object.keys(updates).length > 0)
8431
+ store.update(updates);
8432
+ });
8018
8433
  // Spec 4: flip CHAT_LIFECYCLE.firstMessageSent when the agent's stream
8019
8434
  // starts, regardless of submit path (input-bound, programmatic, suggestion-
8020
8435
  // click). Sticky — guarded so we never re-set a flag that's already true.
@@ -8125,6 +8540,35 @@ class ChatComponent {
8125
8540
  }
8126
8541
  this.partialEventsLastIndex = events.length;
8127
8542
  });
8543
+ // Connect the client-tools coordinator to the agent. connect() ships the
8544
+ // tool catalog, starts the function-tool executor, and installs the view
8545
+ // auto-ack effect. Those installs create effect()s of their own, which
8546
+ // Angular forbids from *within* a running effect (NG0602). So this effect
8547
+ // only detects readiness (both coordinator + agent present) and defers the
8548
+ // actual connect() to a microtask that runs OUTSIDE the reactive context,
8549
+ // re-entering the component's injection context so the coordinator's
8550
+ // effects bind to this component's lifecycle. `connected` guards against
8551
+ // re-running per coordinator identity (a new clientTools registry yields a
8552
+ // new coordinator and re-connects; the old one's effects are abandoned).
8553
+ let connected;
8554
+ effect(() => {
8555
+ const coord = this.coordinator();
8556
+ let agentRef;
8557
+ try {
8558
+ agentRef = this.agent();
8559
+ }
8560
+ catch {
8561
+ return;
8562
+ }
8563
+ if (!coord || !agentRef)
8564
+ return;
8565
+ if (connected === coord)
8566
+ return;
8567
+ connected = coord;
8568
+ queueMicrotask(() => {
8569
+ runInInjectionContext(this.injector, () => coord.connect(agentRef));
8570
+ });
8571
+ });
8128
8572
  effect(() => {
8129
8573
  // janitor: drop classifiers for messages no longer in the agent's list
8130
8574
  let liveIds;
@@ -8342,6 +8786,25 @@ class ChatComponent {
8342
8786
  onSpecEvent(event, messageIndex) {
8343
8787
  this.renderEvent.emit({ messageIndex, event });
8344
8788
  }
8789
+ /**
8790
+ * Forwards a render event bubbled up from a `<chat-tool-views>` component
8791
+ * (a client-declared `view`/`ask` tool's rendered UI) to the client-tools
8792
+ * coordinator. The coordinator resolves the matching pending `ask` tool call
8793
+ * when the event is a `result`. No-op when no client tools are wired.
8794
+ */
8795
+ onClientToolEvent(event) {
8796
+ const coord = this.coordinator();
8797
+ if (!coord)
8798
+ return;
8799
+ let agentRef;
8800
+ try {
8801
+ agentRef = this.agent();
8802
+ }
8803
+ catch {
8804
+ return;
8805
+ }
8806
+ coord.handleRenderEvent(agentRef, event);
8807
+ }
8345
8808
  onA2uiAction(message) {
8346
8809
  void this.agent().submit({ message: JSON.stringify(message) });
8347
8810
  }
@@ -8362,7 +8825,7 @@ class ChatComponent {
8362
8825
  this.messageCopy.emit({ messageIndex: idx, content });
8363
8826
  }
8364
8827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8365
- 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: [
8828
+ 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: [
8366
8829
  { provide: CHAT_LIFECYCLE, useFactory: createChatLifecycle },
8367
8830
  ], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
8368
8831
  @if (showWelcome()) {
@@ -8419,20 +8882,34 @@ class ChatComponent {
8419
8882
  [durationMs]="message.reasoningDurationMs"
8420
8883
  />
8421
8884
  }
8422
- <chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="genuiToolNames()">
8885
+ <chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="excludedToolNames()">
8423
8886
  <ng-container ngProjectAs="[chatToolCallTemplate]">
8424
8887
  <ng-content select="[chatToolCallTemplate]" />
8425
8888
  </ng-container>
8426
8889
  </chat-tool-calls>
8890
+ <chat-tool-views
8891
+ [agent]="agent()"
8892
+ [message]="message"
8893
+ [views]="effectiveViews()"
8894
+ [store]="resolvedStore()"
8895
+ [handlers]="handlers()"
8896
+ (events)="onClientToolEvent($event)"
8897
+ />
8427
8898
  <chat-subagents [agent]="agent()" />
8428
8899
  @if (classified.markdown(); as md) {
8429
8900
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8430
8901
  }
8431
8902
  @if (classified.spec(); as spec) {
8903
+ <!-- Pass ONLY the explicit consumer store (may be
8904
+ undefined) — never the conversation-wide internal
8905
+ fallback. Without a consumer store, render-spec
8906
+ self-seeds a per-instance store from spec.state so
8907
+ same-key dashboards across messages stay isolated
8908
+ (a2ui parity). -->
8432
8909
  <chat-generative-ui
8433
8910
  [spec]="spec"
8434
8911
  [registry]="renderRegistry()"
8435
- [store]="resolvedStore()"
8912
+ [store]="store()"
8436
8913
  [handlers]="handlers()"
8437
8914
  [loading]="agent().isLoading()"
8438
8915
  (events)="onSpecEvent($event, i)"
@@ -8506,7 +8983,7 @@ class ChatComponent {
8506
8983
  </div>
8507
8984
  </div>
8508
8985
  }
8509
- `, 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: 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 });
8986
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: ChatSubagentsComponent, selector: "chat-subagents", inputs: ["agent"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8510
8987
  }
8511
8988
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
8512
8989
  type: Component,
@@ -8515,7 +8992,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8515
8992
  ChatWindowComponent, ChatMessageListComponent, MessageTemplateDirective, ChatMessageComponent,
8516
8993
  ChatInputComponent, ChatTypingIndicatorComponent, ChatErrorComponent,
8517
8994
  ChatThreadListComponent, ChatGenerativeUiComponent,
8518
- ChatStreamingMdComponent, ChatToolCallsComponent, ChatSubagentsComponent, A2uiSurfaceComponent,
8995
+ ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent, ChatSubagentsComponent, A2uiSurfaceComponent,
8519
8996
  ChatMessageActionsComponent, ChatWelcomeComponent, ChatSelectComponent, ChatReasoningComponent,
8520
8997
  ChatScrollBubbleComponent,
8521
8998
  ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
@@ -8575,20 +9052,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8575
9052
  [durationMs]="message.reasoningDurationMs"
8576
9053
  />
8577
9054
  }
8578
- <chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="genuiToolNames()">
9055
+ <chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="excludedToolNames()">
8579
9056
  <ng-container ngProjectAs="[chatToolCallTemplate]">
8580
9057
  <ng-content select="[chatToolCallTemplate]" />
8581
9058
  </ng-container>
8582
9059
  </chat-tool-calls>
9060
+ <chat-tool-views
9061
+ [agent]="agent()"
9062
+ [message]="message"
9063
+ [views]="effectiveViews()"
9064
+ [store]="resolvedStore()"
9065
+ [handlers]="handlers()"
9066
+ (events)="onClientToolEvent($event)"
9067
+ />
8583
9068
  <chat-subagents [agent]="agent()" />
8584
9069
  @if (classified.markdown(); as md) {
8585
9070
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8586
9071
  }
8587
9072
  @if (classified.spec(); as spec) {
9073
+ <!-- Pass ONLY the explicit consumer store (may be
9074
+ undefined) — never the conversation-wide internal
9075
+ fallback. Without a consumer store, render-spec
9076
+ self-seeds a per-instance store from spec.state so
9077
+ same-key dashboards across messages stay isolated
9078
+ (a2ui parity). -->
8588
9079
  <chat-generative-ui
8589
9080
  [spec]="spec"
8590
9081
  [registry]="renderRegistry()"
8591
- [store]="resolvedStore()"
9082
+ [store]="store()"
8592
9083
  [handlers]="handlers()"
8593
9084
  [loading]="agent().isLoading()"
8594
9085
  (events)="onSpecEvent($event, i)"
@@ -8663,7 +9154,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8663
9154
  </div>
8664
9155
  }
8665
9156
  `, 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"] }]
8666
- }], 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 }] }] } });
9157
+ }], 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 }] }] } });
8667
9158
 
8668
9159
  // libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts
8669
9160
  // SPDX-License-Identifier: MIT
@@ -8673,6 +9164,8 @@ class ChatPopupComponent {
8673
9164
  * messages classified as A2UI parse correctly but never mount a
8674
9165
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8675
9166
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9167
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9168
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8676
9169
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8677
9170
  * renders in the chat-input chrome. */
8678
9171
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8727,7 +9220,7 @@ class ChatPopupComponent {
8727
9220
  openWindow() { this.open.set(true); }
8728
9221
  closeWindow() { this.open.set(false); }
8729
9222
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8730
- 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: `
9223
+ 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: `
8731
9224
  <div class="chat-popup__launcher">
8732
9225
  <chat-launcher-button (clicked)="toggle()" />
8733
9226
  </div>
@@ -8738,6 +9231,7 @@ class ChatPopupComponent {
8738
9231
  <chat
8739
9232
  [agent]="agent()"
8740
9233
  [views]="views()"
9234
+ [clientTools]="clientTools()"
8741
9235
  [modelOptions]="modelOptions()"
8742
9236
  [showModelPicker]="showModelPicker()"
8743
9237
  [selectedModel]="selectedModel()"
@@ -8747,7 +9241,7 @@ class ChatPopupComponent {
8747
9241
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
8748
9242
  </chat>
8749
9243
  </div>
8750
- `, 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 });
9244
+ `, 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 });
8751
9245
  }
8752
9246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, decorators: [{
8753
9247
  type: Component,
@@ -8762,6 +9256,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8762
9256
  <chat
8763
9257
  [agent]="agent()"
8764
9258
  [views]="views()"
9259
+ [clientTools]="clientTools()"
8765
9260
  [modelOptions]="modelOptions()"
8766
9261
  [showModelPicker]="showModelPicker()"
8767
9262
  [selectedModel]="selectedModel()"
@@ -8772,7 +9267,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8772
9267
  </chat>
8773
9268
  </div>
8774
9269
  `, 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"] }]
8775
- }], 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 }] }] } });
9270
+ }], 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 }] }] } });
8776
9271
 
8777
9272
  // libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts
8778
9273
  // SPDX-License-Identifier: MIT
@@ -8782,6 +9277,8 @@ class ChatSidebarComponent {
8782
9277
  * messages classified as A2UI parse correctly but never mount a
8783
9278
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8784
9279
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9280
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9281
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8785
9282
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8786
9283
  * renders in the chat-input chrome. */
8787
9284
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8835,7 +9332,7 @@ class ChatSidebarComponent {
8835
9332
  openWindow() { this.open.set(true); }
8836
9333
  closeWindow() { this.open.set(false); }
8837
9334
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8838
- 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: `
9335
+ 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: `
8839
9336
  <div class="chat-sidebar__content"><ng-content /></div>
8840
9337
  <div class="chat-sidebar__launcher">
8841
9338
  <chat-launcher-button (clicked)="toggle()" />
@@ -8852,6 +9349,7 @@ class ChatSidebarComponent {
8852
9349
  <chat
8853
9350
  [agent]="agent()"
8854
9351
  [views]="views()"
9352
+ [clientTools]="clientTools()"
8855
9353
  [modelOptions]="modelOptions()"
8856
9354
  [showModelPicker]="showModelPicker()"
8857
9355
  [selectedModel]="selectedModel()"
@@ -8861,7 +9359,7 @@ class ChatSidebarComponent {
8861
9359
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
8862
9360
  </chat>
8863
9361
  </aside>
8864
- `, 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 });
9362
+ `, 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 });
8865
9363
  }
8866
9364
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, decorators: [{
8867
9365
  type: Component,
@@ -8885,6 +9383,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8885
9383
  <chat
8886
9384
  [agent]="agent()"
8887
9385
  [views]="views()"
9386
+ [clientTools]="clientTools()"
8888
9387
  [modelOptions]="modelOptions()"
8889
9388
  [showModelPicker]="showModelPicker()"
8890
9389
  [selectedModel]="selectedModel()"
@@ -8895,7 +9394,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8895
9394
  </chat>
8896
9395
  </aside>
8897
9396
  `, 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"] }]
8898
- }], 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 }] }] } });
9397
+ }], 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 }] }] } });
8899
9398
 
8900
9399
  // libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
8901
9400
  // SPDX-License-Identifier: MIT
@@ -10117,76 +10616,155 @@ class ChatInterruptPanelComponent {
10117
10616
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatInterruptPanelComponent, isStandalone: true, selector: "chat-interrupt-panel", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action" }, ngImport: i0, template: `
10118
10617
  @if (interrupt()) {
10119
10618
  <div role="alert" class="chat-interrupt-panel">
10120
- <p class="chat-interrupt-panel__title">
10121
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
10122
- Agent paused
10619
+ <p class="chat-interrupt-panel__eyebrow">
10620
+ <span class="chat-interrupt-panel__dot" aria-hidden="true"></span>
10621
+ Agent paused — review needed
10123
10622
  </p>
10124
10623
  <p class="chat-interrupt-panel__body">{{ interruptReason() }}</p>
10125
-
10126
10624
  <div class="chat-interrupt-panel__actions">
10127
- <button
10128
- type="button"
10129
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--primary"
10130
- (click)="action.emit('accept')"
10131
- >Accept</button>
10132
- <button
10133
- type="button"
10134
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--secondary"
10135
- (click)="action.emit('edit')"
10136
- >Edit</button>
10137
- <button
10138
- type="button"
10139
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--secondary"
10140
- (click)="action.emit('respond')"
10141
- >Respond</button>
10142
- <button
10143
- type="button"
10144
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--tertiary"
10145
- (click)="action.emit('ignore')"
10146
- >Ignore</button>
10625
+ <button type="button" class="btn btn-primary" (click)="action.emit('accept')">Accept</button>
10626
+ <button type="button" class="btn btn-secondary" (click)="action.emit('edit')">Edit</button>
10627
+ <button type="button" class="btn btn-secondary" (click)="action.emit('respond')">Respond</button>
10628
+ <button type="button" class="btn btn-text" (click)="action.emit('ignore')">Ignore</button>
10147
10629
  </div>
10148
10630
  </div>
10149
10631
  }
10150
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ".chat-interrupt-panel{background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator);border-left:3px solid var(--ngaf-chat-warning-text);border-radius:var(--ngaf-chat-radius-card);padding:12px 16px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__title{font-weight:600;margin:0 0 4px;display:flex;align-items:center;gap:6px;color:var(--ngaf-chat-text)}.chat-interrupt-panel__title svg{color:var(--ngaf-chat-warning-text)}.chat-interrupt-panel__body{margin:0 0 12px;color:var(--ngaf-chat-text-muted);white-space:pre-wrap}.chat-interrupt-panel__actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.chat-interrupt-panel__btn{padding:6px 14px;font-size:var(--ngaf-chat-font-size-sm);border-radius:var(--ngaf-chat-radius-button);border:0;cursor:pointer;font-weight:500;transition:transform .2s ease,opacity .2s ease}.chat-interrupt-panel__btn:hover{transform:scale(1.03)}.chat-interrupt-panel__btn--primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.chat-interrupt-panel__btn--secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.chat-interrupt-panel__btn--tertiary{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 8px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__btn--tertiary:hover{color:var(--ngaf-chat-text)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10632
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ".chat-interrupt-panel{background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);padding:14px 16px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__eyebrow{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--ngaf-chat-warning-text);margin:0 0 8px;display:flex;align-items:center;gap:6px}.chat-interrupt-panel__dot{width:6px;height:6px;border-radius:999px;background:var(--ngaf-chat-warning-text);flex:0 0 6px}.chat-interrupt-panel__body{margin:0 0 12px;color:var(--ngaf-chat-text);white-space:pre-wrap}.chat-interrupt-panel__actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center}.btn{border:0;padding:6px 14px;border-radius:var(--ngaf-chat-radius-button);font-size:12px;font-weight:500;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.btn:hover{transform:scale(1.03)}.btn-primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.btn-secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.btn-text{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 10px}.btn-text:hover{color:var(--ngaf-chat-text)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10151
10633
  }
10152
10634
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatInterruptPanelComponent, decorators: [{
10153
10635
  type: Component,
10154
10636
  args: [{ selector: 'chat-interrupt-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
10155
10637
  @if (interrupt()) {
10156
10638
  <div role="alert" class="chat-interrupt-panel">
10157
- <p class="chat-interrupt-panel__title">
10158
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
10159
- Agent paused
10639
+ <p class="chat-interrupt-panel__eyebrow">
10640
+ <span class="chat-interrupt-panel__dot" aria-hidden="true"></span>
10641
+ Agent paused — review needed
10160
10642
  </p>
10161
10643
  <p class="chat-interrupt-panel__body">{{ interruptReason() }}</p>
10162
-
10163
10644
  <div class="chat-interrupt-panel__actions">
10164
- <button
10165
- type="button"
10166
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--primary"
10167
- (click)="action.emit('accept')"
10168
- >Accept</button>
10169
- <button
10170
- type="button"
10171
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--secondary"
10172
- (click)="action.emit('edit')"
10173
- >Edit</button>
10174
- <button
10175
- type="button"
10176
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--secondary"
10177
- (click)="action.emit('respond')"
10178
- >Respond</button>
10179
- <button
10180
- type="button"
10181
- class="chat-interrupt-panel__btn chat-interrupt-panel__btn--tertiary"
10182
- (click)="action.emit('ignore')"
10183
- >Ignore</button>
10645
+ <button type="button" class="btn btn-primary" (click)="action.emit('accept')">Accept</button>
10646
+ <button type="button" class="btn btn-secondary" (click)="action.emit('edit')">Edit</button>
10647
+ <button type="button" class="btn btn-secondary" (click)="action.emit('respond')">Respond</button>
10648
+ <button type="button" class="btn btn-text" (click)="action.emit('ignore')">Ignore</button>
10184
10649
  </div>
10185
10650
  </div>
10186
10651
  }
10187
- `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ".chat-interrupt-panel{background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator);border-left:3px solid var(--ngaf-chat-warning-text);border-radius:var(--ngaf-chat-radius-card);padding:12px 16px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__title{font-weight:600;margin:0 0 4px;display:flex;align-items:center;gap:6px;color:var(--ngaf-chat-text)}.chat-interrupt-panel__title svg{color:var(--ngaf-chat-warning-text)}.chat-interrupt-panel__body{margin:0 0 12px;color:var(--ngaf-chat-text-muted);white-space:pre-wrap}.chat-interrupt-panel__actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.chat-interrupt-panel__btn{padding:6px 14px;font-size:var(--ngaf-chat-font-size-sm);border-radius:var(--ngaf-chat-radius-button);border:0;cursor:pointer;font-weight:500;transition:transform .2s ease,opacity .2s ease}.chat-interrupt-panel__btn:hover{transform:scale(1.03)}.chat-interrupt-panel__btn--primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.chat-interrupt-panel__btn--secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.chat-interrupt-panel__btn--tertiary{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 8px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__btn--tertiary:hover{color:var(--ngaf-chat-text)}\n"] }]
10652
+ `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ".chat-interrupt-panel{background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);padding:14px 16px;font-size:var(--ngaf-chat-font-size-sm)}.chat-interrupt-panel__eyebrow{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--ngaf-chat-warning-text);margin:0 0 8px;display:flex;align-items:center;gap:6px}.chat-interrupt-panel__dot{width:6px;height:6px;border-radius:999px;background:var(--ngaf-chat-warning-text);flex:0 0 6px}.chat-interrupt-panel__body{margin:0 0 12px;color:var(--ngaf-chat-text);white-space:pre-wrap}.chat-interrupt-panel__actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center}.btn{border:0;padding:6px 14px;border-radius:var(--ngaf-chat-radius-button);font-size:12px;font-weight:500;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.btn:hover{transform:scale(1.03)}.btn-primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.btn-secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.btn-text{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 10px}.btn-text:hover{color:var(--ngaf-chat-text)}\n"] }]
10188
10653
  }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], action: [{ type: i0.Output, args: ["action"] }] } });
10189
10654
 
10655
+ // SPDX-License-Identifier: MIT
10656
+ class ChatApprovalCardComponent {
10657
+ agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
10658
+ matchKind = input(undefined, ...(ngDevMode ? [{ debugName: "matchKind" }] : []));
10659
+ title = input('Approval required', ...(ngDevMode ? [{ debugName: "title" }] : []));
10660
+ showEdit = input(false, ...(ngDevMode ? [{ debugName: "showEdit" }] : []));
10661
+ action = output();
10662
+ bodyTemplate = contentChild('body', ...(ngDevMode ? [{ debugName: "bodyTemplate" }] : []));
10663
+ dialogRef = viewChild('dialogEl', ...(ngDevMode ? [{ debugName: "dialogRef" }] : []));
10664
+ interrupt = computed(() => this.agent().interrupt?.(), ...(ngDevMode ? [{ debugName: "interrupt" }] : []));
10665
+ payload = computed(() => {
10666
+ const i = this.interrupt();
10667
+ if (!i)
10668
+ return undefined;
10669
+ const v = i.value;
10670
+ const want = this.matchKind();
10671
+ if (want !== undefined) {
10672
+ if (!v || typeof v !== 'object' || v.kind !== want) {
10673
+ return undefined;
10674
+ }
10675
+ }
10676
+ return v;
10677
+ }, ...(ngDevMode ? [{ debugName: "payload" }] : []));
10678
+ constructor() {
10679
+ effect(() => {
10680
+ const p = this.payload();
10681
+ const dialog = this.dialogRef()?.nativeElement;
10682
+ if (!dialog)
10683
+ return;
10684
+ if (p && !dialog.open) {
10685
+ dialog.showModal();
10686
+ }
10687
+ else if (!p && dialog.open) {
10688
+ dialog.close();
10689
+ }
10690
+ });
10691
+ }
10692
+ emit(action) {
10693
+ this.action.emit(action);
10694
+ // 'approve' and 'cancel' are terminal — they resolve the interrupt, so the
10695
+ // dialog closes. 'edit' is non-terminal: the caller reveals an inline
10696
+ // editor in the body slot and submits the resume itself, so we leave the
10697
+ // dialog open.
10698
+ if (action !== 'edit') {
10699
+ this.closeDialog();
10700
+ }
10701
+ }
10702
+ onCancelEvent(ev) {
10703
+ // Native dialog's cancel event fires on Escape. Treat as cancel.
10704
+ ev.preventDefault();
10705
+ this.action.emit('cancel');
10706
+ this.closeDialog();
10707
+ }
10708
+ closeDialog() {
10709
+ const dialog = this.dialogRef()?.nativeElement;
10710
+ if (!dialog)
10711
+ return;
10712
+ if (dialog.open)
10713
+ dialog.close();
10714
+ }
10715
+ onDialogClose() {
10716
+ // Native close — no-op; emit happens in emit() / onCancelEvent.
10717
+ }
10718
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatApprovalCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10719
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatApprovalCardComponent, isStandalone: true, selector: "chat-approval-card", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, matchKind: { classPropertyName: "matchKind", publicName: "matchKind", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showEdit: { classPropertyName: "showEdit", publicName: "showEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, queries: [{ propertyName: "bodyTemplate", first: true, predicate: ["body"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "dialogRef", first: true, predicate: ["dialogEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
10720
+ <dialog #dialogEl class="chat-approval-card" (close)="onDialogClose()" (cancel)="onCancelEvent($event)">
10721
+ <div class="chat-approval-card__header">
10722
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
10723
+ <h4>{{ title() }}</h4>
10724
+ </div>
10725
+ <div class="chat-approval-card__body">
10726
+ @if (bodyTemplate(); as tpl) {
10727
+ @if (payload(); as p) {
10728
+ <ng-container *ngTemplateOutlet="tpl; context: { $implicit: p }"></ng-container>
10729
+ }
10730
+ }
10731
+ </div>
10732
+ <div class="chat-approval-card__actions">
10733
+ <button type="button" class="btn btn-text" (click)="emit('cancel')">Cancel</button>
10734
+ @if (showEdit()) {
10735
+ <button type="button" class="btn btn-secondary" (click)="emit('edit')">Edit</button>
10736
+ }
10737
+ <button type="button" class="btn btn-primary" (click)="emit('approve')">Approve</button>
10738
+ </div>
10739
+ </dialog>
10740
+ `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:contents}dialog.chat-approval-card{width:440px;max-width:calc(100vw - 32px);margin:auto;padding:0;border:0;border-radius:12px;background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);box-shadow:0 20px 50px #0000002e}dialog.chat-approval-card::backdrop{background:#00000080;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.chat-approval-card__header{padding:14px 16px 12px;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--ngaf-chat-separator)}.chat-approval-card__header h4{margin:0;font-size:14px;font-weight:600;color:var(--ngaf-chat-text)}.chat-approval-card__header svg{color:var(--ngaf-chat-warning-text);width:16px;height:16px;flex:0 0 16px}.chat-approval-card__body{padding:14px 16px;font-size:var(--ngaf-chat-font-size-sm, 13px);color:var(--ngaf-chat-text)}.chat-approval-card__actions{padding:8px 16px 14px;display:flex;gap:6px;justify-content:flex-end;align-items:center}.btn{border:0;padding:6px 14px;border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.btn:hover{transform:scale(1.03)}.btn-primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.btn-secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.btn-text{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 10px}.btn-text:hover{color:var(--ngaf-chat-text)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10741
+ }
10742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatApprovalCardComponent, decorators: [{
10743
+ type: Component,
10744
+ args: [{ selector: 'chat-approval-card', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: `
10745
+ <dialog #dialogEl class="chat-approval-card" (close)="onDialogClose()" (cancel)="onCancelEvent($event)">
10746
+ <div class="chat-approval-card__header">
10747
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
10748
+ <h4>{{ title() }}</h4>
10749
+ </div>
10750
+ <div class="chat-approval-card__body">
10751
+ @if (bodyTemplate(); as tpl) {
10752
+ @if (payload(); as p) {
10753
+ <ng-container *ngTemplateOutlet="tpl; context: { $implicit: p }"></ng-container>
10754
+ }
10755
+ }
10756
+ </div>
10757
+ <div class="chat-approval-card__actions">
10758
+ <button type="button" class="btn btn-text" (click)="emit('cancel')">Cancel</button>
10759
+ @if (showEdit()) {
10760
+ <button type="button" class="btn btn-secondary" (click)="emit('edit')">Edit</button>
10761
+ }
10762
+ <button type="button" class="btn btn-primary" (click)="emit('approve')">Approve</button>
10763
+ </div>
10764
+ </dialog>
10765
+ `, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:contents}dialog.chat-approval-card{width:440px;max-width:calc(100vw - 32px);margin:auto;padding:0;border:0;border-radius:12px;background:var(--ngaf-chat-surface);color:var(--ngaf-chat-text);box-shadow:0 20px 50px #0000002e}dialog.chat-approval-card::backdrop{background:#00000080;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.chat-approval-card__header{padding:14px 16px 12px;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--ngaf-chat-separator)}.chat-approval-card__header h4{margin:0;font-size:14px;font-weight:600;color:var(--ngaf-chat-text)}.chat-approval-card__header svg{color:var(--ngaf-chat-warning-text);width:16px;height:16px;flex:0 0 16px}.chat-approval-card__body{padding:14px 16px;font-size:var(--ngaf-chat-font-size-sm, 13px);color:var(--ngaf-chat-text)}.chat-approval-card__actions{padding:8px 16px 14px;display:flex;gap:6px;justify-content:flex-end;align-items:center}.btn{border:0;padding:6px 14px;border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.btn:hover{transform:scale(1.03)}.btn-primary{background:var(--ngaf-chat-primary);color:var(--ngaf-chat-on-primary)}.btn-secondary{background:transparent;color:var(--ngaf-chat-text);border:1px solid var(--ngaf-chat-separator)}.btn-text{background:transparent;color:var(--ngaf-chat-text-muted);padding:6px 10px}.btn-text:hover{color:var(--ngaf-chat-text)}\n"] }]
10766
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], matchKind: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchKind", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdit", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], bodyTemplate: [{ type: i0.ContentChild, args: ['body', { isSignal: true }] }], dialogRef: [{ type: i0.ViewChild, args: ['dialogEl', { isSignal: true }] }] } });
10767
+
10190
10768
  // SPDX-License-Identifier: MIT
10191
10769
  let markedParse = null;
10192
10770
  let markedLoadAttempted = false;
@@ -10386,24 +10964,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10386
10964
  `, 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"] }]
10387
10965
  }], 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 }] }] } });
10388
10966
 
10389
- // SPDX-License-Identifier: MIT
10390
- /** Emits a data model binding event if the prop has a binding path. */
10391
- function emitBinding(emit, bindings, prop, value) {
10967
+ /** Writes a typed value to the render state store if the prop has a binding path. */
10968
+ function emitBinding(host, bindings, prop, value) {
10392
10969
  const path = bindings?.[prop];
10393
10970
  if (path) {
10394
- emit(`a2ui:datamodel:${path}:${value}`);
10971
+ host.set(path, value);
10395
10972
  }
10396
10973
  }
10397
10974
 
10398
10975
  // SPDX-License-Identifier: MIT
10399
10976
  class A2uiCheckBoxComponent {
10977
+ host = injectRenderHost();
10400
10978
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10401
10979
  /** v1 canonical prop: boolean checked state. */
10402
10980
  value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
10403
10981
  /** Pre-v1 alias retained for back-compat. */
10404
10982
  checked = input(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
10405
10983
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10406
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10407
10984
  // Framework inputs required by the render harness.
10408
10985
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10409
10986
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10416,14 +10993,14 @@ class A2uiCheckBoxComponent {
10416
10993
  // `value`; pre-v1 used `checked`.
10417
10994
  const bound = this._bindings();
10418
10995
  if (bound['value']) {
10419
- emitBinding(this.emit(), bound, 'value', val);
10996
+ emitBinding(this.host, bound, 'value', val);
10420
10997
  }
10421
10998
  else {
10422
- emitBinding(this.emit(), bound, 'checked', val);
10999
+ emitBinding(this.host, bound, 'checked', val);
10423
11000
  }
10424
11001
  }
10425
11002
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiCheckBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10426
- 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: `
11003
+ 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: `
10427
11004
  <label class="a2ui-cb">
10428
11005
  <input type="checkbox" [checked]="effectiveValue()" (change)="onChange($event)" class="a2ui-cb__input" />
10429
11006
  {{ label() }}
@@ -10438,7 +11015,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10438
11015
  {{ label() }}
10439
11016
  </label>
10440
11017
  `, 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"] }]
10441
- }], 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 }] }] } });
11018
+ }], 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 }] }] } });
10442
11019
 
10443
11020
  // SPDX-License-Identifier: MIT
10444
11021
  const ALIGN_MAP = {
@@ -10481,6 +11058,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10481
11058
  class A2uiDateTimeInputComponent {
10482
11059
  static _idCounter = 0;
10483
11060
  _inputId = `a2ui-date-time-input-${++A2uiDateTimeInputComponent._idCounter}`;
11061
+ host = injectRenderHost();
10484
11062
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10485
11063
  /** v1 prop: value (resolved DynamicString). */
10486
11064
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -10489,7 +11067,6 @@ class A2uiDateTimeInputComponent {
10489
11067
  /** v1 prop: enableTime — include time portion. */
10490
11068
  enableTime = input(false, ...(ngDevMode ? [{ debugName: "enableTime" }] : []));
10491
11069
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10492
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10493
11070
  // Framework inputs required by the render harness.
10494
11071
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10495
11072
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10507,10 +11084,10 @@ class A2uiDateTimeInputComponent {
10507
11084
  }, ...(ngDevMode ? [{ debugName: "htmlInputType" }] : []));
10508
11085
  onChange(event) {
10509
11086
  const val = event.target.value;
10510
- emitBinding(this.emit(), this._bindings(), 'value', val);
11087
+ emitBinding(this.host, this._bindings(), 'value', val);
10511
11088
  }
10512
11089
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiDateTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10513
- 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: `
11090
+ 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: `
10514
11091
  <div class="a2ui-dti">
10515
11092
  @if (label()) {
10516
11093
  <label [htmlFor]="_inputId" class="a2ui-dti__label">{{ label() }}</label>
@@ -10541,7 +11118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10541
11118
  />
10542
11119
  </div>
10543
11120
  `, 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"] }]
10544
- }], 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 }] }] } });
11121
+ }], 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 }] }] } });
10545
11122
 
10546
11123
  // SPDX-License-Identifier: MIT
10547
11124
  class A2uiDividerComponent {
@@ -10832,6 +11409,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10832
11409
 
10833
11410
  // SPDX-License-Identifier: MIT
10834
11411
  class A2uiMultipleChoiceComponent {
11412
+ host = injectRenderHost();
10835
11413
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10836
11414
  /** Resolved current selections from surface-to-spec. Normalized in
10837
11415
  * `selectionsArray` because LLMs sometimes seed the data model with a
@@ -10851,7 +11429,6 @@ class A2uiMultipleChoiceComponent {
10851
11429
  /** When ≤ 1 — render as single-select <select>; otherwise multi-select checkboxes. */
10852
11430
  maxAllowedSelections = input(1, ...(ngDevMode ? [{ debugName: "maxAllowedSelections" }] : []));
10853
11431
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10854
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10855
11432
  // Framework inputs required by the render harness.
10856
11433
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10857
11434
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10863,7 +11440,7 @@ class A2uiMultipleChoiceComponent {
10863
11440
  }
10864
11441
  onSelectChange(event) {
10865
11442
  const val = event.target.value;
10866
- emitBinding(this.emit(), this._bindings(), 'selections', val);
11443
+ emitBinding(this.host, this._bindings(), 'selections', val);
10867
11444
  }
10868
11445
  onCheckChange(value, event) {
10869
11446
  const checked = event.target.checked;
@@ -10875,11 +11452,11 @@ class A2uiMultipleChoiceComponent {
10875
11452
  else if (!checked && idx !== -1) {
10876
11453
  current.splice(idx, 1);
10877
11454
  }
10878
- // Emit the updated selections array as a JSON string so emitBinding can convey it.
10879
- emitBinding(this.emit(), this._bindings(), 'selections', JSON.stringify(current));
11455
+ // Pass the updated selections array directly (typed value, no JSON stringification needed).
11456
+ emitBinding(this.host, this._bindings(), 'selections', current);
10880
11457
  }
10881
11458
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiMultipleChoiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10882
- 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: `
11459
+ 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: `
10883
11460
  <div class="a2ui-mc">
10884
11461
  @if (label()) {
10885
11462
  <span class="a2ui-mc__label">{{ label() }}</span>
@@ -10944,7 +11521,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10944
11521
  }
10945
11522
  </div>
10946
11523
  `, 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"] }]
10947
- }], 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 }] }] } });
11524
+ }], 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 }] }] } });
10948
11525
 
10949
11526
  // SPDX-License-Identifier: MIT
10950
11527
  const ROW_ALIGN_MAP = {
@@ -11002,6 +11579,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11002
11579
  class A2uiSliderComponent {
11003
11580
  static _idCounter = 0;
11004
11581
  _inputId = `a2ui-slider-${++A2uiSliderComponent._idCounter}`;
11582
+ host = injectRenderHost();
11005
11583
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11006
11584
  /** v1 prop: value (resolved DynamicNumber). */
11007
11585
  value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -11011,7 +11589,6 @@ class A2uiSliderComponent {
11011
11589
  maxValue = input(100, ...(ngDevMode ? [{ debugName: "maxValue" }] : []));
11012
11590
  step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
11013
11591
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11014
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11015
11592
  // Framework inputs required by the render harness.
11016
11593
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11017
11594
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11019,10 +11596,10 @@ class A2uiSliderComponent {
11019
11596
  spec = input(undefined, ...(ngDevMode ? [{ debugName: "spec" }] : []));
11020
11597
  onInput(event) {
11021
11598
  const val = Number(event.target.value);
11022
- emitBinding(this.emit(), this._bindings(), 'value', val);
11599
+ emitBinding(this.host, this._bindings(), 'value', val);
11023
11600
  }
11024
11601
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11025
- 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: `
11602
+ 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: `
11026
11603
  <div class="a2ui-slider">
11027
11604
  @if (label()) {
11028
11605
  <label [htmlFor]="_inputId" class="a2ui-slider__label">{{ label() }}: {{ value() }}</label>
@@ -11059,7 +11636,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11059
11636
  />
11060
11637
  </div>
11061
11638
  `, 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"] }]
11062
- }], 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 }] }] } });
11639
+ }], 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 }] }] } });
11063
11640
 
11064
11641
  // SPDX-License-Identifier: MIT
11065
11642
  class A2uiTabsComponent {
@@ -11166,6 +11743,7 @@ const TYPE_MAP = {
11166
11743
  class A2uiTextFieldComponent {
11167
11744
  static _idCounter = 0;
11168
11745
  _inputId = `a2ui-text-field-${++A2uiTextFieldComponent._idCounter}`;
11746
+ host = injectRenderHost();
11169
11747
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11170
11748
  /** v1 prop: text (resolved string value). */
11171
11749
  text = input('', ...(ngDevMode ? [{ debugName: "text" }] : []));
@@ -11175,7 +11753,6 @@ class A2uiTextFieldComponent {
11175
11753
  textFieldType = input('shortText', ...(ngDevMode ? [{ debugName: "textFieldType" }] : []));
11176
11754
  validationRegexp = input('', ...(ngDevMode ? [{ debugName: "validationRegexp" }] : []));
11177
11755
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11178
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11179
11756
  // Framework inputs required by the render harness.
11180
11757
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11181
11758
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11187,14 +11764,14 @@ class A2uiTextFieldComponent {
11187
11764
  // Emit on 'text' binding (v1 prop name); also try 'value' for compat.
11188
11765
  const bound = this._bindings();
11189
11766
  if (bound['text']) {
11190
- emitBinding(this.emit(), bound, 'text', val);
11767
+ emitBinding(this.host, bound, 'text', val);
11191
11768
  }
11192
11769
  else {
11193
- emitBinding(this.emit(), bound, 'value', val);
11770
+ emitBinding(this.host, bound, 'value', val);
11194
11771
  }
11195
11772
  }
11196
11773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiTextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11197
- 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: `
11774
+ 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: `
11198
11775
  <div class="a2ui-tf">
11199
11776
  @if (label()) {
11200
11777
  <label [htmlFor]="_inputId" class="a2ui-tf__label">{{ label() }}</label>
@@ -11251,7 +11828,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11251
11828
  }
11252
11829
  </div>
11253
11830
  `, 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"] }]
11254
- }], 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 }] }] } });
11831
+ }], 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 }] }] } });
11255
11832
 
11256
11833
  // SPDX-License-Identifier: MIT
11257
11834
  class A2uiVideoComponent {
@@ -11311,6 +11888,23 @@ function a2uiBasicCatalog() {
11311
11888
  });
11312
11889
  }
11313
11890
 
11891
+ /** Async function tool — its resolved return value becomes the tool result. */
11892
+ function action(description, schema, handler) {
11893
+ return { kind: 'function', description, schema, handler };
11894
+ }
11895
+ /** Render-only component tool — the model fills its props; auto-acknowledged. */
11896
+ function view(description, schema, component) {
11897
+ return { kind: 'view', description, schema, component };
11898
+ }
11899
+ /** Interactive (HITL) component tool — the value it emits becomes the result. */
11900
+ function ask(description, schema, component) {
11901
+ return { kind: 'ask', description, schema, component };
11902
+ }
11903
+ /** Collect named client tools into a frozen registry (the key is the tool name). */
11904
+ function tools(map) {
11905
+ return Object.freeze({ ...map });
11906
+ }
11907
+
11314
11908
  // SPDX-License-Identifier: MIT
11315
11909
  function mockAgent(opts = {}) {
11316
11910
  const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
@@ -11362,5 +11956,5 @@ function mockAgent(opts = {}) {
11362
11956
  * Generated bundle index. Do not edit.
11363
11957
  */
11364
11958
 
11365
- 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, 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, 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 };
11959
+ export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, CHAT_CONFIG, CHAT_LIFECYCLE, CHAT_MARKDOWN_STYLES, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, ICON_AGENT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CHEVRON_UP, ICON_SEND, ICON_TOOL, ICON_WARNING, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createClientToolsCoordinator, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveJsonSchema, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, getInterrupt, getMessageType, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, surfaceToSpec, toClientToolSpecs, tools, validateArgs, view };
11366
11960
  //# sourceMappingURL=threadplane-chat.mjs.map