@genesislcap/ai-assistant 14.495.0 → 14.496.0

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.
Files changed (36) hide show
  1. package/dist/ai-assistant.api.json +576 -211
  2. package/dist/ai-assistant.d.ts +128 -5
  3. package/dist/chat-driver.cjs +5608 -0
  4. package/dist/chat-driver.cjs.map +7 -0
  5. package/dist/chat-driver.mjs +5566 -0
  6. package/dist/chat-driver.mjs.map +7 -0
  7. package/dist/custom-elements.json +306 -2
  8. package/dist/dts/channel/ai-activity-bus.d.ts +36 -0
  9. package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
  10. package/dist/dts/chat-driver-node.d.ts +28 -0
  11. package/dist/dts/chat-driver-node.d.ts.map +1 -0
  12. package/dist/dts/components/chat-driver/chat-driver.d.ts +49 -5
  13. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  14. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +3 -0
  15. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  16. package/dist/dts/config/config.d.ts +37 -2
  17. package/dist/dts/config/config.d.ts.map +1 -1
  18. package/dist/dts/main/main.d.ts.map +1 -1
  19. package/dist/esm/channel/ai-activity-bus.js +48 -12
  20. package/dist/esm/chat-driver-node.js +33 -0
  21. package/dist/esm/components/chat-driver/chat-driver.js +28 -14
  22. package/dist/esm/components/chat-driver/chat-driver.test.js +32 -40
  23. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +7 -1
  24. package/dist/esm/main/main.js +8 -1
  25. package/dist/esm/main/popout-interaction-gate.test.js +6 -10
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +29 -19
  28. package/scripts/build-chat-driver-node.mjs +42 -0
  29. package/src/channel/ai-activity-bus.ts +64 -10
  30. package/src/chat-driver-node.ts +54 -0
  31. package/src/components/chat-driver/chat-driver.test.ts +36 -62
  32. package/src/components/chat-driver/chat-driver.ts +96 -28
  33. package/src/components/orchestrating-driver/orchestrating-driver.ts +10 -11
  34. package/src/config/config.ts +40 -1
  35. package/src/main/main.ts +8 -11
  36. package/src/main/popout-interaction-gate.test.ts +6 -11
@@ -5,6 +5,7 @@ import type {
5
5
  CachePolicy,
6
6
  ChatAttachment,
7
7
  ChatDriverResult,
8
+ ChatFallback,
8
9
  ChatMessage,
9
10
  ChatRequestOptions,
10
11
  ChatToolCall,
@@ -24,11 +25,12 @@ import {
24
25
  MalformedFunctionCallError,
25
26
  ResponseTruncatedError,
26
27
  } from '@genesislcap/foundation-ai';
27
- import { agenticActivityBus } from '../../channel/ai-activity-bus';
28
+ import { type ActivityBus, NOOP_ACTIVITY_BUS } from '../../channel/ai-activity-bus';
28
29
  import type {
29
30
  AgentConfig,
30
31
  CachePolicyInput,
31
32
  ProviderInput,
33
+ ResponseSchemaInput,
32
34
  SystemPromptContext,
33
35
  SystemPromptInput,
34
36
  TailContextInput,
@@ -192,6 +194,39 @@ interface FoldStackFrame {
192
194
  previousHandlers: ChatToolHandlers;
193
195
  }
194
196
 
197
+ /**
198
+ * Construction-time configuration for {@link ChatDriver}. Everything except the provider
199
+ * registry is optional — most fields are also settable per-agent via `applyAgent`, so a
200
+ * bare `new ChatDriver(registry)` is valid. Mirrors the `(registry, options)` shape of
201
+ * `OrchestratingDriver`.
202
+ *
203
+ * @beta
204
+ */
205
+ export interface ChatDriverConfig {
206
+ /** Initial tool handlers (static map or per-turn factory). Default `{}`. */
207
+ toolHandlers?: ToolHandlersInput;
208
+ /** Initial tool definitions (static array or per-turn factory). Default `[]`. */
209
+ toolDefinitions?: ToolDefinitionsInput;
210
+ /** Initial system prompt (string or per-turn resolver). */
211
+ systemPrompt?: SystemPromptInput;
212
+ /** Primer history prepended to the conversation. */
213
+ primerHistory?: ChatMessage[];
214
+ /** Hard cap on tool-loop iterations. Default `50`. */
215
+ maxToolIterations?: number;
216
+ /** Hard cap on fold operations. Default `5`. */
217
+ maxFoldOperations?: number;
218
+ /** Ring-buffer size for per-turn snapshots. Default `400`. */
219
+ maxTurnSnapshots?: number;
220
+ /** Session identity used to file meta events onto the shared debug-log timeline. */
221
+ sessionKey?: string;
222
+ /**
223
+ * Activity bus for lifecycle/halo/tool-loop events. Injected by the browser host
224
+ * (the shared cross-tab singleton); omitted off-browser (Node, tests, headless), where
225
+ * it defaults to {@link NOOP_ACTIVITY_BUS} so no `BroadcastChannel` is ever opened.
226
+ */
227
+ activityBus?: ActivityBus;
228
+ }
229
+
195
230
  /**
196
231
  * Plain TS class that drives a multi-turn chat conversation, including the tool-call loop.
197
232
  * Owned by `FoundationAiAssistant` — created in `connectedCallback`, torn down in `disconnectedCallback`.
@@ -454,6 +489,16 @@ export class ChatDriver extends EventTarget implements AiDriver {
454
489
  * the resolved string in a `<system-reminder>` marker and injects it at the message tail.
455
490
  */
456
491
  private activeTailContextInput?: TailContextInput;
492
+ /**
493
+ * Active agent's structured-output schema selector (static value or per-turn resolver).
494
+ * When it resolves to a schema, the model's final answer is constrained to it this turn.
495
+ */
496
+ private activeResponseSchemaInput?: ResponseSchemaInput;
497
+ /**
498
+ * Active agent's refusal-fallback chain (static). Passed through to the provider so a refused
499
+ * turn (e.g. Fable 5) is re-run on the next model server-side.
500
+ */
501
+ private activeFallbacks?: ChatFallback[];
457
502
  /**
458
503
  * Active agent's unresolved-tool hook, captured from `applyAgent`. Consulted
459
504
  * only when a tool call cannot be dispatched (a stale or hallucinated name);
@@ -499,19 +544,32 @@ export class ChatDriver extends EventTarget implements AiDriver {
499
544
  */
500
545
  private unsubscribeRegistry?: () => void;
501
546
 
547
+ /** Hard cap on tool-loop iterations. */
548
+ private readonly maxToolIterations: number;
549
+ /** Session identity used to file meta events onto the shared debug-log timeline. */
550
+ private readonly sessionKey: string;
551
+ /** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
552
+ private readonly activityBus: ActivityBus;
553
+
502
554
  constructor(
503
555
  private readonly providerRegistry: AIProviderRegistry,
504
- toolHandlers: ToolHandlersInput = {},
505
- toolDefinitions: ToolDefinitionsInput = [],
506
- systemPrompt?: SystemPromptInput,
507
- primerHistory?: ChatMessage[],
508
- private readonly maxToolIterations: number = DEFAULT_MAX_TOOL_ITERATIONS,
509
- maxFoldOperations: number = DEFAULT_MAX_FOLD_OPERATIONS,
510
- maxTurnSnapshots: number = DEFAULT_MAX_TURN_SNAPSHOTS,
511
- /** Session identity used to file meta events onto the shared debug-log timeline. */
512
- private readonly sessionKey: string = '',
556
+ config: ChatDriverConfig = {},
513
557
  ) {
514
558
  super();
559
+ const {
560
+ toolHandlers = {},
561
+ toolDefinitions = [],
562
+ systemPrompt,
563
+ primerHistory,
564
+ maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS,
565
+ maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
566
+ maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
567
+ sessionKey = '',
568
+ activityBus = NOOP_ACTIVITY_BUS,
569
+ } = config;
570
+ this.maxToolIterations = maxToolIterations;
571
+ this.sessionKey = sessionKey;
572
+ this.activityBus = activityBus;
515
573
  if (typeof toolHandlers === 'function') {
516
574
  this.toolHandlersFactory = toolHandlers;
517
575
  this.toolHandlers = {};
@@ -705,6 +763,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
705
763
  this.activeToolChoiceInput = config.toolChoice;
706
764
  this.activeCachePolicyInput = config.cachePolicy;
707
765
  this.activeTailContextInput = config.tailContext;
766
+ this.activeResponseSchemaInput = config.responseSchema;
767
+ this.activeFallbacks = config.fallbacks;
708
768
  this.activeOnUnresolvedTool = config.onUnresolvedTool;
709
769
  this.resolvedProviderCache.clear();
710
770
  this.lastResolvedProviderName = undefined;
@@ -1297,7 +1357,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1297
1357
  // "actively computing" from "parked awaiting the user" — the latter is a
1298
1358
  // safe window for actions disallowed mid-request (e.g. switching provider
1299
1359
  // during a long journey step). Paired with `interaction-resolved`.
1300
- agenticActivityBus.publish('interaction-requested', undefined);
1360
+ this.activityBus.publish('interaction-requested', undefined);
1301
1361
  if (chatInputDuringExecution) {
1302
1362
  this.dispatchEvent(
1303
1363
  new CustomEvent('interaction-start', {
@@ -1372,7 +1432,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1372
1432
  // The park is ending and the loop is about to resume computing — paired
1373
1433
  // with `interaction-requested`. Fires for every resolution path (user
1374
1434
  // completion, timeout, cancellation), since all route through here.
1375
- agenticActivityBus.publish('interaction-resolved', undefined);
1435
+ this.activityBus.publish('interaction-resolved', undefined);
1376
1436
  interaction.resolve(result);
1377
1437
  this.pendingInteractions.delete(interactionId);
1378
1438
  // Tear down the live context on RESOLVE (not on element unmount) — this closes
@@ -1440,7 +1500,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1440
1500
  phase: 'sendMessage',
1441
1501
  agent: this.activeAgentName,
1442
1502
  });
1443
- agenticActivityBus.publish('tool-loop-start', undefined);
1503
+ this.activityBus.publish('tool-loop-start', undefined);
1444
1504
 
1445
1505
  // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1446
1506
  let result: ChatDriverResult = { reason: 'done' };
@@ -1470,7 +1530,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1470
1530
  });
1471
1531
  this.busy = false;
1472
1532
  this.endTurn();
1473
- agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1533
+ this.activityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1474
1534
  }
1475
1535
  }
1476
1536
 
@@ -1636,17 +1696,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
1636
1696
  // be harvested into THIS session on completion and then discarded.
1637
1697
  const invocationId = crypto.randomUUID();
1638
1698
  const childSessionKey = `${this.sessionKey}::sub:${invocationId}`;
1639
- const child = new ChatDriver(
1640
- this.providerRegistry,
1641
- {},
1642
- [],
1643
- undefined,
1644
- undefined,
1645
- undefined,
1646
- undefined,
1647
- undefined,
1648
- childSessionKey,
1649
- );
1699
+ const child = new ChatDriver(this.providerRegistry, {
1700
+ sessionKey: childSessionKey,
1701
+ // Inherit the parent's bus so the sub-agent's tool-loop events still surface
1702
+ // (off-browser this is the shared no-op).
1703
+ activityBus: this.activityBus,
1704
+ });
1650
1705
  // Mark before the first turn so the child forces tool use and reports a
1651
1706
  // typed failure (rather than user-facing text) if it never completes.
1652
1707
  child.markAsSubAgent();
@@ -1815,7 +1870,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1815
1870
  phase: 'continueFromHistory',
1816
1871
  agent: this.activeAgentName,
1817
1872
  });
1818
- agenticActivityBus.publish('tool-loop-start', undefined);
1873
+ this.activityBus.publish('tool-loop-start', undefined);
1819
1874
  // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1820
1875
  let result: ChatDriverResult = { reason: 'done' };
1821
1876
  try {
@@ -1844,7 +1899,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1844
1899
  });
1845
1900
  this.busy = false;
1846
1901
  this.endTurn();
1847
- agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1902
+ this.activityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1848
1903
  }
1849
1904
  }
1850
1905
 
@@ -2178,13 +2233,20 @@ export class ChatDriver extends EventTarget implements AiDriver {
2178
2233
  // provider is resolved — static value or a function of the turn context
2179
2234
  // (which carries the live state for stateful agents). Resolved before the
2180
2235
  // snapshot so the debug log records the exact request config the model saw.
2181
- const [resolvedTemperature, resolvedToolChoice, resolvedCachePolicy, resolvedTailContext] =
2236
+ const [
2237
+ resolvedTemperature,
2238
+ resolvedToolChoice,
2239
+ resolvedCachePolicy,
2240
+ resolvedTailContext,
2241
+ resolvedResponseSchema,
2242
+ ] =
2182
2243
  // oxlint-disable-next-line no-await-in-loop
2183
2244
  await Promise.all([
2184
2245
  this.resolveTurnInput<number>(this.activeTemperatureInput, promptCtx),
2185
2246
  this.resolveTurnInput<ChatToolChoice>(this.activeToolChoiceInput, promptCtx),
2186
2247
  this.resolveTurnInput<CachePolicy>(this.activeCachePolicyInput, promptCtx),
2187
2248
  this.resolveTurnInput<string>(this.activeTailContextInput, promptCtx),
2249
+ this.resolveTurnInput<object | undefined>(this.activeResponseSchemaInput, promptCtx),
2188
2250
  ]);
2189
2251
  // The system prompt is always just the agent's resolved prompt — byte-stable, so it can be
2190
2252
  // cached. The framework's volatile additions (fold suffix, retry nudge) and the agent's tail
@@ -2235,6 +2297,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
2235
2297
  cachePolicy: resolvedCachePolicy,
2236
2298
  // Framed volatile context injected at the message tail (never stored). Undefined → none.
2237
2299
  tailContext,
2300
+ // Structured-output schema for this turn (agent/state-resolved). When set, the transport
2301
+ // constrains the final answer to it natively where the model supports it. Undefined → free text.
2302
+ responseSchema: resolvedResponseSchema,
2303
+ // Refusal-fallback chain (e.g. Fable 5 → Opus 4.8). Passed to the provider; applied
2304
+ // server-side where supported. Undefined → no fallback.
2305
+ fallbacks: this.activeFallbacks,
2238
2306
  };
2239
2307
 
2240
2308
  // Resolve the active provider for this turn. Static names were validated
@@ -5,6 +5,7 @@ import type {
5
5
  ChatMessage,
6
6
  ChatRequestOptions,
7
7
  } from '@genesislcap/foundation-ai';
8
+ import type { ActivityBus } from '../../channel/ai-activity-bus';
8
9
  import type {
9
10
  AgentConfig,
10
11
  FallbackAgentConfig,
@@ -137,6 +138,8 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
137
138
  maxToolIterations?: number;
138
139
  maxFoldOperations?: number;
139
140
  maxTurnSnapshots?: number;
141
+ /** Activity bus passed through to the inner ChatDriver (browser host injects the singleton). */
142
+ activityBus?: ActivityBus;
140
143
  } = {},
141
144
  ) {
142
145
  super();
@@ -166,17 +169,13 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
166
169
  ? { ...rawFallback, systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }
167
170
  : undefined;
168
171
 
169
- this.chatDriver = new ChatDriver(
170
- providerRegistry,
171
- {},
172
- [],
173
- undefined,
174
- undefined,
175
- options.maxToolIterations,
176
- options.maxFoldOperations,
177
- options.maxTurnSnapshots,
178
- this.sessionKey,
179
- );
172
+ this.chatDriver = new ChatDriver(providerRegistry, {
173
+ maxToolIterations: options.maxToolIterations,
174
+ maxFoldOperations: options.maxFoldOperations,
175
+ maxTurnSnapshots: options.maxTurnSnapshots,
176
+ sessionKey: this.sessionKey,
177
+ activityBus: options.activityBus,
178
+ });
180
179
 
181
180
  // Proxy events from the shared driver
182
181
  this.chatDriver.addEventListener('history-updated', (e: Event) => {
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  CachePolicy,
3
+ ChatFallback,
3
4
  ChatInputDuringExecutionMode,
4
5
  ChatMessage,
5
6
  ChatToolChoice,
@@ -7,7 +8,7 @@ import type {
7
8
  ChatToolHandlers,
8
9
  } from '@genesislcap/foundation-ai';
9
10
 
10
- export type { CachePolicy, ChatInputDuringExecutionMode, ChatToolChoice };
11
+ export type { CachePolicy, ChatFallback, ChatInputDuringExecutionMode, ChatToolChoice };
11
12
 
12
13
  /**
13
14
  * Context passed to `onActivate` / `onDeactivate` lifecycle hooks on an agent.
@@ -174,6 +175,25 @@ export type CachePolicyInput =
174
175
  */
175
176
  export type TailContextInput = string | ((ctx: SystemPromptContext) => string | Promise<string>);
176
177
 
178
+ /**
179
+ * Structured-output schema for an agent. When set, the model's final (non-tool) answer is
180
+ * constrained to this JSON Schema instead of free text. Either a static schema or a function
181
+ * resolved each tool-loop iteration — return the schema on the turn(s) it should apply and
182
+ * `undefined` otherwise, so "whole-loop" vs "finalize-turn" enforcement is just what the
183
+ * resolver returns (e.g. `({ state }) => state.machine.matches('finalizing') ? schema : undefined`).
184
+ *
185
+ * Orthogonal to {@link ToolChoiceInput}: an agent can carry both `tools` and a `responseSchema`.
186
+ * Each provider applies it natively where possible (Anthropic `output_config.format`, Gemini
187
+ * JSON mode) and degrades gracefully otherwise. The schema is a plain JSON Schema object; keep to
188
+ * the portable subset providers share (`additionalProperties: false`, explicit `required`, enums,
189
+ * `anyOf` for nullables — no numeric/string constraints or recursion).
190
+ *
191
+ * @beta
192
+ */
193
+ export type ResponseSchemaInput =
194
+ | object
195
+ | ((ctx: SystemPromptContext) => object | undefined | Promise<object | undefined>);
196
+
177
197
  /**
178
198
  * Context passed to an agent's `onUnresolvedTool` hook when the model calls a
179
199
  * tool the driver cannot dispatch.
@@ -337,6 +357,25 @@ interface BaseAgentConfig {
337
357
  * @beta
338
358
  */
339
359
  tailContext?: TailContextInput;
360
+ /**
361
+ * Structured-output schema for this agent. When resolved to a schema on a turn, the model's
362
+ * final (non-tool) answer is constrained to it instead of free text. Composes with `tools`;
363
+ * resolved per turn like {@link BaseAgentConfig.cachePolicy}, so returning `undefined` on
364
+ * working turns and the schema on the finalize turn gives finalize-turn enforcement for free.
365
+ * See {@link ResponseSchemaInput}.
366
+ *
367
+ * @beta
368
+ */
369
+ responseSchema?: ResponseSchemaInput;
370
+ /**
371
+ * Refusal-fallback chain for this agent (provider-neutral). If the model declines a turn
372
+ * (`stop_reason: 'refusal'` — e.g. Fable 5 safety classifiers), the provider re-runs it on the
373
+ * next listed model. Typical use: a Fable 5 agent with `[{ model: 'claude-opus-4-8' }]`. Static
374
+ * (not per-turn); providers that support it apply it server-side, others ignore it.
375
+ *
376
+ * @beta
377
+ */
378
+ fallbacks?: ChatFallback[];
340
379
  /**
341
380
  * Optional hook consulted when the model calls a tool the driver cannot
342
381
  * dispatch — either a *stale* tool (advertised earlier this activation but
package/src/main/main.ts CHANGED
@@ -1249,20 +1249,17 @@ export class FoundationAiAssistant extends GenesisElement {
1249
1249
  maxToolIterations: agent.maxToolIterations,
1250
1250
  maxFoldOperations: agent.maxFoldOperations,
1251
1251
  maxTurnSnapshots: agent.maxTurnSnapshots,
1252
+ activityBus: agenticActivityBus,
1252
1253
  });
1253
1254
  }
1254
1255
 
1255
- return new ChatDriver(
1256
- this.providerRegistry,
1257
- {},
1258
- [],
1259
- undefined,
1260
- undefined,
1261
- agent.maxToolIterations,
1262
- agent.maxFoldOperations,
1263
- agent.maxTurnSnapshots,
1264
- this.getStateKey() ?? '',
1265
- );
1256
+ return new ChatDriver(this.providerRegistry, {
1257
+ maxToolIterations: agent.maxToolIterations,
1258
+ maxFoldOperations: agent.maxFoldOperations,
1259
+ maxTurnSnapshots: agent.maxTurnSnapshots,
1260
+ sessionKey: this.getStateKey() ?? '',
1261
+ activityBus: agenticActivityBus,
1262
+ });
1266
1263
  }
1267
1264
 
1268
1265
  /**
@@ -1,6 +1,5 @@
1
1
  import type { ChatMessage } from '@genesislcap/foundation-ai';
2
2
  import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import { agenticActivityBus } from '../channel/ai-activity-bus';
4
3
  import { FoundationAiAssistant } from './main';
5
4
 
6
5
  // Hold a reference so the custom-element registration isn't tree-shaken.
@@ -14,19 +13,15 @@ FoundationAiAssistant;
14
13
  // (`main.template.ts`). This suite pins the GATE LOGIC (`hasActivePendingInteraction`)
15
14
  // that binding depends on, so a change to when the gate opens/closes is caught.
16
15
  //
17
- // Note: this deliberately does NOT mount the assistant. Mounting runs
18
- // `connectedCallback`, which activates the `agenticActivityBus` `BroadcastChannel` and
19
- // leaves the runner's event loop open (the suite hangs on exit). A getter-level test
20
- // needs no mount and stays fast + reliable. `document.createElement` only upgrades the
21
- // element (constructor) it does not connect so no bus subscription is created;
22
- // importing the module still opens the module-load channel, so close it after the suite.
16
+ // Note: this deliberately does NOT mount the assistant. Mounting runs `connectedCallback`,
17
+ // which subscribes to `agenticActivityBus` — opening a cross-tab `BroadcastChannel` in a browser
18
+ // env and leaving the runner's event loop open. A getter-level test needs no mount and stays fast
19
+ // + reliable. `document.createElement` only upgrades the element (constructor) — it does not
20
+ // connectso nothing subscribes, and the bus opens its channel lazily on first use, so none is
21
+ // ever created here (no teardown needed).
23
22
 
24
23
  const Suite = createLogicSuite('FoundationAiAssistant popout interaction gate');
25
24
 
26
- Suite.after(() => {
27
- agenticActivityBus.close();
28
- });
29
-
30
25
  /** A fresh (unconnected) element with a fake session store wired in. */
31
26
  function elementWith(state: string, messages: ChatMessage[]): FoundationAiAssistant {
32
27
  const el = document.createElement('foundation-ai-assistant') as FoundationAiAssistant;