@copilotkit/vue 1.61.1 → 1.62.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/index.cjs +1 -1
  2. package/dist/index.mjs +12 -11
  3. package/dist/styles.css +1 -1
  4. package/dist/{use-render-activity-message-BB43N8wJ.js → use-render-activity-message-Bzh3Bckz.js} +3796 -3706
  5. package/dist/use-render-activity-message-Bzh3Bckz.js.map +1 -0
  6. package/dist/use-render-activity-message-CW3vipBB.cjs +85 -0
  7. package/dist/use-render-activity-message-CW3vipBB.cjs.map +1 -0
  8. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  9. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  10. package/dist/v2/components/chat/index.d.ts.map +1 -1
  11. package/dist/v2/hooks/use-interrupt.d.ts +21 -8
  12. package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
  13. package/dist/v2/index.cjs +1 -1
  14. package/dist/v2/index.d.cts +1 -0
  15. package/dist/v2/index.d.mts +1 -0
  16. package/dist/v2/index.d.ts +1 -0
  17. package/dist/v2/index.d.ts.map +1 -1
  18. package/dist/v2/index.mjs +8 -7
  19. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  20. package/dist/v2/types/interrupt.d.ts +11 -2
  21. package/dist/v2/types/interrupt.d.ts.map +1 -1
  22. package/package.json +4 -4
  23. package/src/v2/__tests__/exports.test.ts +7 -0
  24. package/src/v2/components/chat/CopilotChatInput.vue +1 -1
  25. package/src/v2/components/chat/CopilotChatMessageView.vue +3 -0
  26. package/src/v2/components/chat/CopilotChatSuggestionView.vue +1 -1
  27. package/src/v2/components/chat/CopilotChatView.vue +3 -3
  28. package/src/v2/hooks/__tests__/use-interrupt.test.ts +187 -2
  29. package/src/v2/hooks/use-interrupt.ts +218 -44
  30. package/src/v2/index.ts +5 -0
  31. package/src/v2/providers/CopilotKitProvider.vue +25 -12
  32. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +78 -0
  33. package/src/v2/types/interrupt.ts +18 -2
  34. package/dist/use-render-activity-message-B0zDl3Lc.cjs +0 -85
  35. package/dist/use-render-activity-message-B0zDl3Lc.cjs.map +0 -1
  36. package/dist/use-render-activity-message-BB43N8wJ.js.map +0 -1
@@ -1,17 +1,39 @@
1
1
  import { computed, onScopeDispose, shallowRef, watch } from "vue";
2
2
  import type { ComputedRef, Ref } from "vue";
3
+ import { buildResumeArray, isInterruptExpired } from "@ag-ui/client";
4
+ import type { Interrupt, RunAgentResult } from "@ag-ui/client";
3
5
  import { useCopilotKit } from "../providers/useCopilotKit";
4
6
  import { useAgent } from "./use-agent";
5
7
  import type {
6
8
  InterruptEvent,
7
9
  InterruptHandlerProps,
8
10
  InterruptRenderProps,
11
+ InterruptResolveFn,
12
+ InterruptCancelFn,
9
13
  } from "../types/interrupt";
10
14
 
11
- export type { InterruptEvent, InterruptHandlerProps, InterruptRenderProps };
15
+ export type {
16
+ InterruptEvent,
17
+ InterruptHandlerProps,
18
+ InterruptRenderProps,
19
+ Interrupt,
20
+ };
12
21
 
13
22
  const INTERRUPT_EVENT_NAME = "on_interrupt";
14
23
 
24
+ /** Internal accumulator response shape consumed by buildResumeArray. */
25
+ type ResumeResponse =
26
+ | { status: "resolved"; payload?: unknown }
27
+ | { status: "cancelled" };
28
+
29
+ /**
30
+ * Normalized pending interrupt. `legacy` carries the custom-event payload;
31
+ * `standard` carries the AG-UI `outcome:"interrupt"` interrupts array.
32
+ */
33
+ type PendingInterrupt =
34
+ | { kind: "legacy"; event: InterruptEvent }
35
+ | { kind: "standard"; interrupts: Interrupt[] };
36
+
15
37
  type InterruptHandlerFn<TValue, TResult> = (
16
38
  props: InterruptHandlerProps<TValue>,
17
39
  ) => TResult | PromiseLike<TResult>;
@@ -39,7 +61,14 @@ export interface UseInterruptResult<TValue = unknown, TResult = never> {
39
61
  interrupt: Ref<InterruptEvent<TValue> | null>;
40
62
  result: Ref<InterruptResult<TValue, TResult>>;
41
63
  hasInterrupt: ComputedRef<boolean>;
42
- resolveInterrupt: (response: unknown) => void;
64
+ /** Resolve the pending interrupt. Standard: records resolved response and submits when all addressed. Legacy: resumes via forwardedProps.command. */
65
+ resolve: InterruptResolveFn;
66
+ /** Alias of resolve for back-compat. */
67
+ resolveInterrupt: InterruptResolveFn;
68
+ /** Cancel the pending interrupt. Standard: records cancelled response and submits when all addressed. */
69
+ cancel: InterruptCancelFn;
70
+ /** Alias of cancel for back-compat. */
71
+ cancelInterrupt: InterruptCancelFn;
43
72
  slotProps: ComputedRef<InterruptRenderProps<
44
73
  TValue,
45
74
  InterruptResult<TValue, TResult>
@@ -72,16 +101,27 @@ function normalizeAsyncResult<TValue>(
72
101
  });
73
102
  }
74
103
 
104
+ /** Derive the legacy-compatible `event` for any pending interrupt. */
105
+ function toLegacyEvent(pending: PendingInterrupt): InterruptEvent {
106
+ if (pending.kind === "legacy") return pending.event;
107
+ return { name: INTERRUPT_EVENT_NAME, value: pending.interrupts[0] };
108
+ }
109
+
75
110
  /**
76
- * Vue composable for handling `on_interrupt` custom events from an agent.
111
+ * Vue composable for handling agent interrupts with optional filtering,
112
+ * preprocessing, and resume behavior.
77
113
  *
78
- * It tracks the latest pending interrupt, optionally derives UI data via
79
- * `handler`, and can publish slot state into `CopilotChat` so consumers render
80
- * interrupts through the `#interrupt` slot instead of render functions/TSX.
114
+ * Supports both the AG-UI standard interrupt flow (`RUN_FINISHED` with
115
+ * `outcome:"interrupt"`) and the legacy custom-event flow (`on_interrupt`).
116
+ * For standard interrupts, `slotProps` receives `interrupt` (the primary one)
117
+ * and `interrupts` (the full open set); call `resolve(payload)` to resume or
118
+ * `cancel()` to cancel. Resuming addresses the targeted interrupt and, once
119
+ * every open interrupt is addressed, submits a single spec `resume` array via
120
+ * `copilotkit.runAgent`.
81
121
  *
82
122
  * @example
83
123
  * ```ts
84
- * const { interrupt, hasInterrupt, resolveInterrupt } = useInterrupt({
124
+ * const { interrupt, hasInterrupt, resolve, cancel } = useInterrupt({
85
125
  * handler: ({ event }) => ({ label: String(event.value) }),
86
126
  * });
87
127
  * ```
@@ -91,40 +131,64 @@ export function useInterrupt<TValue = unknown, TResult = never>(
91
131
  ): UseInterruptResult<TValue, TResult> {
92
132
  const { copilotkit } = useCopilotKit();
93
133
  const { agent } = useAgent({ agentId: config.agentId });
94
- const interrupt = shallowRef<InterruptEvent<TValue> | null>(null);
134
+ const pending = shallowRef<PendingInterrupt | null>(null);
95
135
  const result = shallowRef<InterruptResult<TValue, TResult>>(null);
96
136
 
137
+ // Accumulated per-interrupt responses for the current standard interrupt set.
138
+ const responses: Record<string, ResumeResponse> = {};
139
+
97
140
  watch(
98
141
  agent,
99
142
  (resolvedAgent, _previousAgent, onCleanup) => {
100
143
  if (!resolvedAgent) {
101
- interrupt.value = null;
144
+ pending.value = null;
102
145
  result.value = null;
103
146
  return;
104
147
  }
105
148
 
106
- let localInterrupt: InterruptEvent<TValue> | null = null;
149
+ let localLegacy: InterruptEvent<TValue> | null = null;
150
+ let localStandard: Interrupt[] | null = null;
151
+
107
152
  const subscription = resolvedAgent.subscribe({
108
153
  onCustomEvent: ({ event }) => {
109
154
  if (event.name === INTERRUPT_EVENT_NAME) {
110
- localInterrupt = {
155
+ localLegacy = {
111
156
  name: event.name,
112
157
  value: event.value as TValue,
113
158
  };
114
159
  }
115
160
  },
161
+ onRunFinishedEvent: (params) => {
162
+ if (params.outcome === "interrupt") {
163
+ localStandard = params.interrupts;
164
+ }
165
+ },
116
166
  onRunStartedEvent: () => {
117
- localInterrupt = null;
118
- interrupt.value = null;
167
+ localLegacy = null;
168
+ localStandard = null;
169
+ // Reset accumulated responses for the new run.
170
+ for (const k of Object.keys(responses)) {
171
+ delete responses[k];
172
+ }
173
+ pending.value = null;
119
174
  },
120
175
  onRunFinalized: () => {
121
- if (localInterrupt) {
122
- interrupt.value = localInterrupt;
123
- localInterrupt = null;
176
+ // Standard wins if both somehow appear for one run.
177
+ if (localStandard && localStandard.length > 0) {
178
+ pending.value = { kind: "standard", interrupts: localStandard };
179
+ } else if (localLegacy) {
180
+ pending.value = { kind: "legacy", event: localLegacy };
124
181
  }
182
+ localLegacy = null;
183
+ localStandard = null;
125
184
  },
126
185
  onRunFailed: () => {
127
- localInterrupt = null;
186
+ localLegacy = null;
187
+ localStandard = null;
188
+ for (const k of Object.keys(responses)) {
189
+ delete responses[k];
190
+ }
191
+ pending.value = null;
128
192
  },
129
193
  });
130
194
 
@@ -133,38 +197,122 @@ export function useInterrupt<TValue = unknown, TResult = never>(
133
197
  { immediate: true },
134
198
  );
135
199
 
136
- const resolveInterrupt = (response: unknown) => {
200
+ /** Submit the accumulated standard responses once all open interrupts are addressed. */
201
+ const submitStandardIfComplete = async (
202
+ interrupts: Interrupt[],
203
+ ): Promise<RunAgentResult | void> => {
204
+ const allAddressed = interrupts.every((i) => responses[i.id]);
205
+ if (!allAddressed) return;
206
+
207
+ const expired = interrupts.find((i) => isInterruptExpired(i));
208
+ if (expired) {
209
+ console.error(
210
+ `[CopilotKit] useInterrupt: interrupt ${expired.id} expired at ${expired.expiresAt}; not resuming.`,
211
+ );
212
+ for (const k of Object.keys(responses)) {
213
+ delete responses[k];
214
+ }
215
+ pending.value = null;
216
+ return;
217
+ }
218
+
219
+ const resume = buildResumeArray(interrupts, responses);
220
+ for (const k of Object.keys(responses)) {
221
+ delete responses[k];
222
+ }
223
+ const resolvedAgent = agent.value;
224
+ if (!resolvedAgent) return;
225
+ try {
226
+ return await copilotkit.value.runAgent({ agent: resolvedAgent, resume });
227
+ } catch (err) {
228
+ console.error(
229
+ "[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing",
230
+ err,
231
+ );
232
+ pending.value = null;
233
+ throw err;
234
+ }
235
+ };
236
+
237
+ const resolve: InterruptResolveFn = async (payload?, interruptId?) => {
238
+ const current = pending.value;
239
+ if (!current) return;
240
+
137
241
  const resolvedAgent = agent.value;
138
242
  if (!resolvedAgent) return;
139
243
 
140
- const interruptEventValue = interrupt.value?.value;
141
- interrupt.value = null;
142
- void copilotkit.value
143
- .runAgent({
144
- agent: resolvedAgent,
145
- forwardedProps: {
146
- command: {
147
- resume: response,
148
- interruptEvent: interruptEventValue,
244
+ if (current.kind === "legacy") {
245
+ const interruptEventValue = current.event.value;
246
+ try {
247
+ return await copilotkit.value.runAgent({
248
+ agent: resolvedAgent,
249
+ forwardedProps: {
250
+ command: {
251
+ resume: payload,
252
+ interruptEvent: interruptEventValue,
253
+ },
149
254
  },
150
- },
151
- })
152
- .catch((error) => {
255
+ });
256
+ } catch (err) {
153
257
  console.error(
154
- "[CopilotKit] useInterrupt: failed to resume agent:",
155
- error,
258
+ "[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing",
259
+ err,
156
260
  );
157
- });
261
+ pending.value = null;
262
+ throw err;
263
+ }
264
+ }
265
+
266
+ if (current.interrupts.length > 1 && interruptId === undefined) {
267
+ console.warn(
268
+ `[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`,
269
+ );
270
+ }
271
+ const id = interruptId ?? current.interrupts[0]?.id;
272
+ if (!id) return;
273
+ responses[id] = { status: "resolved", payload };
274
+ return submitStandardIfComplete(current.interrupts);
158
275
  };
159
276
 
277
+ const cancel: InterruptCancelFn = async (interruptId?) => {
278
+ const current = pending.value;
279
+ if (!current) return;
280
+
281
+ if (current.kind === "legacy") {
282
+ // Legacy interrupts have no cancel semantics; dismiss without resuming.
283
+ console.warn(
284
+ "[CopilotKit] useInterrupt: cancel() is not supported for legacy on_interrupt interrupts; dismissing.",
285
+ );
286
+ pending.value = null;
287
+ return;
288
+ }
289
+
290
+ if (current.interrupts.length > 1 && interruptId === undefined) {
291
+ console.warn(
292
+ `[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`,
293
+ );
294
+ }
295
+ const id = interruptId ?? current.interrupts[0]?.id;
296
+ if (!id) return;
297
+ responses[id] = { status: "cancelled" };
298
+ return submitStandardIfComplete(current.interrupts);
299
+ };
300
+
301
+ // Keep resolveInterrupt and cancelInterrupt as aliases for back-compat.
302
+ const resolveInterrupt: InterruptResolveFn = resolve;
303
+ const cancelInterrupt: InterruptCancelFn = cancel;
304
+
160
305
  watch(
161
- interrupt,
162
- (pendingEvent, _previous, onCleanup) => {
163
- if (!pendingEvent) {
306
+ pending,
307
+ (currentPending, _previous, onCleanup) => {
308
+ if (!currentPending) {
164
309
  result.value = null;
165
310
  return;
166
311
  }
167
- if (config.enabled && !config.enabled(pendingEvent)) {
312
+ const legacyEvent = toLegacyEvent(
313
+ currentPending,
314
+ ) as InterruptEvent<TValue>;
315
+ if (config.enabled && !config.enabled(legacyEvent)) {
168
316
  result.value = null;
169
317
  return;
170
318
  }
@@ -176,8 +324,15 @@ export function useInterrupt<TValue = unknown, TResult = never>(
176
324
 
177
325
  let cancelled = false;
178
326
  const maybePromise = handler({
179
- event: pendingEvent,
180
- resolve: resolveInterrupt,
327
+ event: legacyEvent,
328
+ interrupt:
329
+ currentPending.kind === "standard"
330
+ ? (currentPending.interrupts[0] ?? null)
331
+ : null,
332
+ interrupts:
333
+ currentPending.kind === "standard" ? currentPending.interrupts : [],
334
+ resolve,
335
+ cancel,
181
336
  });
182
337
 
183
338
  if (isPromiseLike(maybePromise)) {
@@ -204,17 +359,32 @@ export function useInterrupt<TValue = unknown, TResult = never>(
204
359
  { immediate: true },
205
360
  );
206
361
 
362
+ // Compute the legacy-compat "interrupt" ref for existing consumers (InterruptEvent shape).
363
+ const interrupt = computed<InterruptEvent<TValue> | null>(() => {
364
+ if (!pending.value) return null;
365
+ return toLegacyEvent(pending.value) as InterruptEvent<TValue>;
366
+ });
367
+
207
368
  const slotProps = computed<InterruptRenderProps<
208
369
  TValue,
209
370
  InterruptResult<TValue, TResult>
210
371
  > | null>(() => {
211
- if (!interrupt.value) return null;
212
- if (config.enabled && !config.enabled(interrupt.value)) return null;
372
+ const currentPending = pending.value;
373
+ if (!currentPending) return null;
374
+ const legacyEvent = toLegacyEvent(currentPending) as InterruptEvent<TValue>;
375
+ if (config.enabled && !config.enabled(legacyEvent)) return null;
213
376
 
214
377
  return {
215
- event: interrupt.value,
378
+ event: legacyEvent,
379
+ interrupt:
380
+ currentPending.kind === "standard"
381
+ ? (currentPending.interrupts[0] ?? null)
382
+ : null,
383
+ interrupts:
384
+ currentPending.kind === "standard" ? currentPending.interrupts : [],
216
385
  result: result.value,
217
- resolve: resolveInterrupt,
386
+ resolve,
387
+ cancel,
218
388
  };
219
389
  });
220
390
 
@@ -226,6 +396,7 @@ export function useInterrupt<TValue = unknown, TResult = never>(
226
396
  }
227
397
 
228
398
  core.setInterruptState(
399
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
400
  nextSlotProps as InterruptRenderProps<any, any> | null,
230
401
  );
231
402
  const publishedState = nextSlotProps;
@@ -250,10 +421,13 @@ export function useInterrupt<TValue = unknown, TResult = never>(
250
421
  });
251
422
 
252
423
  return {
253
- interrupt: interrupt as Ref<InterruptEvent<TValue> | null>,
424
+ interrupt: interrupt as unknown as Ref<InterruptEvent<TValue> | null>,
254
425
  result: result as Ref<InterruptResult<TValue, TResult>>,
255
426
  hasInterrupt: computed(() => slotProps.value !== null),
427
+ resolve,
256
428
  resolveInterrupt,
429
+ cancel,
430
+ cancelInterrupt,
257
431
  slotProps: slotProps as ComputedRef<InterruptRenderProps<
258
432
  TValue,
259
433
  InterruptResult<TValue, TResult>
package/src/v2/index.ts CHANGED
@@ -4,6 +4,11 @@ export * from "@ag-ui/client";
4
4
 
5
5
  // Local V2 vue code
6
6
  export * from "./components";
7
+ // Explicit re-export so the default A2UI catalog is reachable as a public
8
+ // named export. Vue users need a catalog to pass to `a2ui.catalog` for the
9
+ // catalog-on-provider path; the nested `export *` barrel above gets
10
+ // tree-shaken by the library build, so surface it directly here.
11
+ export { vueBasicCatalog } from "./components/a2ui/catalog";
7
12
  export * from "./hooks";
8
13
  export * from "./providers";
9
14
  export * from "./types";
@@ -16,6 +16,7 @@ import type {
16
16
  FrontendTool,
17
17
  } from "@copilotkit/core";
18
18
  import { schemaToJsonSchema } from "@copilotkit/shared";
19
+ import type { RuntimeLicenseStatus } from "@copilotkit/shared";
19
20
  import { zodToJsonSchema } from "zod-to-json-schema";
20
21
  import { CopilotKitCoreVue } from "../lib/vue-core";
21
22
  import { createA2UIMessageRenderer } from "../components/A2UIMessageRenderer";
@@ -37,8 +38,8 @@ import { CopilotKitKey, SandboxFunctionsKey } from "./keys";
37
38
  import {
38
39
  LicenseContextKey,
39
40
  createLicenseContextValue,
40
- type LicenseContextValue,
41
41
  } from "./license-context";
42
+ import type { LicenseContextValue } from "./license-context";
42
43
  import CopilotKitInspector from "../components/CopilotKitInspector.vue";
43
44
  import LicenseWarningBanner from "../components/LicenseWarningBanner.vue";
44
45
  import type { CopilotKitProviderProps } from "./CopilotKitProvider.types";
@@ -290,10 +291,25 @@ const allRenderCustomMessages = computed(
290
291
  );
291
292
  const runtimeA2UIEnabled = ref(false);
292
293
  const runtimeOpenGenerativeUIEnabled = ref(false);
293
- const runtimeLicenseStatus = ref<string | undefined>(undefined);
294
+ const runtimeLicenseStatus = ref<RuntimeLicenseStatus | undefined>(undefined);
294
295
  const openGenerativeUIActive = computed(
295
296
  () => runtimeOpenGenerativeUIEnabled.value || !!props.openGenerativeUI,
296
297
  );
298
+ // A catalog passed to the provider is enough to turn A2UI on: render the
299
+ // surfaces locally and forward the catalog signal so the runtime injects the
300
+ // render tool — no runtime-side `a2ui` config required.
301
+ const a2uiCatalogProvided = computed(() => !!props.a2ui?.catalog);
302
+ const a2uiActive = computed(
303
+ () => runtimeA2UIEnabled.value || a2uiCatalogProvided.value,
304
+ );
305
+ // Forward a per-run signal when the provider has an A2UI catalog so the
306
+ // runtime can turn A2UI on (and inject the render tool) without a separate
307
+ // `a2ui.injectA2UITool` flag on the runtime.
308
+ const resolvedProperties = computed(() =>
309
+ a2uiCatalogProvided.value
310
+ ? { ...props.properties, a2uiCatalogAvailable: true }
311
+ : props.properties,
312
+ );
297
313
  const sandboxFunctions = computed<readonly SandboxFunction[]>(
298
314
  () => props.openGenerativeUI?.sandboxFunctions ?? [],
299
315
  );
@@ -342,7 +358,7 @@ const builtInActivityRenderers = computed<
342
358
  });
343
359
  }
344
360
 
345
- if (runtimeA2UIEnabled.value) {
361
+ if (a2uiActive.value) {
346
362
  renderers.unshift(
347
363
  createA2UIMessageRenderer({
348
364
  theme: props.a2ui?.theme ?? viewerTheme,
@@ -383,7 +399,7 @@ const createCopilotKit = () => {
383
399
  : "auto",
384
400
  headers: mergedHeaders.value,
385
401
  credentials: props.credentials,
386
- properties: props.properties,
402
+ properties: resolvedProperties.value,
387
403
  agents__unsafe_dev_only: mergedAgents.value,
388
404
  tools: allTools.value,
389
405
  renderToolCalls: allRenderToolCalls.value,
@@ -502,7 +518,7 @@ function syncRuntimeConfig() {
502
518
  );
503
519
  copilotkit.value.setHeaders(mergedHeaders.value);
504
520
  copilotkit.value.setCredentials(props.credentials);
505
- copilotkit.value.setProperties(props.properties);
521
+ copilotkit.value.setProperties(resolvedProperties.value);
506
522
  copilotkit.value.setAgents__unsafe_dev_only(mergedAgents.value);
507
523
  copilotkit.value.setDebug(props.debug);
508
524
  applyDefaultThrottleMs(copilotkit.value);
@@ -513,7 +529,7 @@ watch(
513
529
  () => chatApiEndpoint.value,
514
530
  () => mergedHeaders.value,
515
531
  () => props.credentials,
516
- () => props.properties,
532
+ () => resolvedProperties.value,
517
533
  () => mergedAgents.value,
518
534
  () => props.useSingleEndpoint,
519
535
  () => props.debug,
@@ -548,11 +564,11 @@ const a2uiLoadingComponent = computed(() => props.a2ui?.loadingComponent);
548
564
  const a2uiIncludeSchema = computed(() => props.a2ui?.includeSchema ?? true);
549
565
 
550
566
  // A2UI tool call renderer (progress indicator) — auto-registered when A2UI enabled
551
- registerA2UIBuiltInToolCallRenderer(copilotkit, () => runtimeA2UIEnabled.value);
567
+ registerA2UIBuiltInToolCallRenderer(copilotkit, () => a2uiActive.value);
552
568
 
553
569
  // A2UI catalog context, schema, and generation/design guidelines
554
570
  registerA2UICatalogContext(copilotkit, {
555
- enabled: () => runtimeA2UIEnabled.value,
571
+ enabled: () => a2uiActive.value,
556
572
  catalog: () => props.a2ui?.catalog,
557
573
  includeSchema: () => props.a2ui?.includeSchema ?? true,
558
574
  });
@@ -616,11 +632,8 @@ provide(CopilotKitKey, {
616
632
  provide(SandboxFunctionsKey, sandboxFunctions);
617
633
 
618
634
  // License context — driven by server-reported `/info` license status.
619
- // Stays at the permissive default (`createLicenseContextValue(null)`)
620
- // to mirror React's current provider behavior; banner rendering below
621
- // is the sole consumer of `runtimeLicenseStatus`.
622
635
  const licenseContextValue = computed<LicenseContextValue>(() =>
623
- createLicenseContextValue(null),
636
+ createLicenseContextValue(runtimeLicenseStatus.value),
624
637
  );
625
638
  provide(LicenseContextKey, licenseContextValue);
626
639
 
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { defineComponent, h, ref } from "vue";
3
+ import { mount } from "@vue/test-utils";
4
+ import CopilotKitProvider from "../CopilotKitProvider.vue";
5
+ import { useCopilotKit } from "../useCopilotKit";
6
+
7
+ function mountProvider(props: Record<string, unknown>) {
8
+ const observedCore =
9
+ ref<ReturnType<typeof useCopilotKit>["copilotkit"]["value"]>();
10
+
11
+ const Child = defineComponent({
12
+ setup() {
13
+ const { copilotkit } = useCopilotKit();
14
+ observedCore.value = copilotkit.value;
15
+ return () => h("div");
16
+ },
17
+ });
18
+
19
+ const wrapper = mount(CopilotKitProvider, {
20
+ props: {
21
+ runtimeUrl: "/api/copilotkit",
22
+ ...props,
23
+ },
24
+ slots: {
25
+ default: () => h(Child),
26
+ },
27
+ });
28
+
29
+ return { wrapper, observedCore };
30
+ }
31
+
32
+ describe("CopilotKitProvider a2ui catalog auto-enable", () => {
33
+ it("forwards a2uiCatalogAvailable when a catalog is passed to the provider", () => {
34
+ const { observedCore } = mountProvider({
35
+ a2ui: { catalog: { id: "test", components: new Map() } },
36
+ });
37
+
38
+ expect(observedCore.value?.properties.a2uiCatalogAvailable).toBe(true);
39
+ });
40
+
41
+ it("does not forward a2uiCatalogAvailable when no catalog is passed", () => {
42
+ const { observedCore } = mountProvider({ a2ui: {} });
43
+
44
+ expect(observedCore.value?.properties.a2uiCatalogAvailable).toBeUndefined();
45
+ });
46
+
47
+ it("preserves user-provided properties alongside the catalog signal", () => {
48
+ const { observedCore } = mountProvider({
49
+ a2ui: { catalog: { id: "test", components: new Map() } },
50
+ properties: { tenant: "acme" },
51
+ });
52
+
53
+ expect(observedCore.value?.properties).toMatchObject({
54
+ tenant: "acme",
55
+ a2uiCatalogAvailable: true,
56
+ });
57
+ });
58
+
59
+ it("registers an a2ui-surface renderer when a catalog is provided, without a runtime signal", () => {
60
+ const { observedCore } = mountProvider({
61
+ a2ui: { catalog: { id: "test", components: new Map() } },
62
+ });
63
+
64
+ const a2uiRenderer = observedCore.value?.renderActivityMessages.find(
65
+ (r) => r.activityType === "a2ui-surface",
66
+ );
67
+ expect(a2uiRenderer).toBeDefined();
68
+ });
69
+
70
+ it("does not register an a2ui-surface renderer when no catalog is provided and runtime has not signaled a2uiEnabled", () => {
71
+ const { observedCore } = mountProvider({ a2ui: { theme: {} } });
72
+
73
+ const a2uiRenderer = observedCore.value?.renderActivityMessages.find(
74
+ (r) => r.activityType === "a2ui-surface",
75
+ );
76
+ expect(a2uiRenderer).toBeUndefined();
77
+ });
78
+ });
@@ -1,15 +1,31 @@
1
+ import type { Interrupt, RunAgentResult } from "@ag-ui/client";
2
+
1
3
  export interface InterruptEvent<TValue = unknown> {
2
4
  name: string;
3
5
  value: TValue;
4
6
  }
5
7
 
8
+ export type InterruptResolveFn = (
9
+ payload?: unknown,
10
+ interruptId?: string,
11
+ ) => Promise<RunAgentResult | void>;
12
+ export type InterruptCancelFn = (
13
+ interruptId?: string,
14
+ ) => Promise<RunAgentResult | void>;
15
+
6
16
  export interface InterruptHandlerProps<TValue = unknown> {
7
17
  event: InterruptEvent<TValue>;
8
- resolve: (response: unknown) => void;
18
+ interrupt: Interrupt | null;
19
+ interrupts: Interrupt[];
20
+ resolve: InterruptResolveFn;
21
+ cancel: InterruptCancelFn;
9
22
  }
10
23
 
11
24
  export interface InterruptRenderProps<TValue = unknown, TResult = unknown> {
12
25
  event: InterruptEvent<TValue>;
26
+ interrupt: Interrupt | null;
27
+ interrupts: Interrupt[];
13
28
  result: TResult;
14
- resolve: (response: unknown) => void;
29
+ resolve: InterruptResolveFn;
30
+ cancel: InterruptCancelFn;
15
31
  }