@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.
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +12 -11
- package/dist/styles.css +1 -1
- package/dist/{use-render-activity-message-BB43N8wJ.js → use-render-activity-message-Bzh3Bckz.js} +3796 -3706
- package/dist/use-render-activity-message-Bzh3Bckz.js.map +1 -0
- package/dist/use-render-activity-message-CW3vipBB.cjs +85 -0
- package/dist/use-render-activity-message-CW3vipBB.cjs.map +1 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/index.d.ts.map +1 -1
- package/dist/v2/hooks/use-interrupt.d.ts +21 -8
- package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.d.cts +1 -0
- package/dist/v2/index.d.mts +1 -0
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/index.d.ts.map +1 -1
- package/dist/v2/index.mjs +8 -7
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/types/interrupt.d.ts +11 -2
- package/dist/v2/types/interrupt.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/v2/__tests__/exports.test.ts +7 -0
- package/src/v2/components/chat/CopilotChatInput.vue +1 -1
- package/src/v2/components/chat/CopilotChatMessageView.vue +3 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.vue +1 -1
- package/src/v2/components/chat/CopilotChatView.vue +3 -3
- package/src/v2/hooks/__tests__/use-interrupt.test.ts +187 -2
- package/src/v2/hooks/use-interrupt.ts +218 -44
- package/src/v2/index.ts +5 -0
- package/src/v2/providers/CopilotKitProvider.vue +25 -12
- package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +78 -0
- package/src/v2/types/interrupt.ts +18 -2
- package/dist/use-render-activity-message-B0zDl3Lc.cjs +0 -85
- package/dist/use-render-activity-message-B0zDl3Lc.cjs.map +0 -1
- 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 {
|
|
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
|
-
|
|
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
|
|
111
|
+
* Vue composable for handling agent interrupts with optional filtering,
|
|
112
|
+
* preprocessing, and resume behavior.
|
|
77
113
|
*
|
|
78
|
-
*
|
|
79
|
-
* `
|
|
80
|
-
* interrupts
|
|
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,
|
|
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
|
|
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
|
-
|
|
144
|
+
pending.value = null;
|
|
102
145
|
result.value = null;
|
|
103
146
|
return;
|
|
104
147
|
}
|
|
105
148
|
|
|
106
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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:
|
|
155
|
-
|
|
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
|
-
|
|
162
|
-
(
|
|
163
|
-
if (!
|
|
306
|
+
pending,
|
|
307
|
+
(currentPending, _previous, onCleanup) => {
|
|
308
|
+
if (!currentPending) {
|
|
164
309
|
result.value = null;
|
|
165
310
|
return;
|
|
166
311
|
}
|
|
167
|
-
|
|
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:
|
|
180
|
-
|
|
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
|
-
|
|
212
|
-
if (
|
|
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:
|
|
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
|
|
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<
|
|
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 (
|
|
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:
|
|
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(
|
|
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
|
-
() =>
|
|
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, () =>
|
|
567
|
+
registerA2UIBuiltInToolCallRenderer(copilotkit, () => a2uiActive.value);
|
|
552
568
|
|
|
553
569
|
// A2UI catalog context, schema, and generation/design guidelines
|
|
554
570
|
registerA2UICatalogContext(copilotkit, {
|
|
555
|
-
enabled: () =>
|
|
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(
|
|
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
|
-
|
|
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:
|
|
29
|
+
resolve: InterruptResolveFn;
|
|
30
|
+
cancel: InterruptCancelFn;
|
|
15
31
|
}
|