@copilotkit/vue 2.0.0-rc.26 → 2.0.0-rc.28

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 (65) hide show
  1. package/CONTRIBUTOR_GUIDE.md +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.mjs +2 -2
  4. package/dist/{use-render-activity-message-BVZr0Z-G.js → use-render-activity-message-BLHkxK7Q.js} +5686 -5983
  5. package/dist/use-render-activity-message-BLHkxK7Q.js.map +1 -0
  6. package/dist/use-render-activity-message-d1Uz5YiB.cjs +113 -0
  7. package/dist/use-render-activity-message-d1Uz5YiB.cjs.map +1 -0
  8. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +3 -6
  9. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
  10. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  11. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  12. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -1
  13. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +1 -16
  14. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  15. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  16. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  17. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  18. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
  19. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
  20. package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
  21. package/dist/v2/components/chat/message-feedback.d.ts +20 -0
  22. package/dist/v2/components/chat/message-feedback.d.ts.map +1 -1
  23. package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
  24. package/dist/v2/hooks/use-default-render-tool.d.ts +2 -7
  25. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
  26. package/dist/v2/hooks/use-frontend-tool.d.ts +2 -2
  27. package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
  28. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  29. package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
  30. package/dist/v2/hooks/use-threads.d.ts +6 -15
  31. package/dist/v2/hooks/use-threads.d.ts.map +1 -1
  32. package/dist/v2/index.cjs +1 -1
  33. package/dist/v2/index.mjs +1 -1
  34. package/dist/v2/lib/vue-core.d.ts +16 -2
  35. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  36. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  37. package/package.json +8 -8
  38. package/src/__tests__/build-entries.test.ts +3 -1
  39. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +4 -19
  40. package/src/v2/components/chat/CopilotChat.vue +50 -218
  41. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +28 -141
  42. package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +11 -76
  43. package/src/v2/components/chat/CopilotChatInput.vue +82 -269
  44. package/src/v2/components/chat/CopilotChatMessageView.vue +14 -64
  45. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +25 -52
  46. package/src/v2/components/chat/CopilotChatView.vue +92 -135
  47. package/src/v2/components/chat/CopilotPopupViewInternal.vue +18 -58
  48. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +22 -45
  49. package/src/v2/components/chat/CopilotThreadsDrawer.vue +6 -3
  50. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +43 -0
  51. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.ts +43 -1
  52. package/src/v2/components/chat/message-feedback.ts +33 -1
  53. package/src/v2/hooks/use-configure-suggestions.ts +3 -4
  54. package/src/v2/hooks/use-default-render-tool.ts +7 -68
  55. package/src/v2/hooks/use-frontend-tool.ts +10 -20
  56. package/src/v2/hooks/use-render-activity-message.ts +4 -2
  57. package/src/v2/hooks/use-suggestions.ts +9 -4
  58. package/src/v2/hooks/use-threads.ts +6 -15
  59. package/src/v2/lib/vue-core.ts +19 -2
  60. package/src/v2/providers/CopilotKitProvider.vue +107 -184
  61. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +18 -0
  62. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +35 -2
  63. package/dist/use-render-activity-message-8p2HwK2y.cjs +0 -114
  64. package/dist/use-render-activity-message-8p2HwK2y.cjs.map +0 -1
  65. package/dist/use-render-activity-message-BVZr0Z-G.js.map +0 -1
@@ -1,5 +1,4 @@
1
1
  <script setup lang="ts">
2
- import { ɵanySchema } from "@copilotkit/shared";
3
2
  import {
4
3
  computed,
5
4
  h,
@@ -16,25 +15,30 @@ import type { Component, FunctionalComponent } from "vue";
16
15
  import type { Catalog, ComponentApi } from "@a2ui/web_core/v0_9";
17
16
  import {
18
17
  ɵLearningAutoStarter,
19
- ɵbuildHumanInTheLoopTool,
18
+ ɵassembleActivityRenderers,
19
+ ɵassembleHumanInTheLoop,
20
+ ɵassembleProviderToolRenderers,
21
+ ɵassembleProviderTools,
20
22
  ɵhitlRenderProps,
23
+ ɵresolveA2UIActivation,
24
+ ɵstableArrayPropWarning,
21
25
  ɵtoLearningConfig,
22
26
  } from "@copilotkit/core";
23
27
  import type {
24
28
  CopilotKitHeadersSource,
25
29
  FrontendTool,
30
+ ɵHumanInTheLoopRegistry,
26
31
  ɵRuntimeInfo,
32
+ ɵStableArrayProp,
27
33
  } from "@copilotkit/core";
28
34
  import {
29
- ɵSTABLE_ARRAY_WARNINGS,
30
- ɵbuildOpenGenerativeUIContexts,
35
+ ɵaddOpenGenerativeUIContexts,
31
36
  ɵcreateOpenGenerativeUITool,
32
37
  ɵisOpenGenerativeUIActive,
33
38
  ɵpublicKeyHeaderDefaults,
34
39
  ɵreportProviderConfigDiagnostics,
35
40
  ɵresolveLicenseState,
36
41
  ɵresolveProviderConfig,
37
- ɵtoolRendererShapeChanged,
38
42
  ɵwithHeaderDefaults,
39
43
  } from "@copilotkit/core";
40
44
  import { shouldEnableInspector } from "@copilotkit/shared";
@@ -136,74 +140,36 @@ provide(InspectorKey, {
136
140
  openInspector,
137
141
  });
138
142
 
139
- const initialRenderToolCalls = props.renderToolCalls ?? [];
140
- const initialFrontendTools = props.frontendTools;
141
- const initialHumanInTheLoop = props.humanInTheLoop;
142
- const initialRenderCustomMessages = props.renderCustomMessages;
143
- const initialRenderActivityMessages = props.renderActivityMessages;
144
- const initialSandboxFunctions = props.openGenerativeUI?.sandboxFunctions;
145
-
146
143
  // Props that come from reactive state arrive as deep proxies. Zod 4 schemas
147
144
  // break behind a Vue proxy (their `_zod` property is read-only and
148
145
  // non-configurable), so read tools and renderers through their raw objects.
149
146
  const rawEntries = <T>(entries: readonly T[] | undefined): T[] =>
150
147
  [...toRaw(entries ?? [])].map((entry) => toRaw(entry));
151
148
 
152
- // Only the shape (agentId:name pairs) counts, so new render closures for the
153
- // same tools do not warn.
154
- watch(
155
- () => props.renderToolCalls,
156
- (next) => {
157
- if (ɵtoolRendererShapeChanged(initialRenderToolCalls, next ?? [])) {
158
- console.error(ɵSTABLE_ARRAY_WARNINGS.renderToolCalls);
159
- }
160
- },
161
- );
162
-
163
- watch(
164
- () => props.frontendTools,
165
- (next) => {
166
- if (next !== initialFrontendTools) {
167
- console.error(ɵSTABLE_ARRAY_WARNINGS.frontendTools);
168
- }
169
- },
170
- );
171
-
172
- watch(
173
- () => props.humanInTheLoop,
174
- (next) => {
175
- if (next !== initialHumanInTheLoop) {
176
- console.error(ɵSTABLE_ARRAY_WARNINGS.humanInTheLoop);
177
- }
178
- },
179
- );
180
-
181
- watch(
182
- () => props.renderCustomMessages,
183
- (next) => {
184
- if (next !== initialRenderCustomMessages) {
185
- console.error(ɵSTABLE_ARRAY_WARNINGS.renderCustomMessages);
186
- }
187
- },
188
- );
189
-
190
- watch(
191
- () => props.renderActivityMessages,
192
- (next) => {
193
- if (next !== initialRenderActivityMessages) {
194
- console.error(ɵSTABLE_ARRAY_WARNINGS.renderActivityMessages);
195
- }
196
- },
197
- );
198
-
199
- watch(
200
- () => props.openGenerativeUI?.sandboxFunctions,
201
- (next) => {
202
- if (next !== initialSandboxFunctions) {
203
- console.error(ɵSTABLE_ARRAY_WARNINGS.sandboxFunctions);
204
- }
205
- },
206
- );
149
+ // Array props must keep their identity; core decides when a change warns
150
+ // (for `renderToolCalls`, only a change of the agentId:name pairs).
151
+ const stableArrayProps: Record<
152
+ ɵStableArrayProp,
153
+ () => readonly unknown[] | undefined
154
+ > = {
155
+ renderToolCalls: () => props.renderToolCalls,
156
+ frontendTools: () => props.frontendTools,
157
+ humanInTheLoop: () => props.humanInTheLoop,
158
+ renderCustomMessages: () => props.renderCustomMessages,
159
+ renderActivityMessages: () => props.renderActivityMessages,
160
+ sandboxFunctions: () => props.openGenerativeUI?.sandboxFunctions,
161
+ };
162
+ for (const [name, read] of Object.entries(stableArrayProps)) {
163
+ const initial = read();
164
+ watch(read, (next) => {
165
+ const warning = ɵstableArrayPropWarning(
166
+ name as ɵStableArrayProp,
167
+ initial,
168
+ next,
169
+ );
170
+ if (warning) console.error(warning);
171
+ });
172
+ }
207
173
 
208
174
  // Key fallback, Copilot Cloud URL, transport, agent merge and the catalog
209
175
  // property all come from core.
@@ -270,71 +236,42 @@ watch(
270
236
  { immediate: true },
271
237
  );
272
238
 
273
- const processedHumanInTheLoop = computed(() => {
274
- const tools: FrontendTool[] = [];
275
- const renderToolCalls: VueToolCallRenderer<unknown>[] = [];
276
- // Core parks each call by tool call id until the render responds. The core
277
- // is created further down, so read its registry lazily.
278
- const registry = () => copilotkit.value.ɵhumanInTheLoop;
279
-
280
- for (const tool of rawEntries(props.humanInTheLoop)) {
281
- const { render: ToolComponent, ...config } = tool;
282
- tools.push(ɵbuildHumanInTheLoopTool(config, registry) as FrontendTool);
283
- if (ToolComponent) {
284
- const render: VueToolCallRenderer<unknown>["render"] = (
285
- renderProps: VueToolCallRendererRenderProps<unknown>,
286
- ) =>
239
+ type FrontendToolRender = NonNullable<VueFrontendTool["render"]>;
240
+
241
+ // The tools and tool-call renderers for core (assembly rules from core).
242
+ // Human-in-the-loop renders are wrapped once per `humanInTheLoop` change: a
243
+ // new wrapper is a new component type, so Vue would remount the card.
244
+ const humanInTheLoopAssembly = computed(() =>
245
+ ɵassembleHumanInTheLoop<FrontendToolRender, unknown>({
246
+ humanInTheLoop: rawEntries(props.humanInTheLoop),
247
+ // Core parks each call by tool call id until the render responds. The core
248
+ // is created further down, so read its registry lazily.
249
+ humanInTheLoopRegistry: (): ɵHumanInTheLoopRegistry =>
250
+ copilotkit.value.ɵhumanInTheLoop,
251
+ wrapHumanInTheLoopRender: (ToolComponent, tool, registry) =>
252
+ ((renderProps: VueToolCallRendererRenderProps<unknown>) =>
287
253
  h(
288
254
  ToolComponent as Parameters<typeof h>[0],
289
255
  ɵhitlRenderProps(renderProps, tool, registry()),
290
- );
291
-
292
- renderToolCalls.push({
293
- name: tool.name,
294
- args: tool.parameters,
295
- render,
296
- ...(tool.agentId && { agentId: tool.agentId }),
297
- } as VueToolCallRenderer<unknown>);
298
- }
299
- }
300
- return { tools, renderToolCalls };
301
- });
302
-
303
- const allTools = computed(() => {
304
- const tools: FrontendTool[] = [];
305
- for (const t of rawEntries(props.frontendTools)) {
306
- tools.push(t as FrontendTool);
307
- }
308
- for (const t of builtInFrontendTools.value) {
309
- tools.push(t as FrontendTool);
310
- }
311
- tools.push(...processedHumanInTheLoop.value.tools);
312
- return tools;
313
- });
314
-
315
- const allRenderToolCalls = computed(() => {
316
- const combined: VueToolCallRenderer<unknown>[] = [
317
- ...rawEntries(props.renderToolCalls),
318
- ];
319
- for (const tool of [
320
- ...rawEntries(props.frontendTools),
321
- ...builtInFrontendTools.value,
322
- ]) {
323
- if (tool.render) {
324
- const args =
325
- tool.parameters ?? (tool.name === "*" ? ɵanySchema : undefined);
326
- if (args) {
327
- combined.push({
328
- name: tool.name,
329
- args,
330
- render: tool.render,
331
- } as VueToolCallRenderer<unknown>);
332
- }
333
- }
334
- }
335
- combined.push(...processedHumanInTheLoop.value.renderToolCalls);
336
- return combined;
337
- });
256
+ )) as FrontendToolRender,
257
+ }),
258
+ );
259
+ const allTools = computed<FrontendTool[]>(() =>
260
+ ɵassembleProviderTools({
261
+ frontendTools: rawEntries(props.frontendTools),
262
+ builtInFrontendTools: builtInFrontendTools.value,
263
+ humanInTheLoop: humanInTheLoopAssembly.value,
264
+ }),
265
+ );
266
+ const allRenderToolCalls = computed(
267
+ () =>
268
+ ɵassembleProviderToolRenderers({
269
+ renderToolCalls: rawEntries(props.renderToolCalls),
270
+ frontendTools: rawEntries(props.frontendTools),
271
+ builtInFrontendTools: builtInFrontendTools.value,
272
+ humanInTheLoop: humanInTheLoopAssembly.value,
273
+ }) as VueToolCallRenderer<unknown>[],
274
+ );
338
275
 
339
276
  const allRenderCustomMessages = computed(
340
277
  () => props.renderCustomMessages ?? [],
@@ -363,9 +300,12 @@ const openGenerativeUIActive = computed(() =>
363
300
  // A catalog passed to the provider is enough to turn A2UI on: render the
364
301
  // surfaces locally and forward the catalog signal so the runtime injects the
365
302
  // render tool — no runtime-side `a2ui` config required.
366
- const a2uiCatalogProvided = computed(() => !!props.a2ui?.catalog);
367
303
  const a2uiActive = computed(
368
- () => runtimeA2UIEnabled.value || a2uiCatalogProvided.value,
304
+ () =>
305
+ ɵresolveA2UIActivation({
306
+ runtimeEnabled: runtimeA2UIEnabled.value,
307
+ hasCatalog: !!props.a2ui?.catalog,
308
+ }).active,
369
309
  );
370
310
  // The catalog with components disabled in the Inspector removed; set by
371
311
  // `registerA2UICatalog` below once the core exists.
@@ -390,47 +330,36 @@ const builtInFrontendTools = computed<VueFrontendTool[]>(() => {
390
330
  ];
391
331
  });
392
332
 
393
- const builtInActivityRenderers = computed<
394
- VueActivityMessageRenderer<unknown>[]
395
- >(() => {
396
- const renderers: VueActivityMessageRenderer<unknown>[] = [
397
- {
398
- activityType: MCPAppsActivityType,
399
- content: MCPAppsActivityContentSchema,
400
- render: (props.mcpApps?.allowSameOriginFallback
401
- ? SameOriginMCPAppsActivityRenderer
402
- : MCPAppsActivityRenderer) as unknown as VueActivityMessageRenderer<unknown>["render"],
403
- },
404
- ];
405
-
406
- if (openGenerativeUIActive.value) {
407
- renderers.push({
408
- activityType: OpenGenerativeUIActivityType,
409
- content: OpenGenerativeUIContentSchema,
410
- render:
411
- OpenGenerativeUIActivityRenderer as unknown as VueActivityMessageRenderer<unknown>["render"],
412
- });
413
- }
414
-
415
- if (a2uiActive.value) {
416
- renderers.unshift(
417
- createA2UIMessageRenderer({
418
- theme: props.a2ui?.theme ?? {},
419
- catalog: a2uiFilteredCatalog.value ?? props.a2ui?.catalog,
420
- loadingComponent: props.a2ui?.loadingComponent,
421
- recovery: props.a2ui?.recovery,
422
- onAction: props.a2ui?.onAction,
423
- }),
424
- );
425
- }
426
-
427
- return renderers;
428
- });
429
-
430
- const allRenderActivityMessages = computed(() => [
431
- ...rawEntries(props.renderActivityMessages),
432
- ...builtInActivityRenderers.value,
433
- ]);
333
+ // The app's activity renderers, then the built-in ones (order from core).
334
+ const allRenderActivityMessages = computed(() =>
335
+ ɵassembleActivityRenderers<VueActivityMessageRenderer<unknown>>({
336
+ app: rawEntries(props.renderActivityMessages),
337
+ a2ui: a2uiActive.value
338
+ ? createA2UIMessageRenderer({
339
+ theme: props.a2ui?.theme ?? {},
340
+ catalog: a2uiFilteredCatalog.value ?? props.a2ui?.catalog,
341
+ loadingComponent: props.a2ui?.loadingComponent,
342
+ recovery: props.a2ui?.recovery,
343
+ onAction: props.a2ui?.onAction,
344
+ })
345
+ : undefined,
346
+ builtIns: [
347
+ {
348
+ activityType: MCPAppsActivityType,
349
+ content: MCPAppsActivityContentSchema,
350
+ render: (props.mcpApps?.allowSameOriginFallback
351
+ ? SameOriginMCPAppsActivityRenderer
352
+ : MCPAppsActivityRenderer) as unknown as VueActivityMessageRenderer<unknown>["render"],
353
+ },
354
+ openGenerativeUIActive.value && {
355
+ activityType: OpenGenerativeUIActivityType,
356
+ content: OpenGenerativeUIContentSchema,
357
+ render:
358
+ OpenGenerativeUIActivityRenderer as unknown as VueActivityMessageRenderer<unknown>["render"],
359
+ },
360
+ ],
361
+ }),
362
+ );
434
363
 
435
364
  const learningConfig = computed(() => ɵtoLearningConfig(props.learning));
436
365
 
@@ -633,7 +562,6 @@ registerA2UICatalog(copilotkit, {
633
562
  },
634
563
  });
635
564
 
636
- const providerContextIds = ref<string[]>([]);
637
565
  watch(
638
566
  [
639
567
  openGenerativeUIActive,
@@ -643,17 +571,12 @@ watch(
643
571
  ([active, functions, designSkill], _previous, onCleanup) => {
644
572
  if (!active) return;
645
573
 
646
- const ids = ɵbuildOpenGenerativeUIContexts({
647
- designSkill,
648
- sandboxFunctions: functions,
649
- }).map((entry) => copilotkit.value.addContext(entry));
650
-
651
- providerContextIds.value = ids;
652
- onCleanup(() => {
653
- for (const id of ids) {
654
- copilotkit.value.removeContext(id);
655
- }
656
- });
574
+ onCleanup(
575
+ ɵaddOpenGenerativeUIContexts(copilotkit.value, {
576
+ designSkill,
577
+ sandboxFunctions: functions,
578
+ }),
579
+ );
657
580
  },
658
581
  { immediate: true },
659
582
  );
@@ -70,6 +70,24 @@ describe("CopilotKitProvider a2ui catalog auto-enable", () => {
70
70
  expect(a2uiRenderer).toBeDefined();
71
71
  });
72
72
 
73
+ it("keeps the built-in render_a2ui renderer and the app's renderers together when the app's renderers change", async () => {
74
+ const catalog = { id: "test", components: new Map() };
75
+ const weather = { name: "weather", args: z.any(), render: () => null };
76
+ const { wrapper, observedCore } = mountProvider({
77
+ a2ui: { catalog },
78
+ renderToolCalls: [weather],
79
+ });
80
+ const names = () =>
81
+ observedCore.value?.renderToolCalls.map((renderer) => renderer.name);
82
+ expect(names()).toEqual(["weather", "render_a2ui"]);
83
+
84
+ await wrapper.setProps({
85
+ renderToolCalls: [weather, { ...weather, name: "search" }],
86
+ });
87
+
88
+ expect(names()).toEqual(["weather", "search", "render_a2ui"]);
89
+ });
90
+
73
91
  it("does not register an a2ui-surface renderer when no catalog is provided and runtime has not signaled a2uiEnabled", () => {
74
92
  const { observedCore } = mountProvider({ a2ui: { theme: {} } });
75
93
 
@@ -322,7 +322,7 @@ describe("CopilotKitProvider", () => {
322
322
  expect(renderTool?.render).toStrictEqual(TestComponent);
323
323
  });
324
324
 
325
- it("does not scope frontendTools-derived render entries by agentId", () => {
325
+ it("scopes frontendTools-derived render entries by agentId, like useFrontendTool", () => {
326
326
  const TestComponent = defineComponent({
327
327
  setup() {
328
328
  return () => h("div", "Test");
@@ -344,7 +344,7 @@ describe("CopilotKitProvider", () => {
344
344
  );
345
345
 
346
346
  expect(renderTool).toBeDefined();
347
- expect(renderTool?.agentId).toBeUndefined();
347
+ expect(renderTool?.agentId).toBe("specific-agent");
348
348
  });
349
349
  });
350
350
 
@@ -411,6 +411,39 @@ describe("CopilotKitProvider", () => {
411
411
  expect(typeof renderTool?.render).toBe("function");
412
412
  });
413
413
 
414
+ it("keeps the wrapped render and the tools when only renderToolCalls changes", async () => {
415
+ // A new wrapper is a new component type, so Vue would remount the card
416
+ // and lose its state.
417
+ const humanInTheLoop: VueHumanInTheLoop[] = [
418
+ {
419
+ name: "approvalTool",
420
+ parameters: z.object({}),
421
+ render: HitlComponent,
422
+ },
423
+ ];
424
+ const weather = () => [
425
+ { name: "weather", args: z.any(), render: HitlComponent },
426
+ ];
427
+ const { wrapper, getCore } = mountWithProvider(() => h("div"), {
428
+ humanInTheLoop,
429
+ renderToolCalls: weather(),
430
+ });
431
+ const approveRender = () =>
432
+ getCore().renderToolCalls.find((rc) => rc.name === "approvalTool")
433
+ ?.render;
434
+ const before = approveRender();
435
+ const setTools = vi.spyOn(getCore(), "setTools");
436
+
437
+ await wrapper.setProps({ renderToolCalls: weather() });
438
+ await flush();
439
+
440
+ expect(getCore().renderToolCalls.map((rc) => rc.name)).toContain(
441
+ "weather",
442
+ );
443
+ expect(approveRender()).toBe(before);
444
+ expect(setTools).not.toHaveBeenCalled();
445
+ });
446
+
414
447
  it("keeps the tool call pending until respond is called", async () => {
415
448
  // Reproduces OSS-803: the handler used to warn and resolve undefined
416
449
  // immediately, so the HITL UI flashed and disappeared instead of waiting