@threadplane/chat 0.0.49 → 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';
5
+ import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
6
6
  export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
7
- import * as i1 from '@angular/forms';
8
- import { FormsModule } from '@angular/forms';
9
7
  import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
10
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>
@@ -4342,6 +4351,54 @@ const CHAT_GENERATIVE_UI_STYLES = `
4342
4351
  }
4343
4352
  `;
4344
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
+
4345
4402
  // libs/chat/src/lib/primitives/chat-generative-ui/chat-generative-ui.component.ts
4346
4403
  // SPDX-License-Identifier: MIT
4347
4404
  class ChatGenerativeUiComponent {
@@ -4351,11 +4408,58 @@ class ChatGenerativeUiComponent {
4351
4408
  handlers = input(undefined, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
4352
4409
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
4353
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
+ }
4354
4458
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4355
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: `
4356
- @if (spec()) {
4460
+ @if (normalizedSpec()) {
4357
4461
  <render-spec
4358
- [spec]="spec()"
4462
+ [spec]="normalizedSpec()"
4359
4463
  [registry]="registry()"
4360
4464
  [store]="store()"
4361
4465
  [handlers]="handlers()"
@@ -4368,9 +4472,9 @@ class ChatGenerativeUiComponent {
4368
4472
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatGenerativeUiComponent, decorators: [{
4369
4473
  type: Component,
4370
4474
  args: [{ selector: 'chat-generative-ui', standalone: true, imports: [RenderSpecComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4371
- @if (spec()) {
4475
+ @if (normalizedSpec()) {
4372
4476
  <render-spec
4373
- [spec]="spec()"
4477
+ [spec]="normalizedSpec()"
4374
4478
  [registry]="registry()"
4375
4479
  [store]="store()"
4376
4480
  [handlers]="handlers()"
@@ -4379,7 +4483,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4379
4483
  />
4380
4484
  }
4381
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"] }]
4382
- }], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], events: [{ type: i0.Output, args: ["events"] }] } });
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"] }] } });
4383
4487
 
4384
4488
  // libs/chat/src/lib/primitives/chat-tool-views/chat-tool-views.component.ts
4385
4489
  // SPDX-License-Identifier: MIT
@@ -4398,6 +4502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4398
4502
  */
4399
4503
  class ChatToolViewsComponent {
4400
4504
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
4505
+ events = output();
4401
4506
  message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
4402
4507
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
4403
4508
  store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
@@ -4416,7 +4521,7 @@ class ChatToolViewsComponent {
4416
4521
  .map((tc) => ({ id: tc.id, loading: tc.status === 'running', spec: toToolViewSpec(tc) }));
4417
4522
  }, ...(ngDevMode ? [{ debugName: "toolViews" }] : []));
4418
4523
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolViewsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4419
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatToolViewsComponent, isStandalone: true, selector: "chat-tool-views", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
4420
4525
  @for (view of toolViews(); track view.id) {
4421
4526
  <chat-generative-ui
4422
4527
  [spec]="view.spec"
@@ -4424,6 +4529,7 @@ class ChatToolViewsComponent {
4424
4529
  [store]="store()"
4425
4530
  [handlers]="handlers()"
4426
4531
  [loading]="view.loading"
4532
+ (events)="events.emit($event)"
4427
4533
  />
4428
4534
  }
4429
4535
  `, isInline: true, dependencies: [{ kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -4443,11 +4549,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
4443
4549
  [store]="store()"
4444
4550
  [handlers]="handlers()"
4445
4551
  [loading]="view.loading"
4552
+ (events)="events.emit($event)"
4446
4553
  />
4447
4554
  }
4448
4555
  `,
4449
4556
  }]
4450
- }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }] } });
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 }] }] } });
4451
4558
  /** Wraps a tool call into a synthetic single-element render spec. */
4452
4559
  function toToolViewSpec(tc) {
4453
4560
  const args = isRecord$2(tc.args) ? tc.args : {};
@@ -6420,6 +6527,143 @@ function provideChat(config) {
6420
6527
  // SPDX-License-Identifier: MIT
6421
6528
  const CHAT_LIFECYCLE = new InjectionToken('CHAT_LIFECYCLE');
6422
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
+
6423
6667
  const RESERVED_PROP_KEYS = new Set(['child', 'children', 'action', 'tabItems', 'entryPointChild', 'contentChild']);
6424
6668
  /** Pull the (single) component-type key + its props from a v1 ComponentDef wrapper. */
6425
6669
  function unwrapComponentDef(def) {
@@ -6485,9 +6729,8 @@ function surfaceToSpec(surface) {
6485
6729
  // snapshot taken at conversion time and never reflects user
6486
6730
  // input writes back into the store. The `_bindings` map below
6487
6731
  // tells the catalog component which prop names map to which
6488
- // paths so its emit() callback can write back via the
6489
- // a2ui:datamodel:<path>:<value> magic-string protocol that
6490
- // render-element's emitFn intercepts.
6732
+ // paths so its injectRenderHost().set(path, value) call can
6733
+ // write the typed value back to the render state store.
6491
6734
  const path = value.path;
6492
6735
  bindings[key] = path;
6493
6736
  resolvedProps[key] = { $bindState: path };
@@ -7926,6 +8169,14 @@ function isPinned(scrollHeight, scrollTop, clientHeight, tolerance = 150) {
7926
8169
  class ChatComponent {
7927
8170
  agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
7928
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" }] : []));
7929
8180
  store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
7930
8181
  handlers = input({}, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
7931
8182
  threads = input([], ...(ngDevMode ? [{ debugName: "threads" }] : []));
@@ -7982,18 +8233,45 @@ class ChatComponent {
7982
8233
  const explicit = this.store();
7983
8234
  if (explicit)
7984
8235
  return explicit;
7985
- 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())
7986
8239
  return this._internalStore;
7987
8240
  return undefined;
7988
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" }] : []));
7989
8266
  renderRegistry = computed(() => {
7990
8267
  const v = this.views();
7991
8268
  return v ? toRenderRegistry(v) : undefined;
7992
8269
  }, ...(ngDevMode ? [{ debugName: "renderRegistry" }] : []));
7993
- /** Tool names that have a registered view (keys of the `views` registry).
7994
- * These render as inline tool-views and are excluded from the default
7995
- * tool-call card so they don't render twice. */
7996
- viewToolNames = computed(() => Object.keys(this.views() ?? {}), ...(ngDevMode ? [{ debugName: "viewToolNames" }] : []));
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" }] : []));
7997
8275
  /** Union of GenUI dispatcher tool names and registered view tool names. */
7998
8276
  excludedToolNames = computed(() => [
7999
8277
  ...this.genuiToolNames(),
@@ -8040,6 +8318,7 @@ class ChatComponent {
8040
8318
  }
8041
8319
  classifiers = new Map();
8042
8320
  destroyRef = inject(DestroyRef);
8321
+ injector = inject(Injector);
8043
8322
  // Resolved against the component's own `providers` in normal use. The fallback
8044
8323
  // is for tests that construct ChatComponent via `new` inside a bare injection
8045
8324
  // context (no element injector, so component-level providers are skipped).
@@ -8261,6 +8540,35 @@ class ChatComponent {
8261
8540
  }
8262
8541
  this.partialEventsLastIndex = events.length;
8263
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
+ });
8264
8572
  effect(() => {
8265
8573
  // janitor: drop classifiers for messages no longer in the agent's list
8266
8574
  let liveIds;
@@ -8478,6 +8786,25 @@ class ChatComponent {
8478
8786
  onSpecEvent(event, messageIndex) {
8479
8787
  this.renderEvent.emit({ messageIndex, event });
8480
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
+ }
8481
8808
  onA2uiAction(message) {
8482
8809
  void this.agent().submit({ message: JSON.stringify(message) });
8483
8810
  }
@@ -8498,7 +8825,7 @@ class ChatComponent {
8498
8825
  this.messageCopy.emit({ messageIndex: idx, content });
8499
8826
  }
8500
8827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8501
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatComponent, isStandalone: true, selector: "chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, activeThreadId: { classPropertyName: "activeThreadId", publicName: "activeThreadId", isSignal: true, isRequired: false, transformFunction: null }, welcomeDisabled: { classPropertyName: "welcomeDisabled", publicName: "welcomeDisabled", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, modelPickerPlaceholder: { classPropertyName: "modelPickerPlaceholder", publicName: "modelPickerPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, genuiToolNames: { classPropertyName: "genuiToolNames", publicName: "genuiToolNames", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", threadSelected: "threadSelected", renderEvent: "renderEvent", regenerate: "regenerate", rate: "rate", messageCopy: "messageCopy" }, providers: [
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: [
8502
8829
  { provide: CHAT_LIFECYCLE, useFactory: createChatLifecycle },
8503
8830
  ], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
8504
8831
  @if (showWelcome()) {
@@ -8563,19 +8890,26 @@ class ChatComponent {
8563
8890
  <chat-tool-views
8564
8891
  [agent]="agent()"
8565
8892
  [message]="message"
8566
- [views]="views()"
8893
+ [views]="effectiveViews()"
8567
8894
  [store]="resolvedStore()"
8568
8895
  [handlers]="handlers()"
8896
+ (events)="onClientToolEvent($event)"
8569
8897
  />
8570
8898
  <chat-subagents [agent]="agent()" />
8571
8899
  @if (classified.markdown(); as md) {
8572
8900
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8573
8901
  }
8574
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). -->
8575
8909
  <chat-generative-ui
8576
8910
  [spec]="spec"
8577
8911
  [registry]="renderRegistry()"
8578
- [store]="resolvedStore()"
8912
+ [store]="store()"
8579
8913
  [handlers]="handlers()"
8580
8914
  [loading]="agent().isLoading()"
8581
8915
  (events)="onSpecEvent($event, i)"
@@ -8649,7 +8983,7 @@ class ChatComponent {
8649
8983
  </div>
8650
8984
  </div>
8651
8985
  }
8652
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"] }, { kind: "component", type: ChatSubagentsComponent, selector: "chat-subagents", inputs: ["agent"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
8653
8987
  }
8654
8988
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
8655
8989
  type: Component,
@@ -8726,19 +9060,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8726
9060
  <chat-tool-views
8727
9061
  [agent]="agent()"
8728
9062
  [message]="message"
8729
- [views]="views()"
9063
+ [views]="effectiveViews()"
8730
9064
  [store]="resolvedStore()"
8731
9065
  [handlers]="handlers()"
9066
+ (events)="onClientToolEvent($event)"
8732
9067
  />
8733
9068
  <chat-subagents [agent]="agent()" />
8734
9069
  @if (classified.markdown(); as md) {
8735
9070
  <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
8736
9071
  }
8737
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). -->
8738
9079
  <chat-generative-ui
8739
9080
  [spec]="spec"
8740
9081
  [registry]="renderRegistry()"
8741
- [store]="resolvedStore()"
9082
+ [store]="store()"
8742
9083
  [handlers]="handlers()"
8743
9084
  [loading]="agent().isLoading()"
8744
9085
  (events)="onSpecEvent($event, i)"
@@ -8813,7 +9154,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8813
9154
  </div>
8814
9155
  }
8815
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"] }]
8816
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], welcomeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeDisabled", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], modelPickerPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPickerPlaceholder", required: false }] }], genuiToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "genuiToolNames", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], renderEvent: [{ type: i0.Output, args: ["renderEvent"] }], regenerate: [{ type: i0.Output, args: ["regenerate"] }], rate: [{ type: i0.Output, args: ["rate"] }], messageCopy: [{ type: i0.Output, args: ["messageCopy"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
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 }] }] } });
8817
9158
 
8818
9159
  // libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts
8819
9160
  // SPDX-License-Identifier: MIT
@@ -8823,6 +9164,8 @@ class ChatPopupComponent {
8823
9164
  * messages classified as A2UI parse correctly but never mount a
8824
9165
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8825
9166
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9167
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9168
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8826
9169
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8827
9170
  * renders in the chat-input chrome. */
8828
9171
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8877,7 +9220,7 @@ class ChatPopupComponent {
8877
9220
  openWindow() { this.open.set(true); }
8878
9221
  closeWindow() { this.open.set(false); }
8879
9222
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8880
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatPopupComponent, isStandalone: true, selector: "chat-popup", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, ngImport: i0, template: `
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: `
8881
9224
  <div class="chat-popup__launcher">
8882
9225
  <chat-launcher-button (clicked)="toggle()" />
8883
9226
  </div>
@@ -8888,6 +9231,7 @@ class ChatPopupComponent {
8888
9231
  <chat
8889
9232
  [agent]="agent()"
8890
9233
  [views]="views()"
9234
+ [clientTools]="clientTools()"
8891
9235
  [modelOptions]="modelOptions()"
8892
9236
  [showModelPicker]="showModelPicker()"
8893
9237
  [selectedModel]="selectedModel()"
@@ -8897,7 +9241,7 @@ class ChatPopupComponent {
8897
9241
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
8898
9242
  </chat>
8899
9243
  </div>
8900
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--ngaf-chat-bg);border:1px solid var(--ngaf-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
8901
9245
  }
8902
9246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, decorators: [{
8903
9247
  type: Component,
@@ -8912,6 +9256,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8912
9256
  <chat
8913
9257
  [agent]="agent()"
8914
9258
  [views]="views()"
9259
+ [clientTools]="clientTools()"
8915
9260
  [modelOptions]="modelOptions()"
8916
9261
  [showModelPicker]="showModelPicker()"
8917
9262
  [selectedModel]="selectedModel()"
@@ -8922,7 +9267,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
8922
9267
  </chat>
8923
9268
  </div>
8924
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"] }]
8925
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }] } });
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 }] }] } });
8926
9271
 
8927
9272
  // libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts
8928
9273
  // SPDX-License-Identifier: MIT
@@ -8932,6 +9277,8 @@ class ChatSidebarComponent {
8932
9277
  * messages classified as A2UI parse correctly but never mount a
8933
9278
  * surface. Pass `a2uiBasicCatalog()` from `@threadplane/chat`. */
8934
9279
  views = input(undefined, ...(ngDevMode ? [{ debugName: "views" }] : []));
9280
+ /** Frontend-declared client tools forwarded to the inner `<chat>`. */
9281
+ clientTools = input(undefined, ...(ngDevMode ? [{ debugName: "clientTools" }] : []));
8935
9282
  /** Forwarded to the inner <chat>. When non-empty, a model picker pill
8936
9283
  * renders in the chat-input chrome. */
8937
9284
  modelOptions = input([], ...(ngDevMode ? [{ debugName: "modelOptions" }] : []));
@@ -8985,7 +9332,7 @@ class ChatSidebarComponent {
8985
9332
  openWindow() { this.open.set(true); }
8986
9333
  closeWindow() { this.open.set(false); }
8987
9334
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8988
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatSidebarComponent, isStandalone: true, selector: "chat-sidebar", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, pushContent: { classPropertyName: "pushContent", publicName: "pushContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", open: "openChange" }, host: { properties: { "attr.data-push": "pushContent() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"true\" : \"false\"" } }, ngImport: i0, template: `
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: `
8989
9336
  <div class="chat-sidebar__content"><ng-content /></div>
8990
9337
  <div class="chat-sidebar__launcher">
8991
9338
  <chat-launcher-button (clicked)="toggle()" />
@@ -9002,6 +9349,7 @@ class ChatSidebarComponent {
9002
9349
  <chat
9003
9350
  [agent]="agent()"
9004
9351
  [views]="views()"
9352
+ [clientTools]="clientTools()"
9005
9353
  [modelOptions]="modelOptions()"
9006
9354
  [showModelPicker]="showModelPicker()"
9007
9355
  [selectedModel]="selectedModel()"
@@ -9011,7 +9359,7 @@ class ChatSidebarComponent {
9011
9359
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
9012
9360
  </chat>
9013
9361
  </aside>
9014
- `, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-sidebar__content{transition:margin-right .3s ease;min-height:100vh}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:28rem}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--ngaf-chat-debug-claim-bottom, 0);width:28rem;background:var(--ngaf-chat-bg);border-left:1px solid var(--ngaf-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--ngaf-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--ngaf-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--ngaf-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--ngaf-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--ngaf-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
9015
9363
  }
9016
9364
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, decorators: [{
9017
9365
  type: Component,
@@ -9035,6 +9383,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
9035
9383
  <chat
9036
9384
  [agent]="agent()"
9037
9385
  [views]="views()"
9386
+ [clientTools]="clientTools()"
9038
9387
  [modelOptions]="modelOptions()"
9039
9388
  [showModelPicker]="showModelPicker()"
9040
9389
  [selectedModel]="selectedModel()"
@@ -9045,7 +9394,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
9045
9394
  </chat>
9046
9395
  </aside>
9047
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"] }]
9048
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], pushContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pushContent", required: false }] }] } });
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 }] }] } });
9049
9398
 
9050
9399
  // libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
9051
9400
  // SPDX-License-Identifier: MIT
@@ -10615,24 +10964,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10615
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"] }]
10616
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 }] }] } });
10617
10966
 
10618
- // SPDX-License-Identifier: MIT
10619
- /** Emits a data model binding event if the prop has a binding path. */
10620
- function emitBinding(emit, bindings, prop, value) {
10967
+ /** Writes a typed value to the render state store if the prop has a binding path. */
10968
+ function emitBinding(host, bindings, prop, value) {
10621
10969
  const path = bindings?.[prop];
10622
10970
  if (path) {
10623
- emit(`a2ui:datamodel:${path}:${value}`);
10971
+ host.set(path, value);
10624
10972
  }
10625
10973
  }
10626
10974
 
10627
10975
  // SPDX-License-Identifier: MIT
10628
10976
  class A2uiCheckBoxComponent {
10977
+ host = injectRenderHost();
10629
10978
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10630
10979
  /** v1 canonical prop: boolean checked state. */
10631
10980
  value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
10632
10981
  /** Pre-v1 alias retained for back-compat. */
10633
10982
  checked = input(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
10634
10983
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10635
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10636
10984
  // Framework inputs required by the render harness.
10637
10985
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10638
10986
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10645,14 +10993,14 @@ class A2uiCheckBoxComponent {
10645
10993
  // `value`; pre-v1 used `checked`.
10646
10994
  const bound = this._bindings();
10647
10995
  if (bound['value']) {
10648
- emitBinding(this.emit(), bound, 'value', val);
10996
+ emitBinding(this.host, bound, 'value', val);
10649
10997
  }
10650
10998
  else {
10651
- emitBinding(this.emit(), bound, 'checked', val);
10999
+ emitBinding(this.host, bound, 'checked', val);
10652
11000
  }
10653
11001
  }
10654
11002
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiCheckBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10655
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: A2uiCheckBoxComponent, isStandalone: true, selector: "a2ui-check-box", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
10656
11004
  <label class="a2ui-cb">
10657
11005
  <input type="checkbox" [checked]="effectiveValue()" (change)="onChange($event)" class="a2ui-cb__input" />
10658
11006
  {{ label() }}
@@ -10667,7 +11015,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10667
11015
  {{ label() }}
10668
11016
  </label>
10669
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"] }]
10670
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
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 }] }] } });
10671
11019
 
10672
11020
  // SPDX-License-Identifier: MIT
10673
11021
  const ALIGN_MAP = {
@@ -10710,6 +11058,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10710
11058
  class A2uiDateTimeInputComponent {
10711
11059
  static _idCounter = 0;
10712
11060
  _inputId = `a2ui-date-time-input-${++A2uiDateTimeInputComponent._idCounter}`;
11061
+ host = injectRenderHost();
10713
11062
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
10714
11063
  /** v1 prop: value (resolved DynamicString). */
10715
11064
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -10718,7 +11067,6 @@ class A2uiDateTimeInputComponent {
10718
11067
  /** v1 prop: enableTime — include time portion. */
10719
11068
  enableTime = input(false, ...(ngDevMode ? [{ debugName: "enableTime" }] : []));
10720
11069
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
10721
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
10722
11070
  // Framework inputs required by the render harness.
10723
11071
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
10724
11072
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -10736,10 +11084,10 @@ class A2uiDateTimeInputComponent {
10736
11084
  }, ...(ngDevMode ? [{ debugName: "htmlInputType" }] : []));
10737
11085
  onChange(event) {
10738
11086
  const val = event.target.value;
10739
- emitBinding(this.emit(), this._bindings(), 'value', val);
11087
+ emitBinding(this.host, this._bindings(), 'value', val);
10740
11088
  }
10741
11089
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiDateTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10742
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiDateTimeInputComponent, isStandalone: true, selector: "a2ui-date-time-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, enableDate: { classPropertyName: "enableDate", publicName: "enableDate", isSignal: true, isRequired: false, transformFunction: null }, enableTime: { classPropertyName: "enableTime", publicName: "enableTime", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
10743
11091
  <div class="a2ui-dti">
10744
11092
  @if (label()) {
10745
11093
  <label [htmlFor]="_inputId" class="a2ui-dti__label">{{ label() }}</label>
@@ -10770,7 +11118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10770
11118
  />
10771
11119
  </div>
10772
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"] }]
10773
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], enableDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableDate", required: false }] }], enableTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTime", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
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 }] }] } });
10774
11122
 
10775
11123
  // SPDX-License-Identifier: MIT
10776
11124
  class A2uiDividerComponent {
@@ -11061,6 +11409,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11061
11409
 
11062
11410
  // SPDX-License-Identifier: MIT
11063
11411
  class A2uiMultipleChoiceComponent {
11412
+ host = injectRenderHost();
11064
11413
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11065
11414
  /** Resolved current selections from surface-to-spec. Normalized in
11066
11415
  * `selectionsArray` because LLMs sometimes seed the data model with a
@@ -11080,7 +11429,6 @@ class A2uiMultipleChoiceComponent {
11080
11429
  /** When ≤ 1 — render as single-select <select>; otherwise multi-select checkboxes. */
11081
11430
  maxAllowedSelections = input(1, ...(ngDevMode ? [{ debugName: "maxAllowedSelections" }] : []));
11082
11431
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11083
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11084
11432
  // Framework inputs required by the render harness.
11085
11433
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11086
11434
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11092,7 +11440,7 @@ class A2uiMultipleChoiceComponent {
11092
11440
  }
11093
11441
  onSelectChange(event) {
11094
11442
  const val = event.target.value;
11095
- emitBinding(this.emit(), this._bindings(), 'selections', val);
11443
+ emitBinding(this.host, this._bindings(), 'selections', val);
11096
11444
  }
11097
11445
  onCheckChange(value, event) {
11098
11446
  const checked = event.target.checked;
@@ -11104,11 +11452,11 @@ class A2uiMultipleChoiceComponent {
11104
11452
  else if (!checked && idx !== -1) {
11105
11453
  current.splice(idx, 1);
11106
11454
  }
11107
- // Emit the updated selections array as a JSON string so emitBinding can convey it.
11108
- emitBinding(this.emit(), this._bindings(), 'selections', JSON.stringify(current));
11455
+ // Pass the updated selections array directly (typed value, no JSON stringification needed).
11456
+ emitBinding(this.host, this._bindings(), 'selections', current);
11109
11457
  }
11110
11458
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiMultipleChoiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11111
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiMultipleChoiceComponent, isStandalone: true, selector: "a2ui-multiple-choice", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxAllowedSelections: { classPropertyName: "maxAllowedSelections", publicName: "maxAllowedSelections", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
11112
11460
  <div class="a2ui-mc">
11113
11461
  @if (label()) {
11114
11462
  <span class="a2ui-mc__label">{{ label() }}</span>
@@ -11173,7 +11521,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11173
11521
  }
11174
11522
  </div>
11175
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"] }]
11176
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], selections: [{ type: i0.Input, args: [{ isSignal: true, alias: "selections", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], maxAllowedSelections: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxAllowedSelections", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
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 }] }] } });
11177
11525
 
11178
11526
  // SPDX-License-Identifier: MIT
11179
11527
  const ROW_ALIGN_MAP = {
@@ -11231,6 +11579,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11231
11579
  class A2uiSliderComponent {
11232
11580
  static _idCounter = 0;
11233
11581
  _inputId = `a2ui-slider-${++A2uiSliderComponent._idCounter}`;
11582
+ host = injectRenderHost();
11234
11583
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11235
11584
  /** v1 prop: value (resolved DynamicNumber). */
11236
11585
  value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
@@ -11240,7 +11589,6 @@ class A2uiSliderComponent {
11240
11589
  maxValue = input(100, ...(ngDevMode ? [{ debugName: "maxValue" }] : []));
11241
11590
  step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
11242
11591
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11243
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11244
11592
  // Framework inputs required by the render harness.
11245
11593
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11246
11594
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11248,10 +11596,10 @@ class A2uiSliderComponent {
11248
11596
  spec = input(undefined, ...(ngDevMode ? [{ debugName: "spec" }] : []));
11249
11597
  onInput(event) {
11250
11598
  const val = Number(event.target.value);
11251
- emitBinding(this.emit(), this._bindings(), 'value', val);
11599
+ emitBinding(this.host, this._bindings(), 'value', val);
11252
11600
  }
11253
11601
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11254
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiSliderComponent, isStandalone: true, selector: "a2ui-slider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
11255
11603
  <div class="a2ui-slider">
11256
11604
  @if (label()) {
11257
11605
  <label [htmlFor]="_inputId" class="a2ui-slider__label">{{ label() }}: {{ value() }}</label>
@@ -11288,7 +11636,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11288
11636
  />
11289
11637
  </div>
11290
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"] }]
11291
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], minValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "minValue", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
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 }] }] } });
11292
11640
 
11293
11641
  // SPDX-License-Identifier: MIT
11294
11642
  class A2uiTabsComponent {
@@ -11395,6 +11743,7 @@ const TYPE_MAP = {
11395
11743
  class A2uiTextFieldComponent {
11396
11744
  static _idCounter = 0;
11397
11745
  _inputId = `a2ui-text-field-${++A2uiTextFieldComponent._idCounter}`;
11746
+ host = injectRenderHost();
11398
11747
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
11399
11748
  /** v1 prop: text (resolved string value). */
11400
11749
  text = input('', ...(ngDevMode ? [{ debugName: "text" }] : []));
@@ -11404,7 +11753,6 @@ class A2uiTextFieldComponent {
11404
11753
  textFieldType = input('shortText', ...(ngDevMode ? [{ debugName: "textFieldType" }] : []));
11405
11754
  validationRegexp = input('', ...(ngDevMode ? [{ debugName: "validationRegexp" }] : []));
11406
11755
  _bindings = input({}, ...(ngDevMode ? [{ debugName: "_bindings" }] : []));
11407
- emit = input(() => { }, ...(ngDevMode ? [{ debugName: "emit" }] : []));
11408
11756
  // Framework inputs required by the render harness.
11409
11757
  bindings = input({}, ...(ngDevMode ? [{ debugName: "bindings" }] : []));
11410
11758
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
@@ -11416,14 +11764,14 @@ class A2uiTextFieldComponent {
11416
11764
  // Emit on 'text' binding (v1 prop name); also try 'value' for compat.
11417
11765
  const bound = this._bindings();
11418
11766
  if (bound['text']) {
11419
- emitBinding(this.emit(), bound, 'text', val);
11767
+ emitBinding(this.host, bound, 'text', val);
11420
11768
  }
11421
11769
  else {
11422
- emitBinding(this.emit(), bound, 'value', val);
11770
+ emitBinding(this.host, bound, 'value', val);
11423
11771
  }
11424
11772
  }
11425
11773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiTextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11426
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiTextFieldComponent, isStandalone: true, selector: "a2ui-text-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, textFieldType: { classPropertyName: "textFieldType", publicName: "textFieldType", isSignal: true, isRequired: false, transformFunction: null }, validationRegexp: { classPropertyName: "validationRegexp", publicName: "validationRegexp", isSignal: true, isRequired: false, transformFunction: null }, _bindings: { classPropertyName: "_bindings", publicName: "_bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
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: `
11427
11775
  <div class="a2ui-tf">
11428
11776
  @if (label()) {
11429
11777
  <label [htmlFor]="_inputId" class="a2ui-tf__label">{{ label() }}</label>
@@ -11480,7 +11828,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
11480
11828
  }
11481
11829
  </div>
11482
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"] }]
11483
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], textFieldType: [{ type: i0.Input, args: [{ isSignal: true, alias: "textFieldType", required: false }] }], validationRegexp: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationRegexp", required: false }] }], _bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "_bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
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 }] }] } });
11484
11832
 
11485
11833
  // SPDX-License-Identifier: MIT
11486
11834
  class A2uiVideoComponent {
@@ -11540,6 +11888,23 @@ function a2uiBasicCatalog() {
11540
11888
  });
11541
11889
  }
11542
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
+
11543
11908
  // SPDX-License-Identifier: MIT
11544
11909
  function mockAgent(opts = {}) {
11545
11910
  const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
@@ -11591,5 +11956,5 @@ function mockAgent(opts = {}) {
11591
11956
  * Generated bundle index. Do not edit.
11592
11957
  */
11593
11958
 
11594
- export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, CHAT_CONFIG, CHAT_LIFECYCLE, CHAT_MARKDOWN_STYLES, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, ICON_AGENT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CHEVRON_UP, ICON_SEND, ICON_TOOL, ICON_WARNING, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createContentClassifier, createParseTreeStore, createPartialArgsBridge, emitBinding, extractErrorMessage, formatDuration, getInterrupt, getMessageType, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, statusColor, submitMessage, surfaceToSpec };
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 };
11595
11960
  //# sourceMappingURL=threadplane-chat.mjs.map