@copilotkit/vue 1.67.1 → 1.68.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/PARITY.md +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{use-render-activity-message-CaArNmtw.js → use-render-activity-message-DljRBrXN.js} +3401 -3370
- package/dist/use-render-activity-message-DljRBrXN.js.map +1 -0
- package/dist/use-render-activity-message-bkrPa-92.cjs +85 -0
- package/dist/use-render-activity-message-bkrPa-92.cjs.map +1 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/hooks/use-agent.d.ts +66 -5
- package/dist/v2/hooks/use-agent.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/package.json +5 -5
- package/src/v2/components/chat/CopilotChat.vue +14 -1
- package/src/v2/components/chat/CopilotChatMessageView.vue +5 -7
- package/src/v2/components/chat/__tests__/CopilotChat.clearOnFresh.test.ts +14 -25
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.ts +1 -2
- package/src/v2/components/chat/__tests__/CopilotChat.test.ts +2 -3
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +6 -6
- package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.ts +14 -0
- package/src/v2/hooks/__tests__/use-agent-thread-pinning.test.ts +179 -0
- package/src/v2/hooks/use-agent.ts +205 -78
- package/dist/use-render-activity-message-CNbp9Fob.cjs +0 -85
- package/dist/use-render-activity-message-CNbp9Fob.cjs.map +0 -1
- package/dist/use-render-activity-message-CaArNmtw.js.map +0 -1
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.ts +0 -433
|
@@ -26,9 +26,7 @@ const ALL_UPDATES: UseAgentUpdate[] = [
|
|
|
26
26
|
UseAgentUpdate.OnRunStatusChanged,
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
agentId?: MaybeRefOrGetter<string | undefined>;
|
|
31
|
-
threadId?: MaybeRefOrGetter<string | undefined>;
|
|
29
|
+
interface UseAgentPropsBase {
|
|
32
30
|
updates?: UseAgentUpdate[];
|
|
33
31
|
/**
|
|
34
32
|
* Throttle interval (in milliseconds) for re-renders triggered by
|
|
@@ -55,65 +53,73 @@ export interface UseAgentProps {
|
|
|
55
53
|
throttleMs?: MaybeRefOrGetter<number | undefined>;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Thread-scoped variant. `agentId`, `runtimeAgentId` and `threadId` are a matched
|
|
58
|
+
* set: together they give this hook a private proxied agent — registered under the
|
|
59
|
+
* local `agentId`, routing outbound to `runtimeAgentId` — that is safe to pin a
|
|
60
|
+
* thread onto. Mirrors React's `UseAgentThreadScopedProps`.
|
|
61
|
+
*/
|
|
62
|
+
interface UseAgentThreadScopedProps {
|
|
63
|
+
/**
|
|
64
|
+
* The *local* registry id to register this hook's proxied agent under. Required
|
|
65
|
+
* here: the usual fallbacks (chat configuration, then `DEFAULT_AGENT_ID`) name
|
|
66
|
+
* agents that already exist, and registering over one of those throws.
|
|
67
|
+
*/
|
|
68
|
+
agentId: MaybeRefOrGetter<string>;
|
|
69
|
+
/**
|
|
70
|
+
* The runtime agent to route outbound requests to while this hook exposes a
|
|
71
|
+
* distinct local `agentId`. Registers a proxied agent via
|
|
72
|
+
* `CopilotKitCore.registerProxiedAgent`, so several frontend agents (e.g. one
|
|
73
|
+
* per open thread) can mount against one runtime agent.
|
|
74
|
+
*
|
|
75
|
+
* REQUIRES `threadId` — a private agent with no thread to scope behaves like
|
|
76
|
+
* the shared one, minus a registration and a local id to keep unique.
|
|
77
|
+
*/
|
|
78
|
+
runtimeAgentId: MaybeRefOrGetter<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Thread to scope the agent's run to. Written onto the underlying agent, so
|
|
81
|
+
* `/agent/run`, `/agent/connect` and `/agent/stop` address this thread.
|
|
82
|
+
*
|
|
83
|
+
* REQUIRES `runtimeAgentId` — an agent resolved by `agentId` alone is shared,
|
|
84
|
+
* so a per-hook thread written onto it would clobber every other holder.
|
|
85
|
+
*/
|
|
86
|
+
threadId: MaybeRefOrGetter<string>;
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Default variant: no thread scoping. Binds to the shared agent registered under
|
|
91
|
+
* `agentId` and takes its thread from the surrounding chat configuration, gated
|
|
92
|
+
* on `hasExplicitThreadId`. Mirrors React's `UseAgentUnscopedProps`.
|
|
93
|
+
*
|
|
94
|
+
* `threadId` and `runtimeAgentId` are typed `undefined` rather than omitted so
|
|
95
|
+
* that supplying either alone matches *neither* branch — that is the type-level
|
|
96
|
+
* enforcement of the all-or-nothing rule.
|
|
97
|
+
*/
|
|
98
|
+
interface UseAgentUnscopedProps {
|
|
99
|
+
/**
|
|
100
|
+
* Agent to bind to. Resolution precedence: this property, then the surrounding
|
|
101
|
+
* chat configuration's agentId, then the global default.
|
|
102
|
+
*/
|
|
103
|
+
agentId?: MaybeRefOrGetter<string | undefined>;
|
|
104
|
+
/** Requires `runtimeAgentId`. See {@link UseAgentThreadScopedProps.threadId}. */
|
|
105
|
+
threadId?: undefined;
|
|
106
|
+
/** Requires `threadId`. See {@link UseAgentThreadScopedProps.runtimeAgentId}. */
|
|
107
|
+
runtimeAgentId?: undefined;
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
existing.threadId = threadId;
|
|
107
|
-
if (existing instanceof HttpAgent) {
|
|
108
|
-
existing.headers = { ...headers };
|
|
109
|
-
}
|
|
110
|
-
return existing;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const clone = cloneForThread(source, threadId, headers);
|
|
114
|
-
byThread.set(threadId, clone);
|
|
115
|
-
return clone;
|
|
116
|
-
}
|
|
110
|
+
/**
|
|
111
|
+
* Props for {@link useAgent}. Two valid shapes, nothing in between:
|
|
112
|
+
*
|
|
113
|
+
* - **Bind to an agent** — `useAgent()`, `useAgent({ agentId })`. The shared
|
|
114
|
+
* registry instance; the thread comes from the chat configuration.
|
|
115
|
+
* - **Bind a private agent to a thread** —
|
|
116
|
+
* `useAgent({ agentId, runtimeAgentId, threadId })`. All three required.
|
|
117
|
+
*
|
|
118
|
+
* So `{ agentId, threadId }`, `{ agentId, runtimeAgentId }` and
|
|
119
|
+
* `{ runtimeAgentId, threadId }` are all compile errors, matching React.
|
|
120
|
+
*/
|
|
121
|
+
export type UseAgentProps = UseAgentPropsBase &
|
|
122
|
+
(UseAgentThreadScopedProps | UseAgentUnscopedProps);
|
|
117
123
|
|
|
118
124
|
/**
|
|
119
125
|
* Resolves and subscribes to a CopilotKit agent for the current Vue scope.
|
|
@@ -128,13 +134,64 @@ function getOrCreateThreadClone(
|
|
|
128
134
|
* ```
|
|
129
135
|
*/
|
|
130
136
|
export function useAgent(props: UseAgentProps = {}) {
|
|
137
|
+
// `threadId`, `runtimeAgentId` and an explicit `agentId` are all-or-nothing.
|
|
138
|
+
// UseAgentProps rejects a partial set at compile time; these are the runtime
|
|
139
|
+
// backstop for callers TypeScript doesn't reach (plain JS, `as any`). Same
|
|
140
|
+
// three checks, same messages, as React's useAgent.
|
|
141
|
+
const hasThreadId = props.threadId !== undefined;
|
|
142
|
+
const hasRuntimeAgentId = props.runtimeAgentId !== undefined;
|
|
143
|
+
const hasAgentId = props.agentId !== undefined;
|
|
144
|
+
|
|
145
|
+
if (hasThreadId && !hasRuntimeAgentId) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
"useAgent: `threadId` requires `runtimeAgentId`. A threadId is written onto a " +
|
|
148
|
+
"single agent, but an agent resolved by agentId alone is shared, so scoping a " +
|
|
149
|
+
"thread to it would clobber other useAgent callers. Pass a distinct local `agentId` " +
|
|
150
|
+
'and the runtime agent to route to, e.g. useAgent({ agentId: "chat-1", ' +
|
|
151
|
+
'runtimeAgentId: "assistant", threadId }).',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (hasRuntimeAgentId && !hasThreadId) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"useAgent: `runtimeAgentId` requires `threadId`. A proxied agent exists to scope a " +
|
|
158
|
+
"thread to a private instance; without a threadId it behaves like the shared agent " +
|
|
159
|
+
"while adding a registration and a local agentId to keep unique. Either pass the " +
|
|
160
|
+
"thread, or bind to the agent directly with useAgent({ agentId }).",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (hasRuntimeAgentId && !hasAgentId) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
"useAgent: `runtimeAgentId` requires an explicit `agentId`. The proxied agent is " +
|
|
167
|
+
"registered under `agentId`, and the usual fallbacks (chat configuration, then " +
|
|
168
|
+
`"${DEFAULT_AGENT_ID}") name agents that already exist — registering over one throws ` +
|
|
169
|
+
"or shadows it. Pick a local id for this hook, e.g. " +
|
|
170
|
+
'useAgent({ agentId: "chat-1", runtimeAgentId: "assistant", threadId }).',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// After the guards, all three are present or none are.
|
|
175
|
+
const isThreadScoped = hasRuntimeAgentId;
|
|
176
|
+
|
|
131
177
|
const chatConfig = useCopilotChatConfiguration();
|
|
132
178
|
const agentId = computed(
|
|
133
179
|
() =>
|
|
134
180
|
toValue(props.agentId) ?? chatConfig.value?.agentId ?? DEFAULT_AGENT_ID,
|
|
135
181
|
);
|
|
136
|
-
const
|
|
137
|
-
|
|
182
|
+
const runtimeAgentId = computed(() =>
|
|
183
|
+
isThreadScoped ? toValue(props.runtimeAgentId) : undefined,
|
|
184
|
+
);
|
|
185
|
+
// Mirrors React: an explicit `threadId` prop wins, otherwise the chat
|
|
186
|
+
// configuration's thread, gated on `hasExplicitThreadId` so a
|
|
187
|
+
// ThreadsProvider-minted placeholder UUID doesn't overwrite the agent's own
|
|
188
|
+
// auto-minted one (both are random and useless to the backend).
|
|
189
|
+
const resolvedThreadId = computed(
|
|
190
|
+
() =>
|
|
191
|
+
toValue(props.threadId) ??
|
|
192
|
+
(chatConfig.value?.hasExplicitThreadId
|
|
193
|
+
? chatConfig.value.threadId
|
|
194
|
+
: undefined),
|
|
138
195
|
);
|
|
139
196
|
const { copilotkit } = useCopilotKit();
|
|
140
197
|
const updateFlags = computed(() => props.updates ?? ALL_UPDATES);
|
|
@@ -165,22 +222,79 @@ export function useAgent(props: UseAgentProps = {}) {
|
|
|
165
222
|
return provisional;
|
|
166
223
|
};
|
|
167
224
|
|
|
225
|
+
// On the thread-scoped path this hook owns an agent registered under
|
|
226
|
+
// `agentId` that routes to `runtimeAgentId`. Register/unregister as one
|
|
227
|
+
// balanced watcher — the cleanup runs both when the ids change and when the
|
|
228
|
+
// scope is disposed. Mirrors React's registration effect, including its dep
|
|
229
|
+
// set (core identity plus the two ids).
|
|
230
|
+
const registeredProxy = shallowRef<AbstractAgent | null>(null);
|
|
231
|
+
if (isThreadScoped) {
|
|
232
|
+
watch(
|
|
233
|
+
[agentId, runtimeAgentId, () => copilotkit.value],
|
|
234
|
+
([id, rtId], _old, onCleanup) => {
|
|
235
|
+
const { agent: proxy, unregister } =
|
|
236
|
+
copilotkit.value.registerProxiedAgent({
|
|
237
|
+
agentId: id as string,
|
|
238
|
+
runtimeAgentId: rtId as string,
|
|
239
|
+
});
|
|
240
|
+
provisionalAgentCache.delete(id as string);
|
|
241
|
+
registeredProxy.value = proxy;
|
|
242
|
+
onCleanup(() => {
|
|
243
|
+
unregister();
|
|
244
|
+
registeredProxy.value = null;
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
{ immediate: true },
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
168
251
|
const resolveAgent = () => {
|
|
169
252
|
const id = agentId.value;
|
|
170
|
-
const resolvedThreadId = threadId.value;
|
|
171
|
-
const cacheKey = resolvedThreadId ? `${id}:${resolvedThreadId}` : id;
|
|
172
253
|
const core = copilotkit.value;
|
|
254
|
+
|
|
255
|
+
// Proxied path: this hook registered its own agent, so bypass the shared
|
|
256
|
+
// registry lookup. Until registration lands, hand back a provisional proxy
|
|
257
|
+
// so `agent` is never null and its identity stays stable.
|
|
258
|
+
if (isThreadScoped) {
|
|
259
|
+
const proxy = registeredProxy.value;
|
|
260
|
+
if (proxy) {
|
|
261
|
+
provisionalAgentCache.delete(id);
|
|
262
|
+
const shouldForceUpdate = agent.value === proxy;
|
|
263
|
+
agent.value = proxy;
|
|
264
|
+
subscriptionAgent.value = proxy;
|
|
265
|
+
if (shouldForceUpdate) triggerRef(agent);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const cachedProxy = provisionalAgentCache.get(id);
|
|
270
|
+
if (cachedProxy) {
|
|
271
|
+
cachedProxy.headers = { ...core.headers };
|
|
272
|
+
agent.value = cachedProxy;
|
|
273
|
+
subscriptionAgent.value = cachedProxy;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const provisionalProxy = new ProxiedCopilotRuntimeAgent({
|
|
278
|
+
runtimeUrl: core.runtimeUrl,
|
|
279
|
+
agentId: id,
|
|
280
|
+
runtimeAgentId: runtimeAgentId.value as string,
|
|
281
|
+
transport: core.runtimeTransport,
|
|
282
|
+
runtimeMode: "pending",
|
|
283
|
+
});
|
|
284
|
+
provisionalProxy.headers = { ...core.headers };
|
|
285
|
+
provisionalAgentCache.set(id, provisionalProxy);
|
|
286
|
+
agent.value = provisionalProxy;
|
|
287
|
+
subscriptionAgent.value = provisionalProxy;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
173
291
|
const existing = core.getAgent(id);
|
|
174
292
|
if (existing) {
|
|
175
|
-
provisionalAgentCache.delete(cacheKey);
|
|
176
293
|
provisionalAgentCache.delete(id);
|
|
177
294
|
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const shouldForceUpdate = agent.value === resolvedAgent;
|
|
182
|
-
agent.value = resolvedAgent;
|
|
183
|
-
subscriptionAgent.value = resolvedAgent;
|
|
295
|
+
const shouldForceUpdate = agent.value === existing;
|
|
296
|
+
agent.value = existing;
|
|
297
|
+
subscriptionAgent.value = existing;
|
|
184
298
|
if (shouldForceUpdate) {
|
|
185
299
|
triggerRef(agent);
|
|
186
300
|
}
|
|
@@ -196,12 +310,9 @@ export function useAgent(props: UseAgentProps = {}) {
|
|
|
196
310
|
status === CopilotKitCoreRuntimeConnectionStatus.Connecting ||
|
|
197
311
|
status === CopilotKitCoreRuntimeConnectionStatus.Error)
|
|
198
312
|
) {
|
|
199
|
-
const cached = provisionalAgentCache.get(
|
|
313
|
+
const cached = provisionalAgentCache.get(id);
|
|
200
314
|
if (cached) {
|
|
201
315
|
cached.headers = { ...core.headers };
|
|
202
|
-
if (resolvedThreadId) {
|
|
203
|
-
cached.threadId = resolvedThreadId;
|
|
204
|
-
}
|
|
205
316
|
agent.value = cached;
|
|
206
317
|
subscriptionAgent.value = cached;
|
|
207
318
|
return;
|
|
@@ -213,10 +324,7 @@ export function useAgent(props: UseAgentProps = {}) {
|
|
|
213
324
|
core.runtimeTransport,
|
|
214
325
|
core.headers,
|
|
215
326
|
);
|
|
216
|
-
|
|
217
|
-
provisional.threadId = resolvedThreadId;
|
|
218
|
-
}
|
|
219
|
-
provisionalAgentCache.set(cacheKey, provisional);
|
|
327
|
+
provisionalAgentCache.set(id, provisional);
|
|
220
328
|
agent.value = provisional;
|
|
221
329
|
subscriptionAgent.value = provisional;
|
|
222
330
|
return;
|
|
@@ -238,6 +346,7 @@ export function useAgent(props: UseAgentProps = {}) {
|
|
|
238
346
|
watch(
|
|
239
347
|
[
|
|
240
348
|
agentId,
|
|
349
|
+
registeredProxy,
|
|
241
350
|
() => copilotkit.value.agents,
|
|
242
351
|
() => copilotkit.value.runtimeConnectionStatus,
|
|
243
352
|
() => copilotkit.value.runtimeUrl,
|
|
@@ -248,12 +357,30 @@ export function useAgent(props: UseAgentProps = {}) {
|
|
|
248
357
|
a.localeCompare(b),
|
|
249
358
|
),
|
|
250
359
|
),
|
|
251
|
-
threadId,
|
|
252
360
|
],
|
|
253
361
|
resolveAgent,
|
|
254
362
|
{ immediate: true },
|
|
255
363
|
);
|
|
256
364
|
|
|
365
|
+
// Pin the resolved thread onto the current agent. AbstractAgent auto-mints a
|
|
366
|
+
// UUID when none is given, so without this the agent would ship its own random
|
|
367
|
+
// id in /agent/run, /agent/connect and /agent/stop. Deps are deliberately just
|
|
368
|
+
// the agent and the thread — matching React — so status/header churn cannot
|
|
369
|
+
// re-fire this and overwrite a thread `CopilotChat` pinned for its own chat.
|
|
370
|
+
// `() => agent.value`, not `agent`: Vue sets `forceTrigger` when any array
|
|
371
|
+
// watch source is a shallow ref, which would re-run this on every
|
|
372
|
+
// `triggerRef(agent)` — i.e. every streamed message — and re-pin a thread that
|
|
373
|
+
// `CopilotChat` may have deliberately overridden for the chat it renders. A
|
|
374
|
+
// getter fires only when the identity actually changes.
|
|
375
|
+
watch(
|
|
376
|
+
[() => agent.value, resolvedThreadId],
|
|
377
|
+
([currentAgent, threadId]) => {
|
|
378
|
+
if (!currentAgent || !threadId) return;
|
|
379
|
+
currentAgent.threadId = threadId as string;
|
|
380
|
+
},
|
|
381
|
+
{ immediate: true },
|
|
382
|
+
);
|
|
383
|
+
|
|
257
384
|
watch(
|
|
258
385
|
[
|
|
259
386
|
subscriptionAgent,
|