@copilotkit/core 0.0.0-mme-a2ui-v0-9-20260402165117
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/LICENSE +21 -0
- package/dist/index.cjs +3415 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1402 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1402 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3376 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +3594 -0
- package/dist/index.umd.js.map +1 -0
- package/eslint.config.mjs +3 -0
- package/package.json +56 -0
- package/tsdown.config.ts +45 -0
- package/vitest.config.mjs +17 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3415 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let _ag_ui_client = require("@ag-ui/client");
|
|
3
|
+
let _copilotkit_shared = require("@copilotkit/shared");
|
|
4
|
+
let rxjs = require("rxjs");
|
|
5
|
+
let rxjs_operators = require("rxjs/operators");
|
|
6
|
+
let phoenix = require("phoenix");
|
|
7
|
+
let zod_to_json_schema = require("zod-to-json-schema");
|
|
8
|
+
let rxjs_fetch = require("rxjs/fetch");
|
|
9
|
+
|
|
10
|
+
//#region src/utils/phoenix-observable.ts
|
|
11
|
+
/**
|
|
12
|
+
* Adapt Phoenix socket open/error callbacks into an observable signal stream.
|
|
13
|
+
*
|
|
14
|
+
* The returned observable is shared and replayable by the caller when needed,
|
|
15
|
+
* but this helper itself does not own socket connection teardown.
|
|
16
|
+
*/
|
|
17
|
+
function ɵcreatePhoenixSocketSignals$(socket) {
|
|
18
|
+
return new rxjs.Observable((observer) => {
|
|
19
|
+
socket.onOpen(() => observer.next({ type: "open" }));
|
|
20
|
+
socket.onError((error) => observer.next({
|
|
21
|
+
type: "error",
|
|
22
|
+
error
|
|
23
|
+
}));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Adapt a Phoenix channel join attempt into a single-outcome observable.
|
|
28
|
+
*/
|
|
29
|
+
function ɵcreatePhoenixJoinOutcome$(channel) {
|
|
30
|
+
return new rxjs.Observable((observer) => {
|
|
31
|
+
channel.join().receive("ok", () => {
|
|
32
|
+
observer.next({ type: "joined" });
|
|
33
|
+
observer.complete();
|
|
34
|
+
}).receive("error", (response) => {
|
|
35
|
+
observer.next({
|
|
36
|
+
type: "error",
|
|
37
|
+
response
|
|
38
|
+
});
|
|
39
|
+
observer.complete();
|
|
40
|
+
}).receive("timeout", () => {
|
|
41
|
+
observer.next({ type: "timeout" });
|
|
42
|
+
observer.complete();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a cold Phoenix socket session.
|
|
48
|
+
*
|
|
49
|
+
* The socket is constructed and connected on subscription, and disconnected on
|
|
50
|
+
* teardown. Each subscription creates an isolated socket instance.
|
|
51
|
+
*/
|
|
52
|
+
function ɵphoenixSocket$(options) {
|
|
53
|
+
return (0, rxjs.defer)(() => {
|
|
54
|
+
const socket = new phoenix.Socket(options.url, options.options);
|
|
55
|
+
const signals$ = ɵcreatePhoenixSocketSignals$(socket).pipe((0, rxjs_operators.shareReplay)({
|
|
56
|
+
bufferSize: 1,
|
|
57
|
+
refCount: true
|
|
58
|
+
}));
|
|
59
|
+
socket.connect();
|
|
60
|
+
return (0, rxjs.concat)((0, rxjs.of)({
|
|
61
|
+
socket,
|
|
62
|
+
signals$
|
|
63
|
+
}), rxjs.NEVER).pipe((0, rxjs_operators.finalize)(() => socket.disconnect()));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a cold Phoenix channel session from a socket session stream.
|
|
68
|
+
*
|
|
69
|
+
* A channel is created and joined for each active socket session. If the
|
|
70
|
+
* upstream socket session changes, the previous channel is left before the
|
|
71
|
+
* next one becomes active.
|
|
72
|
+
*/
|
|
73
|
+
function ɵphoenixChannel$(options) {
|
|
74
|
+
return options.socket$.pipe((0, rxjs_operators.switchMap)(({ socket }) => (0, rxjs.defer)(() => {
|
|
75
|
+
const channel = socket.channel(options.topic, options.params);
|
|
76
|
+
return (0, rxjs.concat)((0, rxjs.of)({
|
|
77
|
+
channel,
|
|
78
|
+
joinOutcome$: ɵcreatePhoenixJoinOutcome$(channel).pipe((0, rxjs_operators.shareReplay)({
|
|
79
|
+
bufferSize: 1,
|
|
80
|
+
refCount: true
|
|
81
|
+
}))
|
|
82
|
+
}), rxjs.NEVER).pipe((0, rxjs_operators.finalize)(() => {
|
|
83
|
+
if (options.leaveOnUnsubscribe !== false) channel.leave();
|
|
84
|
+
}));
|
|
85
|
+
})));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Observe a named Phoenix channel event as an observable payload stream.
|
|
89
|
+
*/
|
|
90
|
+
function ɵobservePhoenixEvent$(channel, eventName) {
|
|
91
|
+
return new rxjs.Observable((observer) => {
|
|
92
|
+
const ref = channel.on(eventName, (payload) => observer.next(payload));
|
|
93
|
+
return () => {
|
|
94
|
+
channel.off(eventName, ref);
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Flatten channel sessions into their join-outcome stream.
|
|
100
|
+
*/
|
|
101
|
+
function ɵobservePhoenixJoinOutcome$(channel$) {
|
|
102
|
+
return channel$.pipe((0, rxjs_operators.switchMap)((session) => session.joinOutcome$));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Complete when a channel joins successfully, or error if the join fails.
|
|
106
|
+
*/
|
|
107
|
+
function ɵjoinPhoenixChannel$(channel$) {
|
|
108
|
+
return ɵobservePhoenixJoinOutcome$(channel$).pipe((0, rxjs_operators.take)(1), (0, rxjs_operators.mergeMap)((outcome) => {
|
|
109
|
+
if (outcome.type === "joined") return rxjs.EMPTY;
|
|
110
|
+
throw outcome.type === "timeout" ? /* @__PURE__ */ new Error("Timed out joining channel") : /* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(outcome.response)}`);
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Flatten socket sessions into their lifecycle signal stream.
|
|
115
|
+
*/
|
|
116
|
+
function ɵobservePhoenixSocketSignals$(socket$) {
|
|
117
|
+
return socket$.pipe((0, rxjs_operators.switchMap)((session) => session.signals$));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Error after a socket emits the configured number of consecutive error
|
|
121
|
+
* signals, resetting the counter after each successful open signal.
|
|
122
|
+
*/
|
|
123
|
+
function ɵobservePhoenixSocketHealth$(socketSignals$, maxConsecutiveErrors) {
|
|
124
|
+
return socketSignals$.pipe((0, rxjs_operators.scan)((consecutiveErrors, signal) => signal.type === "open" ? 0 : consecutiveErrors + 1, 0), (0, rxjs_operators.filter)((consecutiveErrors) => consecutiveErrors >= maxConsecutiveErrors), (0, rxjs_operators.take)(1), (0, rxjs_operators.mergeMap)((consecutiveErrors) => (0, rxjs.throwError)(() => /* @__PURE__ */ new Error(`WebSocket connection failed after ${consecutiveErrors} consecutive errors`))));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/intelligence-agent.ts
|
|
129
|
+
const CLIENT_AG_UI_EVENT = "ag_ui_event";
|
|
130
|
+
const STOP_RUN_EVENT = "stop_run";
|
|
131
|
+
var AgentThreadLockedError = class extends Error {
|
|
132
|
+
constructor(threadId) {
|
|
133
|
+
super(threadId ? `Thread ${threadId} is locked` : "Thread is locked");
|
|
134
|
+
this.name = "AgentThreadLockedError";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAgent {
|
|
138
|
+
config;
|
|
139
|
+
socket = null;
|
|
140
|
+
activeChannel = null;
|
|
141
|
+
runId = null;
|
|
142
|
+
sharedState;
|
|
143
|
+
constructor(config, sharedState = { lastSeenEventIds: /* @__PURE__ */ new Map() }) {
|
|
144
|
+
super();
|
|
145
|
+
this.config = config;
|
|
146
|
+
this.sharedState = sharedState;
|
|
147
|
+
}
|
|
148
|
+
clone() {
|
|
149
|
+
return new IntelligenceAgent(this.config, this.sharedState);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Override of AbstractAgent.connectAgent that removes the `verifyEvents` step.
|
|
153
|
+
*
|
|
154
|
+
* Background: AbstractAgent's connectAgent pipeline runs events through
|
|
155
|
+
* `verifyEvents`, which validates that the stream follows the AG-UI protocol
|
|
156
|
+
* lifecycle — specifically, it expects a RUN_STARTED event before any content
|
|
157
|
+
* events and a RUN_FINISHED/RUN_ERROR event to complete the stream.
|
|
158
|
+
*
|
|
159
|
+
* IntelligenceAgent uses long-lived WebSocket connections rather than
|
|
160
|
+
* request-scoped SSE streams. When connecting to replay historical messages
|
|
161
|
+
* for an existing thread, the connection semantics don't map to a single
|
|
162
|
+
* agent run start/stop cycle. The replayed events may not include
|
|
163
|
+
* RUN_STARTED/RUN_FINISHED bookends (or may contain events from multiple
|
|
164
|
+
* past runs), which causes verifyEvents to either never complete or to
|
|
165
|
+
* error out.
|
|
166
|
+
*
|
|
167
|
+
* This override replicates the base connectAgent implementation exactly,
|
|
168
|
+
* substituting only `transformChunks` (which is still needed for message
|
|
169
|
+
* reassembly) and omitting `verifyEvents`.
|
|
170
|
+
*
|
|
171
|
+
* TODO: Remove this override once AG-UI's AbstractAgent supports opting out
|
|
172
|
+
* of verifyEvents for transports with different connection life-cycles.
|
|
173
|
+
*/
|
|
174
|
+
async connectAgent(parameters, subscriber) {
|
|
175
|
+
const self = this;
|
|
176
|
+
try {
|
|
177
|
+
this.isRunning = true;
|
|
178
|
+
this.agentId = this.agentId ?? (0, _ag_ui_client.randomUUID)();
|
|
179
|
+
const input = this.prepareRunAgentInput(parameters);
|
|
180
|
+
let result;
|
|
181
|
+
const previousMessageIds = new Set(this.messages.map((m) => m.id));
|
|
182
|
+
const subscribers = [
|
|
183
|
+
{ onRunFinishedEvent: (event) => {
|
|
184
|
+
result = event.result;
|
|
185
|
+
} },
|
|
186
|
+
...this.subscribers,
|
|
187
|
+
subscriber ?? {}
|
|
188
|
+
];
|
|
189
|
+
await this.onInitialize(input, subscribers);
|
|
190
|
+
self.activeRunDetach$ = new rxjs.Subject();
|
|
191
|
+
let resolveCompletion;
|
|
192
|
+
self.activeRunCompletionPromise = new Promise((resolve) => {
|
|
193
|
+
resolveCompletion = resolve;
|
|
194
|
+
});
|
|
195
|
+
const source$ = (0, rxjs.defer)(() => this.connect(input)).pipe((0, _ag_ui_client.transformChunks)(this.debug), (0, rxjs_operators.takeUntil)(self.activeRunDetach$));
|
|
196
|
+
const applied$ = this.apply(input, source$, subscribers);
|
|
197
|
+
await (0, rxjs.lastValueFrom)(this.processApplyEvents(input, applied$, subscribers).pipe((0, rxjs_operators.catchError)((error) => {
|
|
198
|
+
this.isRunning = false;
|
|
199
|
+
return this.onError(input, error, subscribers);
|
|
200
|
+
}), (0, rxjs_operators.finalize)(() => {
|
|
201
|
+
this.isRunning = false;
|
|
202
|
+
this.onFinalize(input, subscribers);
|
|
203
|
+
resolveCompletion?.();
|
|
204
|
+
resolveCompletion = void 0;
|
|
205
|
+
self.activeRunCompletionPromise = void 0;
|
|
206
|
+
self.activeRunDetach$ = void 0;
|
|
207
|
+
})), { defaultValue: void 0 });
|
|
208
|
+
const newMessages = (0, _ag_ui_client.structuredClone_)(this.messages).filter((m) => !previousMessageIds.has(m.id));
|
|
209
|
+
return {
|
|
210
|
+
result,
|
|
211
|
+
newMessages
|
|
212
|
+
};
|
|
213
|
+
} finally {
|
|
214
|
+
this.isRunning = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
abortRun() {
|
|
218
|
+
if (this.activeChannel && this.runId) {
|
|
219
|
+
const fallback = setTimeout(() => clear(), 5e3);
|
|
220
|
+
const clear = () => {
|
|
221
|
+
clearTimeout(fallback);
|
|
222
|
+
this.detachActiveRun();
|
|
223
|
+
this.cleanup();
|
|
224
|
+
};
|
|
225
|
+
this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.runId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
|
|
226
|
+
} else {
|
|
227
|
+
this.detachActiveRun();
|
|
228
|
+
this.cleanup();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Trigger the run via REST, then join the realtime thread channel and relay
|
|
233
|
+
* server-pushed AG-UI events to the Observable subscriber.
|
|
234
|
+
*/
|
|
235
|
+
run(input) {
|
|
236
|
+
this.threadId = input.threadId;
|
|
237
|
+
this.runId = input.runId;
|
|
238
|
+
return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => this.observeThread$(input, credentials, {
|
|
239
|
+
completeOnRunError: false,
|
|
240
|
+
streamMode: "run"
|
|
241
|
+
})));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Reconnect to an existing thread by fetching websocket credentials and
|
|
245
|
+
* joining the realtime thread channel.
|
|
246
|
+
*/
|
|
247
|
+
connect(input) {
|
|
248
|
+
this.threadId = input.threadId;
|
|
249
|
+
this.runId = input.runId;
|
|
250
|
+
return (0, rxjs.defer)(() => this.requestConnectPlan$(input)).pipe((0, rxjs.switchMap)((plan) => {
|
|
251
|
+
if (plan === null) return rxjs.EMPTY;
|
|
252
|
+
if (plan.mode === "bootstrap") {
|
|
253
|
+
this.setLastSeenEventId(input.threadId, plan.latestEventId);
|
|
254
|
+
for (const event of plan.events) this.updateRunIdFromEvent(event);
|
|
255
|
+
return (0, rxjs.from)(plan.events);
|
|
256
|
+
}
|
|
257
|
+
this.setLastSeenEventId(input.threadId, plan.joinFromEventId);
|
|
258
|
+
for (const event of plan.events) this.updateRunIdFromEvent(event);
|
|
259
|
+
return (0, rxjs.concat)((0, rxjs.from)(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
|
|
260
|
+
completeOnRunError: true,
|
|
261
|
+
streamMode: "connect",
|
|
262
|
+
replayCursor: plan.joinFromEventId
|
|
263
|
+
}));
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Tear down a specific channel + socket pair that belongs to one pipeline.
|
|
268
|
+
* Only nulls instance references when they still point to the owned resource,
|
|
269
|
+
* so a concurrent pipeline's resources are never clobbered.
|
|
270
|
+
*/
|
|
271
|
+
cleanupOwned(ownChannel, ownSocket) {
|
|
272
|
+
if (ownChannel) {
|
|
273
|
+
ownChannel.leave();
|
|
274
|
+
if (this.activeChannel === ownChannel) this.activeChannel = null;
|
|
275
|
+
}
|
|
276
|
+
if (ownSocket) {
|
|
277
|
+
ownSocket.disconnect();
|
|
278
|
+
if (this.socket === ownSocket) this.socket = null;
|
|
279
|
+
}
|
|
280
|
+
if (this.threadId) this.sharedState.lastSeenEventIds.delete(this.threadId);
|
|
281
|
+
this.runId = null;
|
|
282
|
+
}
|
|
283
|
+
cleanup() {
|
|
284
|
+
this.cleanupOwned(this.activeChannel, this.socket);
|
|
285
|
+
}
|
|
286
|
+
requestJoinCredentials$(mode, input) {
|
|
287
|
+
return (0, rxjs.defer)(async () => {
|
|
288
|
+
try {
|
|
289
|
+
const response = await fetch(this.buildRuntimeUrl(mode), {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
"Content-Type": "application/json",
|
|
293
|
+
...this.config.headers
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify({
|
|
296
|
+
threadId: input.threadId,
|
|
297
|
+
runId: input.runId,
|
|
298
|
+
messages: input.messages,
|
|
299
|
+
tools: input.tools,
|
|
300
|
+
context: input.context,
|
|
301
|
+
state: input.state,
|
|
302
|
+
forwardedProps: input.forwardedProps
|
|
303
|
+
}),
|
|
304
|
+
...this.config.credentials ? { credentials: this.config.credentials } : {}
|
|
305
|
+
});
|
|
306
|
+
if (response.status === 409) throw new AgentThreadLockedError(input.threadId);
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
const text = await response.text().catch(() => "");
|
|
309
|
+
throw new Error(text || response.statusText || String(response.status));
|
|
310
|
+
}
|
|
311
|
+
const payload = await response.json();
|
|
312
|
+
if (!payload.joinToken) throw new Error("missing joinToken");
|
|
313
|
+
return { joinToken: payload.joinToken };
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof AgentThreadLockedError) throw error;
|
|
316
|
+
throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
requestConnectPlan$(input) {
|
|
321
|
+
return (0, rxjs.defer)(async () => {
|
|
322
|
+
try {
|
|
323
|
+
const response = await fetch(this.buildRuntimeUrl("connect"), {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
"Content-Type": "application/json",
|
|
327
|
+
...this.config.headers
|
|
328
|
+
},
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
threadId: input.threadId,
|
|
331
|
+
runId: input.runId,
|
|
332
|
+
messages: input.messages,
|
|
333
|
+
tools: input.tools,
|
|
334
|
+
context: input.context,
|
|
335
|
+
state: input.state,
|
|
336
|
+
forwardedProps: input.forwardedProps,
|
|
337
|
+
lastSeenEventId: this.getReconnectCursor(input)
|
|
338
|
+
}),
|
|
339
|
+
...this.config.credentials ? { credentials: this.config.credentials } : {}
|
|
340
|
+
});
|
|
341
|
+
if (response.status === 204) return null;
|
|
342
|
+
if (!response.ok) {
|
|
343
|
+
const text = await response.text().catch(() => "");
|
|
344
|
+
throw new Error(text || response.statusText || String(response.status));
|
|
345
|
+
}
|
|
346
|
+
return this.normalizeConnectPlan(await response.json());
|
|
347
|
+
} catch (error) {
|
|
348
|
+
throw new Error(`REST connect request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
normalizeConnectPlan(payload) {
|
|
353
|
+
const envelope = payload && typeof payload === "object" ? payload : null;
|
|
354
|
+
if (envelope?.mode === "bootstrap") return {
|
|
355
|
+
mode: "bootstrap",
|
|
356
|
+
latestEventId: typeof envelope.latestEventId === "string" ? envelope.latestEventId : null,
|
|
357
|
+
events: Array.isArray(envelope.events) ? envelope.events : []
|
|
358
|
+
};
|
|
359
|
+
if (envelope?.mode === "live") {
|
|
360
|
+
if (typeof envelope.joinToken !== "string" || envelope.joinToken.length === 0) throw new Error("missing joinToken");
|
|
361
|
+
return {
|
|
362
|
+
mode: "live",
|
|
363
|
+
joinToken: envelope.joinToken,
|
|
364
|
+
joinFromEventId: typeof envelope.joinFromEventId === "string" ? envelope.joinFromEventId : null,
|
|
365
|
+
events: Array.isArray(envelope.events) ? envelope.events : []
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
throw new Error("invalid connect plan");
|
|
369
|
+
}
|
|
370
|
+
observeThread$(input, credentials, options) {
|
|
371
|
+
return (0, rxjs.defer)(() => {
|
|
372
|
+
let ownSocket = null;
|
|
373
|
+
let ownChannel = null;
|
|
374
|
+
const socket$ = ɵphoenixSocket$({
|
|
375
|
+
url: this.config.url,
|
|
376
|
+
options: {
|
|
377
|
+
params: {
|
|
378
|
+
...this.config.socketParams ?? {},
|
|
379
|
+
join_token: credentials.joinToken
|
|
380
|
+
},
|
|
381
|
+
reconnectAfterMs: (0, _copilotkit_shared.phoenixExponentialBackoff)(100, 1e4),
|
|
382
|
+
rejoinAfterMs: (0, _copilotkit_shared.phoenixExponentialBackoff)(1e3, 3e4)
|
|
383
|
+
}
|
|
384
|
+
}).pipe((0, rxjs_operators.tap)(({ socket }) => {
|
|
385
|
+
ownSocket = socket;
|
|
386
|
+
this.socket = ownSocket;
|
|
387
|
+
}), (0, rxjs_operators.shareReplay)({
|
|
388
|
+
bufferSize: 1,
|
|
389
|
+
refCount: true
|
|
390
|
+
}));
|
|
391
|
+
const { topic, params } = this.createThreadChannelDescriptor(input, options.streamMode, options.replayCursor);
|
|
392
|
+
const channel$ = ɵphoenixChannel$({
|
|
393
|
+
socket$,
|
|
394
|
+
topic,
|
|
395
|
+
params
|
|
396
|
+
}).pipe((0, rxjs_operators.tap)(({ channel }) => {
|
|
397
|
+
ownChannel = channel;
|
|
398
|
+
this.activeChannel = ownChannel;
|
|
399
|
+
}), (0, rxjs_operators.shareReplay)({
|
|
400
|
+
bufferSize: 1,
|
|
401
|
+
refCount: true
|
|
402
|
+
}));
|
|
403
|
+
const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe((0, rxjs_operators.share)());
|
|
404
|
+
const threadCompleted$ = threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1));
|
|
405
|
+
return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(threadCompleted$)), threadEvents$).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
joinThreadChannel$(channel$) {
|
|
409
|
+
return ɵjoinPhoenixChannel$(channel$);
|
|
410
|
+
}
|
|
411
|
+
observeSocketHealth$(socket$) {
|
|
412
|
+
return ɵobservePhoenixSocketHealth$(ɵobservePhoenixSocketSignals$(socket$), 5);
|
|
413
|
+
}
|
|
414
|
+
observeThreadEvents$(threadId, channel$, options) {
|
|
415
|
+
return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), (0, rxjs_operators.tap)((payload) => {
|
|
416
|
+
this.updateLastSeenEventId(threadId, payload);
|
|
417
|
+
this.updateRunIdFromEvent(payload);
|
|
418
|
+
}), (0, rxjs_operators.mergeMap)((payload) => (0, rxjs.from)(this.createThreadNotifications(payload, options.completeOnRunError))), (0, rxjs.dematerialize)());
|
|
419
|
+
}
|
|
420
|
+
observeChannelEvent$(channel, eventName) {
|
|
421
|
+
return ɵobservePhoenixEvent$(channel, eventName);
|
|
422
|
+
}
|
|
423
|
+
createThreadNotifications(payload, completeOnRunError) {
|
|
424
|
+
if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()];
|
|
425
|
+
if (payload.type === _ag_ui_client.EventType.RUN_ERROR) {
|
|
426
|
+
const errorMessage = payload.message ?? "Run error";
|
|
427
|
+
return completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))];
|
|
428
|
+
}
|
|
429
|
+
return [rxjs.Notification.createNext(payload)];
|
|
430
|
+
}
|
|
431
|
+
buildRuntimeUrl(mode) {
|
|
432
|
+
const path = `${this.config.runtimeUrl}/agent/${encodeURIComponent(this.config.agentId)}/${mode}`;
|
|
433
|
+
const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
|
|
434
|
+
return new URL(path, new URL(this.config.runtimeUrl, origin)).toString();
|
|
435
|
+
}
|
|
436
|
+
createThreadChannelDescriptor(input, streamMode, replayCursor) {
|
|
437
|
+
const params = streamMode === "run" ? {
|
|
438
|
+
stream_mode: "run",
|
|
439
|
+
run_id: input.runId
|
|
440
|
+
} : {
|
|
441
|
+
stream_mode: "connect",
|
|
442
|
+
last_seen_event_id: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
topic: `thread:${input.threadId}`,
|
|
446
|
+
params
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
getLastSeenEventId(threadId) {
|
|
450
|
+
return this.sharedState.lastSeenEventIds.get(threadId) ?? null;
|
|
451
|
+
}
|
|
452
|
+
getReconnectCursor(input) {
|
|
453
|
+
return this.hasLocalThreadMessages(input) ? this.getLastSeenEventId(input.threadId) : null;
|
|
454
|
+
}
|
|
455
|
+
hasLocalThreadMessages(input) {
|
|
456
|
+
return Array.isArray(input.messages) && input.messages.length > 0;
|
|
457
|
+
}
|
|
458
|
+
updateLastSeenEventId(threadId, payload) {
|
|
459
|
+
const eventId = this.readEventId(payload);
|
|
460
|
+
if (!eventId) return;
|
|
461
|
+
this.sharedState.lastSeenEventIds.set(threadId, eventId);
|
|
462
|
+
}
|
|
463
|
+
setLastSeenEventId(threadId, eventId) {
|
|
464
|
+
if (!eventId) return;
|
|
465
|
+
this.sharedState.lastSeenEventIds.set(threadId, eventId);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Keep `this.runId` in sync with the backend's actual run ID.
|
|
469
|
+
*
|
|
470
|
+
* During a `connect` (resume) flow the client generates a fresh `runId`
|
|
471
|
+
* via `prepareRunAgentInput`, but the backend is running under its own
|
|
472
|
+
* run ID. If the client later sends `STOP_RUN_EVENT` with the wrong
|
|
473
|
+
* `runId`, the gateway's runner channel will not match it and the agent
|
|
474
|
+
* keeps running. Extracting the run ID from live events fixes this.
|
|
475
|
+
*
|
|
476
|
+
* The runner normalises events to `run_id` (snake_case) before pushing
|
|
477
|
+
* to the gateway, so we check both `runId` and `run_id`.
|
|
478
|
+
*/
|
|
479
|
+
updateRunIdFromEvent(payload) {
|
|
480
|
+
const record = payload;
|
|
481
|
+
const eventRunId = record.runId ?? record.run_id;
|
|
482
|
+
if (typeof eventRunId === "string" && eventRunId.length > 0) this.runId = eventRunId;
|
|
483
|
+
}
|
|
484
|
+
readEventId(payload) {
|
|
485
|
+
const metadata = payload.metadata;
|
|
486
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
487
|
+
const runnerEventId = metadata.cpki_event_id;
|
|
488
|
+
return typeof runnerEventId === "string" ? runnerEventId : null;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/agent.ts
|
|
494
|
+
function hasHeaders(agent) {
|
|
495
|
+
return "headers" in agent;
|
|
496
|
+
}
|
|
497
|
+
function hasCredentials(agent) {
|
|
498
|
+
return "credentials" in agent;
|
|
499
|
+
}
|
|
500
|
+
function isZodError(error) {
|
|
501
|
+
return error !== null && typeof error === "object" && "name" in error && error.name === "ZodError";
|
|
502
|
+
}
|
|
503
|
+
function isAbortError(error) {
|
|
504
|
+
return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
|
|
505
|
+
}
|
|
506
|
+
function withAbortErrorHandling(observable) {
|
|
507
|
+
return observable.pipe((0, rxjs_operators.catchError)((error) => {
|
|
508
|
+
if (isZodError(error) || isAbortError(error)) return rxjs.EMPTY;
|
|
509
|
+
throw error;
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui_client.HttpAgent {
|
|
513
|
+
runtimeUrl;
|
|
514
|
+
credentials;
|
|
515
|
+
transport;
|
|
516
|
+
singleEndpointUrl;
|
|
517
|
+
runtimeMode;
|
|
518
|
+
intelligence;
|
|
519
|
+
delegate;
|
|
520
|
+
runtimeInfoPromise;
|
|
521
|
+
constructor(config) {
|
|
522
|
+
const normalizedRuntimeUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/$/, "") : void 0;
|
|
523
|
+
const transport = config.transport ?? "rest";
|
|
524
|
+
const runUrl = transport === "single" ? normalizedRuntimeUrl ?? config.runtimeUrl ?? "" : `${normalizedRuntimeUrl ?? config.runtimeUrl}/agent/${encodeURIComponent(config.agentId ?? "")}/run`;
|
|
525
|
+
if (!runUrl) throw new Error("ProxiedCopilotRuntimeAgent requires a runtimeUrl when transport is set to 'single'.");
|
|
526
|
+
super({
|
|
527
|
+
...config,
|
|
528
|
+
url: runUrl
|
|
529
|
+
});
|
|
530
|
+
this.runtimeUrl = normalizedRuntimeUrl ?? config.runtimeUrl;
|
|
531
|
+
this.credentials = config.credentials;
|
|
532
|
+
this.transport = transport;
|
|
533
|
+
this.runtimeMode = config.runtimeMode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
534
|
+
this.intelligence = config.intelligence;
|
|
535
|
+
if (this.transport === "single") this.singleEndpointUrl = this.runtimeUrl;
|
|
536
|
+
}
|
|
537
|
+
async detachActiveRun() {
|
|
538
|
+
if (this.delegate) await this.delegate.detachActiveRun();
|
|
539
|
+
await super.detachActiveRun();
|
|
540
|
+
}
|
|
541
|
+
abortRun() {
|
|
542
|
+
if (this.delegate) {
|
|
543
|
+
this.syncDelegate(this.delegate);
|
|
544
|
+
this.delegate.abortRun();
|
|
545
|
+
this.detachActiveRun();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (!this.agentId || !this.threadId) return;
|
|
549
|
+
if (typeof fetch === "undefined") return;
|
|
550
|
+
if (this.transport === "single") {
|
|
551
|
+
if (!this.singleEndpointUrl) return;
|
|
552
|
+
const headers = new Headers({
|
|
553
|
+
...this.headers,
|
|
554
|
+
"Content-Type": "application/json"
|
|
555
|
+
});
|
|
556
|
+
fetch(this.singleEndpointUrl, {
|
|
557
|
+
method: "POST",
|
|
558
|
+
headers,
|
|
559
|
+
body: JSON.stringify({
|
|
560
|
+
method: "agent/stop",
|
|
561
|
+
params: {
|
|
562
|
+
agentId: this.agentId,
|
|
563
|
+
threadId: this.threadId
|
|
564
|
+
}
|
|
565
|
+
}),
|
|
566
|
+
...this.credentials ? { credentials: this.credentials } : {}
|
|
567
|
+
}).catch((error) => {
|
|
568
|
+
console.error("ProxiedCopilotRuntimeAgent: stop request failed", error);
|
|
569
|
+
});
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (!this.runtimeUrl) return;
|
|
573
|
+
const stopPath = `${this.runtimeUrl}/agent/${encodeURIComponent(this.agentId)}/stop/${encodeURIComponent(this.threadId)}`;
|
|
574
|
+
const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
|
|
575
|
+
const base = new URL(this.runtimeUrl, origin);
|
|
576
|
+
const stopUrl = new URL(stopPath, base);
|
|
577
|
+
fetch(stopUrl.toString(), {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: {
|
|
580
|
+
"Content-Type": "application/json",
|
|
581
|
+
...this.headers
|
|
582
|
+
},
|
|
583
|
+
...this.credentials ? { credentials: this.credentials } : {}
|
|
584
|
+
}).catch((error) => {
|
|
585
|
+
console.error("ProxiedCopilotRuntimeAgent: stop request failed", error);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async connectAgent(parameters, subscriber) {
|
|
589
|
+
if (this.runtimeMode !== _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return super.connectAgent(parameters, subscriber);
|
|
590
|
+
if (this.delegate) await this.delegate.detachActiveRun();
|
|
591
|
+
await this.resolveDelegate();
|
|
592
|
+
const delegate = this.delegate;
|
|
593
|
+
const bridgeSub = delegate.subscribe({
|
|
594
|
+
onMessagesChanged: () => {
|
|
595
|
+
this.setMessages([...delegate.messages]);
|
|
596
|
+
},
|
|
597
|
+
onStateChanged: () => {
|
|
598
|
+
this.setState({ ...delegate.state });
|
|
599
|
+
},
|
|
600
|
+
onRunInitialized: () => {
|
|
601
|
+
this.isRunning = true;
|
|
602
|
+
},
|
|
603
|
+
onRunFinalized: () => {
|
|
604
|
+
this.isRunning = false;
|
|
605
|
+
},
|
|
606
|
+
onRunFailed: () => {
|
|
607
|
+
this.isRunning = false;
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
const forwardedSubs = this.subscribers.map((s) => delegate.subscribe(s));
|
|
611
|
+
try {
|
|
612
|
+
const result = await delegate.connectAgent(parameters, subscriber);
|
|
613
|
+
this.setMessages([...delegate.messages]);
|
|
614
|
+
this.setState({ ...delegate.state });
|
|
615
|
+
return result;
|
|
616
|
+
} finally {
|
|
617
|
+
this.isRunning = false;
|
|
618
|
+
bridgeSub.unsubscribe();
|
|
619
|
+
for (const sub of forwardedSubs) sub.unsubscribe();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
connect(input) {
|
|
623
|
+
if (this.runtimeMode === _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return this.#connectViaDelegate(input);
|
|
624
|
+
return this.#connectViaHttp(input);
|
|
625
|
+
}
|
|
626
|
+
run(input) {
|
|
627
|
+
if (this.runtimeMode === _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return this.#runViaDelegate(input);
|
|
628
|
+
return this.#runViaHttp(input);
|
|
629
|
+
}
|
|
630
|
+
#connectViaDelegate(input) {
|
|
631
|
+
return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.connect(input))));
|
|
632
|
+
}
|
|
633
|
+
#connectViaHttp(input) {
|
|
634
|
+
if (this.transport === "single") {
|
|
635
|
+
if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
|
|
636
|
+
const requestInit = this.createSingleRouteRequestInit(input, "agent/connect", { agentId: this.agentId });
|
|
637
|
+
return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(this.singleEndpointUrl, requestInit)));
|
|
638
|
+
}
|
|
639
|
+
return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(`${this.runtimeUrl}/agent/${this.agentId}/connect`, this.requestInit(input))));
|
|
640
|
+
}
|
|
641
|
+
#runViaDelegate(input) {
|
|
642
|
+
return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.run(input))));
|
|
643
|
+
}
|
|
644
|
+
#runViaHttp(input) {
|
|
645
|
+
if (this.transport === "single") {
|
|
646
|
+
if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
|
|
647
|
+
const requestInit = this.createSingleRouteRequestInit(input, "agent/run", { agentId: this.agentId });
|
|
648
|
+
return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(this.singleEndpointUrl, requestInit)));
|
|
649
|
+
}
|
|
650
|
+
return withAbortErrorHandling(super.run(input));
|
|
651
|
+
}
|
|
652
|
+
clone() {
|
|
653
|
+
const cloned = new ProxiedCopilotRuntimeAgent({
|
|
654
|
+
runtimeUrl: this.runtimeUrl,
|
|
655
|
+
agentId: this.agentId,
|
|
656
|
+
description: this.description,
|
|
657
|
+
headers: { ...this.headers },
|
|
658
|
+
credentials: this.credentials,
|
|
659
|
+
transport: this.transport,
|
|
660
|
+
runtimeMode: this.runtimeMode,
|
|
661
|
+
intelligence: this.intelligence
|
|
662
|
+
});
|
|
663
|
+
cloned.threadId = this.threadId;
|
|
664
|
+
cloned.setState(this.state);
|
|
665
|
+
cloned.setMessages(this.messages);
|
|
666
|
+
if (this.delegate) {
|
|
667
|
+
cloned.delegate = this.delegate.clone();
|
|
668
|
+
cloned.syncDelegate(cloned.delegate);
|
|
669
|
+
}
|
|
670
|
+
return cloned;
|
|
671
|
+
}
|
|
672
|
+
async resolveDelegate() {
|
|
673
|
+
await this.ensureRuntimeMode();
|
|
674
|
+
if (!this.delegate) {
|
|
675
|
+
if (this.runtimeMode !== _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) throw new Error("A delegate is only created for Intelligence mode");
|
|
676
|
+
this.delegate = this.createIntelligenceDelegate();
|
|
677
|
+
}
|
|
678
|
+
this.syncDelegate(this.delegate);
|
|
679
|
+
return this.delegate;
|
|
680
|
+
}
|
|
681
|
+
async ensureRuntimeMode() {
|
|
682
|
+
if (this.runtimeMode !== "pending") return;
|
|
683
|
+
if (!this.runtimeUrl) throw new Error("Runtime URL is not set");
|
|
684
|
+
this.runtimeInfoPromise ??= this.fetchRuntimeInfo().then((runtimeInfo) => {
|
|
685
|
+
this.runtimeMode = runtimeInfo.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
686
|
+
this.intelligence = runtimeInfo.intelligence;
|
|
687
|
+
});
|
|
688
|
+
await this.runtimeInfoPromise;
|
|
689
|
+
}
|
|
690
|
+
async fetchRuntimeInfo() {
|
|
691
|
+
const headers = { ...this.headers };
|
|
692
|
+
let init;
|
|
693
|
+
let url;
|
|
694
|
+
if (this.transport === "single") {
|
|
695
|
+
if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
|
|
696
|
+
if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
|
|
697
|
+
url = this.runtimeUrl;
|
|
698
|
+
init = {
|
|
699
|
+
method: "POST",
|
|
700
|
+
body: JSON.stringify({ method: "info" })
|
|
701
|
+
};
|
|
702
|
+
} else {
|
|
703
|
+
url = `${this.runtimeUrl}/info`;
|
|
704
|
+
init = {};
|
|
705
|
+
}
|
|
706
|
+
const response = await fetch(url, {
|
|
707
|
+
...init,
|
|
708
|
+
headers,
|
|
709
|
+
...this.credentials ? { credentials: this.credentials } : {}
|
|
710
|
+
});
|
|
711
|
+
if (!response.ok) throw new Error(`Runtime info request failed with status ${response.status}`);
|
|
712
|
+
return await response.json();
|
|
713
|
+
}
|
|
714
|
+
createSingleRouteRequestInit(input, method, params) {
|
|
715
|
+
if (!this.agentId) throw new Error("ProxiedCopilotRuntimeAgent requires agentId to make runtime requests");
|
|
716
|
+
const baseInit = super.requestInit(input);
|
|
717
|
+
const headers = new Headers(baseInit.headers ?? {});
|
|
718
|
+
headers.set("Content-Type", "application/json");
|
|
719
|
+
headers.set("Accept", headers.get("Accept") ?? "text/event-stream");
|
|
720
|
+
let originalBody = void 0;
|
|
721
|
+
if (typeof baseInit.body === "string") try {
|
|
722
|
+
originalBody = JSON.parse(baseInit.body);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.warn("ProxiedCopilotRuntimeAgent: failed to parse request body for single route transport", error);
|
|
725
|
+
}
|
|
726
|
+
const envelope = { method };
|
|
727
|
+
if (params && Object.keys(params).length > 0) envelope.params = params;
|
|
728
|
+
if (originalBody !== void 0) envelope.body = originalBody;
|
|
729
|
+
return {
|
|
730
|
+
...baseInit,
|
|
731
|
+
headers,
|
|
732
|
+
body: JSON.stringify(envelope),
|
|
733
|
+
...this.credentials ? { credentials: this.credentials } : {}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
createIntelligenceDelegate() {
|
|
737
|
+
if (!this.runtimeUrl || !this.agentId || !this.intelligence?.wsUrl) throw new Error("Intelligence mode requires runtimeUrl, agentId, and intelligence websocket metadata");
|
|
738
|
+
return new IntelligenceAgent({
|
|
739
|
+
url: this.intelligence.wsUrl,
|
|
740
|
+
runtimeUrl: this.runtimeUrl,
|
|
741
|
+
agentId: this.agentId,
|
|
742
|
+
headers: { ...this.headers },
|
|
743
|
+
credentials: this.credentials
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
syncDelegate(delegate) {
|
|
747
|
+
delegate.agentId = this.agentId;
|
|
748
|
+
delegate.description = this.description;
|
|
749
|
+
delegate.threadId = this.threadId;
|
|
750
|
+
delegate.setMessages(this.messages);
|
|
751
|
+
delegate.setState(this.state);
|
|
752
|
+
if (hasHeaders(delegate)) delegate.headers = { ...this.headers };
|
|
753
|
+
if (hasCredentials(delegate)) delegate.credentials = this.credentials;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/core/agent-registry.ts
|
|
759
|
+
/**
|
|
760
|
+
* Manages agent registration, lifecycle, and runtime connectivity for CopilotKitCore.
|
|
761
|
+
* Handles both local development agents and remote runtime agents.
|
|
762
|
+
*/
|
|
763
|
+
var AgentRegistry = class {
|
|
764
|
+
_agents = {};
|
|
765
|
+
localAgents = {};
|
|
766
|
+
remoteAgents = {};
|
|
767
|
+
_runtimeUrl;
|
|
768
|
+
_runtimeVersion;
|
|
769
|
+
_runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Disconnected;
|
|
770
|
+
_runtimeTransport = "rest";
|
|
771
|
+
_audioFileTranscriptionEnabled = false;
|
|
772
|
+
_runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
773
|
+
_intelligence;
|
|
774
|
+
_a2uiEnabled = false;
|
|
775
|
+
_licenseStatus;
|
|
776
|
+
constructor(core) {
|
|
777
|
+
this.core = core;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get all agents as a readonly record
|
|
781
|
+
*/
|
|
782
|
+
get agents() {
|
|
783
|
+
return this._agents;
|
|
784
|
+
}
|
|
785
|
+
get runtimeUrl() {
|
|
786
|
+
return this._runtimeUrl;
|
|
787
|
+
}
|
|
788
|
+
get runtimeVersion() {
|
|
789
|
+
return this._runtimeVersion;
|
|
790
|
+
}
|
|
791
|
+
get runtimeConnectionStatus() {
|
|
792
|
+
return this._runtimeConnectionStatus;
|
|
793
|
+
}
|
|
794
|
+
get runtimeTransport() {
|
|
795
|
+
return this._runtimeTransport;
|
|
796
|
+
}
|
|
797
|
+
get audioFileTranscriptionEnabled() {
|
|
798
|
+
return this._audioFileTranscriptionEnabled;
|
|
799
|
+
}
|
|
800
|
+
get runtimeMode() {
|
|
801
|
+
return this._runtimeMode;
|
|
802
|
+
}
|
|
803
|
+
get intelligence() {
|
|
804
|
+
return this._intelligence;
|
|
805
|
+
}
|
|
806
|
+
get a2uiEnabled() {
|
|
807
|
+
return this._a2uiEnabled;
|
|
808
|
+
}
|
|
809
|
+
get licenseStatus() {
|
|
810
|
+
return this._licenseStatus;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Initialize agents from configuration
|
|
814
|
+
*/
|
|
815
|
+
initialize(agents) {
|
|
816
|
+
this.localAgents = this.assignAgentIds(agents);
|
|
817
|
+
this.applyHeadersToAgents(this.localAgents);
|
|
818
|
+
this._agents = this.localAgents;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Set the runtime URL and update connection
|
|
822
|
+
*/
|
|
823
|
+
setRuntimeUrl(runtimeUrl) {
|
|
824
|
+
const normalizedRuntimeUrl = runtimeUrl ? runtimeUrl.replace(/\/$/, "") : void 0;
|
|
825
|
+
if (this._runtimeUrl === normalizedRuntimeUrl) return;
|
|
826
|
+
this._runtimeUrl = normalizedRuntimeUrl;
|
|
827
|
+
this.updateRuntimeConnection();
|
|
828
|
+
}
|
|
829
|
+
setRuntimeTransport(runtimeTransport) {
|
|
830
|
+
if (this._runtimeTransport === runtimeTransport) return;
|
|
831
|
+
this._runtimeTransport = runtimeTransport;
|
|
832
|
+
this.updateRuntimeConnection();
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Set all agents at once (for development use)
|
|
836
|
+
*/
|
|
837
|
+
setAgents__unsafe_dev_only(agents) {
|
|
838
|
+
Object.entries(agents).forEach(([id, agent]) => {
|
|
839
|
+
if (agent) this.validateAndAssignAgentId(id, agent);
|
|
840
|
+
});
|
|
841
|
+
this.localAgents = agents;
|
|
842
|
+
this._agents = {
|
|
843
|
+
...this.localAgents,
|
|
844
|
+
...this.remoteAgents
|
|
845
|
+
};
|
|
846
|
+
this.applyHeadersToAgents(this._agents);
|
|
847
|
+
this.notifyAgentsChanged();
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Add a single agent (for development use)
|
|
851
|
+
*/
|
|
852
|
+
addAgent__unsafe_dev_only({ id, agent }) {
|
|
853
|
+
this.validateAndAssignAgentId(id, agent);
|
|
854
|
+
this.localAgents[id] = agent;
|
|
855
|
+
this.applyHeadersToAgent(agent);
|
|
856
|
+
this._agents = {
|
|
857
|
+
...this.localAgents,
|
|
858
|
+
...this.remoteAgents
|
|
859
|
+
};
|
|
860
|
+
this.notifyAgentsChanged();
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Remove an agent by ID (for development use)
|
|
864
|
+
*/
|
|
865
|
+
removeAgent__unsafe_dev_only(id) {
|
|
866
|
+
delete this.localAgents[id];
|
|
867
|
+
this._agents = {
|
|
868
|
+
...this.localAgents,
|
|
869
|
+
...this.remoteAgents
|
|
870
|
+
};
|
|
871
|
+
this.notifyAgentsChanged();
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Get an agent by ID
|
|
875
|
+
*/
|
|
876
|
+
getAgent(id) {
|
|
877
|
+
if (id in this._agents) return this._agents[id];
|
|
878
|
+
if (this.runtimeUrl !== void 0 && (this.runtimeConnectionStatus === CopilotKitCoreRuntimeConnectionStatus.Disconnected || this.runtimeConnectionStatus === CopilotKitCoreRuntimeConnectionStatus.Connecting)) return;
|
|
879
|
+
console.warn(`Agent ${id} not found`);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Apply current headers to an agent
|
|
883
|
+
*/
|
|
884
|
+
applyHeadersToAgent(agent) {
|
|
885
|
+
if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...this.core.headers };
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Apply current headers to all agents
|
|
889
|
+
*/
|
|
890
|
+
applyHeadersToAgents(agents) {
|
|
891
|
+
Object.values(agents).forEach((agent) => {
|
|
892
|
+
this.applyHeadersToAgent(agent);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Apply current credentials to an agent
|
|
897
|
+
*/
|
|
898
|
+
applyCredentialsToAgent(agent) {
|
|
899
|
+
if (agent instanceof ProxiedCopilotRuntimeAgent) agent.credentials = this.core.credentials;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Apply current credentials to all agents
|
|
903
|
+
*/
|
|
904
|
+
applyCredentialsToAgents(agents) {
|
|
905
|
+
Object.values(agents).forEach((agent) => {
|
|
906
|
+
this.applyCredentialsToAgent(agent);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Update runtime connection and fetch remote agents
|
|
911
|
+
*/
|
|
912
|
+
async updateRuntimeConnection() {
|
|
913
|
+
if (typeof window === "undefined") return;
|
|
914
|
+
if (!this.runtimeUrl) {
|
|
915
|
+
this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Disconnected;
|
|
916
|
+
this._runtimeVersion = void 0;
|
|
917
|
+
this._audioFileTranscriptionEnabled = false;
|
|
918
|
+
this._runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
919
|
+
this._intelligence = void 0;
|
|
920
|
+
this._a2uiEnabled = false;
|
|
921
|
+
this.remoteAgents = {};
|
|
922
|
+
this._agents = this.localAgents;
|
|
923
|
+
await this.notifyRuntimeStatusChanged(CopilotKitCoreRuntimeConnectionStatus.Disconnected);
|
|
924
|
+
await this.notifyAgentsChanged();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Connecting;
|
|
928
|
+
await this.notifyRuntimeStatusChanged(CopilotKitCoreRuntimeConnectionStatus.Connecting);
|
|
929
|
+
try {
|
|
930
|
+
const runtimeInfoResponse = await this.fetchRuntimeInfo();
|
|
931
|
+
const { version, ...runtimeInfo } = runtimeInfoResponse;
|
|
932
|
+
const credentials = this.core.credentials;
|
|
933
|
+
this.remoteAgents = Object.fromEntries(Object.entries(runtimeInfo.agents).map(([id, { description }]) => {
|
|
934
|
+
const agent = new ProxiedCopilotRuntimeAgent({
|
|
935
|
+
runtimeUrl: this.runtimeUrl,
|
|
936
|
+
agentId: id,
|
|
937
|
+
description,
|
|
938
|
+
transport: this._runtimeTransport,
|
|
939
|
+
credentials,
|
|
940
|
+
runtimeMode: runtimeInfoResponse.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE,
|
|
941
|
+
intelligence: runtimeInfoResponse.intelligence
|
|
942
|
+
});
|
|
943
|
+
this.applyHeadersToAgent(agent);
|
|
944
|
+
return [id, agent];
|
|
945
|
+
}));
|
|
946
|
+
this._agents = {
|
|
947
|
+
...this.localAgents,
|
|
948
|
+
...this.remoteAgents
|
|
949
|
+
};
|
|
950
|
+
this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
951
|
+
this._runtimeVersion = version;
|
|
952
|
+
this._audioFileTranscriptionEnabled = runtimeInfoResponse.audioFileTranscriptionEnabled ?? false;
|
|
953
|
+
this._runtimeMode = runtimeInfoResponse.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
954
|
+
this._intelligence = runtimeInfoResponse.intelligence;
|
|
955
|
+
this._a2uiEnabled = runtimeInfoResponse.a2uiEnabled ?? false;
|
|
956
|
+
this._licenseStatus = runtimeInfoResponse.licenseStatus;
|
|
957
|
+
await this.notifyRuntimeStatusChanged(CopilotKitCoreRuntimeConnectionStatus.Connected);
|
|
958
|
+
await this.notifyAgentsChanged();
|
|
959
|
+
} catch (error) {
|
|
960
|
+
this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Error;
|
|
961
|
+
this._runtimeVersion = void 0;
|
|
962
|
+
this._audioFileTranscriptionEnabled = false;
|
|
963
|
+
this._runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
|
|
964
|
+
this._intelligence = void 0;
|
|
965
|
+
this._a2uiEnabled = false;
|
|
966
|
+
this.remoteAgents = {};
|
|
967
|
+
this._agents = this.localAgents;
|
|
968
|
+
await this.notifyRuntimeStatusChanged(CopilotKitCoreRuntimeConnectionStatus.Error);
|
|
969
|
+
await this.notifyAgentsChanged();
|
|
970
|
+
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
971
|
+
_copilotkit_shared.logger.warn(`Failed to load runtime info (${this.runtimeUrl}/info): ${message}`);
|
|
972
|
+
const runtimeError = error instanceof Error ? error : new Error(String(error));
|
|
973
|
+
await this.core.emitError({
|
|
974
|
+
error: runtimeError,
|
|
975
|
+
code: CopilotKitCoreErrorCode.RUNTIME_INFO_FETCH_FAILED,
|
|
976
|
+
context: { runtimeUrl: this.runtimeUrl }
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
async fetchRuntimeInfo() {
|
|
981
|
+
if (!this.runtimeUrl) throw new Error("Runtime URL is not set");
|
|
982
|
+
const baseHeaders = this.core.headers;
|
|
983
|
+
const credentials = this.core.credentials;
|
|
984
|
+
const headers = { ...baseHeaders };
|
|
985
|
+
if (this._runtimeTransport === "single") {
|
|
986
|
+
if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
|
|
987
|
+
const response = await fetch(this.runtimeUrl, {
|
|
988
|
+
method: "POST",
|
|
989
|
+
headers,
|
|
990
|
+
body: JSON.stringify({ method: "info" }),
|
|
991
|
+
...credentials ? { credentials } : {}
|
|
992
|
+
});
|
|
993
|
+
if ("ok" in response && !response.ok) throw new Error(`Runtime info request failed with status ${response.status}`);
|
|
994
|
+
return await response.json();
|
|
995
|
+
}
|
|
996
|
+
const response = await fetch(`${this.runtimeUrl}/info`, {
|
|
997
|
+
headers,
|
|
998
|
+
...credentials ? { credentials } : {}
|
|
999
|
+
});
|
|
1000
|
+
if ("ok" in response && !response.ok) throw new Error(`Runtime info request failed with status ${response.status}`);
|
|
1001
|
+
return await response.json();
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Assign agent IDs to a record of agents
|
|
1005
|
+
*/
|
|
1006
|
+
assignAgentIds(agents) {
|
|
1007
|
+
Object.entries(agents).forEach(([id, agent]) => {
|
|
1008
|
+
if (agent) this.validateAndAssignAgentId(id, agent);
|
|
1009
|
+
});
|
|
1010
|
+
return agents;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Validate and assign an agent ID
|
|
1014
|
+
*/
|
|
1015
|
+
validateAndAssignAgentId(registrationId, agent) {
|
|
1016
|
+
if (agent.agentId && agent.agentId !== registrationId) throw new Error(`Agent registration mismatch: Agent with ID "${agent.agentId}" cannot be registered under key "${registrationId}". The agent ID must match the registration key or be undefined.`);
|
|
1017
|
+
if (!agent.agentId) agent.agentId = registrationId;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Notify subscribers of runtime status changes
|
|
1021
|
+
*/
|
|
1022
|
+
async notifyRuntimeStatusChanged(status) {
|
|
1023
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onRuntimeConnectionStatusChanged?.({
|
|
1024
|
+
copilotkit: this.core,
|
|
1025
|
+
status
|
|
1026
|
+
}), "Error in CopilotKitCore subscriber (onRuntimeConnectionStatusChanged):");
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Notify subscribers of agent changes
|
|
1030
|
+
*/
|
|
1031
|
+
async notifyAgentsChanged() {
|
|
1032
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onAgentsChanged?.({
|
|
1033
|
+
copilotkit: this.core,
|
|
1034
|
+
agents: this._agents
|
|
1035
|
+
}), "Subscriber onAgentsChanged error:");
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/core/context-store.ts
|
|
1041
|
+
/**
|
|
1042
|
+
* Manages context storage and lifecycle for CopilotKitCore.
|
|
1043
|
+
* Context represents additional information available to agents during execution.
|
|
1044
|
+
*/
|
|
1045
|
+
var ContextStore = class {
|
|
1046
|
+
_context = {};
|
|
1047
|
+
constructor(core) {
|
|
1048
|
+
this.core = core;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Get all context entries as a readonly record
|
|
1052
|
+
*/
|
|
1053
|
+
get context() {
|
|
1054
|
+
return this._context;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Add a new context entry
|
|
1058
|
+
* @returns The ID of the created context entry
|
|
1059
|
+
*/
|
|
1060
|
+
addContext({ description, value }) {
|
|
1061
|
+
const id = (0, _copilotkit_shared.randomUUID)();
|
|
1062
|
+
this._context[id] = {
|
|
1063
|
+
description,
|
|
1064
|
+
value
|
|
1065
|
+
};
|
|
1066
|
+
this.notifySubscribers();
|
|
1067
|
+
return id;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Remove a context entry by ID
|
|
1071
|
+
*/
|
|
1072
|
+
removeContext(id) {
|
|
1073
|
+
delete this._context[id];
|
|
1074
|
+
this.notifySubscribers();
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Notify all subscribers of context changes
|
|
1078
|
+
*/
|
|
1079
|
+
async notifySubscribers() {
|
|
1080
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onContextChanged?.({
|
|
1081
|
+
copilotkit: this.core,
|
|
1082
|
+
context: this._context
|
|
1083
|
+
}), "Subscriber onContextChanged error:");
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
//#endregion
|
|
1088
|
+
//#region src/core/suggestion-engine.ts
|
|
1089
|
+
/**
|
|
1090
|
+
* Manages suggestion generation, streaming, and lifecycle for CopilotKitCore.
|
|
1091
|
+
* Handles both dynamic (AI-generated) and static suggestions.
|
|
1092
|
+
*/
|
|
1093
|
+
var SuggestionEngine = class {
|
|
1094
|
+
_suggestionsConfig = {};
|
|
1095
|
+
_suggestions = {};
|
|
1096
|
+
_runningSuggestions = {};
|
|
1097
|
+
constructor(core) {
|
|
1098
|
+
this.core = core;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Initialize with suggestion configs
|
|
1102
|
+
*/
|
|
1103
|
+
initialize(suggestionsConfig) {
|
|
1104
|
+
for (const config of suggestionsConfig) this._suggestionsConfig[(0, _copilotkit_shared.randomUUID)()] = config;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Add a suggestion configuration
|
|
1108
|
+
* @returns The ID of the created config
|
|
1109
|
+
*/
|
|
1110
|
+
addSuggestionsConfig(config) {
|
|
1111
|
+
const id = (0, _copilotkit_shared.randomUUID)();
|
|
1112
|
+
this._suggestionsConfig[id] = config;
|
|
1113
|
+
this.notifySuggestionsConfigChanged();
|
|
1114
|
+
return id;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Remove a suggestion configuration by ID
|
|
1118
|
+
*/
|
|
1119
|
+
removeSuggestionsConfig(id) {
|
|
1120
|
+
delete this._suggestionsConfig[id];
|
|
1121
|
+
this.notifySuggestionsConfigChanged();
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Reload suggestions for a specific agent
|
|
1125
|
+
* This triggers generation of new suggestions based on current configs
|
|
1126
|
+
*
|
|
1127
|
+
* @param agentId - The consumer agent ID
|
|
1128
|
+
* @param consumerAgent - Optional: the specific agent instance whose messages should be used
|
|
1129
|
+
* for availability filtering and context. When running with per-thread clones, the thread
|
|
1130
|
+
* clone holds the conversation messages; passing it here ensures dynamic suggestions fire
|
|
1131
|
+
* after the first message even though the registry agent has an empty message list.
|
|
1132
|
+
*/
|
|
1133
|
+
reloadSuggestions(agentId, consumerAgent) {
|
|
1134
|
+
this.clearSuggestions(agentId);
|
|
1135
|
+
const agent = consumerAgent ?? this.core.getAgent(agentId);
|
|
1136
|
+
if (!agent) return;
|
|
1137
|
+
const messageCount = agent.messages?.length ?? 0;
|
|
1138
|
+
let hasAnySuggestions = false;
|
|
1139
|
+
for (const config of Object.values(this._suggestionsConfig)) {
|
|
1140
|
+
if (config.consumerAgentId !== void 0 && config.consumerAgentId !== "*" && config.consumerAgentId !== agentId) continue;
|
|
1141
|
+
if (!this.shouldShowSuggestions(config, messageCount)) continue;
|
|
1142
|
+
const suggestionId = (0, _copilotkit_shared.randomUUID)();
|
|
1143
|
+
if (isDynamicSuggestionsConfig(config)) {
|
|
1144
|
+
if (!hasAnySuggestions) {
|
|
1145
|
+
hasAnySuggestions = true;
|
|
1146
|
+
this.notifySuggestionsStartedLoading(agentId);
|
|
1147
|
+
}
|
|
1148
|
+
this.generateSuggestions(suggestionId, config, agentId, agent);
|
|
1149
|
+
} else if (isStaticSuggestionsConfig(config)) this.addStaticSuggestions(suggestionId, config, agentId);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Clear all suggestions for a specific agent
|
|
1154
|
+
*/
|
|
1155
|
+
clearSuggestions(agentId) {
|
|
1156
|
+
const runningAgents = this._runningSuggestions[agentId];
|
|
1157
|
+
if (runningAgents) {
|
|
1158
|
+
for (const agent of runningAgents) agent.abortRun();
|
|
1159
|
+
delete this._runningSuggestions[agentId];
|
|
1160
|
+
}
|
|
1161
|
+
this._suggestions[agentId] = {};
|
|
1162
|
+
this.notifySuggestionsChanged(agentId, []);
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Get current suggestions for an agent
|
|
1166
|
+
*/
|
|
1167
|
+
getSuggestions(agentId) {
|
|
1168
|
+
return {
|
|
1169
|
+
suggestions: Object.values(this._suggestions[agentId] ?? {}).flat(),
|
|
1170
|
+
isLoading: (this._runningSuggestions[agentId]?.length ?? 0) > 0
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Generate suggestions using a provider agent
|
|
1175
|
+
*/
|
|
1176
|
+
async generateSuggestions(suggestionId, config, consumerAgentId, consumerAgent) {
|
|
1177
|
+
let agent = void 0;
|
|
1178
|
+
try {
|
|
1179
|
+
const suggestionsProviderAgent = this.core.getAgent(config.providerAgentId ?? "default");
|
|
1180
|
+
if (!suggestionsProviderAgent) throw new Error(`Suggestions provider agent not found: ${config.providerAgentId}`);
|
|
1181
|
+
const suggestionsConsumerAgent = consumerAgent ?? this.core.getAgent(consumerAgentId);
|
|
1182
|
+
if (!suggestionsConsumerAgent) throw new Error(`Suggestions consumer agent not found: ${consumerAgentId}`);
|
|
1183
|
+
agent = suggestionsProviderAgent.clone();
|
|
1184
|
+
agent.threadId = suggestionId;
|
|
1185
|
+
agent.messages = JSON.parse(JSON.stringify(suggestionsConsumerAgent.messages));
|
|
1186
|
+
agent.state = JSON.parse(JSON.stringify(suggestionsConsumerAgent.state));
|
|
1187
|
+
this._suggestions[consumerAgentId] = {
|
|
1188
|
+
...this._suggestions[consumerAgentId] ?? {},
|
|
1189
|
+
[suggestionId]: []
|
|
1190
|
+
};
|
|
1191
|
+
this._runningSuggestions[consumerAgentId] = [...this._runningSuggestions[consumerAgentId] ?? [], agent];
|
|
1192
|
+
agent.addMessage({
|
|
1193
|
+
id: suggestionId,
|
|
1194
|
+
role: "user",
|
|
1195
|
+
content: [
|
|
1196
|
+
`Suggest what the user could say next. Provide clear, highly relevant suggestions by calling the \`copilotkitSuggest\` tool.`,
|
|
1197
|
+
`Provide at least ${config.minSuggestions ?? 1} and at most ${config.maxSuggestions ?? 3} suggestions.`,
|
|
1198
|
+
`The user has the following tools available: ${JSON.stringify(this.core.buildFrontendTools(consumerAgentId))}.`,
|
|
1199
|
+
` ${config.instructions}`
|
|
1200
|
+
].join("\n")
|
|
1201
|
+
});
|
|
1202
|
+
await agent.runAgent({
|
|
1203
|
+
context: Object.values(this.core.context),
|
|
1204
|
+
forwardedProps: {
|
|
1205
|
+
...this.core.properties,
|
|
1206
|
+
toolChoice: {
|
|
1207
|
+
type: "function",
|
|
1208
|
+
function: { name: "copilotkitSuggest" }
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
tools: [SUGGEST_TOOL]
|
|
1212
|
+
}, { onMessagesChanged: ({ messages }) => {
|
|
1213
|
+
this.extractSuggestions(messages, suggestionId, consumerAgentId, true);
|
|
1214
|
+
} });
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
console.warn("Error generating suggestions:", error);
|
|
1217
|
+
} finally {
|
|
1218
|
+
this.finalizeSuggestions(suggestionId, consumerAgentId);
|
|
1219
|
+
const runningAgents = this._runningSuggestions[consumerAgentId];
|
|
1220
|
+
if (agent && runningAgents) {
|
|
1221
|
+
const filteredAgents = runningAgents.filter((a) => a !== agent);
|
|
1222
|
+
this._runningSuggestions[consumerAgentId] = filteredAgents;
|
|
1223
|
+
if (filteredAgents.length === 0) {
|
|
1224
|
+
delete this._runningSuggestions[consumerAgentId];
|
|
1225
|
+
await this.notifySuggestionsFinishedLoading(consumerAgentId);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Finalize suggestions by marking them as no longer loading
|
|
1232
|
+
*/
|
|
1233
|
+
finalizeSuggestions(suggestionId, consumerAgentId) {
|
|
1234
|
+
const agentSuggestions = this._suggestions[consumerAgentId];
|
|
1235
|
+
const currentSuggestions = agentSuggestions?.[suggestionId];
|
|
1236
|
+
if (agentSuggestions && currentSuggestions && currentSuggestions.length > 0) {
|
|
1237
|
+
const finalizedSuggestions = currentSuggestions.filter((suggestion) => suggestion.title !== "" || suggestion.message !== "").map((suggestion) => ({
|
|
1238
|
+
...suggestion,
|
|
1239
|
+
isLoading: false
|
|
1240
|
+
}));
|
|
1241
|
+
if (finalizedSuggestions.length > 0) agentSuggestions[suggestionId] = finalizedSuggestions;
|
|
1242
|
+
else delete agentSuggestions[suggestionId];
|
|
1243
|
+
const allSuggestions = Object.values(this._suggestions[consumerAgentId] ?? {}).flat();
|
|
1244
|
+
this.notifySuggestionsChanged(consumerAgentId, allSuggestions, "finalized");
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Extract suggestions from messages (called during streaming)
|
|
1249
|
+
*/
|
|
1250
|
+
extractSuggestions(messages, suggestionId, consumerAgentId, isRunning) {
|
|
1251
|
+
const idx = messages.findIndex((message) => message.id === suggestionId);
|
|
1252
|
+
if (idx == -1) return;
|
|
1253
|
+
const suggestions = [];
|
|
1254
|
+
const newMessages = messages.slice(idx + 1);
|
|
1255
|
+
for (const message of newMessages) if (message.role === "assistant" && message.toolCalls) {
|
|
1256
|
+
for (const toolCall of message.toolCalls) if (toolCall.function.name === "copilotkitSuggest") {
|
|
1257
|
+
const parsed = (0, _copilotkit_shared.partialJSONParse)(Array.isArray(toolCall.function.arguments) ? toolCall.function.arguments.join("") : toolCall.function.arguments);
|
|
1258
|
+
if (parsed && typeof parsed === "object" && "suggestions" in parsed) {
|
|
1259
|
+
const parsedSuggestions = parsed.suggestions;
|
|
1260
|
+
if (Array.isArray(parsedSuggestions)) {
|
|
1261
|
+
for (const item of parsedSuggestions) if (item && typeof item === "object" && "title" in item) suggestions.push({
|
|
1262
|
+
title: item.title ?? "",
|
|
1263
|
+
message: item.message ?? "",
|
|
1264
|
+
isLoading: false
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (isRunning && suggestions.length > 0) suggestions[suggestions.length - 1].isLoading = true;
|
|
1271
|
+
const agentSuggestions = this._suggestions[consumerAgentId];
|
|
1272
|
+
if (agentSuggestions) {
|
|
1273
|
+
agentSuggestions[suggestionId] = suggestions;
|
|
1274
|
+
const allSuggestions = Object.values(this._suggestions[consumerAgentId] ?? {}).flat();
|
|
1275
|
+
this.notifySuggestionsChanged(consumerAgentId, allSuggestions, "suggestions changed");
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Notify subscribers of suggestions config changes
|
|
1280
|
+
*/
|
|
1281
|
+
async notifySuggestionsConfigChanged() {
|
|
1282
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onSuggestionsConfigChanged?.({
|
|
1283
|
+
copilotkit: this.core,
|
|
1284
|
+
suggestionsConfig: this._suggestionsConfig
|
|
1285
|
+
}), "Subscriber onSuggestionsConfigChanged error:");
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Notify subscribers of suggestions changes
|
|
1289
|
+
*/
|
|
1290
|
+
async notifySuggestionsChanged(agentId, suggestions, context = "") {
|
|
1291
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onSuggestionsChanged?.({
|
|
1292
|
+
copilotkit: this.core,
|
|
1293
|
+
agentId,
|
|
1294
|
+
suggestions
|
|
1295
|
+
}), `Subscriber onSuggestionsChanged error: ${context}`);
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Notify subscribers that suggestions started loading
|
|
1299
|
+
*/
|
|
1300
|
+
async notifySuggestionsStartedLoading(agentId) {
|
|
1301
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onSuggestionsStartedLoading?.({
|
|
1302
|
+
copilotkit: this.core,
|
|
1303
|
+
agentId
|
|
1304
|
+
}), "Subscriber onSuggestionsStartedLoading error:");
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Notify subscribers that suggestions finished loading
|
|
1308
|
+
*/
|
|
1309
|
+
async notifySuggestionsFinishedLoading(agentId) {
|
|
1310
|
+
await this.core.notifySubscribers((subscriber) => subscriber.onSuggestionsFinishedLoading?.({
|
|
1311
|
+
copilotkit: this.core,
|
|
1312
|
+
agentId
|
|
1313
|
+
}), "Subscriber onSuggestionsFinishedLoading error:");
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Check if suggestions should be shown based on availability and message count
|
|
1317
|
+
*/
|
|
1318
|
+
shouldShowSuggestions(config, messageCount) {
|
|
1319
|
+
const availability = config.available;
|
|
1320
|
+
if (!availability) if (isDynamicSuggestionsConfig(config)) return messageCount > 0;
|
|
1321
|
+
else return messageCount === 0;
|
|
1322
|
+
switch (availability) {
|
|
1323
|
+
case "disabled": return false;
|
|
1324
|
+
case "before-first-message": return messageCount === 0;
|
|
1325
|
+
case "after-first-message": return messageCount > 0;
|
|
1326
|
+
case "always": return true;
|
|
1327
|
+
default: return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Add static suggestions directly without AI generation
|
|
1332
|
+
*/
|
|
1333
|
+
addStaticSuggestions(suggestionId, config, consumerAgentId) {
|
|
1334
|
+
const suggestions = config.suggestions.map((s) => ({
|
|
1335
|
+
...s,
|
|
1336
|
+
isLoading: false
|
|
1337
|
+
}));
|
|
1338
|
+
this._suggestions[consumerAgentId] = {
|
|
1339
|
+
...this._suggestions[consumerAgentId] ?? {},
|
|
1340
|
+
[suggestionId]: suggestions
|
|
1341
|
+
};
|
|
1342
|
+
const allSuggestions = Object.values(this._suggestions[consumerAgentId] ?? {}).flat();
|
|
1343
|
+
this.notifySuggestionsChanged(consumerAgentId, allSuggestions, "static suggestions added");
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
/**
|
|
1347
|
+
* Type guard for dynamic suggestions config
|
|
1348
|
+
*/
|
|
1349
|
+
function isDynamicSuggestionsConfig(config) {
|
|
1350
|
+
return "instructions" in config;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Type guard for static suggestions config
|
|
1354
|
+
*/
|
|
1355
|
+
function isStaticSuggestionsConfig(config) {
|
|
1356
|
+
return "suggestions" in config;
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* The tool definition for AI-generated suggestions
|
|
1360
|
+
*/
|
|
1361
|
+
const SUGGEST_TOOL = {
|
|
1362
|
+
name: "copilotkitSuggest",
|
|
1363
|
+
description: "Suggest what the user could say next",
|
|
1364
|
+
parameters: {
|
|
1365
|
+
type: "object",
|
|
1366
|
+
properties: { suggestions: {
|
|
1367
|
+
type: "array",
|
|
1368
|
+
description: "List of suggestions shown to the user as buttons.",
|
|
1369
|
+
items: {
|
|
1370
|
+
type: "object",
|
|
1371
|
+
properties: {
|
|
1372
|
+
title: {
|
|
1373
|
+
type: "string",
|
|
1374
|
+
description: "The title of the suggestion. This is shown as a button and should be short."
|
|
1375
|
+
},
|
|
1376
|
+
message: {
|
|
1377
|
+
type: "string",
|
|
1378
|
+
description: "The message to send when the suggestion is clicked. This should be a clear, complete sentence and will be sent as an instruction to the AI."
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
required: ["title", "message"]
|
|
1382
|
+
}
|
|
1383
|
+
} },
|
|
1384
|
+
required: ["suggestions"]
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region src/core/run-handler.ts
|
|
1390
|
+
/**
|
|
1391
|
+
* Handles agent execution, tool calling, and agent connectivity for CopilotKitCore.
|
|
1392
|
+
* Manages the complete lifecycle of agent runs including tool execution and follow-ups.
|
|
1393
|
+
*/
|
|
1394
|
+
var RunHandler = class {
|
|
1395
|
+
_tools = [];
|
|
1396
|
+
/**
|
|
1397
|
+
* Tracks whether the current run (including in-flight tool execution)
|
|
1398
|
+
* has been aborted via `stopAgent()` or `agent.abortRun()`. Created
|
|
1399
|
+
* fresh in `runAgent()`, aborted by `abortCurrentRun()`.
|
|
1400
|
+
*/
|
|
1401
|
+
_runAbortController = null;
|
|
1402
|
+
/**
|
|
1403
|
+
* Tracks recursive `runAgent` depth so that the abort controller and
|
|
1404
|
+
* `agent.abortRun()` intercept are only set up / torn down at the
|
|
1405
|
+
* top-level call, not on follow-up recursive calls from
|
|
1406
|
+
* `processAgentResult`.
|
|
1407
|
+
*/
|
|
1408
|
+
_runDepth = 0;
|
|
1409
|
+
constructor(core) {
|
|
1410
|
+
this.core = core;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Abort the current run. Called by `CopilotKitCore.stopAgent()` to signal
|
|
1414
|
+
* that in-flight tool handlers should stop and `processAgentResult` should
|
|
1415
|
+
* not start a follow-up run.
|
|
1416
|
+
*/
|
|
1417
|
+
abortCurrentRun() {
|
|
1418
|
+
this._runAbortController?.abort();
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Typed access to CopilotKitCore's internal ("friend") methods.
|
|
1422
|
+
* Centralises the single unavoidable cast so call-sites stay clean.
|
|
1423
|
+
*/
|
|
1424
|
+
get _internal() {
|
|
1425
|
+
return this.core;
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Get all tools as a readonly array
|
|
1429
|
+
*/
|
|
1430
|
+
get tools() {
|
|
1431
|
+
return this._tools;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Initialize with tools
|
|
1435
|
+
*/
|
|
1436
|
+
initialize(tools) {
|
|
1437
|
+
this._tools = tools;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Add a tool to the registry
|
|
1441
|
+
*/
|
|
1442
|
+
addTool(tool) {
|
|
1443
|
+
if (this._tools.findIndex((t) => t.name === tool.name && t.agentId === tool.agentId) !== -1) {
|
|
1444
|
+
_copilotkit_shared.logger.warn(`Tool already exists: '${tool.name}' for agent '${tool.agentId || "global"}', skipping.`);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
this._tools.push(tool);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Remove a tool by name and optionally by agentId
|
|
1451
|
+
*/
|
|
1452
|
+
removeTool(id, agentId) {
|
|
1453
|
+
this._tools = this._tools.filter((tool) => {
|
|
1454
|
+
if (agentId !== void 0) return !(tool.name === id && tool.agentId === agentId);
|
|
1455
|
+
return !(tool.name === id && !tool.agentId);
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Get a tool by name and optionally by agentId.
|
|
1460
|
+
* If agentId is provided, it will first look for an agent-specific tool,
|
|
1461
|
+
* then fall back to a global tool with the same name.
|
|
1462
|
+
*/
|
|
1463
|
+
getTool(params) {
|
|
1464
|
+
const { toolName, agentId } = params;
|
|
1465
|
+
if (agentId) {
|
|
1466
|
+
const agentTool = this._tools.find((tool) => tool.name === toolName && tool.agentId === agentId);
|
|
1467
|
+
if (agentTool) return agentTool;
|
|
1468
|
+
}
|
|
1469
|
+
return this._tools.find((tool) => tool.name === toolName && !tool.agentId);
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Set all tools at once. Replaces existing tools.
|
|
1473
|
+
*/
|
|
1474
|
+
setTools(tools) {
|
|
1475
|
+
this._tools = [...tools];
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Connect an agent (establish initial connection)
|
|
1479
|
+
*/
|
|
1480
|
+
async connectAgent({ agent }) {
|
|
1481
|
+
try {
|
|
1482
|
+
await agent.detachActiveRun();
|
|
1483
|
+
agent.setMessages([]);
|
|
1484
|
+
agent.setState({});
|
|
1485
|
+
if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...this._internal.headers };
|
|
1486
|
+
const runAgentResult = await agent.connectAgent({
|
|
1487
|
+
forwardedProps: this._internal.properties,
|
|
1488
|
+
tools: this.buildFrontendTools(agent.agentId),
|
|
1489
|
+
context: Object.values(this._internal.context)
|
|
1490
|
+
}, this.createAgentErrorSubscriber(agent));
|
|
1491
|
+
return this.processAgentResult({
|
|
1492
|
+
runAgentResult,
|
|
1493
|
+
agent
|
|
1494
|
+
});
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
const connectError = error instanceof Error ? error : new Error(String(error));
|
|
1497
|
+
if (!(connectError.name === "AbortError" || connectError.message === "Fetch is aborted" || connectError.message === "signal is aborted without reason" || connectError.message === "component unmounted")) {
|
|
1498
|
+
const context = {};
|
|
1499
|
+
if (agent.agentId) context.agentId = agent.agentId;
|
|
1500
|
+
await this._internal.emitError({
|
|
1501
|
+
error: connectError,
|
|
1502
|
+
code: CopilotKitCoreErrorCode.AGENT_CONNECT_FAILED,
|
|
1503
|
+
context
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
return { newMessages: [] };
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Run an agent
|
|
1511
|
+
*/
|
|
1512
|
+
async runAgent({ agent, forwardedProps }) {
|
|
1513
|
+
if (agent.agentId) this._internal.suggestionEngine.clearSuggestions(agent.agentId);
|
|
1514
|
+
if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...this._internal.headers };
|
|
1515
|
+
if (agent.detachActiveRun) await agent.detachActiveRun();
|
|
1516
|
+
this._internal.subscribeAgentToStateManager(agent);
|
|
1517
|
+
const isTopLevel = this._runDepth === 0;
|
|
1518
|
+
let originalAbortRun;
|
|
1519
|
+
if (isTopLevel) {
|
|
1520
|
+
this._runAbortController = new AbortController();
|
|
1521
|
+
const controller = this._runAbortController;
|
|
1522
|
+
originalAbortRun = agent.abortRun.bind(agent);
|
|
1523
|
+
agent.abortRun = () => {
|
|
1524
|
+
controller.abort();
|
|
1525
|
+
originalAbortRun();
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
this._runDepth++;
|
|
1529
|
+
try {
|
|
1530
|
+
const runAgentResult = await agent.runAgent({
|
|
1531
|
+
forwardedProps: {
|
|
1532
|
+
...this._internal.properties,
|
|
1533
|
+
...forwardedProps
|
|
1534
|
+
},
|
|
1535
|
+
tools: this.buildFrontendTools(agent.agentId),
|
|
1536
|
+
context: Object.values(this._internal.context)
|
|
1537
|
+
}, this.createAgentErrorSubscriber(agent));
|
|
1538
|
+
return await this.processAgentResult({
|
|
1539
|
+
runAgentResult,
|
|
1540
|
+
agent
|
|
1541
|
+
});
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
const runError = error instanceof Error ? error : new Error(String(error));
|
|
1544
|
+
const context = {};
|
|
1545
|
+
if (agent.agentId) context.agentId = agent.agentId;
|
|
1546
|
+
await this._internal.emitError({
|
|
1547
|
+
error: runError,
|
|
1548
|
+
code: CopilotKitCoreErrorCode.AGENT_RUN_FAILED,
|
|
1549
|
+
context
|
|
1550
|
+
});
|
|
1551
|
+
return { newMessages: [] };
|
|
1552
|
+
} finally {
|
|
1553
|
+
this._runDepth--;
|
|
1554
|
+
if (isTopLevel && originalAbortRun) agent.abortRun = originalAbortRun;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Process agent result and execute tools
|
|
1559
|
+
*/
|
|
1560
|
+
async processAgentResult({ runAgentResult, agent }) {
|
|
1561
|
+
const { newMessages } = runAgentResult;
|
|
1562
|
+
const agentId = agent.agentId;
|
|
1563
|
+
let needsFollowUp = false;
|
|
1564
|
+
for (const message of newMessages) if (message.role === "assistant") {
|
|
1565
|
+
for (const toolCall of message.toolCalls || []) if (newMessages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id) === -1) {
|
|
1566
|
+
const tool = this.getTool({
|
|
1567
|
+
toolName: toolCall.function.name,
|
|
1568
|
+
agentId: agent.agentId
|
|
1569
|
+
});
|
|
1570
|
+
if (tool) {
|
|
1571
|
+
if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId)) needsFollowUp = true;
|
|
1572
|
+
} else {
|
|
1573
|
+
const wildcardTool = this.getTool({
|
|
1574
|
+
toolName: "*",
|
|
1575
|
+
agentId: agent.agentId
|
|
1576
|
+
});
|
|
1577
|
+
if (wildcardTool) {
|
|
1578
|
+
if (await this.executeWildcardTool(wildcardTool, toolCall, message, agent, agentId)) needsFollowUp = true;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (needsFollowUp && !this._runAbortController?.signal.aborted) {
|
|
1584
|
+
await this._internal.waitForPendingFrameworkUpdates();
|
|
1585
|
+
return await this.runAgent({ agent });
|
|
1586
|
+
}
|
|
1587
|
+
this._internal.suggestionEngine.reloadSuggestions(agentId, agent);
|
|
1588
|
+
return runAgentResult;
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Shared handler execution logic used by executeSpecificTool, executeWildcardTool, and runTool.
|
|
1592
|
+
* Handles arg parsing, subscriber notifications, handler invocation, result stringification,
|
|
1593
|
+
* and error handling.
|
|
1594
|
+
*/
|
|
1595
|
+
async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId }) {
|
|
1596
|
+
let toolCallResult = "";
|
|
1597
|
+
let errorMessage;
|
|
1598
|
+
let isArgumentError = false;
|
|
1599
|
+
let parsedArgs;
|
|
1600
|
+
try {
|
|
1601
|
+
parsedArgs = parseToolArguments(handlerArgs, toolCall.function.name);
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
const parseError = error instanceof Error ? error : new Error(String(error));
|
|
1604
|
+
errorMessage = parseError.message;
|
|
1605
|
+
isArgumentError = true;
|
|
1606
|
+
await this._internal.emitError({
|
|
1607
|
+
error: parseError,
|
|
1608
|
+
code: CopilotKitCoreErrorCode.TOOL_ARGUMENT_PARSE_FAILED,
|
|
1609
|
+
context: {
|
|
1610
|
+
agentId,
|
|
1611
|
+
toolCallId: toolCall.id,
|
|
1612
|
+
toolName: toolCall.function.name,
|
|
1613
|
+
rawArguments: handlerArgs,
|
|
1614
|
+
toolType,
|
|
1615
|
+
...messageId ? { messageId } : {}
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionStart?.({
|
|
1620
|
+
copilotkit: this.core,
|
|
1621
|
+
toolCallId: toolCall.id,
|
|
1622
|
+
agentId,
|
|
1623
|
+
toolName: toolCall.function.name,
|
|
1624
|
+
args: parsedArgs
|
|
1625
|
+
}), "Subscriber onToolExecutionStart error:");
|
|
1626
|
+
if (!errorMessage) try {
|
|
1627
|
+
const result = await tool.handler(parsedArgs, {
|
|
1628
|
+
toolCall,
|
|
1629
|
+
agent,
|
|
1630
|
+
signal: this._runAbortController?.signal
|
|
1631
|
+
});
|
|
1632
|
+
if (result === void 0 || result === null) toolCallResult = "";
|
|
1633
|
+
else if (typeof result === "string") toolCallResult = result;
|
|
1634
|
+
else toolCallResult = JSON.stringify(result);
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
const handlerError = error instanceof Error ? error : new Error(String(error));
|
|
1637
|
+
errorMessage = handlerError.message;
|
|
1638
|
+
await this._internal.emitError({
|
|
1639
|
+
error: handlerError,
|
|
1640
|
+
code: CopilotKitCoreErrorCode.TOOL_HANDLER_FAILED,
|
|
1641
|
+
context: {
|
|
1642
|
+
agentId,
|
|
1643
|
+
toolCallId: toolCall.id,
|
|
1644
|
+
toolName: toolCall.function.name,
|
|
1645
|
+
parsedArgs,
|
|
1646
|
+
toolType,
|
|
1647
|
+
...messageId ? { messageId } : {}
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
|
|
1652
|
+
await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
|
|
1653
|
+
copilotkit: this.core,
|
|
1654
|
+
toolCallId: toolCall.id,
|
|
1655
|
+
agentId,
|
|
1656
|
+
toolName: toolCall.function.name,
|
|
1657
|
+
result: errorMessage ? "" : toolCallResult,
|
|
1658
|
+
error: errorMessage
|
|
1659
|
+
}), "Subscriber onToolExecutionEnd error:");
|
|
1660
|
+
return {
|
|
1661
|
+
result: toolCallResult,
|
|
1662
|
+
error: errorMessage,
|
|
1663
|
+
isArgumentError
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Execute a specific tool
|
|
1668
|
+
*/
|
|
1669
|
+
async executeSpecificTool(tool, toolCall, message, agent, agentId) {
|
|
1670
|
+
if (tool?.agentId && tool.agentId !== agent.agentId) return false;
|
|
1671
|
+
let handlerResult = {
|
|
1672
|
+
result: "",
|
|
1673
|
+
error: void 0,
|
|
1674
|
+
isArgumentError: false
|
|
1675
|
+
};
|
|
1676
|
+
if (tool?.handler) handlerResult = await this.executeToolHandler({
|
|
1677
|
+
tool,
|
|
1678
|
+
toolCall,
|
|
1679
|
+
agent,
|
|
1680
|
+
agentId,
|
|
1681
|
+
handlerArgs: toolCall.function.arguments,
|
|
1682
|
+
toolType: "specific",
|
|
1683
|
+
messageId: message.id
|
|
1684
|
+
});
|
|
1685
|
+
{
|
|
1686
|
+
const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
|
|
1687
|
+
if (messageIndex === -1) return false;
|
|
1688
|
+
const toolMessage = {
|
|
1689
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1690
|
+
role: "tool",
|
|
1691
|
+
toolCallId: toolCall.id,
|
|
1692
|
+
content: handlerResult.result
|
|
1693
|
+
};
|
|
1694
|
+
agent.messages.splice(messageIndex + 1, 0, toolMessage);
|
|
1695
|
+
if (!handlerResult.error && tool?.followUp !== false) return true;
|
|
1696
|
+
}
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Execute a wildcard tool.
|
|
1701
|
+
* Wildcard tools receive args wrapped as `{toolName, args}`, which differs from
|
|
1702
|
+
* specific tools, so this method keeps its own arg-wrapping logic rather than
|
|
1703
|
+
* delegating to `executeToolHandler`.
|
|
1704
|
+
*/
|
|
1705
|
+
async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId) {
|
|
1706
|
+
if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
|
|
1707
|
+
let toolCallResult = "";
|
|
1708
|
+
let errorMessage;
|
|
1709
|
+
if (wildcardTool?.handler) {
|
|
1710
|
+
let parsedArgs;
|
|
1711
|
+
try {
|
|
1712
|
+
parsedArgs = parseToolArguments(toolCall.function.arguments, toolCall.function.name);
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
const parseError = error instanceof Error ? error : new Error(String(error));
|
|
1715
|
+
errorMessage = parseError.message;
|
|
1716
|
+
await this._internal.emitError({
|
|
1717
|
+
error: parseError,
|
|
1718
|
+
code: CopilotKitCoreErrorCode.TOOL_ARGUMENT_PARSE_FAILED,
|
|
1719
|
+
context: {
|
|
1720
|
+
agentId,
|
|
1721
|
+
toolCallId: toolCall.id,
|
|
1722
|
+
toolName: toolCall.function.name,
|
|
1723
|
+
rawArguments: toolCall.function.arguments,
|
|
1724
|
+
toolType: "wildcard",
|
|
1725
|
+
messageId: message.id
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
const wildcardArgs = {
|
|
1730
|
+
toolName: toolCall.function.name,
|
|
1731
|
+
args: parsedArgs
|
|
1732
|
+
};
|
|
1733
|
+
await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionStart?.({
|
|
1734
|
+
copilotkit: this.core,
|
|
1735
|
+
toolCallId: toolCall.id,
|
|
1736
|
+
agentId,
|
|
1737
|
+
toolName: toolCall.function.name,
|
|
1738
|
+
args: wildcardArgs
|
|
1739
|
+
}), "Subscriber onToolExecutionStart error:");
|
|
1740
|
+
if (!errorMessage) try {
|
|
1741
|
+
const result = await wildcardTool.handler(wildcardArgs, {
|
|
1742
|
+
toolCall,
|
|
1743
|
+
agent
|
|
1744
|
+
});
|
|
1745
|
+
if (result === void 0 || result === null) toolCallResult = "";
|
|
1746
|
+
else if (typeof result === "string") toolCallResult = result;
|
|
1747
|
+
else toolCallResult = JSON.stringify(result);
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
const handlerError = error instanceof Error ? error : new Error(String(error));
|
|
1750
|
+
errorMessage = handlerError.message;
|
|
1751
|
+
await this._internal.emitError({
|
|
1752
|
+
error: handlerError,
|
|
1753
|
+
code: CopilotKitCoreErrorCode.TOOL_HANDLER_FAILED,
|
|
1754
|
+
context: {
|
|
1755
|
+
agentId,
|
|
1756
|
+
toolCallId: toolCall.id,
|
|
1757
|
+
toolName: toolCall.function.name,
|
|
1758
|
+
parsedArgs: wildcardArgs,
|
|
1759
|
+
toolType: "wildcard",
|
|
1760
|
+
messageId: message.id
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
|
|
1765
|
+
await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
|
|
1766
|
+
copilotkit: this.core,
|
|
1767
|
+
toolCallId: toolCall.id,
|
|
1768
|
+
agentId,
|
|
1769
|
+
toolName: toolCall.function.name,
|
|
1770
|
+
result: errorMessage ? "" : toolCallResult,
|
|
1771
|
+
error: errorMessage
|
|
1772
|
+
}), "Subscriber onToolExecutionEnd error:");
|
|
1773
|
+
}
|
|
1774
|
+
{
|
|
1775
|
+
const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
|
|
1776
|
+
if (messageIndex === -1) return false;
|
|
1777
|
+
const toolMessage = {
|
|
1778
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1779
|
+
role: "tool",
|
|
1780
|
+
toolCallId: toolCall.id,
|
|
1781
|
+
content: toolCallResult
|
|
1782
|
+
};
|
|
1783
|
+
agent.messages.splice(messageIndex + 1, 0, toolMessage);
|
|
1784
|
+
if (!errorMessage && wildcardTool?.followUp !== false) return true;
|
|
1785
|
+
}
|
|
1786
|
+
return false;
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Programmatically execute a registered frontend tool without going through an LLM turn.
|
|
1790
|
+
* The handler runs, render components show up in the UI, and both the tool call and
|
|
1791
|
+
* result messages are added to `agent.messages`.
|
|
1792
|
+
*/
|
|
1793
|
+
async runTool(params) {
|
|
1794
|
+
const { name, agentId, parameters = {}, followUp = false } = params;
|
|
1795
|
+
const tool = this.getTool({
|
|
1796
|
+
toolName: name,
|
|
1797
|
+
agentId
|
|
1798
|
+
});
|
|
1799
|
+
if (!tool) {
|
|
1800
|
+
const error = /* @__PURE__ */ new Error(`Tool not found: ${name}`);
|
|
1801
|
+
await this._internal.emitError({
|
|
1802
|
+
error,
|
|
1803
|
+
code: CopilotKitCoreErrorCode.TOOL_NOT_FOUND,
|
|
1804
|
+
context: {
|
|
1805
|
+
toolName: name,
|
|
1806
|
+
agentId
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
throw error;
|
|
1810
|
+
}
|
|
1811
|
+
const resolvedAgentId = agentId ?? "default";
|
|
1812
|
+
const agent = this._internal.getAgent(resolvedAgentId);
|
|
1813
|
+
if (!agent) {
|
|
1814
|
+
const error = /* @__PURE__ */ new Error(`Agent not found: ${resolvedAgentId}`);
|
|
1815
|
+
await this._internal.emitError({
|
|
1816
|
+
error,
|
|
1817
|
+
code: CopilotKitCoreErrorCode.AGENT_NOT_FOUND,
|
|
1818
|
+
context: { agentId: resolvedAgentId }
|
|
1819
|
+
});
|
|
1820
|
+
throw error;
|
|
1821
|
+
}
|
|
1822
|
+
const toolCallId = (0, _copilotkit_shared.randomUUID)();
|
|
1823
|
+
const assistantMessage = {
|
|
1824
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1825
|
+
role: "assistant",
|
|
1826
|
+
content: "",
|
|
1827
|
+
toolCalls: [{
|
|
1828
|
+
id: toolCallId,
|
|
1829
|
+
type: "function",
|
|
1830
|
+
function: {
|
|
1831
|
+
name,
|
|
1832
|
+
arguments: JSON.stringify(parameters)
|
|
1833
|
+
}
|
|
1834
|
+
}]
|
|
1835
|
+
};
|
|
1836
|
+
agent.messages.push(assistantMessage);
|
|
1837
|
+
let handlerResult = {
|
|
1838
|
+
result: "",
|
|
1839
|
+
error: void 0,
|
|
1840
|
+
isArgumentError: false
|
|
1841
|
+
};
|
|
1842
|
+
if (tool.handler) handlerResult = await this.executeToolHandler({
|
|
1843
|
+
tool,
|
|
1844
|
+
toolCall: assistantMessage.toolCalls[0],
|
|
1845
|
+
agent,
|
|
1846
|
+
agentId: resolvedAgentId,
|
|
1847
|
+
handlerArgs: parameters,
|
|
1848
|
+
toolType: "runTool"
|
|
1849
|
+
});
|
|
1850
|
+
const toolResultMessage = {
|
|
1851
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1852
|
+
role: "tool",
|
|
1853
|
+
toolCallId,
|
|
1854
|
+
content: handlerResult.result
|
|
1855
|
+
};
|
|
1856
|
+
const assistantIndex = agent.messages.findIndex((m) => m.id === assistantMessage.id);
|
|
1857
|
+
if (assistantIndex !== -1) agent.messages.splice(assistantIndex + 1, 0, toolResultMessage);
|
|
1858
|
+
else agent.messages.push(toolResultMessage);
|
|
1859
|
+
if (!handlerResult.error && followUp !== false) {
|
|
1860
|
+
if (typeof followUp === "string" && followUp !== "generate") {
|
|
1861
|
+
const userMessage = {
|
|
1862
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1863
|
+
role: "user",
|
|
1864
|
+
content: followUp
|
|
1865
|
+
};
|
|
1866
|
+
agent.messages.push(userMessage);
|
|
1867
|
+
}
|
|
1868
|
+
await this._internal.waitForPendingFrameworkUpdates();
|
|
1869
|
+
await this.runAgent({ agent });
|
|
1870
|
+
}
|
|
1871
|
+
return {
|
|
1872
|
+
toolCallId,
|
|
1873
|
+
result: handlerResult.result,
|
|
1874
|
+
error: handlerResult.error
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Build frontend tools for an agent
|
|
1879
|
+
*/
|
|
1880
|
+
buildFrontendTools(agentId) {
|
|
1881
|
+
return this._tools.filter((tool) => tool.available !== false && (!tool.agentId || tool.agentId === agentId)).map((tool) => ({
|
|
1882
|
+
name: tool.name,
|
|
1883
|
+
description: tool.description ?? "",
|
|
1884
|
+
parameters: createToolSchema(tool)
|
|
1885
|
+
}));
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Create an agent error subscriber
|
|
1889
|
+
*/
|
|
1890
|
+
createAgentErrorSubscriber(agent) {
|
|
1891
|
+
const emitAgentError = async (error, code, extraContext = {}) => {
|
|
1892
|
+
const context = { ...extraContext };
|
|
1893
|
+
if (agent.agentId) context.agentId = agent.agentId;
|
|
1894
|
+
await this._internal.emitError({
|
|
1895
|
+
error,
|
|
1896
|
+
code,
|
|
1897
|
+
context
|
|
1898
|
+
});
|
|
1899
|
+
};
|
|
1900
|
+
return {
|
|
1901
|
+
onRunFailed: async ({ error }) => {
|
|
1902
|
+
await emitAgentError(error, error instanceof AgentThreadLockedError ? CopilotKitCoreErrorCode.AGENT_THREAD_LOCKED : CopilotKitCoreErrorCode.AGENT_RUN_FAILED_EVENT, { source: "onRunFailed" });
|
|
1903
|
+
},
|
|
1904
|
+
onRunErrorEvent: async ({ event }) => {
|
|
1905
|
+
const eventError = event?.rawEvent instanceof Error ? event.rawEvent : event?.rawEvent?.error instanceof Error ? event.rawEvent.error : void 0;
|
|
1906
|
+
const errorMessage = typeof event?.rawEvent?.error === "string" ? event.rawEvent.error : event?.message ?? "Agent run error";
|
|
1907
|
+
const rawError = eventError ?? new Error(errorMessage);
|
|
1908
|
+
if (event?.code && !rawError.code) rawError.code = event.code;
|
|
1909
|
+
await emitAgentError(rawError, CopilotKitCoreErrorCode.AGENT_RUN_ERROR_EVENT, {
|
|
1910
|
+
source: "onRunErrorEvent",
|
|
1911
|
+
event,
|
|
1912
|
+
runtimeErrorCode: event?.code
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
/**
|
|
1919
|
+
* Empty tool schema constant
|
|
1920
|
+
*/
|
|
1921
|
+
const EMPTY_TOOL_SCHEMA = {
|
|
1922
|
+
type: "object",
|
|
1923
|
+
properties: {}
|
|
1924
|
+
};
|
|
1925
|
+
/**
|
|
1926
|
+
* Create a JSON schema from a tool's parameters
|
|
1927
|
+
*/
|
|
1928
|
+
function createToolSchema(tool) {
|
|
1929
|
+
if (!tool.parameters) return { ...EMPTY_TOOL_SCHEMA };
|
|
1930
|
+
const rawSchema = (0, _copilotkit_shared.schemaToJsonSchema)(tool.parameters, { zodToJsonSchema: zod_to_json_schema.zodToJsonSchema });
|
|
1931
|
+
if (!rawSchema || typeof rawSchema !== "object") return { ...EMPTY_TOOL_SCHEMA };
|
|
1932
|
+
const { $schema, ...schema } = rawSchema;
|
|
1933
|
+
if (typeof schema.type !== "string") schema.type = "object";
|
|
1934
|
+
if (typeof schema.properties !== "object" || schema.properties === null) schema.properties = {};
|
|
1935
|
+
stripAdditionalProperties(schema);
|
|
1936
|
+
return schema;
|
|
1937
|
+
}
|
|
1938
|
+
function stripAdditionalProperties(schema) {
|
|
1939
|
+
if (!schema || typeof schema !== "object") return;
|
|
1940
|
+
if (Array.isArray(schema)) {
|
|
1941
|
+
schema.forEach(stripAdditionalProperties);
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
const record = schema;
|
|
1945
|
+
if (record.additionalProperties !== void 0) delete record.additionalProperties;
|
|
1946
|
+
for (const value of Object.values(record)) stripAdditionalProperties(value);
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Ensures parsed tool arguments are a plain object.
|
|
1950
|
+
* Throws for non-object values so the caller's catch block can emit
|
|
1951
|
+
* a structured TOOL_ARGUMENT_PARSE_FAILED error.
|
|
1952
|
+
*
|
|
1953
|
+
* @internal Exported for testing only.
|
|
1954
|
+
*/
|
|
1955
|
+
function ensureObjectArgs(parsed, toolName) {
|
|
1956
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
|
|
1957
|
+
throw new Error(`Tool arguments for ${toolName} parsed to non-object (${typeof parsed})`);
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Parses raw tool call arguments into a validated object.
|
|
1961
|
+
*
|
|
1962
|
+
* Some LLM providers (e.g. @ai-sdk/openai-compatible) may send empty string "",
|
|
1963
|
+
* null, or undefined instead of "{}". This function normalises those cases to an
|
|
1964
|
+
* empty object so callers don't crash on JSON.parse("").
|
|
1965
|
+
*
|
|
1966
|
+
* A debug-level warning is emitted when the fallback triggers so silent coercion
|
|
1967
|
+
* is observable in logs.
|
|
1968
|
+
*
|
|
1969
|
+
* @internal Exported for testing only.
|
|
1970
|
+
*/
|
|
1971
|
+
function parseToolArguments(rawArgs, toolName) {
|
|
1972
|
+
if (rawArgs === "" || rawArgs === null || rawArgs === void 0) {
|
|
1973
|
+
_copilotkit_shared.logger.debug(`[parseToolArguments] Tool "${toolName}" received empty/null/undefined arguments — defaulting to {}`);
|
|
1974
|
+
return {};
|
|
1975
|
+
}
|
|
1976
|
+
return ensureObjectArgs(typeof rawArgs === "string" ? JSON.parse(rawArgs) : rawArgs, toolName);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
//#endregion
|
|
1980
|
+
//#region src/core/state-manager.ts
|
|
1981
|
+
/**
|
|
1982
|
+
* Manages state and message tracking by run for CopilotKitCore.
|
|
1983
|
+
* Tracks agent state snapshots and message-to-run associations.
|
|
1984
|
+
*/
|
|
1985
|
+
var StateManager = class {
|
|
1986
|
+
stateByRun = /* @__PURE__ */ new Map();
|
|
1987
|
+
messageToRun = /* @__PURE__ */ new Map();
|
|
1988
|
+
activeRun = /* @__PURE__ */ new Map();
|
|
1989
|
+
agentSubscriptions = /* @__PURE__ */ new Map();
|
|
1990
|
+
constructor(core) {
|
|
1991
|
+
this.core = core;
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Initialize state tracking for an agent
|
|
1995
|
+
*/
|
|
1996
|
+
initialize() {}
|
|
1997
|
+
/**
|
|
1998
|
+
* Subscribe to an agent's events to track state and messages.
|
|
1999
|
+
*
|
|
2000
|
+
* Registry agents (subscribed via `onAgentsChanged`) use the bare `agentId`
|
|
2001
|
+
* key so that `unsubscribeFromAgent(agentId)` can remove them when they
|
|
2002
|
+
* are replaced. Per-thread clones (subscribed via `subscribeAgentToStateManager`)
|
|
2003
|
+
* pass `{ isClone: true }` to use a composite `agentId:threadId` key, keeping
|
|
2004
|
+
* their subscription independent of the registry agent's.
|
|
2005
|
+
*/
|
|
2006
|
+
subscribeToAgent(agent, { isClone = false } = {}) {
|
|
2007
|
+
if (!agent.agentId) return;
|
|
2008
|
+
const agentId = agent.agentId;
|
|
2009
|
+
const subscriptionKey = isClone && agent.threadId ? `${agentId}:${agent.threadId}` : agentId;
|
|
2010
|
+
const existingUnsubscribe = this.agentSubscriptions.get(subscriptionKey);
|
|
2011
|
+
if (existingUnsubscribe) {
|
|
2012
|
+
existingUnsubscribe();
|
|
2013
|
+
this.agentSubscriptions.delete(subscriptionKey);
|
|
2014
|
+
}
|
|
2015
|
+
let revoked = false;
|
|
2016
|
+
let subRunId;
|
|
2017
|
+
let runFinished = false;
|
|
2018
|
+
const effectiveInput = (input) => ({
|
|
2019
|
+
...input,
|
|
2020
|
+
runId: subRunId ?? input.runId
|
|
2021
|
+
});
|
|
2022
|
+
const { unsubscribe } = agent.subscribe({
|
|
2023
|
+
onRunStartedEvent: ({ input, state }) => {
|
|
2024
|
+
if (revoked) return;
|
|
2025
|
+
if (runFinished && input.runId === subRunId) subRunId = (0, _ag_ui_client.randomUUID)();
|
|
2026
|
+
else subRunId = input.runId;
|
|
2027
|
+
runFinished = false;
|
|
2028
|
+
this.handleRunStarted(agent, effectiveInput(input), state);
|
|
2029
|
+
},
|
|
2030
|
+
onRunFinishedEvent: ({ input, state }) => {
|
|
2031
|
+
if (revoked) return;
|
|
2032
|
+
runFinished = true;
|
|
2033
|
+
this.handleRunFinished(agent, effectiveInput(input), state);
|
|
2034
|
+
},
|
|
2035
|
+
onStateSnapshotEvent: ({ event, input, state }) => {
|
|
2036
|
+
if (revoked) return;
|
|
2037
|
+
this.handleStateSnapshot(agent, event, effectiveInput(input), state);
|
|
2038
|
+
},
|
|
2039
|
+
onStateDeltaEvent: ({ event, input, state }) => {
|
|
2040
|
+
if (revoked) return;
|
|
2041
|
+
this.handleStateDelta(agent, event, effectiveInput(input), state);
|
|
2042
|
+
},
|
|
2043
|
+
onMessagesSnapshotEvent: ({ event, input, messages }) => {
|
|
2044
|
+
if (revoked) return;
|
|
2045
|
+
this.handleMessagesSnapshot(agent, event, effectiveInput(input), messages);
|
|
2046
|
+
},
|
|
2047
|
+
onNewMessage: ({ message, input }) => {
|
|
2048
|
+
if (revoked) return;
|
|
2049
|
+
this.handleNewMessage(agent, message, input ? effectiveInput(input) : void 0);
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
this.agentSubscriptions.set(subscriptionKey, () => {
|
|
2053
|
+
revoked = true;
|
|
2054
|
+
unsubscribe();
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Unsubscribe a registry agent's subscription (bare `agentId` key).
|
|
2059
|
+
* Per-thread clone subscriptions use composite `agentId:threadId` keys and
|
|
2060
|
+
* are replaced (not removed) by subsequent subscribeToAgent(agent, { isClone: true })
|
|
2061
|
+
* calls for the same (agentId, threadId) pair.
|
|
2062
|
+
*/
|
|
2063
|
+
unsubscribeFromAgent(agentId) {
|
|
2064
|
+
const unsubscribe = this.agentSubscriptions.get(agentId);
|
|
2065
|
+
if (unsubscribe) {
|
|
2066
|
+
unsubscribe();
|
|
2067
|
+
this.agentSubscriptions.delete(agentId);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Get state for a specific run
|
|
2072
|
+
* Returns a deep copy to prevent external mutations
|
|
2073
|
+
*/
|
|
2074
|
+
getStateByRun(agentId, threadId, runId) {
|
|
2075
|
+
const state = this.stateByRun.get(agentId)?.get(threadId)?.get(runId);
|
|
2076
|
+
if (!state) return void 0;
|
|
2077
|
+
return JSON.parse(JSON.stringify(state));
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Get runId associated with a message
|
|
2081
|
+
*/
|
|
2082
|
+
getRunIdForMessage(agentId, threadId, messageId) {
|
|
2083
|
+
return this.messageToRun.get(agentId)?.get(threadId)?.get(messageId);
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Get all states for an agent's thread
|
|
2087
|
+
*/
|
|
2088
|
+
getStatesForThread(agentId, threadId) {
|
|
2089
|
+
return this.stateByRun.get(agentId)?.get(threadId) ?? /* @__PURE__ */ new Map();
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Get all run IDs for an agent's thread
|
|
2093
|
+
*/
|
|
2094
|
+
getRunIdsForThread(agentId, threadId) {
|
|
2095
|
+
const threadStates = this.stateByRun.get(agentId)?.get(threadId);
|
|
2096
|
+
return threadStates ? Array.from(threadStates.keys()) : [];
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Handle run started event
|
|
2100
|
+
*/
|
|
2101
|
+
handleRunStarted(agent, input, state) {
|
|
2102
|
+
if (!agent.agentId) return;
|
|
2103
|
+
const { threadId, runId } = input;
|
|
2104
|
+
this.activeRun.set(`${agent.agentId}:${threadId}`, runId);
|
|
2105
|
+
if (state && Object.keys(state).length > 0) this.saveState(agent.agentId, threadId, runId, state);
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Handle run finished event
|
|
2109
|
+
*/
|
|
2110
|
+
handleRunFinished(agent, input, state) {
|
|
2111
|
+
if (!agent.agentId) return;
|
|
2112
|
+
const { threadId, runId } = input;
|
|
2113
|
+
this.activeRun.delete(`${agent.agentId}:${threadId}`);
|
|
2114
|
+
if (state && Object.keys(state).length > 0) this.saveState(agent.agentId, threadId, runId, state);
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Handle state snapshot event
|
|
2118
|
+
*/
|
|
2119
|
+
handleStateSnapshot(agent, event, input, state) {
|
|
2120
|
+
if (!agent.agentId) return;
|
|
2121
|
+
const { threadId, runId } = input;
|
|
2122
|
+
const mergedState = {
|
|
2123
|
+
...state,
|
|
2124
|
+
...event.snapshot
|
|
2125
|
+
};
|
|
2126
|
+
this.saveState(agent.agentId, threadId, runId, mergedState);
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Handle state delta event
|
|
2130
|
+
*/
|
|
2131
|
+
handleStateDelta(agent, event, input, state) {
|
|
2132
|
+
if (!agent.agentId) return;
|
|
2133
|
+
const { threadId, runId } = input;
|
|
2134
|
+
this.saveState(agent.agentId, threadId, runId, state);
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Handle messages snapshot event
|
|
2138
|
+
*/
|
|
2139
|
+
handleMessagesSnapshot(agent, event, input, messages) {
|
|
2140
|
+
if (!agent.agentId) return;
|
|
2141
|
+
const { threadId, runId } = input;
|
|
2142
|
+
for (const message of event.messages) this.associateMessageWithRun(agent.agentId, threadId, message.id, runId);
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Handle new message event
|
|
2146
|
+
*/
|
|
2147
|
+
handleNewMessage(agent, message, input) {
|
|
2148
|
+
if (!agent.agentId) return;
|
|
2149
|
+
if (!input) {
|
|
2150
|
+
const threadId = agent.threadId ?? "";
|
|
2151
|
+
const runId = this.activeRun.get(`${agent.agentId}:${threadId}`);
|
|
2152
|
+
if (runId) this.associateMessageWithRun(agent.agentId, threadId, message.id, runId);
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const { threadId, runId } = input;
|
|
2156
|
+
this.associateMessageWithRun(agent.agentId, threadId, message.id, runId);
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Save state for a specific run
|
|
2160
|
+
*/
|
|
2161
|
+
saveState(agentId, threadId, runId, state) {
|
|
2162
|
+
if (!this.stateByRun.has(agentId)) this.stateByRun.set(agentId, /* @__PURE__ */ new Map());
|
|
2163
|
+
const agentStates = this.stateByRun.get(agentId);
|
|
2164
|
+
if (!agentStates.has(threadId)) agentStates.set(threadId, /* @__PURE__ */ new Map());
|
|
2165
|
+
agentStates.get(threadId).set(runId, JSON.parse(JSON.stringify(state)));
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Associate a message with a run
|
|
2169
|
+
*/
|
|
2170
|
+
associateMessageWithRun(agentId, threadId, messageId, runId) {
|
|
2171
|
+
if (!this.messageToRun.has(agentId)) this.messageToRun.set(agentId, /* @__PURE__ */ new Map());
|
|
2172
|
+
const agentMessages = this.messageToRun.get(agentId);
|
|
2173
|
+
if (!agentMessages.has(threadId)) agentMessages.set(threadId, /* @__PURE__ */ new Map());
|
|
2174
|
+
agentMessages.get(threadId).set(messageId, runId);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Clear all state for an agent
|
|
2178
|
+
*/
|
|
2179
|
+
clearAgentState(agentId) {
|
|
2180
|
+
this.stateByRun.delete(agentId);
|
|
2181
|
+
this.messageToRun.delete(agentId);
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Clear all state for a thread
|
|
2185
|
+
*/
|
|
2186
|
+
clearThreadState(agentId, threadId) {
|
|
2187
|
+
this.stateByRun.get(agentId)?.delete(threadId);
|
|
2188
|
+
this.messageToRun.get(agentId)?.delete(threadId);
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
//#endregion
|
|
2193
|
+
//#region src/core/core.ts
|
|
2194
|
+
let CopilotKitCoreErrorCode = /* @__PURE__ */ function(CopilotKitCoreErrorCode) {
|
|
2195
|
+
CopilotKitCoreErrorCode["RUNTIME_INFO_FETCH_FAILED"] = "runtime_info_fetch_failed";
|
|
2196
|
+
CopilotKitCoreErrorCode["AGENT_CONNECT_FAILED"] = "agent_connect_failed";
|
|
2197
|
+
CopilotKitCoreErrorCode["AGENT_RUN_FAILED"] = "agent_run_failed";
|
|
2198
|
+
CopilotKitCoreErrorCode["AGENT_RUN_FAILED_EVENT"] = "agent_run_failed_event";
|
|
2199
|
+
CopilotKitCoreErrorCode["AGENT_RUN_ERROR_EVENT"] = "agent_run_error_event";
|
|
2200
|
+
CopilotKitCoreErrorCode["TOOL_ARGUMENT_PARSE_FAILED"] = "tool_argument_parse_failed";
|
|
2201
|
+
CopilotKitCoreErrorCode["TOOL_HANDLER_FAILED"] = "tool_handler_failed";
|
|
2202
|
+
CopilotKitCoreErrorCode["TOOL_NOT_FOUND"] = "tool_not_found";
|
|
2203
|
+
CopilotKitCoreErrorCode["AGENT_NOT_FOUND"] = "agent_not_found";
|
|
2204
|
+
/**
|
|
2205
|
+
* Emitted when an agent run fails because the thread is already locked
|
|
2206
|
+
* by another active run.
|
|
2207
|
+
*
|
|
2208
|
+
* @example
|
|
2209
|
+
* ```tsx
|
|
2210
|
+
* <CopilotKitProvider
|
|
2211
|
+
* onError={({ code, error, context }) => {
|
|
2212
|
+
* if (code === "agent_thread_locked") {
|
|
2213
|
+
* // Show "Agent is busy, retry?" UI
|
|
2214
|
+
* }
|
|
2215
|
+
* }}
|
|
2216
|
+
* />
|
|
2217
|
+
* ```
|
|
2218
|
+
*/
|
|
2219
|
+
CopilotKitCoreErrorCode["AGENT_THREAD_LOCKED"] = "agent_thread_locked";
|
|
2220
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_FAILED"] = "transcription_failed";
|
|
2221
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_SERVICE_NOT_CONFIGURED"] = "transcription_service_not_configured";
|
|
2222
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_INVALID_AUDIO"] = "transcription_invalid_audio";
|
|
2223
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_RATE_LIMITED"] = "transcription_rate_limited";
|
|
2224
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_AUTH_FAILED"] = "transcription_auth_failed";
|
|
2225
|
+
CopilotKitCoreErrorCode["TRANSCRIPTION_NETWORK_ERROR"] = "transcription_network_error";
|
|
2226
|
+
return CopilotKitCoreErrorCode;
|
|
2227
|
+
}({});
|
|
2228
|
+
let CopilotKitCoreRuntimeConnectionStatus = /* @__PURE__ */ function(CopilotKitCoreRuntimeConnectionStatus) {
|
|
2229
|
+
CopilotKitCoreRuntimeConnectionStatus["Disconnected"] = "disconnected";
|
|
2230
|
+
CopilotKitCoreRuntimeConnectionStatus["Connected"] = "connected";
|
|
2231
|
+
CopilotKitCoreRuntimeConnectionStatus["Connecting"] = "connecting";
|
|
2232
|
+
CopilotKitCoreRuntimeConnectionStatus["Error"] = "error";
|
|
2233
|
+
return CopilotKitCoreRuntimeConnectionStatus;
|
|
2234
|
+
}({});
|
|
2235
|
+
var CopilotKitCore = class {
|
|
2236
|
+
_headers;
|
|
2237
|
+
_credentials;
|
|
2238
|
+
_properties;
|
|
2239
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
2240
|
+
agentRegistry;
|
|
2241
|
+
contextStore;
|
|
2242
|
+
suggestionEngine;
|
|
2243
|
+
runHandler;
|
|
2244
|
+
stateManager;
|
|
2245
|
+
constructor({ runtimeUrl, runtimeTransport = "rest", headers = {}, credentials, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [] }) {
|
|
2246
|
+
this._headers = headers;
|
|
2247
|
+
this._credentials = credentials;
|
|
2248
|
+
this._properties = properties;
|
|
2249
|
+
this.agentRegistry = new AgentRegistry(this);
|
|
2250
|
+
this.contextStore = new ContextStore(this);
|
|
2251
|
+
this.suggestionEngine = new SuggestionEngine(this);
|
|
2252
|
+
this.runHandler = new RunHandler(this);
|
|
2253
|
+
this.stateManager = new StateManager(this);
|
|
2254
|
+
this.agentRegistry.initialize(agents__unsafe_dev_only);
|
|
2255
|
+
this.runHandler.initialize(tools);
|
|
2256
|
+
this.suggestionEngine.initialize(suggestionsConfig);
|
|
2257
|
+
this.stateManager.initialize();
|
|
2258
|
+
this.agentRegistry.setRuntimeTransport(runtimeTransport);
|
|
2259
|
+
this.agentRegistry.setRuntimeUrl(runtimeUrl);
|
|
2260
|
+
this.subscribe({ onAgentsChanged: ({ agents }) => {
|
|
2261
|
+
Object.values(agents).forEach((agent) => {
|
|
2262
|
+
if (agent.agentId) this.stateManager.subscribeToAgent(agent);
|
|
2263
|
+
});
|
|
2264
|
+
} });
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Internal method used by delegate classes and subclasses to notify subscribers
|
|
2268
|
+
*/
|
|
2269
|
+
async notifySubscribers(handler, errorMessage) {
|
|
2270
|
+
await Promise.all(Array.from(this.subscribers).map(async (subscriber) => {
|
|
2271
|
+
try {
|
|
2272
|
+
await handler(subscriber);
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
console.error(errorMessage, error);
|
|
2275
|
+
}
|
|
2276
|
+
}));
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Internal method used by delegate classes to emit errors
|
|
2280
|
+
*/
|
|
2281
|
+
async emitError({ error, code, context = {} }) {
|
|
2282
|
+
await this.notifySubscribers((subscriber) => subscriber.onError?.({
|
|
2283
|
+
copilotkit: this,
|
|
2284
|
+
error,
|
|
2285
|
+
code,
|
|
2286
|
+
context
|
|
2287
|
+
}), "Subscriber onError error:");
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Snapshot accessors
|
|
2291
|
+
*/
|
|
2292
|
+
get context() {
|
|
2293
|
+
return this.contextStore.context;
|
|
2294
|
+
}
|
|
2295
|
+
get agents() {
|
|
2296
|
+
return this.agentRegistry.agents;
|
|
2297
|
+
}
|
|
2298
|
+
get tools() {
|
|
2299
|
+
return this.runHandler.tools;
|
|
2300
|
+
}
|
|
2301
|
+
get runtimeUrl() {
|
|
2302
|
+
return this.agentRegistry.runtimeUrl;
|
|
2303
|
+
}
|
|
2304
|
+
setRuntimeUrl(runtimeUrl) {
|
|
2305
|
+
this.agentRegistry.setRuntimeUrl(runtimeUrl);
|
|
2306
|
+
}
|
|
2307
|
+
get runtimeTransport() {
|
|
2308
|
+
return this.agentRegistry.runtimeTransport;
|
|
2309
|
+
}
|
|
2310
|
+
setRuntimeTransport(runtimeTransport) {
|
|
2311
|
+
this.agentRegistry.setRuntimeTransport(runtimeTransport);
|
|
2312
|
+
}
|
|
2313
|
+
get runtimeVersion() {
|
|
2314
|
+
return this.agentRegistry.runtimeVersion;
|
|
2315
|
+
}
|
|
2316
|
+
get headers() {
|
|
2317
|
+
return this._headers;
|
|
2318
|
+
}
|
|
2319
|
+
get credentials() {
|
|
2320
|
+
return this._credentials;
|
|
2321
|
+
}
|
|
2322
|
+
get properties() {
|
|
2323
|
+
return this._properties;
|
|
2324
|
+
}
|
|
2325
|
+
get runtimeConnectionStatus() {
|
|
2326
|
+
return this.agentRegistry.runtimeConnectionStatus;
|
|
2327
|
+
}
|
|
2328
|
+
get audioFileTranscriptionEnabled() {
|
|
2329
|
+
return this.agentRegistry.audioFileTranscriptionEnabled;
|
|
2330
|
+
}
|
|
2331
|
+
get runtimeMode() {
|
|
2332
|
+
return this.agentRegistry.runtimeMode;
|
|
2333
|
+
}
|
|
2334
|
+
get intelligence() {
|
|
2335
|
+
return this.agentRegistry.intelligence;
|
|
2336
|
+
}
|
|
2337
|
+
get a2uiEnabled() {
|
|
2338
|
+
return this.agentRegistry.a2uiEnabled;
|
|
2339
|
+
}
|
|
2340
|
+
get licenseStatus() {
|
|
2341
|
+
return this.agentRegistry.licenseStatus;
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Configuration updates
|
|
2345
|
+
*/
|
|
2346
|
+
setHeaders(headers) {
|
|
2347
|
+
this._headers = headers;
|
|
2348
|
+
this.agentRegistry.applyHeadersToAgents(this.agentRegistry.agents);
|
|
2349
|
+
this.notifySubscribers((subscriber) => subscriber.onHeadersChanged?.({
|
|
2350
|
+
copilotkit: this,
|
|
2351
|
+
headers: this.headers
|
|
2352
|
+
}), "Subscriber onHeadersChanged error:");
|
|
2353
|
+
}
|
|
2354
|
+
setCredentials(credentials) {
|
|
2355
|
+
this._credentials = credentials;
|
|
2356
|
+
this.agentRegistry.applyCredentialsToAgents(this.agentRegistry.agents);
|
|
2357
|
+
}
|
|
2358
|
+
setProperties(properties) {
|
|
2359
|
+
this._properties = properties;
|
|
2360
|
+
this.notifySubscribers((subscriber) => subscriber.onPropertiesChanged?.({
|
|
2361
|
+
copilotkit: this,
|
|
2362
|
+
properties: this.properties
|
|
2363
|
+
}), "Subscriber onPropertiesChanged error:");
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* Agent management (delegated to AgentRegistry)
|
|
2367
|
+
*/
|
|
2368
|
+
setAgents__unsafe_dev_only(agents) {
|
|
2369
|
+
this.agentRegistry.setAgents__unsafe_dev_only(agents);
|
|
2370
|
+
}
|
|
2371
|
+
addAgent__unsafe_dev_only(params) {
|
|
2372
|
+
this.agentRegistry.addAgent__unsafe_dev_only(params);
|
|
2373
|
+
}
|
|
2374
|
+
removeAgent__unsafe_dev_only(id) {
|
|
2375
|
+
this.agentRegistry.removeAgent__unsafe_dev_only(id);
|
|
2376
|
+
}
|
|
2377
|
+
getAgent(id) {
|
|
2378
|
+
return this.agentRegistry.getAgent(id);
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Context management (delegated to ContextStore)
|
|
2382
|
+
*/
|
|
2383
|
+
addContext(context) {
|
|
2384
|
+
return this.contextStore.addContext(context);
|
|
2385
|
+
}
|
|
2386
|
+
removeContext(id) {
|
|
2387
|
+
this.contextStore.removeContext(id);
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Suggestions management (delegated to SuggestionEngine)
|
|
2391
|
+
*/
|
|
2392
|
+
addSuggestionsConfig(config) {
|
|
2393
|
+
return this.suggestionEngine.addSuggestionsConfig(config);
|
|
2394
|
+
}
|
|
2395
|
+
removeSuggestionsConfig(id) {
|
|
2396
|
+
this.suggestionEngine.removeSuggestionsConfig(id);
|
|
2397
|
+
}
|
|
2398
|
+
reloadSuggestions(agentId) {
|
|
2399
|
+
this.suggestionEngine.reloadSuggestions(agentId);
|
|
2400
|
+
}
|
|
2401
|
+
clearSuggestions(agentId) {
|
|
2402
|
+
this.suggestionEngine.clearSuggestions(agentId);
|
|
2403
|
+
}
|
|
2404
|
+
getSuggestions(agentId) {
|
|
2405
|
+
return this.suggestionEngine.getSuggestions(agentId);
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Tool management (delegated to RunHandler)
|
|
2409
|
+
*/
|
|
2410
|
+
addTool(tool) {
|
|
2411
|
+
this.runHandler.addTool(tool);
|
|
2412
|
+
}
|
|
2413
|
+
removeTool(id, agentId) {
|
|
2414
|
+
this.runHandler.removeTool(id, agentId);
|
|
2415
|
+
}
|
|
2416
|
+
getTool(params) {
|
|
2417
|
+
return this.runHandler.getTool(params);
|
|
2418
|
+
}
|
|
2419
|
+
setTools(tools) {
|
|
2420
|
+
this.runHandler.setTools(tools);
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Subscription lifecycle
|
|
2424
|
+
*/
|
|
2425
|
+
subscribe(subscriber) {
|
|
2426
|
+
this.subscribers.add(subscriber);
|
|
2427
|
+
return { unsubscribe: () => {
|
|
2428
|
+
this.subscribers.delete(subscriber);
|
|
2429
|
+
} };
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Agent connectivity (delegated to RunHandler)
|
|
2433
|
+
*/
|
|
2434
|
+
async connectAgent(params) {
|
|
2435
|
+
return this.runHandler.connectAgent(params);
|
|
2436
|
+
}
|
|
2437
|
+
stopAgent(params) {
|
|
2438
|
+
this.runHandler.abortCurrentRun();
|
|
2439
|
+
params.agent.abortRun();
|
|
2440
|
+
}
|
|
2441
|
+
async runAgent(params) {
|
|
2442
|
+
return this.runHandler.runAgent(params);
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Programmatically execute a registered frontend tool without going through an LLM turn.
|
|
2446
|
+
* The handler runs, render components show up in the UI, and both the tool call and
|
|
2447
|
+
* result messages are added to `agent.messages`.
|
|
2448
|
+
*/
|
|
2449
|
+
async runTool(params) {
|
|
2450
|
+
return this.runHandler.runTool(params);
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* State management (delegated to StateManager)
|
|
2454
|
+
*/
|
|
2455
|
+
getStateByRun(agentId, threadId, runId) {
|
|
2456
|
+
return this.stateManager.getStateByRun(agentId, threadId, runId);
|
|
2457
|
+
}
|
|
2458
|
+
getRunIdForMessage(agentId, threadId, messageId) {
|
|
2459
|
+
return this.stateManager.getRunIdForMessage(agentId, threadId, messageId);
|
|
2460
|
+
}
|
|
2461
|
+
getRunIdsForThread(agentId, threadId) {
|
|
2462
|
+
return this.stateManager.getRunIdsForThread(agentId, threadId);
|
|
2463
|
+
}
|
|
2464
|
+
subscribeAgentToStateManager(agent) {
|
|
2465
|
+
this.stateManager.subscribeToAgent(agent, { isClone: true });
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Internal method used by RunHandler to build frontend tools
|
|
2469
|
+
*/
|
|
2470
|
+
buildFrontendTools(agentId) {
|
|
2471
|
+
return this.runHandler.buildFrontendTools(agentId);
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Called before each follow-up agent run (after tool execution).
|
|
2475
|
+
*
|
|
2476
|
+
* When a frontend tool handler calls framework state setters (e.g. React's
|
|
2477
|
+
* setState), those updates are batched and deferred — they do not take effect
|
|
2478
|
+
* until the framework's scheduler runs (React uses MessageChannel).
|
|
2479
|
+
* useAgentContext registers context via useLayoutEffect, which runs
|
|
2480
|
+
* synchronously after React commits that deferred batch.
|
|
2481
|
+
*
|
|
2482
|
+
* Without yielding here, the follow-up runAgent reads the context store
|
|
2483
|
+
* synchronously while the deferred updates are still pending, producing stale
|
|
2484
|
+
* context for the next agent turn.
|
|
2485
|
+
*
|
|
2486
|
+
* Override in framework-specific subclasses to yield to the framework
|
|
2487
|
+
* scheduler before the follow-up run. The base implementation is a no-op
|
|
2488
|
+
* because non-React environments have no deferred state to flush.
|
|
2489
|
+
*/
|
|
2490
|
+
async waitForPendingFrameworkUpdates() {}
|
|
2491
|
+
};
|
|
2492
|
+
|
|
2493
|
+
//#endregion
|
|
2494
|
+
//#region src/types.ts
|
|
2495
|
+
/**
|
|
2496
|
+
* Status of a tool call execution
|
|
2497
|
+
*/
|
|
2498
|
+
let ToolCallStatus = /* @__PURE__ */ function(ToolCallStatus) {
|
|
2499
|
+
ToolCallStatus["InProgress"] = "inProgress";
|
|
2500
|
+
ToolCallStatus["Executing"] = "executing";
|
|
2501
|
+
ToolCallStatus["Complete"] = "complete";
|
|
2502
|
+
return ToolCallStatus;
|
|
2503
|
+
}({});
|
|
2504
|
+
|
|
2505
|
+
//#endregion
|
|
2506
|
+
//#region src/utils/markdown.ts
|
|
2507
|
+
function completePartialMarkdown(input) {
|
|
2508
|
+
let s = input;
|
|
2509
|
+
const fenceMatches = Array.from(s.matchAll(/^(\s*)(`{3,}|~{3,})/gm));
|
|
2510
|
+
if (fenceMatches.length % 2 === 1) {
|
|
2511
|
+
const [, indent, fence] = fenceMatches[0];
|
|
2512
|
+
s += `\n${indent}${fence}`;
|
|
2513
|
+
}
|
|
2514
|
+
if (s.match(/\[([^\]]*)\]\(([^)]*)$/)) s += ")";
|
|
2515
|
+
const openElements = [];
|
|
2516
|
+
const chars = Array.from(s);
|
|
2517
|
+
const codeBlockRanges = [];
|
|
2518
|
+
const inlineCodeRanges = [];
|
|
2519
|
+
let tempCodeFenceCount = 0;
|
|
2520
|
+
let currentCodeBlockStart = -1;
|
|
2521
|
+
for (let i = 0; i < chars.length; i++) if (i === 0 || chars[i - 1] === "\n") {
|
|
2522
|
+
const lineMatch = s.substring(i).match(/^(\s*)(`{3,}|~{3,})/);
|
|
2523
|
+
if (lineMatch) {
|
|
2524
|
+
tempCodeFenceCount++;
|
|
2525
|
+
if (tempCodeFenceCount % 2 === 1) currentCodeBlockStart = i;
|
|
2526
|
+
else if (currentCodeBlockStart !== -1) {
|
|
2527
|
+
codeBlockRanges.push({
|
|
2528
|
+
start: currentCodeBlockStart,
|
|
2529
|
+
end: i + lineMatch[0].length
|
|
2530
|
+
});
|
|
2531
|
+
currentCodeBlockStart = -1;
|
|
2532
|
+
}
|
|
2533
|
+
i += lineMatch[0].length - 1;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
for (let i = 0; i < chars.length; i++) if (chars[i] === "`") {
|
|
2537
|
+
let backslashCount = 0;
|
|
2538
|
+
for (let j = i - 1; j >= 0 && chars[j] === "\\"; j--) backslashCount++;
|
|
2539
|
+
if (backslashCount % 2 === 0) {
|
|
2540
|
+
for (let j = i + 1; j < chars.length; j++) if (chars[j] === "`") {
|
|
2541
|
+
let closingBackslashCount = 0;
|
|
2542
|
+
for (let k = j - 1; k >= 0 && chars[k] === "\\"; k--) closingBackslashCount++;
|
|
2543
|
+
if (closingBackslashCount % 2 === 0) {
|
|
2544
|
+
inlineCodeRanges.push({
|
|
2545
|
+
start: i,
|
|
2546
|
+
end: j + 1
|
|
2547
|
+
});
|
|
2548
|
+
i = j;
|
|
2549
|
+
break;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
const isInCode = (pos) => {
|
|
2555
|
+
return codeBlockRanges.some((range) => pos >= range.start && pos < range.end) || inlineCodeRanges.some((range) => pos >= range.start && pos < range.end);
|
|
2556
|
+
};
|
|
2557
|
+
for (let i = 0; i < chars.length; i++) {
|
|
2558
|
+
const char = chars[i];
|
|
2559
|
+
const nextChar = chars[i + 1];
|
|
2560
|
+
const prevChar = chars[i - 1];
|
|
2561
|
+
if (isInCode(i)) continue;
|
|
2562
|
+
if (char === "[") {
|
|
2563
|
+
let isCompleteLink = false;
|
|
2564
|
+
let bracketDepth = 1;
|
|
2565
|
+
let j = i + 1;
|
|
2566
|
+
while (j < chars.length && bracketDepth > 0) {
|
|
2567
|
+
if (chars[j] === "[" && !isInCode(j)) bracketDepth++;
|
|
2568
|
+
if (chars[j] === "]" && !isInCode(j)) bracketDepth--;
|
|
2569
|
+
j++;
|
|
2570
|
+
}
|
|
2571
|
+
if (bracketDepth === 0 && chars[j] === "(") {
|
|
2572
|
+
let parenDepth = 1;
|
|
2573
|
+
j++;
|
|
2574
|
+
while (j < chars.length && parenDepth > 0) {
|
|
2575
|
+
if (chars[j] === "(" && !isInCode(j)) parenDepth++;
|
|
2576
|
+
if (chars[j] === ")" && !isInCode(j)) parenDepth--;
|
|
2577
|
+
j++;
|
|
2578
|
+
}
|
|
2579
|
+
if (parenDepth === 0) {
|
|
2580
|
+
isCompleteLink = true;
|
|
2581
|
+
i = j - 1;
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
if (!isCompleteLink) {
|
|
2586
|
+
const existingIndex = openElements.findIndex((el) => el.type === "bracket");
|
|
2587
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2588
|
+
else openElements.push({
|
|
2589
|
+
type: "bracket",
|
|
2590
|
+
marker: "[",
|
|
2591
|
+
position: i
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
} else if (char === "*" && nextChar === "*") {
|
|
2595
|
+
const existingIndex = openElements.findIndex((el) => el.type === "bold_star");
|
|
2596
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2597
|
+
else openElements.push({
|
|
2598
|
+
type: "bold_star",
|
|
2599
|
+
marker: "**",
|
|
2600
|
+
position: i
|
|
2601
|
+
});
|
|
2602
|
+
i++;
|
|
2603
|
+
} else if (char === "_" && nextChar === "_") {
|
|
2604
|
+
const existingIndex = openElements.findIndex((el) => el.type === "bold_underscore");
|
|
2605
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2606
|
+
else openElements.push({
|
|
2607
|
+
type: "bold_underscore",
|
|
2608
|
+
marker: "__",
|
|
2609
|
+
position: i
|
|
2610
|
+
});
|
|
2611
|
+
i++;
|
|
2612
|
+
} else if (char === "~" && nextChar === "~") {
|
|
2613
|
+
const existingIndex = openElements.findIndex((el) => el.type === "strike");
|
|
2614
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2615
|
+
else openElements.push({
|
|
2616
|
+
type: "strike",
|
|
2617
|
+
marker: "~~",
|
|
2618
|
+
position: i
|
|
2619
|
+
});
|
|
2620
|
+
i++;
|
|
2621
|
+
} else if (char === "*" && prevChar !== "*" && nextChar !== "*") {
|
|
2622
|
+
const existingIndex = openElements.findIndex((el) => el.type === "italic_star");
|
|
2623
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2624
|
+
else openElements.push({
|
|
2625
|
+
type: "italic_star",
|
|
2626
|
+
marker: "*",
|
|
2627
|
+
position: i
|
|
2628
|
+
});
|
|
2629
|
+
} else if (char === "_" && prevChar !== "_" && nextChar !== "_") {
|
|
2630
|
+
const existingIndex = openElements.findIndex((el) => el.type === "italic_underscore");
|
|
2631
|
+
if (existingIndex !== -1) openElements.splice(existingIndex, 1);
|
|
2632
|
+
else openElements.push({
|
|
2633
|
+
type: "italic_underscore",
|
|
2634
|
+
marker: "_",
|
|
2635
|
+
position: i
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
let backtickCount = 0;
|
|
2640
|
+
for (let i = 0; i < chars.length; i++) if (chars[i] === "`" && !isInCode(i)) backtickCount++;
|
|
2641
|
+
if (backtickCount % 2 === 1) s += "`";
|
|
2642
|
+
openElements.sort((a, b) => b.position - a.position);
|
|
2643
|
+
const closers = openElements.map((el) => {
|
|
2644
|
+
switch (el.type) {
|
|
2645
|
+
case "bracket": return "]";
|
|
2646
|
+
case "bold_star": return "**";
|
|
2647
|
+
case "bold_underscore": return "__";
|
|
2648
|
+
case "strike": return "~~";
|
|
2649
|
+
case "italic_star": return "*";
|
|
2650
|
+
case "italic_underscore": return "_";
|
|
2651
|
+
default: return "";
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
let result = s + closers.join("");
|
|
2655
|
+
const finalFenceMatches = Array.from(result.matchAll(/^(\s*)(`{3,}|~{3,})/gm));
|
|
2656
|
+
const hasUnclosedBacktick = (result.match(/`/g) || []).length % 2 === 1;
|
|
2657
|
+
const hasUnclosedCodeFence = finalFenceMatches.length % 2 === 1;
|
|
2658
|
+
let shouldCloseParens = !hasUnclosedBacktick && !hasUnclosedCodeFence;
|
|
2659
|
+
if (shouldCloseParens) {
|
|
2660
|
+
const lastOpenParen = result.lastIndexOf("(");
|
|
2661
|
+
if (lastOpenParen !== -1) {
|
|
2662
|
+
if ((result.substring(0, lastOpenParen).match(/`/g) || []).length % 2 === 1) shouldCloseParens = false;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
if (shouldCloseParens) {
|
|
2666
|
+
const openParens = (result.match(/\(/g) || []).length;
|
|
2667
|
+
const closeParens = (result.match(/\)/g) || []).length;
|
|
2668
|
+
if (openParens > closeParens) result += ")".repeat(openParens - closeParens);
|
|
2669
|
+
}
|
|
2670
|
+
return result;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
//#endregion
|
|
2674
|
+
//#region src/utils/micro-redux.ts
|
|
2675
|
+
const INTERNAL_ACTION_TYPES = {
|
|
2676
|
+
boot: "@@micro-redux/boot",
|
|
2677
|
+
init: "@@micro-redux/init",
|
|
2678
|
+
stop: "@@micro-redux/stop"
|
|
2679
|
+
};
|
|
2680
|
+
const INTERNAL_BOOT_ACTION = { type: INTERNAL_ACTION_TYPES.boot };
|
|
2681
|
+
/**
|
|
2682
|
+
* Builds a typed action creator from a type string and payload factory.
|
|
2683
|
+
*/
|
|
2684
|
+
function createTypedActionCreator(type, factory) {
|
|
2685
|
+
const creator = ((...args) => ({
|
|
2686
|
+
...factory(...args),
|
|
2687
|
+
type
|
|
2688
|
+
}));
|
|
2689
|
+
creator.type = type;
|
|
2690
|
+
creator.match = (action) => action.type === type;
|
|
2691
|
+
return creator;
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Declares a payload-based action config for `createActionGroup`.
|
|
2695
|
+
*
|
|
2696
|
+
* @example
|
|
2697
|
+
* ```ts
|
|
2698
|
+
* const actions = createActionGroup("User", {
|
|
2699
|
+
* loaded: props<{ id: string }>(),
|
|
2700
|
+
* });
|
|
2701
|
+
* ```
|
|
2702
|
+
*/
|
|
2703
|
+
function props() {
|
|
2704
|
+
return { kind: "props" };
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Declares a no-payload action config for `createActionGroup`.
|
|
2708
|
+
*
|
|
2709
|
+
* @example
|
|
2710
|
+
* ```ts
|
|
2711
|
+
* const actions = createActionGroup("User", {
|
|
2712
|
+
* reset: empty(),
|
|
2713
|
+
* });
|
|
2714
|
+
* ```
|
|
2715
|
+
*/
|
|
2716
|
+
function empty() {
|
|
2717
|
+
return { kind: "empty" };
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2720
|
+
* Creates a namespaced group of typed action creators.
|
|
2721
|
+
*
|
|
2722
|
+
* Action types are formatted as: `[Source] actionName`.
|
|
2723
|
+
*/
|
|
2724
|
+
function createActionGroup(source, config) {
|
|
2725
|
+
const group = {};
|
|
2726
|
+
for (const eventName of Object.keys(config)) {
|
|
2727
|
+
const eventConfig = config[eventName];
|
|
2728
|
+
if (!eventConfig) continue;
|
|
2729
|
+
const actionType = `[${source}] ${eventName}`;
|
|
2730
|
+
if (eventConfig.kind === "props") {
|
|
2731
|
+
group[eventName] = createTypedActionCreator(actionType, (payload) => ({ ...payload }));
|
|
2732
|
+
continue;
|
|
2733
|
+
}
|
|
2734
|
+
group[eventName] = createTypedActionCreator(actionType, () => ({}));
|
|
2735
|
+
}
|
|
2736
|
+
return group;
|
|
2737
|
+
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Registers one reducer handler for one or more action creators.
|
|
2740
|
+
*
|
|
2741
|
+
* @throws Error when called without at least one action creator and reducer.
|
|
2742
|
+
*/
|
|
2743
|
+
function on(...args) {
|
|
2744
|
+
if (args.length < 2) throw new Error("on requires at least one action creator and one reducer");
|
|
2745
|
+
const reducer = args[args.length - 1];
|
|
2746
|
+
return {
|
|
2747
|
+
creators: args.slice(0, -1),
|
|
2748
|
+
reducer
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Creates a reducer from an initial state and `on(...)` handler entries.
|
|
2753
|
+
*
|
|
2754
|
+
* Unknown action types return the current state unchanged.
|
|
2755
|
+
*/
|
|
2756
|
+
function createReducer(initialState, ...entries) {
|
|
2757
|
+
const reducerMap = /* @__PURE__ */ new Map();
|
|
2758
|
+
for (const entry of entries) for (const creator of entry.creators) {
|
|
2759
|
+
const handlers = reducerMap.get(creator.type) ?? [];
|
|
2760
|
+
handlers.push(entry.reducer);
|
|
2761
|
+
reducerMap.set(creator.type, handlers);
|
|
2762
|
+
}
|
|
2763
|
+
return (state, action) => {
|
|
2764
|
+
const currentState = state ?? initialState;
|
|
2765
|
+
const handlers = reducerMap.get(action.type);
|
|
2766
|
+
if (!handlers || handlers.length === 0) return currentState;
|
|
2767
|
+
let nextState = currentState;
|
|
2768
|
+
for (const handler of handlers) nextState = handler(nextState, action);
|
|
2769
|
+
return nextState;
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Creates a selector that caches and reuses the last computed result
|
|
2774
|
+
* when all input references are unchanged.
|
|
2775
|
+
*/
|
|
2776
|
+
function createSelector(...args) {
|
|
2777
|
+
if (args.length === 1) {
|
|
2778
|
+
const projector = args[0];
|
|
2779
|
+
let hasCached = false;
|
|
2780
|
+
let lastState;
|
|
2781
|
+
let lastResult;
|
|
2782
|
+
return (state) => {
|
|
2783
|
+
if (hasCached && state === lastState) return lastResult;
|
|
2784
|
+
lastState = state;
|
|
2785
|
+
lastResult = projector(state);
|
|
2786
|
+
hasCached = true;
|
|
2787
|
+
return lastResult;
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
const projector = args[args.length - 1];
|
|
2791
|
+
const selectors = args.slice(0, -1);
|
|
2792
|
+
let hasCached = false;
|
|
2793
|
+
let lastInputs = [];
|
|
2794
|
+
let lastResult;
|
|
2795
|
+
return (state) => {
|
|
2796
|
+
const inputs = selectors.map((selector) => selector(state));
|
|
2797
|
+
if (hasCached && inputs.length === lastInputs.length && inputs.every((value, index) => value === lastInputs[index])) return lastResult;
|
|
2798
|
+
lastInputs = inputs;
|
|
2799
|
+
lastResult = projector(...inputs);
|
|
2800
|
+
hasCached = true;
|
|
2801
|
+
return lastResult;
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* RxJS operator that maps state emissions through a selector and suppresses
|
|
2806
|
+
* unchanged projected values via reference equality.
|
|
2807
|
+
*/
|
|
2808
|
+
function select(selector) {
|
|
2809
|
+
return (source$) => source$.pipe((0, rxjs_operators.map)(selector), (0, rxjs_operators.distinctUntilChanged)());
|
|
2810
|
+
}
|
|
2811
|
+
/**
|
|
2812
|
+
* RxJS operator that filters an action stream by action creators and narrows
|
|
2813
|
+
* the output action type to the matched creator union.
|
|
2814
|
+
*
|
|
2815
|
+
* @throws Error when called without at least one action creator.
|
|
2816
|
+
*/
|
|
2817
|
+
function ofType(...creators) {
|
|
2818
|
+
if (creators.length === 0) throw new Error("ofType requires at least one action creator");
|
|
2819
|
+
const actionTypes = new Set(creators.map((creator) => creator.type));
|
|
2820
|
+
return (source$) => {
|
|
2821
|
+
return source$.pipe((0, rxjs_operators.filter)((action) => {
|
|
2822
|
+
return actionTypes.has(action.type);
|
|
2823
|
+
}));
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Creates an effect descriptor consumed by `createStore`.
|
|
2828
|
+
*/
|
|
2829
|
+
function createEffect(factory, options = {}) {
|
|
2830
|
+
if (options.dispatch === false) return {
|
|
2831
|
+
run: factory,
|
|
2832
|
+
dispatch: false
|
|
2833
|
+
};
|
|
2834
|
+
return {
|
|
2835
|
+
run: factory,
|
|
2836
|
+
dispatch: true
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Creates a small observable store with reducer + effects.
|
|
2841
|
+
*
|
|
2842
|
+
* Behavior:
|
|
2843
|
+
* - `init()` starts effects and dispatches `@@micro-redux/init`.
|
|
2844
|
+
* - `stop()` dispatches `@@micro-redux/stop` and unsubscribes all effects.
|
|
2845
|
+
* - Effect action observation is scheduled on `asapScheduler` to avoid
|
|
2846
|
+
* synchronous re-entrancy in the effect loop.
|
|
2847
|
+
* - Any effect error triggers fail-fast teardown and errors both `actions$`
|
|
2848
|
+
* and `state$`.
|
|
2849
|
+
*/
|
|
2850
|
+
function createStore(options) {
|
|
2851
|
+
const reducer = options.reducer;
|
|
2852
|
+
const effects = options.effects ?? [];
|
|
2853
|
+
let hasFatalError = false;
|
|
2854
|
+
let isRunning = false;
|
|
2855
|
+
let effectSubscriptions = new rxjs.Subscription();
|
|
2856
|
+
let currentState = reducer(void 0, INTERNAL_BOOT_ACTION);
|
|
2857
|
+
const stateSubject = new rxjs.BehaviorSubject(currentState);
|
|
2858
|
+
const actionsSubject = new rxjs.Subject();
|
|
2859
|
+
const dispatchInternal = (action) => {
|
|
2860
|
+
if (hasFatalError) throw new Error("Store is in a failed state due to an effect error");
|
|
2861
|
+
currentState = reducer(currentState, action);
|
|
2862
|
+
stateSubject.next(currentState);
|
|
2863
|
+
actionsSubject.next(action);
|
|
2864
|
+
};
|
|
2865
|
+
const failFast = (error) => {
|
|
2866
|
+
if (hasFatalError) return;
|
|
2867
|
+
hasFatalError = true;
|
|
2868
|
+
isRunning = false;
|
|
2869
|
+
effectSubscriptions.unsubscribe();
|
|
2870
|
+
effectSubscriptions = new rxjs.Subscription();
|
|
2871
|
+
actionsSubject.error(error);
|
|
2872
|
+
stateSubject.error(error);
|
|
2873
|
+
};
|
|
2874
|
+
const startEffects = () => {
|
|
2875
|
+
for (const effect of effects) {
|
|
2876
|
+
const scheduledActions$ = actionsSubject.asObservable().pipe((0, rxjs_operators.observeOn)(rxjs.asapScheduler));
|
|
2877
|
+
const state$ = stateSubject.asObservable();
|
|
2878
|
+
if (effect.dispatch) {
|
|
2879
|
+
const subscription = effect.run(scheduledActions$, state$).subscribe({
|
|
2880
|
+
next: (effectAction) => {
|
|
2881
|
+
if (hasFatalError) return;
|
|
2882
|
+
dispatchInternal(effectAction);
|
|
2883
|
+
},
|
|
2884
|
+
error: (error) => {
|
|
2885
|
+
failFast(error);
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
effectSubscriptions.add(subscription);
|
|
2889
|
+
continue;
|
|
2890
|
+
}
|
|
2891
|
+
const subscription = effect.run(scheduledActions$, state$).subscribe({ error: (error) => {
|
|
2892
|
+
failFast(error);
|
|
2893
|
+
} });
|
|
2894
|
+
effectSubscriptions.add(subscription);
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
return {
|
|
2898
|
+
dispatch(action) {
|
|
2899
|
+
dispatchInternal(action);
|
|
2900
|
+
},
|
|
2901
|
+
getState() {
|
|
2902
|
+
return currentState;
|
|
2903
|
+
},
|
|
2904
|
+
get state$() {
|
|
2905
|
+
return stateSubject.asObservable();
|
|
2906
|
+
},
|
|
2907
|
+
get actions$() {
|
|
2908
|
+
return actionsSubject.asObservable();
|
|
2909
|
+
},
|
|
2910
|
+
select(selector) {
|
|
2911
|
+
return stateSubject.asObservable().pipe(select(selector));
|
|
2912
|
+
},
|
|
2913
|
+
init() {
|
|
2914
|
+
if (hasFatalError || isRunning) return;
|
|
2915
|
+
isRunning = true;
|
|
2916
|
+
startEffects();
|
|
2917
|
+
if (hasFatalError) return;
|
|
2918
|
+
dispatchInternal({ type: INTERNAL_ACTION_TYPES.init });
|
|
2919
|
+
},
|
|
2920
|
+
stop() {
|
|
2921
|
+
if (hasFatalError || !isRunning) return;
|
|
2922
|
+
dispatchInternal({ type: INTERNAL_ACTION_TYPES.stop });
|
|
2923
|
+
effectSubscriptions.unsubscribe();
|
|
2924
|
+
effectSubscriptions = new rxjs.Subscription();
|
|
2925
|
+
isRunning = false;
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
//#endregion
|
|
2931
|
+
//#region src/threads.ts
|
|
2932
|
+
const THREADS_CHANNEL_EVENT = "thread_metadata";
|
|
2933
|
+
const THREAD_SUBSCRIBE_PATH = "/threads/subscribe";
|
|
2934
|
+
const MAX_SOCKET_RETRIES = 5;
|
|
2935
|
+
const REQUEST_TIMEOUT_MS = 15e3;
|
|
2936
|
+
const initialThreadState = {
|
|
2937
|
+
threads: [],
|
|
2938
|
+
isLoading: false,
|
|
2939
|
+
isFetchingNextPage: false,
|
|
2940
|
+
error: null,
|
|
2941
|
+
context: null,
|
|
2942
|
+
sessionId: 0,
|
|
2943
|
+
metadataCredentialsRequested: false,
|
|
2944
|
+
metadataJoinCode: null,
|
|
2945
|
+
nextCursor: null
|
|
2946
|
+
};
|
|
2947
|
+
const threadAdapterEvents = createActionGroup("Thread Adapter", {
|
|
2948
|
+
started: empty(),
|
|
2949
|
+
stopped: empty(),
|
|
2950
|
+
contextChanged: props(),
|
|
2951
|
+
fetchNextPageRequested: empty(),
|
|
2952
|
+
renameRequested: props(),
|
|
2953
|
+
archiveRequested: props(),
|
|
2954
|
+
deleteRequested: props()
|
|
2955
|
+
});
|
|
2956
|
+
const threadRestEvents = createActionGroup("Thread REST", {
|
|
2957
|
+
listRequested: props(),
|
|
2958
|
+
listSucceeded: props(),
|
|
2959
|
+
listFailed: props(),
|
|
2960
|
+
nextPageSucceeded: props(),
|
|
2961
|
+
nextPageFailed: props(),
|
|
2962
|
+
metadataCredentialsRequested: props(),
|
|
2963
|
+
metadataCredentialsSucceeded: props(),
|
|
2964
|
+
metadataCredentialsFailed: props(),
|
|
2965
|
+
mutationFinished: props()
|
|
2966
|
+
});
|
|
2967
|
+
const threadSocketEvents = createActionGroup("Thread Socket", {
|
|
2968
|
+
opened: props(),
|
|
2969
|
+
errored: props(),
|
|
2970
|
+
joinFailed: props(),
|
|
2971
|
+
joinTimedOut: props(),
|
|
2972
|
+
metadataReceived: props()
|
|
2973
|
+
});
|
|
2974
|
+
const threadDomainEvents = createActionGroup("Thread Domain", {
|
|
2975
|
+
threadUpserted: props(),
|
|
2976
|
+
threadDeleted: props()
|
|
2977
|
+
});
|
|
2978
|
+
function sortThreadsByUpdatedAt(threads) {
|
|
2979
|
+
return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
2980
|
+
}
|
|
2981
|
+
function upsertThread(threads, thread) {
|
|
2982
|
+
const existingIndex = threads.findIndex((item) => item.id === thread.id);
|
|
2983
|
+
if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
|
|
2984
|
+
const next = [...threads];
|
|
2985
|
+
next[existingIndex] = thread;
|
|
2986
|
+
return sortThreadsByUpdatedAt(next);
|
|
2987
|
+
}
|
|
2988
|
+
const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
|
|
2989
|
+
...state,
|
|
2990
|
+
context,
|
|
2991
|
+
sessionId: state.sessionId + 1,
|
|
2992
|
+
threads: [],
|
|
2993
|
+
isLoading: Boolean(context),
|
|
2994
|
+
isFetchingNextPage: false,
|
|
2995
|
+
error: null,
|
|
2996
|
+
metadataCredentialsRequested: false,
|
|
2997
|
+
metadataJoinCode: null,
|
|
2998
|
+
nextCursor: null
|
|
2999
|
+
})), on(threadAdapterEvents.stopped, (state) => ({
|
|
3000
|
+
...state,
|
|
3001
|
+
threads: [],
|
|
3002
|
+
isLoading: false,
|
|
3003
|
+
isFetchingNextPage: false,
|
|
3004
|
+
error: null,
|
|
3005
|
+
metadataCredentialsRequested: false,
|
|
3006
|
+
metadataJoinCode: null,
|
|
3007
|
+
nextCursor: null
|
|
3008
|
+
})), on(threadRestEvents.listRequested, (state, { sessionId }) => {
|
|
3009
|
+
if (sessionId !== state.sessionId || !state.context) return state;
|
|
3010
|
+
return {
|
|
3011
|
+
...state,
|
|
3012
|
+
isLoading: true,
|
|
3013
|
+
error: null
|
|
3014
|
+
};
|
|
3015
|
+
}), on(threadRestEvents.listSucceeded, (state, { sessionId, threads, joinCode, nextCursor }) => {
|
|
3016
|
+
if (sessionId !== state.sessionId) return state;
|
|
3017
|
+
return {
|
|
3018
|
+
...state,
|
|
3019
|
+
threads: sortThreadsByUpdatedAt(threads),
|
|
3020
|
+
isLoading: false,
|
|
3021
|
+
error: null,
|
|
3022
|
+
metadataJoinCode: joinCode,
|
|
3023
|
+
nextCursor
|
|
3024
|
+
};
|
|
3025
|
+
}), on(threadRestEvents.listFailed, (state, { sessionId, error }) => {
|
|
3026
|
+
if (sessionId !== state.sessionId) return state;
|
|
3027
|
+
return {
|
|
3028
|
+
...state,
|
|
3029
|
+
isLoading: false,
|
|
3030
|
+
error
|
|
3031
|
+
};
|
|
3032
|
+
}), on(threadRestEvents.nextPageSucceeded, (state, { sessionId, threads, nextCursor }) => {
|
|
3033
|
+
if (sessionId !== state.sessionId) return state;
|
|
3034
|
+
let merged = state.threads;
|
|
3035
|
+
for (const thread of threads) merged = upsertThread(merged, thread);
|
|
3036
|
+
return {
|
|
3037
|
+
...state,
|
|
3038
|
+
threads: merged,
|
|
3039
|
+
isFetchingNextPage: false,
|
|
3040
|
+
nextCursor
|
|
3041
|
+
};
|
|
3042
|
+
}), on(threadRestEvents.nextPageFailed, (state, { sessionId, error }) => {
|
|
3043
|
+
if (sessionId !== state.sessionId) return state;
|
|
3044
|
+
return {
|
|
3045
|
+
...state,
|
|
3046
|
+
isFetchingNextPage: false,
|
|
3047
|
+
error
|
|
3048
|
+
};
|
|
3049
|
+
}), on(threadRestEvents.metadataCredentialsFailed, (state, { sessionId, error }) => {
|
|
3050
|
+
if (sessionId !== state.sessionId) return state;
|
|
3051
|
+
return {
|
|
3052
|
+
...state,
|
|
3053
|
+
error
|
|
3054
|
+
};
|
|
3055
|
+
}), on(threadRestEvents.metadataCredentialsRequested, (state, { sessionId }) => {
|
|
3056
|
+
if (sessionId !== state.sessionId) return state;
|
|
3057
|
+
return {
|
|
3058
|
+
...state,
|
|
3059
|
+
metadataCredentialsRequested: true
|
|
3060
|
+
};
|
|
3061
|
+
}), on(threadAdapterEvents.fetchNextPageRequested, (state) => {
|
|
3062
|
+
if (!state.nextCursor || state.isFetchingNextPage) return state;
|
|
3063
|
+
return {
|
|
3064
|
+
...state,
|
|
3065
|
+
isFetchingNextPage: true
|
|
3066
|
+
};
|
|
3067
|
+
}), on(threadRestEvents.mutationFinished, (state, { outcome }) => ({
|
|
3068
|
+
...state,
|
|
3069
|
+
error: outcome.ok ? state.error : outcome.error
|
|
3070
|
+
})), on(threadDomainEvents.threadUpserted, (state, { sessionId, thread }) => {
|
|
3071
|
+
if (sessionId !== state.sessionId) return state;
|
|
3072
|
+
return {
|
|
3073
|
+
...state,
|
|
3074
|
+
threads: upsertThread(state.threads, thread)
|
|
3075
|
+
};
|
|
3076
|
+
}), on(threadDomainEvents.threadDeleted, (state, { sessionId, threadId }) => {
|
|
3077
|
+
if (sessionId !== state.sessionId) return state;
|
|
3078
|
+
return {
|
|
3079
|
+
...state,
|
|
3080
|
+
threads: state.threads.filter((thread) => thread.id !== threadId)
|
|
3081
|
+
};
|
|
3082
|
+
}));
|
|
3083
|
+
const selectThreads = createSelector((state) => state.threads);
|
|
3084
|
+
const selectThreadsIsLoading = createSelector((state) => state.isLoading);
|
|
3085
|
+
const selectThreadsError = createSelector((state) => state.error);
|
|
3086
|
+
const selectHasNextPage = createSelector((state) => state.nextCursor != null);
|
|
3087
|
+
const selectIsFetchingNextPage = createSelector((state) => state.isFetchingNextPage);
|
|
3088
|
+
let threadRequestId = 0;
|
|
3089
|
+
function createThreadRequestId() {
|
|
3090
|
+
threadRequestId += 1;
|
|
3091
|
+
return `thread-request-${threadRequestId}`;
|
|
3092
|
+
}
|
|
3093
|
+
function createThreadFetchObservable(environment, context, sessionId) {
|
|
3094
|
+
return (0, rxjs.defer)(() => {
|
|
3095
|
+
const params = { agentId: context.agentId };
|
|
3096
|
+
if (context.includeArchived) params.includeArchived = "true";
|
|
3097
|
+
if (context.limit != null) params.limit = String(context.limit);
|
|
3098
|
+
const qs = new URLSearchParams(params);
|
|
3099
|
+
return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}/threads?${qs.toString()}`, {
|
|
3100
|
+
selector: (response) => {
|
|
3101
|
+
if (!response.ok) throw new Error(`Failed to fetch threads: ${response.status}`);
|
|
3102
|
+
return response.json();
|
|
3103
|
+
},
|
|
3104
|
+
fetch: environment.fetch,
|
|
3105
|
+
method: "GET",
|
|
3106
|
+
headers: { ...context.headers }
|
|
3107
|
+
}).pipe((0, rxjs_operators.timeout)({
|
|
3108
|
+
first: REQUEST_TIMEOUT_MS,
|
|
3109
|
+
with: () => {
|
|
3110
|
+
throw new Error("Request timed out");
|
|
3111
|
+
}
|
|
3112
|
+
}), (0, rxjs_operators.map)((data) => threadRestEvents.listSucceeded({
|
|
3113
|
+
sessionId,
|
|
3114
|
+
threads: data.threads,
|
|
3115
|
+
joinCode: typeof data.joinCode === "string" && data.joinCode.length > 0 ? data.joinCode : null,
|
|
3116
|
+
nextCursor: data.nextCursor ?? null
|
|
3117
|
+
})), (0, rxjs_operators.catchError)((error) => {
|
|
3118
|
+
return (0, rxjs.of)(threadRestEvents.listFailed({
|
|
3119
|
+
sessionId,
|
|
3120
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3121
|
+
}));
|
|
3122
|
+
}));
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
function createThreadMetadataCredentialsObservable(environment, context, sessionId) {
|
|
3126
|
+
return (0, rxjs.defer)(() => {
|
|
3127
|
+
return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}${THREAD_SUBSCRIBE_PATH}`, {
|
|
3128
|
+
selector: async (response) => {
|
|
3129
|
+
if (!response.ok) throw new Error(`Failed to fetch thread metadata credentials: ${response.status}`);
|
|
3130
|
+
return response.json();
|
|
3131
|
+
},
|
|
3132
|
+
fetch: environment.fetch,
|
|
3133
|
+
method: "POST",
|
|
3134
|
+
headers: {
|
|
3135
|
+
...context.headers,
|
|
3136
|
+
"Content-Type": "application/json"
|
|
3137
|
+
},
|
|
3138
|
+
body: JSON.stringify({})
|
|
3139
|
+
}).pipe((0, rxjs_operators.timeout)({
|
|
3140
|
+
first: REQUEST_TIMEOUT_MS,
|
|
3141
|
+
with: () => {
|
|
3142
|
+
throw new Error("Request timed out");
|
|
3143
|
+
}
|
|
3144
|
+
}), (0, rxjs_operators.map)((data) => {
|
|
3145
|
+
if (typeof data.joinToken !== "string" || data.joinToken.length === 0) throw new Error("missing joinToken");
|
|
3146
|
+
return threadRestEvents.metadataCredentialsSucceeded({
|
|
3147
|
+
sessionId,
|
|
3148
|
+
joinToken: data.joinToken
|
|
3149
|
+
});
|
|
3150
|
+
}), (0, rxjs_operators.catchError)((error) => {
|
|
3151
|
+
return (0, rxjs.of)(threadRestEvents.metadataCredentialsFailed({
|
|
3152
|
+
sessionId,
|
|
3153
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3154
|
+
}));
|
|
3155
|
+
}));
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
function createThreadMutationObservable(environment, context, request) {
|
|
3159
|
+
return (0, rxjs.defer)(() => {
|
|
3160
|
+
return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}${request.path}`, {
|
|
3161
|
+
selector: async (response) => {
|
|
3162
|
+
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
|
|
3163
|
+
return null;
|
|
3164
|
+
},
|
|
3165
|
+
fetch: environment.fetch,
|
|
3166
|
+
method: request.method,
|
|
3167
|
+
headers: {
|
|
3168
|
+
...context.headers,
|
|
3169
|
+
"Content-Type": "application/json"
|
|
3170
|
+
},
|
|
3171
|
+
body: JSON.stringify(request.body)
|
|
3172
|
+
}).pipe((0, rxjs_operators.map)(() => threadRestEvents.mutationFinished({ outcome: {
|
|
3173
|
+
requestId: request.requestId,
|
|
3174
|
+
ok: true
|
|
3175
|
+
} })), (0, rxjs_operators.catchError)((error) => {
|
|
3176
|
+
return (0, rxjs.of)(threadRestEvents.mutationFinished({ outcome: {
|
|
3177
|
+
requestId: request.requestId,
|
|
3178
|
+
ok: false,
|
|
3179
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3180
|
+
} }));
|
|
3181
|
+
}));
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
function createThreadStore(environment) {
|
|
3185
|
+
const store = createStore({
|
|
3186
|
+
reducer: threadReducer,
|
|
3187
|
+
effects: [
|
|
3188
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.contextChanged), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([, state]) => Boolean(state.context)), (0, rxjs_operators.map)(([, state]) => threadRestEvents.listRequested({ sessionId: state.sessionId })))),
|
|
3189
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.listRequested), (0, rxjs_operators.switchMap)((action) => state$.pipe((0, rxjs_operators.map)((state) => state.context), (0, rxjs_operators.filter)((context) => Boolean(context)), (0, rxjs_operators.take)(1), (0, rxjs_operators.map)((context) => ({
|
|
3190
|
+
action,
|
|
3191
|
+
context
|
|
3192
|
+
})), (0, rxjs_operators.takeUntil)(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), (0, rxjs_operators.switchMap)(({ action: currentAction, context }) => createThreadFetchObservable(environment, context, currentAction.sessionId)))))),
|
|
3193
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.listSucceeded), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => {
|
|
3194
|
+
return action.sessionId === state.sessionId && !state.metadataCredentialsRequested && Boolean(state.context?.wsUrl) && Boolean(state.metadataJoinCode);
|
|
3195
|
+
}), (0, rxjs_operators.map)(([action]) => threadRestEvents.metadataCredentialsRequested({ sessionId: action.sessionId })))),
|
|
3196
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.metadataCredentialsRequested), (0, rxjs_operators.switchMap)((action) => state$.pipe((0, rxjs_operators.map)((state) => state.context), (0, rxjs_operators.filter)((context) => Boolean(context)), (0, rxjs_operators.take)(1), (0, rxjs_operators.map)((context) => ({
|
|
3197
|
+
action,
|
|
3198
|
+
context
|
|
3199
|
+
})), (0, rxjs_operators.takeUntil)(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), (0, rxjs_operators.switchMap)(({ action: currentAction, context }) => createThreadMetadataCredentialsObservable(environment, context, currentAction.sessionId)))))),
|
|
3200
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.metadataCredentialsSucceeded), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => {
|
|
3201
|
+
return action.sessionId === state.sessionId && Boolean(state.context?.wsUrl);
|
|
3202
|
+
}), (0, rxjs_operators.switchMap)(([action, state]) => {
|
|
3203
|
+
const context = state.context;
|
|
3204
|
+
const joinToken = action.joinToken;
|
|
3205
|
+
const joinCode = state.metadataJoinCode;
|
|
3206
|
+
const shutdown$ = actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped));
|
|
3207
|
+
return (0, rxjs.defer)(() => {
|
|
3208
|
+
const socket$ = ɵphoenixSocket$({
|
|
3209
|
+
url: context.wsUrl,
|
|
3210
|
+
options: {
|
|
3211
|
+
params: { join_token: joinToken },
|
|
3212
|
+
reconnectAfterMs: (0, _copilotkit_shared.phoenixExponentialBackoff)(100, 1e4),
|
|
3213
|
+
rejoinAfterMs: (0, _copilotkit_shared.phoenixExponentialBackoff)(1e3, 3e4)
|
|
3214
|
+
}
|
|
3215
|
+
}).pipe((0, rxjs_operators.shareReplay)({
|
|
3216
|
+
bufferSize: 1,
|
|
3217
|
+
refCount: true
|
|
3218
|
+
}));
|
|
3219
|
+
const channel$ = ɵphoenixChannel$({
|
|
3220
|
+
socket$,
|
|
3221
|
+
topic: `user_meta:${joinCode}`
|
|
3222
|
+
}).pipe((0, rxjs_operators.shareReplay)({
|
|
3223
|
+
bufferSize: 1,
|
|
3224
|
+
refCount: true
|
|
3225
|
+
}));
|
|
3226
|
+
const socketSignals$ = ɵobservePhoenixSocketSignals$(socket$).pipe((0, rxjs_operators.share)());
|
|
3227
|
+
const fatalSocketShutdown$ = ɵobservePhoenixSocketHealth$(socketSignals$, MAX_SOCKET_RETRIES).pipe((0, rxjs_operators.catchError)(() => {
|
|
3228
|
+
console.warn(`[threads] WebSocket failed after ${MAX_SOCKET_RETRIES} attempts, giving up`);
|
|
3229
|
+
return (0, rxjs.of)(void 0);
|
|
3230
|
+
}), (0, rxjs_operators.share)());
|
|
3231
|
+
return (0, rxjs.merge)(socketSignals$.pipe((0, rxjs_operators.map)((signal) => signal.type === "open" ? threadSocketEvents.opened({ sessionId: action.sessionId }) : threadSocketEvents.errored({ sessionId: action.sessionId }))), channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => ɵobservePhoenixEvent$(channel, THREADS_CHANNEL_EVENT)), (0, rxjs_operators.map)((payload) => threadSocketEvents.metadataReceived({
|
|
3232
|
+
sessionId: action.sessionId,
|
|
3233
|
+
payload
|
|
3234
|
+
}))), ɵobservePhoenixJoinOutcome$(channel$).pipe((0, rxjs_operators.filter)((outcome) => outcome.type !== "joined"), (0, rxjs_operators.map)((outcome) => outcome.type === "timeout" ? threadSocketEvents.joinTimedOut({ sessionId: action.sessionId }) : threadSocketEvents.joinFailed({ sessionId: action.sessionId })))).pipe((0, rxjs_operators.takeUntil)((0, rxjs.merge)(shutdown$, fatalSocketShutdown$)));
|
|
3235
|
+
});
|
|
3236
|
+
}))),
|
|
3237
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadSocketEvents.metadataReceived), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => action.sessionId === state.sessionId), (0, rxjs_operators.map)(([action, state]) => {
|
|
3238
|
+
if (action.payload.operation === "deleted") return threadDomainEvents.threadDeleted({
|
|
3239
|
+
sessionId: action.sessionId,
|
|
3240
|
+
threadId: action.payload.deleted.id
|
|
3241
|
+
});
|
|
3242
|
+
if (action.payload.operation === "archived" && !state.context?.includeArchived) return threadDomainEvents.threadDeleted({
|
|
3243
|
+
sessionId: action.sessionId,
|
|
3244
|
+
threadId: action.payload.threadId
|
|
3245
|
+
});
|
|
3246
|
+
return threadDomainEvents.threadUpserted({
|
|
3247
|
+
sessionId: action.sessionId,
|
|
3248
|
+
thread: action.payload.thread
|
|
3249
|
+
});
|
|
3250
|
+
}))),
|
|
3251
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.fetchNextPageRequested), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([, state]) => Boolean(state.context) && Boolean(state.nextCursor)), (0, rxjs_operators.switchMap)(([, state]) => {
|
|
3252
|
+
const context = state.context;
|
|
3253
|
+
const params = {
|
|
3254
|
+
agentId: context.agentId,
|
|
3255
|
+
cursor: state.nextCursor
|
|
3256
|
+
};
|
|
3257
|
+
if (context.includeArchived) params.includeArchived = "true";
|
|
3258
|
+
if (context.limit != null) params.limit = String(context.limit);
|
|
3259
|
+
return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}/threads?${new URLSearchParams(params).toString()}`, {
|
|
3260
|
+
selector: (response) => {
|
|
3261
|
+
if (!response.ok) throw new Error(`Failed to fetch next page: ${response.status}`);
|
|
3262
|
+
return response.json();
|
|
3263
|
+
},
|
|
3264
|
+
fetch: environment.fetch,
|
|
3265
|
+
method: "GET",
|
|
3266
|
+
headers: { ...context.headers }
|
|
3267
|
+
}).pipe((0, rxjs_operators.timeout)({
|
|
3268
|
+
first: REQUEST_TIMEOUT_MS,
|
|
3269
|
+
with: () => {
|
|
3270
|
+
throw new Error("Request timed out");
|
|
3271
|
+
}
|
|
3272
|
+
}), (0, rxjs_operators.map)((data) => threadRestEvents.nextPageSucceeded({
|
|
3273
|
+
sessionId: state.sessionId,
|
|
3274
|
+
threads: data.threads,
|
|
3275
|
+
nextCursor: data.nextCursor ?? null
|
|
3276
|
+
})), (0, rxjs_operators.catchError)((error) => (0, rxjs.of)(threadRestEvents.nextPageFailed({
|
|
3277
|
+
sessionId: state.sessionId,
|
|
3278
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3279
|
+
}))), (0, rxjs_operators.takeUntil)(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))));
|
|
3280
|
+
}))),
|
|
3281
|
+
createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.renameRequested, threadAdapterEvents.archiveRequested, threadAdapterEvents.deleteRequested), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.mergeMap)(([action, state]) => {
|
|
3282
|
+
const context = state.context;
|
|
3283
|
+
if (!context?.runtimeUrl) {
|
|
3284
|
+
const requestId = action.requestId;
|
|
3285
|
+
return (0, rxjs.of)(threadRestEvents.mutationFinished({ outcome: {
|
|
3286
|
+
requestId,
|
|
3287
|
+
ok: false,
|
|
3288
|
+
error: /* @__PURE__ */ new Error("Runtime URL is not configured")
|
|
3289
|
+
} }));
|
|
3290
|
+
}
|
|
3291
|
+
const commonBody = { agentId: context.agentId };
|
|
3292
|
+
if (threadAdapterEvents.renameRequested.match(action)) return createThreadMutationObservable(environment, context, {
|
|
3293
|
+
requestId: action.requestId,
|
|
3294
|
+
method: "PATCH",
|
|
3295
|
+
path: `/threads/${encodeURIComponent(action.threadId)}`,
|
|
3296
|
+
body: {
|
|
3297
|
+
...commonBody,
|
|
3298
|
+
name: action.name
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
if (threadAdapterEvents.archiveRequested.match(action)) return createThreadMutationObservable(environment, context, {
|
|
3302
|
+
requestId: action.requestId,
|
|
3303
|
+
method: "POST",
|
|
3304
|
+
path: `/threads/${encodeURIComponent(action.threadId)}/archive`,
|
|
3305
|
+
body: commonBody
|
|
3306
|
+
});
|
|
3307
|
+
return createThreadMutationObservable(environment, context, {
|
|
3308
|
+
requestId: action.requestId,
|
|
3309
|
+
method: "DELETE",
|
|
3310
|
+
path: `/threads/${encodeURIComponent(action.threadId)}`,
|
|
3311
|
+
body: commonBody
|
|
3312
|
+
});
|
|
3313
|
+
})))
|
|
3314
|
+
]
|
|
3315
|
+
});
|
|
3316
|
+
function trackMutation(dispatchAction) {
|
|
3317
|
+
const resultPromise = (0, rxjs.firstValueFrom)((0, rxjs.merge)(store.actions$.pipe(ofType(threadRestEvents.mutationFinished), (0, rxjs_operators.filter)((action) => action.outcome.requestId === dispatchAction.requestId), (0, rxjs_operators.map)((action) => action.outcome)), store.actions$.pipe(ofType(threadAdapterEvents.stopped), (0, rxjs_operators.map)(() => ({
|
|
3318
|
+
requestId: dispatchAction.requestId,
|
|
3319
|
+
ok: false,
|
|
3320
|
+
error: /* @__PURE__ */ new Error("Thread store stopped before mutation completed")
|
|
3321
|
+
})))).pipe((0, rxjs_operators.take)(1))).then((outcome) => {
|
|
3322
|
+
if (outcome.ok) return;
|
|
3323
|
+
throw outcome.error;
|
|
3324
|
+
});
|
|
3325
|
+
store.dispatch(dispatchAction);
|
|
3326
|
+
return resultPromise;
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
start() {
|
|
3330
|
+
store.init();
|
|
3331
|
+
store.dispatch(threadAdapterEvents.started());
|
|
3332
|
+
},
|
|
3333
|
+
stop() {
|
|
3334
|
+
store.dispatch(threadAdapterEvents.stopped());
|
|
3335
|
+
store.stop();
|
|
3336
|
+
},
|
|
3337
|
+
setContext(context) {
|
|
3338
|
+
store.dispatch(threadAdapterEvents.contextChanged({ context }));
|
|
3339
|
+
},
|
|
3340
|
+
fetchNextPage() {
|
|
3341
|
+
store.dispatch(threadAdapterEvents.fetchNextPageRequested());
|
|
3342
|
+
},
|
|
3343
|
+
renameThread(threadId, name) {
|
|
3344
|
+
return trackMutation(threadAdapterEvents.renameRequested({
|
|
3345
|
+
requestId: createThreadRequestId(),
|
|
3346
|
+
threadId,
|
|
3347
|
+
name
|
|
3348
|
+
}));
|
|
3349
|
+
},
|
|
3350
|
+
archiveThread(threadId) {
|
|
3351
|
+
return trackMutation(threadAdapterEvents.archiveRequested({
|
|
3352
|
+
requestId: createThreadRequestId(),
|
|
3353
|
+
threadId
|
|
3354
|
+
}));
|
|
3355
|
+
},
|
|
3356
|
+
deleteThread(threadId) {
|
|
3357
|
+
return trackMutation(threadAdapterEvents.deleteRequested({
|
|
3358
|
+
requestId: createThreadRequestId(),
|
|
3359
|
+
threadId
|
|
3360
|
+
}));
|
|
3361
|
+
},
|
|
3362
|
+
getState() {
|
|
3363
|
+
return store.getState();
|
|
3364
|
+
},
|
|
3365
|
+
select: store.select.bind(store)
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
const ɵthreadAdapterEvents = threadAdapterEvents;
|
|
3369
|
+
const ɵselectThreads = selectThreads;
|
|
3370
|
+
const ɵselectThreadsIsLoading = selectThreadsIsLoading;
|
|
3371
|
+
const ɵselectThreadsError = selectThreadsError;
|
|
3372
|
+
const ɵselectHasNextPage = selectHasNextPage;
|
|
3373
|
+
const ɵselectIsFetchingNextPage = selectIsFetchingNextPage;
|
|
3374
|
+
|
|
3375
|
+
//#endregion
|
|
3376
|
+
exports.AgentRegistry = AgentRegistry;
|
|
3377
|
+
exports.AgentThreadLockedError = AgentThreadLockedError;
|
|
3378
|
+
exports.ContextStore = ContextStore;
|
|
3379
|
+
exports.CopilotKitCore = CopilotKitCore;
|
|
3380
|
+
exports.CopilotKitCoreErrorCode = CopilotKitCoreErrorCode;
|
|
3381
|
+
exports.CopilotKitCoreRuntimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus;
|
|
3382
|
+
exports.IntelligenceAgent = IntelligenceAgent;
|
|
3383
|
+
exports.ProxiedCopilotRuntimeAgent = ProxiedCopilotRuntimeAgent;
|
|
3384
|
+
exports.RunHandler = RunHandler;
|
|
3385
|
+
exports.StateManager = StateManager;
|
|
3386
|
+
exports.SuggestionEngine = SuggestionEngine;
|
|
3387
|
+
exports.ToolCallStatus = ToolCallStatus;
|
|
3388
|
+
exports.completePartialMarkdown = completePartialMarkdown;
|
|
3389
|
+
exports.createActionGroup = createActionGroup;
|
|
3390
|
+
exports.createEffect = createEffect;
|
|
3391
|
+
exports.createReducer = createReducer;
|
|
3392
|
+
exports.createSelector = createSelector;
|
|
3393
|
+
exports.createStore = createStore;
|
|
3394
|
+
exports.empty = empty;
|
|
3395
|
+
exports.ensureObjectArgs = ensureObjectArgs;
|
|
3396
|
+
exports.ofType = ofType;
|
|
3397
|
+
exports.on = on;
|
|
3398
|
+
exports.parseToolArguments = parseToolArguments;
|
|
3399
|
+
exports.props = props;
|
|
3400
|
+
exports.select = select;
|
|
3401
|
+
exports.ɵcreateThreadStore = createThreadStore;
|
|
3402
|
+
exports.ɵjoinPhoenixChannel$ = ɵjoinPhoenixChannel$;
|
|
3403
|
+
exports.ɵobservePhoenixEvent$ = ɵobservePhoenixEvent$;
|
|
3404
|
+
exports.ɵobservePhoenixJoinOutcome$ = ɵobservePhoenixJoinOutcome$;
|
|
3405
|
+
exports.ɵobservePhoenixSocketHealth$ = ɵobservePhoenixSocketHealth$;
|
|
3406
|
+
exports.ɵobservePhoenixSocketSignals$ = ɵobservePhoenixSocketSignals$;
|
|
3407
|
+
exports.ɵphoenixChannel$ = ɵphoenixChannel$;
|
|
3408
|
+
exports.ɵphoenixSocket$ = ɵphoenixSocket$;
|
|
3409
|
+
exports.ɵselectHasNextPage = ɵselectHasNextPage;
|
|
3410
|
+
exports.ɵselectIsFetchingNextPage = ɵselectIsFetchingNextPage;
|
|
3411
|
+
exports.ɵselectThreads = ɵselectThreads;
|
|
3412
|
+
exports.ɵselectThreadsError = ɵselectThreadsError;
|
|
3413
|
+
exports.ɵselectThreadsIsLoading = ɵselectThreadsIsLoading;
|
|
3414
|
+
exports.ɵthreadAdapterEvents = ɵthreadAdapterEvents;
|
|
3415
|
+
//# sourceMappingURL=index.cjs.map
|