@copilotkitnext/runtime 0.0.1
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/.cursor/rules/runtime.always.mdc +9 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-check-types.log +4 -0
- package/.turbo/turbo-lint.log +56 -0
- package/.turbo/turbo-test$colon$coverage.log +149 -0
- package/.turbo/turbo-test.log +107 -0
- package/LICENSE +11 -0
- package/README-RUNNERS.md +78 -0
- package/dist/index.d.mts +245 -0
- package/dist/index.d.ts +245 -0
- package/dist/index.js +1873 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1841 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.mjs +3 -0
- package/package.json +62 -0
- package/src/__tests__/get-runtime-info.test.ts +117 -0
- package/src/__tests__/handle-run.test.ts +69 -0
- package/src/__tests__/handle-transcribe.test.ts +289 -0
- package/src/__tests__/in-process-agent-runner-messages.test.ts +599 -0
- package/src/__tests__/in-process-agent-runner.test.ts +726 -0
- package/src/__tests__/middleware.test.ts +432 -0
- package/src/__tests__/routing.test.ts +257 -0
- package/src/endpoint.ts +150 -0
- package/src/handler.ts +3 -0
- package/src/handlers/get-runtime-info.ts +50 -0
- package/src/handlers/handle-connect.ts +144 -0
- package/src/handlers/handle-run.ts +156 -0
- package/src/handlers/handle-transcribe.ts +126 -0
- package/src/index.ts +8 -0
- package/src/middleware.ts +232 -0
- package/src/runner/__tests__/enterprise-runner.test.ts +992 -0
- package/src/runner/__tests__/event-compaction.test.ts +253 -0
- package/src/runner/__tests__/in-memory-runner.test.ts +483 -0
- package/src/runner/__tests__/sqlite-runner.test.ts +975 -0
- package/src/runner/agent-runner.ts +27 -0
- package/src/runner/enterprise.ts +653 -0
- package/src/runner/event-compaction.ts +250 -0
- package/src/runner/in-memory.ts +322 -0
- package/src/runner/index.ts +0 -0
- package/src/runner/sqlite.ts +481 -0
- package/src/runtime.ts +53 -0
- package/src/transcription-service/transcription-service-openai.ts +29 -0
- package/src/transcription-service/transcription-service.ts +11 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +11 -0
- package/vitest.config.mjs +15 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1841 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "@copilotkitnext/runtime",
|
|
4
|
+
version: "0.0.1",
|
|
5
|
+
description: "Server-side runtime package for CopilotKit2",
|
|
6
|
+
main: "dist/index.js",
|
|
7
|
+
types: "dist/index.d.ts",
|
|
8
|
+
exports: {
|
|
9
|
+
".": {
|
|
10
|
+
types: "./dist/index.d.ts",
|
|
11
|
+
import: "./dist/index.mjs",
|
|
12
|
+
require: "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
publishConfig: {
|
|
16
|
+
access: "public"
|
|
17
|
+
},
|
|
18
|
+
scripts: {
|
|
19
|
+
build: "tsup",
|
|
20
|
+
prepublishOnly: "pnpm run build",
|
|
21
|
+
dev: "tsup --watch",
|
|
22
|
+
lint: "eslint . --max-warnings 0",
|
|
23
|
+
"check-types": "tsc --noEmit",
|
|
24
|
+
clean: "rm -rf dist",
|
|
25
|
+
test: "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"test:coverage": "vitest run --coverage"
|
|
28
|
+
},
|
|
29
|
+
devDependencies: {
|
|
30
|
+
"@copilotkitnext/eslint-config": "workspace:*",
|
|
31
|
+
"@copilotkitnext/typescript-config": "workspace:*",
|
|
32
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
33
|
+
"@types/node": "^22.15.3",
|
|
34
|
+
"better-sqlite3": "^12.2.0",
|
|
35
|
+
eslint: "^9.30.0",
|
|
36
|
+
"ioredis-mock": "^8.9.0",
|
|
37
|
+
openai: "^5.9.0",
|
|
38
|
+
tsup: "^8.5.0",
|
|
39
|
+
typescript: "5.8.2",
|
|
40
|
+
vitest: "^3.0.5"
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
"@ag-ui/client": "0.0.36-alpha.1",
|
|
44
|
+
"@ag-ui/core": "0.0.36-alpha.1",
|
|
45
|
+
"@ag-ui/encoder": "0.0.36-alpha.1",
|
|
46
|
+
"@copilotkitnext/shared": "workspace:*",
|
|
47
|
+
hono: "^4.6.13",
|
|
48
|
+
ioredis: "^5.7.0",
|
|
49
|
+
kysely: "^0.28.5",
|
|
50
|
+
rxjs: "7.8.1"
|
|
51
|
+
},
|
|
52
|
+
peerDependencies: {
|
|
53
|
+
"better-sqlite3": "^12.2.0",
|
|
54
|
+
openai: "^5.9.0"
|
|
55
|
+
},
|
|
56
|
+
peerDependenciesMeta: {
|
|
57
|
+
"better-sqlite3": {
|
|
58
|
+
optional: true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
engines: {
|
|
62
|
+
node: ">=18"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/runner/agent-runner.ts
|
|
67
|
+
var AgentRunner = class {
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/runner/in-memory.ts
|
|
71
|
+
import { ReplaySubject } from "rxjs";
|
|
72
|
+
import {
|
|
73
|
+
EventType as EventType2
|
|
74
|
+
} from "@ag-ui/client";
|
|
75
|
+
|
|
76
|
+
// src/runner/event-compaction.ts
|
|
77
|
+
import {
|
|
78
|
+
EventType
|
|
79
|
+
} from "@ag-ui/client";
|
|
80
|
+
function compactEvents(events) {
|
|
81
|
+
const compacted = [];
|
|
82
|
+
const pendingTextMessages = /* @__PURE__ */ new Map();
|
|
83
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const event of events) {
|
|
85
|
+
if (event.type === EventType.TEXT_MESSAGE_START) {
|
|
86
|
+
const startEvent = event;
|
|
87
|
+
const messageId = startEvent.messageId;
|
|
88
|
+
if (!pendingTextMessages.has(messageId)) {
|
|
89
|
+
pendingTextMessages.set(messageId, {
|
|
90
|
+
contents: [],
|
|
91
|
+
otherEvents: []
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const pending = pendingTextMessages.get(messageId);
|
|
95
|
+
pending.start = startEvent;
|
|
96
|
+
} else if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
97
|
+
const contentEvent = event;
|
|
98
|
+
const messageId = contentEvent.messageId;
|
|
99
|
+
if (!pendingTextMessages.has(messageId)) {
|
|
100
|
+
pendingTextMessages.set(messageId, {
|
|
101
|
+
contents: [],
|
|
102
|
+
otherEvents: []
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const pending = pendingTextMessages.get(messageId);
|
|
106
|
+
pending.contents.push(contentEvent);
|
|
107
|
+
} else if (event.type === EventType.TEXT_MESSAGE_END) {
|
|
108
|
+
const endEvent = event;
|
|
109
|
+
const messageId = endEvent.messageId;
|
|
110
|
+
if (!pendingTextMessages.has(messageId)) {
|
|
111
|
+
pendingTextMessages.set(messageId, {
|
|
112
|
+
contents: [],
|
|
113
|
+
otherEvents: []
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const pending = pendingTextMessages.get(messageId);
|
|
117
|
+
pending.end = endEvent;
|
|
118
|
+
flushTextMessage(messageId, pending, compacted);
|
|
119
|
+
pendingTextMessages.delete(messageId);
|
|
120
|
+
} else if (event.type === EventType.TOOL_CALL_START) {
|
|
121
|
+
const startEvent = event;
|
|
122
|
+
const toolCallId = startEvent.toolCallId;
|
|
123
|
+
if (!pendingToolCalls.has(toolCallId)) {
|
|
124
|
+
pendingToolCalls.set(toolCallId, {
|
|
125
|
+
args: [],
|
|
126
|
+
otherEvents: []
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const pending = pendingToolCalls.get(toolCallId);
|
|
130
|
+
pending.start = startEvent;
|
|
131
|
+
} else if (event.type === EventType.TOOL_CALL_ARGS) {
|
|
132
|
+
const argsEvent = event;
|
|
133
|
+
const toolCallId = argsEvent.toolCallId;
|
|
134
|
+
if (!pendingToolCalls.has(toolCallId)) {
|
|
135
|
+
pendingToolCalls.set(toolCallId, {
|
|
136
|
+
args: [],
|
|
137
|
+
otherEvents: []
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const pending = pendingToolCalls.get(toolCallId);
|
|
141
|
+
pending.args.push(argsEvent);
|
|
142
|
+
} else if (event.type === EventType.TOOL_CALL_END) {
|
|
143
|
+
const endEvent = event;
|
|
144
|
+
const toolCallId = endEvent.toolCallId;
|
|
145
|
+
if (!pendingToolCalls.has(toolCallId)) {
|
|
146
|
+
pendingToolCalls.set(toolCallId, {
|
|
147
|
+
args: [],
|
|
148
|
+
otherEvents: []
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const pending = pendingToolCalls.get(toolCallId);
|
|
152
|
+
pending.end = endEvent;
|
|
153
|
+
flushToolCall(toolCallId, pending, compacted);
|
|
154
|
+
pendingToolCalls.delete(toolCallId);
|
|
155
|
+
} else {
|
|
156
|
+
let addedToBuffer = false;
|
|
157
|
+
for (const [messageId, pending] of pendingTextMessages) {
|
|
158
|
+
if (pending.start && !pending.end) {
|
|
159
|
+
pending.otherEvents.push(event);
|
|
160
|
+
addedToBuffer = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!addedToBuffer) {
|
|
165
|
+
for (const [toolCallId, pending] of pendingToolCalls) {
|
|
166
|
+
if (pending.start && !pending.end) {
|
|
167
|
+
pending.otherEvents.push(event);
|
|
168
|
+
addedToBuffer = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!addedToBuffer) {
|
|
174
|
+
compacted.push(event);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [messageId, pending] of pendingTextMessages) {
|
|
179
|
+
flushTextMessage(messageId, pending, compacted);
|
|
180
|
+
}
|
|
181
|
+
for (const [toolCallId, pending] of pendingToolCalls) {
|
|
182
|
+
flushToolCall(toolCallId, pending, compacted);
|
|
183
|
+
}
|
|
184
|
+
return compacted;
|
|
185
|
+
}
|
|
186
|
+
function flushTextMessage(messageId, pending, compacted) {
|
|
187
|
+
if (pending.start) {
|
|
188
|
+
compacted.push(pending.start);
|
|
189
|
+
}
|
|
190
|
+
if (pending.contents.length > 0) {
|
|
191
|
+
const concatenatedDelta = pending.contents.map((c) => c.delta).join("");
|
|
192
|
+
const compactedContent = {
|
|
193
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
194
|
+
messageId,
|
|
195
|
+
delta: concatenatedDelta
|
|
196
|
+
};
|
|
197
|
+
compacted.push(compactedContent);
|
|
198
|
+
}
|
|
199
|
+
if (pending.end) {
|
|
200
|
+
compacted.push(pending.end);
|
|
201
|
+
}
|
|
202
|
+
for (const otherEvent of pending.otherEvents) {
|
|
203
|
+
compacted.push(otherEvent);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function flushToolCall(toolCallId, pending, compacted) {
|
|
207
|
+
if (pending.start) {
|
|
208
|
+
compacted.push(pending.start);
|
|
209
|
+
}
|
|
210
|
+
if (pending.args.length > 0) {
|
|
211
|
+
const concatenatedArgs = pending.args.map((a) => a.delta).join("");
|
|
212
|
+
const compactedArgs = {
|
|
213
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
214
|
+
toolCallId,
|
|
215
|
+
delta: concatenatedArgs
|
|
216
|
+
};
|
|
217
|
+
compacted.push(compactedArgs);
|
|
218
|
+
}
|
|
219
|
+
if (pending.end) {
|
|
220
|
+
compacted.push(pending.end);
|
|
221
|
+
}
|
|
222
|
+
for (const otherEvent of pending.otherEvents) {
|
|
223
|
+
compacted.push(otherEvent);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/runner/in-memory.ts
|
|
228
|
+
var InMemoryEventStore = class {
|
|
229
|
+
constructor(threadId) {
|
|
230
|
+
this.threadId = threadId;
|
|
231
|
+
}
|
|
232
|
+
/** The subject that current consumers subscribe to. */
|
|
233
|
+
subject = null;
|
|
234
|
+
/** True while a run is actively producing events. */
|
|
235
|
+
isRunning = false;
|
|
236
|
+
/** Lets stop() cancel the current producer. */
|
|
237
|
+
abortController = new AbortController();
|
|
238
|
+
/** Current run ID */
|
|
239
|
+
currentRunId = null;
|
|
240
|
+
/** Historic completed runs */
|
|
241
|
+
historicRuns = [];
|
|
242
|
+
};
|
|
243
|
+
var GLOBAL_STORE = /* @__PURE__ */ new Map();
|
|
244
|
+
var InMemoryAgentRunner = class extends AgentRunner {
|
|
245
|
+
convertMessageToEvents(message) {
|
|
246
|
+
const events = [];
|
|
247
|
+
if ((message.role === "assistant" || message.role === "user" || message.role === "developer" || message.role === "system") && message.content) {
|
|
248
|
+
const textStartEvent = {
|
|
249
|
+
type: EventType2.TEXT_MESSAGE_START,
|
|
250
|
+
messageId: message.id,
|
|
251
|
+
role: message.role
|
|
252
|
+
};
|
|
253
|
+
events.push(textStartEvent);
|
|
254
|
+
const textContentEvent = {
|
|
255
|
+
type: EventType2.TEXT_MESSAGE_CONTENT,
|
|
256
|
+
messageId: message.id,
|
|
257
|
+
delta: message.content
|
|
258
|
+
};
|
|
259
|
+
events.push(textContentEvent);
|
|
260
|
+
const textEndEvent = {
|
|
261
|
+
type: EventType2.TEXT_MESSAGE_END,
|
|
262
|
+
messageId: message.id
|
|
263
|
+
};
|
|
264
|
+
events.push(textEndEvent);
|
|
265
|
+
}
|
|
266
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
267
|
+
for (const toolCall of message.toolCalls) {
|
|
268
|
+
const toolStartEvent = {
|
|
269
|
+
type: EventType2.TOOL_CALL_START,
|
|
270
|
+
toolCallId: toolCall.id,
|
|
271
|
+
toolCallName: toolCall.function.name,
|
|
272
|
+
parentMessageId: message.id
|
|
273
|
+
};
|
|
274
|
+
events.push(toolStartEvent);
|
|
275
|
+
const toolArgsEvent = {
|
|
276
|
+
type: EventType2.TOOL_CALL_ARGS,
|
|
277
|
+
toolCallId: toolCall.id,
|
|
278
|
+
delta: toolCall.function.arguments
|
|
279
|
+
};
|
|
280
|
+
events.push(toolArgsEvent);
|
|
281
|
+
const toolEndEvent = {
|
|
282
|
+
type: EventType2.TOOL_CALL_END,
|
|
283
|
+
toolCallId: toolCall.id
|
|
284
|
+
};
|
|
285
|
+
events.push(toolEndEvent);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (message.role === "tool" && message.toolCallId) {
|
|
289
|
+
const toolResultEvent = {
|
|
290
|
+
type: EventType2.TOOL_CALL_RESULT,
|
|
291
|
+
messageId: message.id,
|
|
292
|
+
toolCallId: message.toolCallId,
|
|
293
|
+
content: message.content,
|
|
294
|
+
role: "tool"
|
|
295
|
+
};
|
|
296
|
+
events.push(toolResultEvent);
|
|
297
|
+
}
|
|
298
|
+
return events;
|
|
299
|
+
}
|
|
300
|
+
run(request) {
|
|
301
|
+
let existingStore = GLOBAL_STORE.get(request.threadId);
|
|
302
|
+
if (!existingStore) {
|
|
303
|
+
existingStore = new InMemoryEventStore(request.threadId);
|
|
304
|
+
GLOBAL_STORE.set(request.threadId, existingStore);
|
|
305
|
+
}
|
|
306
|
+
const store = existingStore;
|
|
307
|
+
if (store.isRunning) {
|
|
308
|
+
throw new Error("Thread already running");
|
|
309
|
+
}
|
|
310
|
+
store.isRunning = true;
|
|
311
|
+
store.currentRunId = request.input.runId;
|
|
312
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
313
|
+
const currentRunEvents = [];
|
|
314
|
+
const historicMessageIds = /* @__PURE__ */ new Set();
|
|
315
|
+
for (const run of store.historicRuns) {
|
|
316
|
+
for (const event of run.events) {
|
|
317
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
318
|
+
historicMessageIds.add(event.messageId);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const nextSubject = new ReplaySubject(Infinity);
|
|
323
|
+
const prevSubject = store.subject;
|
|
324
|
+
store.subject = nextSubject;
|
|
325
|
+
store.abortController = new AbortController();
|
|
326
|
+
const runSubject = new ReplaySubject(Infinity);
|
|
327
|
+
const runAgent = async () => {
|
|
328
|
+
const lastRun = store.historicRuns[store.historicRuns.length - 1];
|
|
329
|
+
const parentRunId = lastRun?.runId ?? null;
|
|
330
|
+
try {
|
|
331
|
+
await request.agent.runAgent(request.input, {
|
|
332
|
+
onEvent: ({ event }) => {
|
|
333
|
+
runSubject.next(event);
|
|
334
|
+
nextSubject.next(event);
|
|
335
|
+
currentRunEvents.push(event);
|
|
336
|
+
},
|
|
337
|
+
onNewMessage: ({ message }) => {
|
|
338
|
+
if (!seenMessageIds.has(message.id)) {
|
|
339
|
+
seenMessageIds.add(message.id);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
onRunStartedEvent: () => {
|
|
343
|
+
if (request.input.messages) {
|
|
344
|
+
for (const message of request.input.messages) {
|
|
345
|
+
if (!seenMessageIds.has(message.id)) {
|
|
346
|
+
seenMessageIds.add(message.id);
|
|
347
|
+
const events = this.convertMessageToEvents(message);
|
|
348
|
+
const isNewMessage = !historicMessageIds.has(message.id);
|
|
349
|
+
for (const event of events) {
|
|
350
|
+
nextSubject.next(event);
|
|
351
|
+
if (isNewMessage) {
|
|
352
|
+
currentRunEvents.push(event);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
if (store.currentRunId) {
|
|
361
|
+
const compactedEvents = compactEvents(currentRunEvents);
|
|
362
|
+
store.historicRuns.push({
|
|
363
|
+
threadId: request.threadId,
|
|
364
|
+
runId: store.currentRunId,
|
|
365
|
+
parentRunId,
|
|
366
|
+
events: compactedEvents,
|
|
367
|
+
createdAt: Date.now()
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
store.isRunning = false;
|
|
371
|
+
store.currentRunId = null;
|
|
372
|
+
runSubject.complete();
|
|
373
|
+
nextSubject.complete();
|
|
374
|
+
} catch {
|
|
375
|
+
if (store.currentRunId && currentRunEvents.length > 0) {
|
|
376
|
+
const compactedEvents = compactEvents(currentRunEvents);
|
|
377
|
+
store.historicRuns.push({
|
|
378
|
+
threadId: request.threadId,
|
|
379
|
+
runId: store.currentRunId,
|
|
380
|
+
parentRunId,
|
|
381
|
+
events: compactedEvents,
|
|
382
|
+
createdAt: Date.now()
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
store.isRunning = false;
|
|
386
|
+
store.currentRunId = null;
|
|
387
|
+
runSubject.complete();
|
|
388
|
+
nextSubject.complete();
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
if (prevSubject) {
|
|
392
|
+
prevSubject.subscribe({
|
|
393
|
+
next: (e) => nextSubject.next(e),
|
|
394
|
+
error: (err) => nextSubject.error(err),
|
|
395
|
+
complete: () => {
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
runAgent();
|
|
400
|
+
return runSubject.asObservable();
|
|
401
|
+
}
|
|
402
|
+
connect(request) {
|
|
403
|
+
const store = GLOBAL_STORE.get(request.threadId);
|
|
404
|
+
const connectionSubject = new ReplaySubject(Infinity);
|
|
405
|
+
if (!store) {
|
|
406
|
+
connectionSubject.complete();
|
|
407
|
+
return connectionSubject.asObservable();
|
|
408
|
+
}
|
|
409
|
+
const allHistoricEvents = [];
|
|
410
|
+
for (const run of store.historicRuns) {
|
|
411
|
+
allHistoricEvents.push(...run.events);
|
|
412
|
+
}
|
|
413
|
+
const compactedEvents = compactEvents(allHistoricEvents);
|
|
414
|
+
const emittedMessageIds = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const event of compactedEvents) {
|
|
416
|
+
connectionSubject.next(event);
|
|
417
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
418
|
+
emittedMessageIds.add(event.messageId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (store.subject && store.isRunning) {
|
|
422
|
+
store.subject.subscribe({
|
|
423
|
+
next: (event) => {
|
|
424
|
+
if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
connectionSubject.next(event);
|
|
428
|
+
},
|
|
429
|
+
complete: () => connectionSubject.complete(),
|
|
430
|
+
error: (err) => connectionSubject.error(err)
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
connectionSubject.complete();
|
|
434
|
+
}
|
|
435
|
+
return connectionSubject.asObservable();
|
|
436
|
+
}
|
|
437
|
+
isRunning(request) {
|
|
438
|
+
const store = GLOBAL_STORE.get(request.threadId);
|
|
439
|
+
return Promise.resolve(store?.isRunning ?? false);
|
|
440
|
+
}
|
|
441
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
442
|
+
stop(_request) {
|
|
443
|
+
throw new Error("Method not implemented.");
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/runtime.ts
|
|
448
|
+
var VERSION = package_default.version;
|
|
449
|
+
var CopilotRuntime = class {
|
|
450
|
+
agents;
|
|
451
|
+
transcriptionService;
|
|
452
|
+
beforeRequestMiddleware;
|
|
453
|
+
afterRequestMiddleware;
|
|
454
|
+
runner;
|
|
455
|
+
constructor({
|
|
456
|
+
agents,
|
|
457
|
+
transcriptionService,
|
|
458
|
+
beforeRequestMiddleware,
|
|
459
|
+
afterRequestMiddleware,
|
|
460
|
+
runner
|
|
461
|
+
}) {
|
|
462
|
+
this.agents = agents;
|
|
463
|
+
this.transcriptionService = transcriptionService;
|
|
464
|
+
this.beforeRequestMiddleware = beforeRequestMiddleware;
|
|
465
|
+
this.afterRequestMiddleware = afterRequestMiddleware;
|
|
466
|
+
this.runner = runner ?? new InMemoryAgentRunner();
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/endpoint.ts
|
|
471
|
+
import { Hono } from "hono";
|
|
472
|
+
|
|
473
|
+
// src/handlers/handle-run.ts
|
|
474
|
+
import {
|
|
475
|
+
RunAgentInputSchema
|
|
476
|
+
} from "@ag-ui/client";
|
|
477
|
+
import { EventEncoder } from "@ag-ui/encoder";
|
|
478
|
+
async function handleRunAgent({
|
|
479
|
+
runtime,
|
|
480
|
+
request,
|
|
481
|
+
agentId
|
|
482
|
+
}) {
|
|
483
|
+
try {
|
|
484
|
+
const agents = await runtime.agents;
|
|
485
|
+
if (!agents[agentId]) {
|
|
486
|
+
return new Response(
|
|
487
|
+
JSON.stringify({
|
|
488
|
+
error: "Agent not found",
|
|
489
|
+
message: `Agent '${agentId}' does not exist`
|
|
490
|
+
}),
|
|
491
|
+
{
|
|
492
|
+
status: 404,
|
|
493
|
+
headers: { "Content-Type": "application/json" }
|
|
494
|
+
}
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const agent = agents[agentId].clone();
|
|
498
|
+
const stream = new TransformStream();
|
|
499
|
+
const writer = stream.writable.getWriter();
|
|
500
|
+
const encoder = new EventEncoder();
|
|
501
|
+
let streamClosed = false;
|
|
502
|
+
(async () => {
|
|
503
|
+
let input;
|
|
504
|
+
try {
|
|
505
|
+
const requestBody = await request.json();
|
|
506
|
+
input = RunAgentInputSchema.parse(requestBody);
|
|
507
|
+
} catch {
|
|
508
|
+
return new Response(
|
|
509
|
+
JSON.stringify({
|
|
510
|
+
error: "Invalid request body"
|
|
511
|
+
}),
|
|
512
|
+
{ status: 400 }
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
agent.setMessages(input.messages);
|
|
516
|
+
agent.setState(input.state);
|
|
517
|
+
agent.threadId = input.threadId;
|
|
518
|
+
runtime.runner.run({
|
|
519
|
+
threadId: input.threadId,
|
|
520
|
+
agent,
|
|
521
|
+
input
|
|
522
|
+
}).subscribe({
|
|
523
|
+
next: async (event) => {
|
|
524
|
+
if (!request.signal.aborted && !streamClosed) {
|
|
525
|
+
try {
|
|
526
|
+
await writer.write(encoder.encode(event));
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
529
|
+
streamClosed = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
error: async (error) => {
|
|
535
|
+
console.error("Error running agent:", error);
|
|
536
|
+
if (!streamClosed) {
|
|
537
|
+
try {
|
|
538
|
+
await writer.close();
|
|
539
|
+
streamClosed = true;
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
complete: async () => {
|
|
545
|
+
if (!streamClosed) {
|
|
546
|
+
try {
|
|
547
|
+
await writer.close();
|
|
548
|
+
streamClosed = true;
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
})().catch((error) => {
|
|
555
|
+
console.error("Error running agent:", error);
|
|
556
|
+
console.error(
|
|
557
|
+
"Error stack:",
|
|
558
|
+
error instanceof Error ? error.stack : "No stack trace"
|
|
559
|
+
);
|
|
560
|
+
console.error("Error details:", {
|
|
561
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
562
|
+
message: error instanceof Error ? error.message : String(error),
|
|
563
|
+
cause: error instanceof Error ? error.cause : void 0
|
|
564
|
+
});
|
|
565
|
+
if (!streamClosed) {
|
|
566
|
+
try {
|
|
567
|
+
writer.close();
|
|
568
|
+
streamClosed = true;
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
return new Response(stream.readable, {
|
|
574
|
+
status: 200,
|
|
575
|
+
headers: {
|
|
576
|
+
"Content-Type": "text/event-stream",
|
|
577
|
+
"Cache-Control": "no-cache",
|
|
578
|
+
Connection: "keep-alive"
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
} catch (error) {
|
|
582
|
+
console.error("Error running agent:", error);
|
|
583
|
+
console.error(
|
|
584
|
+
"Error stack:",
|
|
585
|
+
error instanceof Error ? error.stack : "No stack trace"
|
|
586
|
+
);
|
|
587
|
+
console.error("Error details:", {
|
|
588
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
589
|
+
message: error instanceof Error ? error.message : String(error),
|
|
590
|
+
cause: error instanceof Error ? error.cause : void 0
|
|
591
|
+
});
|
|
592
|
+
return new Response(
|
|
593
|
+
JSON.stringify({
|
|
594
|
+
error: "Failed to run agent",
|
|
595
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
596
|
+
}),
|
|
597
|
+
{
|
|
598
|
+
status: 500,
|
|
599
|
+
headers: { "Content-Type": "application/json" }
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/handlers/get-runtime-info.ts
|
|
606
|
+
async function handleGetRuntimeInfo({
|
|
607
|
+
runtime
|
|
608
|
+
}) {
|
|
609
|
+
try {
|
|
610
|
+
const agents = await runtime.agents;
|
|
611
|
+
const agentsDict = Object.entries(agents).reduce(
|
|
612
|
+
(acc, [name, agent]) => {
|
|
613
|
+
acc[name] = {
|
|
614
|
+
name,
|
|
615
|
+
description: agent.description,
|
|
616
|
+
className: agent.constructor.name
|
|
617
|
+
};
|
|
618
|
+
return acc;
|
|
619
|
+
},
|
|
620
|
+
{}
|
|
621
|
+
);
|
|
622
|
+
const runtimeInfo = {
|
|
623
|
+
version: VERSION,
|
|
624
|
+
agents: agentsDict,
|
|
625
|
+
audioFileTranscriptionEnabled: !!runtime.transcriptionService
|
|
626
|
+
};
|
|
627
|
+
return new Response(JSON.stringify(runtimeInfo), {
|
|
628
|
+
status: 200,
|
|
629
|
+
headers: { "Content-Type": "application/json" }
|
|
630
|
+
});
|
|
631
|
+
} catch (error) {
|
|
632
|
+
return new Response(
|
|
633
|
+
JSON.stringify({
|
|
634
|
+
error: "Failed to retrieve runtime information",
|
|
635
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
636
|
+
}),
|
|
637
|
+
{
|
|
638
|
+
status: 500,
|
|
639
|
+
headers: { "Content-Type": "application/json" }
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/handlers/handle-transcribe.ts
|
|
646
|
+
async function handleTranscribe({
|
|
647
|
+
runtime,
|
|
648
|
+
request
|
|
649
|
+
}) {
|
|
650
|
+
try {
|
|
651
|
+
if (!runtime.transcriptionService) {
|
|
652
|
+
return new Response(
|
|
653
|
+
JSON.stringify({
|
|
654
|
+
error: "Transcription service not configured",
|
|
655
|
+
message: "No transcription service has been configured in the runtime"
|
|
656
|
+
}),
|
|
657
|
+
{
|
|
658
|
+
status: 503,
|
|
659
|
+
headers: { "Content-Type": "application/json" }
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
const contentType = request.headers.get("content-type");
|
|
664
|
+
if (!contentType || !contentType.includes("multipart/form-data")) {
|
|
665
|
+
return new Response(
|
|
666
|
+
JSON.stringify({
|
|
667
|
+
error: "Invalid content type",
|
|
668
|
+
message: "Request must contain multipart/form-data with an audio file"
|
|
669
|
+
}),
|
|
670
|
+
{
|
|
671
|
+
status: 400,
|
|
672
|
+
headers: { "Content-Type": "application/json" }
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
const formData = await request.formData();
|
|
677
|
+
const audioFile = formData.get("audio");
|
|
678
|
+
if (!audioFile || !(audioFile instanceof File)) {
|
|
679
|
+
return new Response(
|
|
680
|
+
JSON.stringify({
|
|
681
|
+
error: "Missing audio file",
|
|
682
|
+
message: "No audio file found in form data. Please include an 'audio' field."
|
|
683
|
+
}),
|
|
684
|
+
{
|
|
685
|
+
status: 400,
|
|
686
|
+
headers: { "Content-Type": "application/json" }
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const validAudioTypes = [
|
|
691
|
+
"audio/mpeg",
|
|
692
|
+
"audio/mp3",
|
|
693
|
+
"audio/mp4",
|
|
694
|
+
"audio/wav",
|
|
695
|
+
"audio/webm",
|
|
696
|
+
"audio/ogg",
|
|
697
|
+
"audio/flac",
|
|
698
|
+
"audio/aac"
|
|
699
|
+
];
|
|
700
|
+
const isValidType = validAudioTypes.includes(audioFile.type) || audioFile.type === "" || audioFile.type === "application/octet-stream";
|
|
701
|
+
if (!isValidType) {
|
|
702
|
+
return new Response(
|
|
703
|
+
JSON.stringify({
|
|
704
|
+
error: "Invalid file type",
|
|
705
|
+
message: `Unsupported audio file type: ${audioFile.type}. Supported types: ${validAudioTypes.join(", ")}, or files with unknown/empty types`
|
|
706
|
+
}),
|
|
707
|
+
{
|
|
708
|
+
status: 400,
|
|
709
|
+
headers: { "Content-Type": "application/json" }
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const transcription = await runtime.transcriptionService.transcribeFile({
|
|
714
|
+
audioFile,
|
|
715
|
+
mimeType: audioFile.type,
|
|
716
|
+
size: audioFile.size
|
|
717
|
+
});
|
|
718
|
+
return new Response(
|
|
719
|
+
JSON.stringify({
|
|
720
|
+
text: transcription,
|
|
721
|
+
size: audioFile.size,
|
|
722
|
+
type: audioFile.type
|
|
723
|
+
}),
|
|
724
|
+
{
|
|
725
|
+
status: 200,
|
|
726
|
+
headers: { "Content-Type": "application/json" }
|
|
727
|
+
}
|
|
728
|
+
);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return new Response(
|
|
731
|
+
JSON.stringify({
|
|
732
|
+
error: "Transcription failed",
|
|
733
|
+
message: error instanceof Error ? error.message : "Unknown error occurred during transcription"
|
|
734
|
+
}),
|
|
735
|
+
{
|
|
736
|
+
status: 500,
|
|
737
|
+
headers: { "Content-Type": "application/json" }
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/endpoint.ts
|
|
744
|
+
import { logger as logger2 } from "@copilotkitnext/shared";
|
|
745
|
+
|
|
746
|
+
// src/middleware.ts
|
|
747
|
+
import { logger } from "@copilotkitnext/shared";
|
|
748
|
+
function isMiddlewareURL(value) {
|
|
749
|
+
return typeof value === "string" && /^https?:\/\//.test(value);
|
|
750
|
+
}
|
|
751
|
+
async function callBeforeRequestMiddleware({
|
|
752
|
+
runtime,
|
|
753
|
+
request,
|
|
754
|
+
path
|
|
755
|
+
}) {
|
|
756
|
+
const mw = runtime.beforeRequestMiddleware;
|
|
757
|
+
if (!mw) return;
|
|
758
|
+
if (typeof mw === "function") {
|
|
759
|
+
return mw({ runtime, request, path });
|
|
760
|
+
}
|
|
761
|
+
if (isMiddlewareURL(mw)) {
|
|
762
|
+
const clone = request.clone();
|
|
763
|
+
const url = new URL(request.url);
|
|
764
|
+
const headersObj = {};
|
|
765
|
+
clone.headers.forEach((v, k) => {
|
|
766
|
+
headersObj[k] = v;
|
|
767
|
+
});
|
|
768
|
+
let bodyJson = void 0;
|
|
769
|
+
try {
|
|
770
|
+
bodyJson = await clone.json();
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
const payload = {
|
|
774
|
+
method: request.method,
|
|
775
|
+
path: url.pathname,
|
|
776
|
+
query: url.search.startsWith("?") ? url.search.slice(1) : url.search,
|
|
777
|
+
headers: headersObj,
|
|
778
|
+
body: bodyJson
|
|
779
|
+
};
|
|
780
|
+
const ac = new AbortController();
|
|
781
|
+
const to = setTimeout(() => ac.abort(), 2e3);
|
|
782
|
+
let res;
|
|
783
|
+
try {
|
|
784
|
+
res = await fetch(mw, {
|
|
785
|
+
method: "POST",
|
|
786
|
+
headers: {
|
|
787
|
+
"content-type": "application/json",
|
|
788
|
+
"X-CopilotKit-Webhook-Stage": "before_request" /* BeforeRequest */
|
|
789
|
+
},
|
|
790
|
+
body: JSON.stringify(payload),
|
|
791
|
+
signal: ac.signal
|
|
792
|
+
});
|
|
793
|
+
} catch {
|
|
794
|
+
clearTimeout(to);
|
|
795
|
+
throw new Response(void 0, { status: 502 });
|
|
796
|
+
}
|
|
797
|
+
clearTimeout(to);
|
|
798
|
+
if (res.status >= 500) {
|
|
799
|
+
throw new Response(void 0, { status: 502 });
|
|
800
|
+
}
|
|
801
|
+
if (res.status >= 400) {
|
|
802
|
+
const errBody = await res.text();
|
|
803
|
+
throw new Response(errBody || null, {
|
|
804
|
+
status: res.status,
|
|
805
|
+
headers: {
|
|
806
|
+
"content-type": res.headers.get("content-type") || "application/json"
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (res.status === 204) return;
|
|
811
|
+
let json;
|
|
812
|
+
try {
|
|
813
|
+
json = await res.json();
|
|
814
|
+
} catch {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (json && typeof json === "object") {
|
|
818
|
+
const { headers, body } = json;
|
|
819
|
+
const init = {
|
|
820
|
+
method: request.method
|
|
821
|
+
};
|
|
822
|
+
if (headers) {
|
|
823
|
+
init.headers = headers;
|
|
824
|
+
}
|
|
825
|
+
if (body !== void 0 && request.method !== "GET" && request.method !== "HEAD") {
|
|
826
|
+
init.body = JSON.stringify(body);
|
|
827
|
+
}
|
|
828
|
+
return new Request(request.url, init);
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
logger.warn({ mw }, "Unsupported beforeRequestMiddleware value \u2013 skipped");
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
async function callAfterRequestMiddleware({
|
|
836
|
+
runtime,
|
|
837
|
+
response,
|
|
838
|
+
path
|
|
839
|
+
}) {
|
|
840
|
+
const mw = runtime.afterRequestMiddleware;
|
|
841
|
+
if (!mw) return;
|
|
842
|
+
if (typeof mw === "function") {
|
|
843
|
+
return mw({ runtime, response, path });
|
|
844
|
+
}
|
|
845
|
+
if (isMiddlewareURL(mw)) {
|
|
846
|
+
const clone = response.clone();
|
|
847
|
+
const headersObj = {};
|
|
848
|
+
clone.headers.forEach((v, k) => {
|
|
849
|
+
headersObj[k] = v;
|
|
850
|
+
});
|
|
851
|
+
let body = "";
|
|
852
|
+
try {
|
|
853
|
+
body = await clone.text();
|
|
854
|
+
} catch {
|
|
855
|
+
}
|
|
856
|
+
const payload = {
|
|
857
|
+
status: clone.status,
|
|
858
|
+
headers: headersObj,
|
|
859
|
+
body
|
|
860
|
+
};
|
|
861
|
+
const ac = new AbortController();
|
|
862
|
+
const to = setTimeout(() => ac.abort(), 2e3);
|
|
863
|
+
let res;
|
|
864
|
+
try {
|
|
865
|
+
res = await fetch(mw, {
|
|
866
|
+
method: "POST",
|
|
867
|
+
headers: {
|
|
868
|
+
"content-type": "application/json",
|
|
869
|
+
"X-CopilotKit-Webhook-Stage": "after_request" /* AfterRequest */
|
|
870
|
+
},
|
|
871
|
+
body: JSON.stringify(payload),
|
|
872
|
+
signal: ac.signal
|
|
873
|
+
});
|
|
874
|
+
} finally {
|
|
875
|
+
clearTimeout(to);
|
|
876
|
+
}
|
|
877
|
+
if (!res.ok) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
`after_request webhook ${mw} responded with ${res.status}`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
logger.warn({ mw }, "Unsupported afterRequestMiddleware value \u2013 skipped");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/handlers/handle-connect.ts
|
|
888
|
+
import { RunAgentInputSchema as RunAgentInputSchema2 } from "@ag-ui/client";
|
|
889
|
+
import { EventEncoder as EventEncoder2 } from "@ag-ui/encoder";
|
|
890
|
+
async function handleConnectAgent({
|
|
891
|
+
runtime,
|
|
892
|
+
request,
|
|
893
|
+
agentId
|
|
894
|
+
}) {
|
|
895
|
+
try {
|
|
896
|
+
const agents = await runtime.agents;
|
|
897
|
+
if (!agents[agentId]) {
|
|
898
|
+
return new Response(
|
|
899
|
+
JSON.stringify({
|
|
900
|
+
error: "Agent not found",
|
|
901
|
+
message: `Agent '${agentId}' does not exist`
|
|
902
|
+
}),
|
|
903
|
+
{
|
|
904
|
+
status: 404,
|
|
905
|
+
headers: { "Content-Type": "application/json" }
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
const stream = new TransformStream();
|
|
910
|
+
const writer = stream.writable.getWriter();
|
|
911
|
+
const encoder = new EventEncoder2();
|
|
912
|
+
let streamClosed = false;
|
|
913
|
+
(async () => {
|
|
914
|
+
let input;
|
|
915
|
+
try {
|
|
916
|
+
const requestBody = await request.json();
|
|
917
|
+
input = RunAgentInputSchema2.parse(requestBody);
|
|
918
|
+
} catch {
|
|
919
|
+
return new Response(
|
|
920
|
+
JSON.stringify({
|
|
921
|
+
error: "Invalid request body"
|
|
922
|
+
}),
|
|
923
|
+
{ status: 400 }
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
runtime.runner.connect({
|
|
927
|
+
threadId: input.threadId
|
|
928
|
+
}).subscribe({
|
|
929
|
+
next: async (event) => {
|
|
930
|
+
if (!request.signal.aborted && !streamClosed) {
|
|
931
|
+
try {
|
|
932
|
+
await writer.write(encoder.encode(event));
|
|
933
|
+
} catch (error) {
|
|
934
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
935
|
+
streamClosed = true;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
error: async (error) => {
|
|
941
|
+
console.error("Error running agent:", error);
|
|
942
|
+
if (!streamClosed) {
|
|
943
|
+
try {
|
|
944
|
+
await writer.close();
|
|
945
|
+
streamClosed = true;
|
|
946
|
+
} catch {
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
complete: async () => {
|
|
951
|
+
if (!streamClosed) {
|
|
952
|
+
try {
|
|
953
|
+
await writer.close();
|
|
954
|
+
streamClosed = true;
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
})().catch((error) => {
|
|
961
|
+
console.error("Error running agent:", error);
|
|
962
|
+
console.error(
|
|
963
|
+
"Error stack:",
|
|
964
|
+
error instanceof Error ? error.stack : "No stack trace"
|
|
965
|
+
);
|
|
966
|
+
console.error("Error details:", {
|
|
967
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
968
|
+
message: error instanceof Error ? error.message : String(error),
|
|
969
|
+
cause: error instanceof Error ? error.cause : void 0
|
|
970
|
+
});
|
|
971
|
+
if (!streamClosed) {
|
|
972
|
+
try {
|
|
973
|
+
writer.close();
|
|
974
|
+
streamClosed = true;
|
|
975
|
+
} catch {
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
return new Response(stream.readable, {
|
|
980
|
+
status: 200,
|
|
981
|
+
headers: {
|
|
982
|
+
"Content-Type": "text/event-stream",
|
|
983
|
+
"Cache-Control": "no-cache",
|
|
984
|
+
Connection: "keep-alive"
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
} catch (error) {
|
|
988
|
+
console.error("Error running agent:", error);
|
|
989
|
+
console.error(
|
|
990
|
+
"Error stack:",
|
|
991
|
+
error instanceof Error ? error.stack : "No stack trace"
|
|
992
|
+
);
|
|
993
|
+
console.error("Error details:", {
|
|
994
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
995
|
+
message: error instanceof Error ? error.message : String(error),
|
|
996
|
+
cause: error instanceof Error ? error.cause : void 0
|
|
997
|
+
});
|
|
998
|
+
return new Response(
|
|
999
|
+
JSON.stringify({
|
|
1000
|
+
error: "Failed to run agent",
|
|
1001
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
1002
|
+
}),
|
|
1003
|
+
{
|
|
1004
|
+
status: 500,
|
|
1005
|
+
headers: { "Content-Type": "application/json" }
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/endpoint.ts
|
|
1012
|
+
function createCopilotEndpoint({
|
|
1013
|
+
runtime,
|
|
1014
|
+
basePath
|
|
1015
|
+
}) {
|
|
1016
|
+
const app = new Hono();
|
|
1017
|
+
return app.basePath(basePath).use("*", async (c, next) => {
|
|
1018
|
+
const request = c.req.raw;
|
|
1019
|
+
const path = c.req.path;
|
|
1020
|
+
try {
|
|
1021
|
+
const maybeModifiedRequest = await callBeforeRequestMiddleware({
|
|
1022
|
+
runtime,
|
|
1023
|
+
request,
|
|
1024
|
+
path
|
|
1025
|
+
});
|
|
1026
|
+
if (maybeModifiedRequest) {
|
|
1027
|
+
c.set("modifiedRequest", maybeModifiedRequest);
|
|
1028
|
+
}
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
logger2.error(
|
|
1031
|
+
{ err: error, url: request.url, path },
|
|
1032
|
+
"Error running before request middleware"
|
|
1033
|
+
);
|
|
1034
|
+
if (error instanceof Response) {
|
|
1035
|
+
return error;
|
|
1036
|
+
}
|
|
1037
|
+
throw error;
|
|
1038
|
+
}
|
|
1039
|
+
await next();
|
|
1040
|
+
}).use("*", async (c, next) => {
|
|
1041
|
+
await next();
|
|
1042
|
+
const response = c.res;
|
|
1043
|
+
const path = c.req.path;
|
|
1044
|
+
callAfterRequestMiddleware({
|
|
1045
|
+
runtime,
|
|
1046
|
+
response,
|
|
1047
|
+
path
|
|
1048
|
+
}).catch((error) => {
|
|
1049
|
+
logger2.error(
|
|
1050
|
+
{ err: error, url: c.req.url, path },
|
|
1051
|
+
"Error running after request middleware"
|
|
1052
|
+
);
|
|
1053
|
+
});
|
|
1054
|
+
}).post("/agent/:agentId/run", async (c) => {
|
|
1055
|
+
const agentId = c.req.param("agentId");
|
|
1056
|
+
const request = c.get("modifiedRequest") || c.req.raw;
|
|
1057
|
+
try {
|
|
1058
|
+
return await handleRunAgent({
|
|
1059
|
+
runtime,
|
|
1060
|
+
request,
|
|
1061
|
+
agentId
|
|
1062
|
+
});
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
logger2.error(
|
|
1065
|
+
{ err: error, url: request.url, path: c.req.path },
|
|
1066
|
+
"Error running request handler"
|
|
1067
|
+
);
|
|
1068
|
+
throw error;
|
|
1069
|
+
}
|
|
1070
|
+
}).post("/agent/:agentId/connect", async (c) => {
|
|
1071
|
+
const agentId = c.req.param("agentId");
|
|
1072
|
+
const request = c.get("modifiedRequest") || c.req.raw;
|
|
1073
|
+
try {
|
|
1074
|
+
return await handleConnectAgent({
|
|
1075
|
+
runtime,
|
|
1076
|
+
request,
|
|
1077
|
+
agentId
|
|
1078
|
+
});
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
logger2.error(
|
|
1081
|
+
{ err: error, url: request.url, path: c.req.path },
|
|
1082
|
+
"Error running request handler"
|
|
1083
|
+
);
|
|
1084
|
+
throw error;
|
|
1085
|
+
}
|
|
1086
|
+
}).get("/info", async (c) => {
|
|
1087
|
+
const request = c.get("modifiedRequest") || c.req.raw;
|
|
1088
|
+
try {
|
|
1089
|
+
return await handleGetRuntimeInfo({
|
|
1090
|
+
runtime,
|
|
1091
|
+
request
|
|
1092
|
+
});
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
logger2.error(
|
|
1095
|
+
{ err: error, url: request.url, path: c.req.path },
|
|
1096
|
+
"Error running request handler"
|
|
1097
|
+
);
|
|
1098
|
+
throw error;
|
|
1099
|
+
}
|
|
1100
|
+
}).post("/transcribe", async (c) => {
|
|
1101
|
+
const request = c.get("modifiedRequest") || c.req.raw;
|
|
1102
|
+
try {
|
|
1103
|
+
return await handleTranscribe({
|
|
1104
|
+
runtime,
|
|
1105
|
+
request
|
|
1106
|
+
});
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
logger2.error(
|
|
1109
|
+
{ err: error, url: request.url, path: c.req.path },
|
|
1110
|
+
"Error running request handler"
|
|
1111
|
+
);
|
|
1112
|
+
throw error;
|
|
1113
|
+
}
|
|
1114
|
+
}).notFound((c) => {
|
|
1115
|
+
return c.json({ error: "Not found" }, 404);
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/runner/sqlite.ts
|
|
1120
|
+
import { ReplaySubject as ReplaySubject2 } from "rxjs";
|
|
1121
|
+
import {
|
|
1122
|
+
EventType as EventType3
|
|
1123
|
+
} from "@ag-ui/client";
|
|
1124
|
+
import Database from "better-sqlite3";
|
|
1125
|
+
var SCHEMA_VERSION = 1;
|
|
1126
|
+
var ACTIVE_CONNECTIONS = /* @__PURE__ */ new Map();
|
|
1127
|
+
var SqliteAgentRunner = class extends AgentRunner {
|
|
1128
|
+
db;
|
|
1129
|
+
constructor(options = {}) {
|
|
1130
|
+
super();
|
|
1131
|
+
const dbPath = options.dbPath ?? ":memory:";
|
|
1132
|
+
if (!Database) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
"better-sqlite3 is required for SqliteAgentRunner but was not found.\nPlease install it in your project:\n npm install better-sqlite3\n or\n pnpm add better-sqlite3\n or\n yarn add better-sqlite3\n\nIf you don't need persistence, use InMemoryAgentRunner instead."
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
this.db = new Database(dbPath);
|
|
1138
|
+
this.initializeSchema();
|
|
1139
|
+
}
|
|
1140
|
+
convertMessageToEvents(message) {
|
|
1141
|
+
const events = [];
|
|
1142
|
+
if ((message.role === "assistant" || message.role === "user" || message.role === "developer" || message.role === "system") && message.content) {
|
|
1143
|
+
const textStartEvent = {
|
|
1144
|
+
type: EventType3.TEXT_MESSAGE_START,
|
|
1145
|
+
messageId: message.id,
|
|
1146
|
+
role: message.role
|
|
1147
|
+
};
|
|
1148
|
+
events.push(textStartEvent);
|
|
1149
|
+
const textContentEvent = {
|
|
1150
|
+
type: EventType3.TEXT_MESSAGE_CONTENT,
|
|
1151
|
+
messageId: message.id,
|
|
1152
|
+
delta: message.content
|
|
1153
|
+
};
|
|
1154
|
+
events.push(textContentEvent);
|
|
1155
|
+
const textEndEvent = {
|
|
1156
|
+
type: EventType3.TEXT_MESSAGE_END,
|
|
1157
|
+
messageId: message.id
|
|
1158
|
+
};
|
|
1159
|
+
events.push(textEndEvent);
|
|
1160
|
+
}
|
|
1161
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
1162
|
+
for (const toolCall of message.toolCalls) {
|
|
1163
|
+
const toolStartEvent = {
|
|
1164
|
+
type: EventType3.TOOL_CALL_START,
|
|
1165
|
+
toolCallId: toolCall.id,
|
|
1166
|
+
toolCallName: toolCall.function.name,
|
|
1167
|
+
parentMessageId: message.id
|
|
1168
|
+
};
|
|
1169
|
+
events.push(toolStartEvent);
|
|
1170
|
+
const toolArgsEvent = {
|
|
1171
|
+
type: EventType3.TOOL_CALL_ARGS,
|
|
1172
|
+
toolCallId: toolCall.id,
|
|
1173
|
+
delta: toolCall.function.arguments
|
|
1174
|
+
};
|
|
1175
|
+
events.push(toolArgsEvent);
|
|
1176
|
+
const toolEndEvent = {
|
|
1177
|
+
type: EventType3.TOOL_CALL_END,
|
|
1178
|
+
toolCallId: toolCall.id
|
|
1179
|
+
};
|
|
1180
|
+
events.push(toolEndEvent);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
if (message.role === "tool" && message.toolCallId) {
|
|
1184
|
+
const toolResultEvent = {
|
|
1185
|
+
type: EventType3.TOOL_CALL_RESULT,
|
|
1186
|
+
messageId: message.id,
|
|
1187
|
+
toolCallId: message.toolCallId,
|
|
1188
|
+
content: message.content,
|
|
1189
|
+
role: "tool"
|
|
1190
|
+
};
|
|
1191
|
+
events.push(toolResultEvent);
|
|
1192
|
+
}
|
|
1193
|
+
return events;
|
|
1194
|
+
}
|
|
1195
|
+
initializeSchema() {
|
|
1196
|
+
this.db.exec(`
|
|
1197
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
1198
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1199
|
+
thread_id TEXT NOT NULL,
|
|
1200
|
+
run_id TEXT NOT NULL UNIQUE,
|
|
1201
|
+
parent_run_id TEXT,
|
|
1202
|
+
events TEXT NOT NULL,
|
|
1203
|
+
input TEXT NOT NULL,
|
|
1204
|
+
created_at INTEGER NOT NULL,
|
|
1205
|
+
version INTEGER NOT NULL
|
|
1206
|
+
)
|
|
1207
|
+
`);
|
|
1208
|
+
this.db.exec(`
|
|
1209
|
+
CREATE TABLE IF NOT EXISTS run_state (
|
|
1210
|
+
thread_id TEXT PRIMARY KEY,
|
|
1211
|
+
is_running INTEGER DEFAULT 0,
|
|
1212
|
+
current_run_id TEXT,
|
|
1213
|
+
updated_at INTEGER NOT NULL
|
|
1214
|
+
)
|
|
1215
|
+
`);
|
|
1216
|
+
this.db.exec(`
|
|
1217
|
+
CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);
|
|
1218
|
+
CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);
|
|
1219
|
+
`);
|
|
1220
|
+
this.db.exec(`
|
|
1221
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
1222
|
+
version INTEGER PRIMARY KEY,
|
|
1223
|
+
applied_at INTEGER NOT NULL
|
|
1224
|
+
)
|
|
1225
|
+
`);
|
|
1226
|
+
const currentVersion = this.db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
|
|
1227
|
+
if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
|
|
1228
|
+
this.db.prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)").run(SCHEMA_VERSION, Date.now());
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
storeRun(threadId, runId, events, input, parentRunId) {
|
|
1232
|
+
const compactedEvents = compactEvents(events);
|
|
1233
|
+
const stmt = this.db.prepare(`
|
|
1234
|
+
INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)
|
|
1235
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1236
|
+
`);
|
|
1237
|
+
stmt.run(
|
|
1238
|
+
threadId,
|
|
1239
|
+
runId,
|
|
1240
|
+
parentRunId ?? null,
|
|
1241
|
+
JSON.stringify(compactedEvents),
|
|
1242
|
+
// Store only this run's compacted events
|
|
1243
|
+
JSON.stringify(input),
|
|
1244
|
+
Date.now(),
|
|
1245
|
+
SCHEMA_VERSION
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
getHistoricRuns(threadId) {
|
|
1249
|
+
const stmt = this.db.prepare(`
|
|
1250
|
+
WITH RECURSIVE run_chain AS (
|
|
1251
|
+
-- Base case: find the root runs (those without parent)
|
|
1252
|
+
SELECT * FROM agent_runs
|
|
1253
|
+
WHERE thread_id = ? AND parent_run_id IS NULL
|
|
1254
|
+
|
|
1255
|
+
UNION ALL
|
|
1256
|
+
|
|
1257
|
+
-- Recursive case: find children of current level
|
|
1258
|
+
SELECT ar.* FROM agent_runs ar
|
|
1259
|
+
INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id
|
|
1260
|
+
WHERE ar.thread_id = ?
|
|
1261
|
+
)
|
|
1262
|
+
SELECT * FROM run_chain
|
|
1263
|
+
ORDER BY created_at ASC
|
|
1264
|
+
`);
|
|
1265
|
+
const rows = stmt.all(threadId, threadId);
|
|
1266
|
+
return rows.map((row) => ({
|
|
1267
|
+
id: row.id,
|
|
1268
|
+
thread_id: row.thread_id,
|
|
1269
|
+
run_id: row.run_id,
|
|
1270
|
+
parent_run_id: row.parent_run_id,
|
|
1271
|
+
events: JSON.parse(row.events),
|
|
1272
|
+
input: JSON.parse(row.input),
|
|
1273
|
+
created_at: row.created_at,
|
|
1274
|
+
version: row.version
|
|
1275
|
+
}));
|
|
1276
|
+
}
|
|
1277
|
+
getLatestRunId(threadId) {
|
|
1278
|
+
const stmt = this.db.prepare(`
|
|
1279
|
+
SELECT run_id FROM agent_runs
|
|
1280
|
+
WHERE thread_id = ?
|
|
1281
|
+
ORDER BY created_at DESC
|
|
1282
|
+
LIMIT 1
|
|
1283
|
+
`);
|
|
1284
|
+
const result = stmt.get(threadId);
|
|
1285
|
+
return result?.run_id ?? null;
|
|
1286
|
+
}
|
|
1287
|
+
setRunState(threadId, isRunning, runId) {
|
|
1288
|
+
const stmt = this.db.prepare(`
|
|
1289
|
+
INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)
|
|
1290
|
+
VALUES (?, ?, ?, ?)
|
|
1291
|
+
`);
|
|
1292
|
+
stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());
|
|
1293
|
+
}
|
|
1294
|
+
getRunState(threadId) {
|
|
1295
|
+
const stmt = this.db.prepare(`
|
|
1296
|
+
SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?
|
|
1297
|
+
`);
|
|
1298
|
+
const result = stmt.get(threadId);
|
|
1299
|
+
return {
|
|
1300
|
+
isRunning: result?.is_running === 1,
|
|
1301
|
+
currentRunId: result?.current_run_id ?? null
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
run(request) {
|
|
1305
|
+
const runState = this.getRunState(request.threadId);
|
|
1306
|
+
if (runState.isRunning) {
|
|
1307
|
+
throw new Error("Thread already running");
|
|
1308
|
+
}
|
|
1309
|
+
this.setRunState(request.threadId, true, request.input.runId);
|
|
1310
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
1311
|
+
const currentRunEvents = [];
|
|
1312
|
+
const historicRuns = this.getHistoricRuns(request.threadId);
|
|
1313
|
+
const historicMessageIds = /* @__PURE__ */ new Set();
|
|
1314
|
+
for (const run of historicRuns) {
|
|
1315
|
+
for (const event of run.events) {
|
|
1316
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
1317
|
+
historicMessageIds.add(event.messageId);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const nextSubject = new ReplaySubject2(Infinity);
|
|
1322
|
+
const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);
|
|
1323
|
+
ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);
|
|
1324
|
+
const runSubject = new ReplaySubject2(Infinity);
|
|
1325
|
+
const runAgent = async () => {
|
|
1326
|
+
const parentRunId = this.getLatestRunId(request.threadId);
|
|
1327
|
+
try {
|
|
1328
|
+
await request.agent.runAgent(request.input, {
|
|
1329
|
+
onEvent: ({ event }) => {
|
|
1330
|
+
runSubject.next(event);
|
|
1331
|
+
nextSubject.next(event);
|
|
1332
|
+
currentRunEvents.push(event);
|
|
1333
|
+
},
|
|
1334
|
+
onNewMessage: ({ message }) => {
|
|
1335
|
+
if (!seenMessageIds.has(message.id)) {
|
|
1336
|
+
seenMessageIds.add(message.id);
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
onRunStartedEvent: () => {
|
|
1340
|
+
if (request.input.messages) {
|
|
1341
|
+
for (const message of request.input.messages) {
|
|
1342
|
+
if (!seenMessageIds.has(message.id)) {
|
|
1343
|
+
seenMessageIds.add(message.id);
|
|
1344
|
+
const events = this.convertMessageToEvents(message);
|
|
1345
|
+
const isNewMessage = !historicMessageIds.has(message.id);
|
|
1346
|
+
for (const event of events) {
|
|
1347
|
+
nextSubject.next(event);
|
|
1348
|
+
if (isNewMessage) {
|
|
1349
|
+
currentRunEvents.push(event);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
this.storeRun(
|
|
1358
|
+
request.threadId,
|
|
1359
|
+
request.input.runId,
|
|
1360
|
+
currentRunEvents,
|
|
1361
|
+
request.input,
|
|
1362
|
+
parentRunId
|
|
1363
|
+
);
|
|
1364
|
+
this.setRunState(request.threadId, false);
|
|
1365
|
+
runSubject.complete();
|
|
1366
|
+
nextSubject.complete();
|
|
1367
|
+
} catch {
|
|
1368
|
+
if (currentRunEvents.length > 0) {
|
|
1369
|
+
this.storeRun(
|
|
1370
|
+
request.threadId,
|
|
1371
|
+
request.input.runId,
|
|
1372
|
+
currentRunEvents,
|
|
1373
|
+
request.input,
|
|
1374
|
+
parentRunId
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
this.setRunState(request.threadId, false);
|
|
1378
|
+
runSubject.complete();
|
|
1379
|
+
nextSubject.complete();
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
if (prevSubject) {
|
|
1383
|
+
prevSubject.subscribe({
|
|
1384
|
+
next: (e) => nextSubject.next(e),
|
|
1385
|
+
error: (err) => nextSubject.error(err),
|
|
1386
|
+
complete: () => {
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
runAgent();
|
|
1391
|
+
return runSubject.asObservable();
|
|
1392
|
+
}
|
|
1393
|
+
connect(request) {
|
|
1394
|
+
const connectionSubject = new ReplaySubject2(Infinity);
|
|
1395
|
+
const historicRuns = this.getHistoricRuns(request.threadId);
|
|
1396
|
+
const allHistoricEvents = [];
|
|
1397
|
+
for (const run of historicRuns) {
|
|
1398
|
+
allHistoricEvents.push(...run.events);
|
|
1399
|
+
}
|
|
1400
|
+
const compactedEvents = compactEvents(allHistoricEvents);
|
|
1401
|
+
const emittedMessageIds = /* @__PURE__ */ new Set();
|
|
1402
|
+
for (const event of compactedEvents) {
|
|
1403
|
+
connectionSubject.next(event);
|
|
1404
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
1405
|
+
emittedMessageIds.add(event.messageId);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);
|
|
1409
|
+
const runState = this.getRunState(request.threadId);
|
|
1410
|
+
if (activeSubject && runState.isRunning) {
|
|
1411
|
+
activeSubject.subscribe({
|
|
1412
|
+
next: (event) => {
|
|
1413
|
+
if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
connectionSubject.next(event);
|
|
1417
|
+
},
|
|
1418
|
+
complete: () => connectionSubject.complete(),
|
|
1419
|
+
error: (err) => connectionSubject.error(err)
|
|
1420
|
+
});
|
|
1421
|
+
} else {
|
|
1422
|
+
connectionSubject.complete();
|
|
1423
|
+
}
|
|
1424
|
+
return connectionSubject.asObservable();
|
|
1425
|
+
}
|
|
1426
|
+
isRunning(request) {
|
|
1427
|
+
const runState = this.getRunState(request.threadId);
|
|
1428
|
+
return Promise.resolve(runState.isRunning);
|
|
1429
|
+
}
|
|
1430
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1431
|
+
stop(_request) {
|
|
1432
|
+
throw new Error("Method not implemented.");
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Close the database connection (for cleanup)
|
|
1436
|
+
*/
|
|
1437
|
+
close() {
|
|
1438
|
+
if (this.db) {
|
|
1439
|
+
this.db.close();
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
// src/runner/enterprise.ts
|
|
1445
|
+
import { ReplaySubject as ReplaySubject3 } from "rxjs";
|
|
1446
|
+
import {
|
|
1447
|
+
EventType as EventType4
|
|
1448
|
+
} from "@ag-ui/client";
|
|
1449
|
+
var SCHEMA_VERSION2 = 1;
|
|
1450
|
+
var redisKeys = {
|
|
1451
|
+
stream: (threadId, runId) => `stream:${threadId}:${runId}`,
|
|
1452
|
+
active: (threadId) => `active:${threadId}`,
|
|
1453
|
+
lock: (threadId) => `lock:${threadId}`
|
|
1454
|
+
};
|
|
1455
|
+
var EnterpriseAgentRunner = class extends AgentRunner {
|
|
1456
|
+
db;
|
|
1457
|
+
redis;
|
|
1458
|
+
redisSub;
|
|
1459
|
+
serverId;
|
|
1460
|
+
streamRetentionMs;
|
|
1461
|
+
streamActiveTTLMs;
|
|
1462
|
+
lockTTLMs;
|
|
1463
|
+
constructor(options) {
|
|
1464
|
+
super();
|
|
1465
|
+
this.db = options.kysely;
|
|
1466
|
+
this.redis = options.redis;
|
|
1467
|
+
this.redisSub = options.redisSub || options.redis.duplicate();
|
|
1468
|
+
this.serverId = options.serverId || this.generateServerId();
|
|
1469
|
+
this.streamRetentionMs = options.streamRetentionMs ?? 36e5;
|
|
1470
|
+
this.streamActiveTTLMs = options.streamActiveTTLMs ?? 3e5;
|
|
1471
|
+
this.lockTTLMs = options.lockTTLMs ?? 3e5;
|
|
1472
|
+
this.initializeSchema();
|
|
1473
|
+
}
|
|
1474
|
+
run(request) {
|
|
1475
|
+
const runSubject = new ReplaySubject3(Infinity);
|
|
1476
|
+
const executeRun = async () => {
|
|
1477
|
+
const { threadId, input, agent } = request;
|
|
1478
|
+
const runId = input.runId;
|
|
1479
|
+
const streamKey = redisKeys.stream(threadId, runId);
|
|
1480
|
+
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
1481
|
+
if (activeRunId) {
|
|
1482
|
+
throw new Error("Thread already running");
|
|
1483
|
+
}
|
|
1484
|
+
const lockAcquired = await this.redis.set(
|
|
1485
|
+
redisKeys.lock(threadId),
|
|
1486
|
+
this.serverId,
|
|
1487
|
+
"PX",
|
|
1488
|
+
this.lockTTLMs,
|
|
1489
|
+
"NX"
|
|
1490
|
+
);
|
|
1491
|
+
if (!lockAcquired) {
|
|
1492
|
+
throw new Error("Thread already running");
|
|
1493
|
+
}
|
|
1494
|
+
await this.redis.setex(
|
|
1495
|
+
redisKeys.active(threadId),
|
|
1496
|
+
Math.floor(this.lockTTLMs / 1e3),
|
|
1497
|
+
runId
|
|
1498
|
+
);
|
|
1499
|
+
await this.setRunState(threadId, true, runId);
|
|
1500
|
+
const currentRunEvents = [];
|
|
1501
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
1502
|
+
const historicRuns = await this.getHistoricRuns(threadId);
|
|
1503
|
+
const historicMessageIds = /* @__PURE__ */ new Set();
|
|
1504
|
+
for (const run of historicRuns) {
|
|
1505
|
+
const events = JSON.parse(run.events);
|
|
1506
|
+
for (const event of events) {
|
|
1507
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
1508
|
+
historicMessageIds.add(event.messageId);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
const parentRunId = historicRuns[historicRuns.length - 1]?.run_id ?? null;
|
|
1513
|
+
try {
|
|
1514
|
+
await agent.runAgent(input, {
|
|
1515
|
+
onEvent: async ({ event }) => {
|
|
1516
|
+
runSubject.next(event);
|
|
1517
|
+
currentRunEvents.push(event);
|
|
1518
|
+
await this.redis.xadd(
|
|
1519
|
+
streamKey,
|
|
1520
|
+
"MAXLEN",
|
|
1521
|
+
"~",
|
|
1522
|
+
"10000",
|
|
1523
|
+
"*",
|
|
1524
|
+
"type",
|
|
1525
|
+
event.type,
|
|
1526
|
+
"data",
|
|
1527
|
+
JSON.stringify(event)
|
|
1528
|
+
);
|
|
1529
|
+
await this.redis.pexpire(streamKey, this.streamActiveTTLMs);
|
|
1530
|
+
if (event.type === EventType4.RUN_FINISHED || event.type === EventType4.RUN_ERROR) {
|
|
1531
|
+
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
onNewMessage: ({ message }) => {
|
|
1535
|
+
if (!seenMessageIds.has(message.id)) {
|
|
1536
|
+
seenMessageIds.add(message.id);
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
onRunStartedEvent: async () => {
|
|
1540
|
+
if (input.messages) {
|
|
1541
|
+
for (const message of input.messages) {
|
|
1542
|
+
if (!seenMessageIds.has(message.id)) {
|
|
1543
|
+
seenMessageIds.add(message.id);
|
|
1544
|
+
const events = this.convertMessageToEvents(message);
|
|
1545
|
+
const isNewMessage = !historicMessageIds.has(message.id);
|
|
1546
|
+
for (const event of events) {
|
|
1547
|
+
await this.redis.xadd(
|
|
1548
|
+
streamKey,
|
|
1549
|
+
"MAXLEN",
|
|
1550
|
+
"~",
|
|
1551
|
+
"10000",
|
|
1552
|
+
"*",
|
|
1553
|
+
"type",
|
|
1554
|
+
event.type,
|
|
1555
|
+
"data",
|
|
1556
|
+
JSON.stringify(event)
|
|
1557
|
+
);
|
|
1558
|
+
if (isNewMessage) {
|
|
1559
|
+
currentRunEvents.push(event);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
await this.redis.pexpire(streamKey, this.streamActiveTTLMs);
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
const compactedEvents = compactEvents(currentRunEvents);
|
|
1569
|
+
await this.storeRun(threadId, runId, compactedEvents, input, parentRunId);
|
|
1570
|
+
} finally {
|
|
1571
|
+
await this.setRunState(threadId, false);
|
|
1572
|
+
await this.redis.del(redisKeys.active(threadId));
|
|
1573
|
+
await this.redis.del(redisKeys.lock(threadId));
|
|
1574
|
+
const exists = await this.redis.exists(streamKey);
|
|
1575
|
+
if (exists) {
|
|
1576
|
+
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
1577
|
+
}
|
|
1578
|
+
runSubject.complete();
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
executeRun().catch((error) => {
|
|
1582
|
+
runSubject.error(error);
|
|
1583
|
+
});
|
|
1584
|
+
return runSubject.asObservable();
|
|
1585
|
+
}
|
|
1586
|
+
connect(request) {
|
|
1587
|
+
const connectionSubject = new ReplaySubject3(Infinity);
|
|
1588
|
+
const streamConnection = async () => {
|
|
1589
|
+
const { threadId } = request;
|
|
1590
|
+
const historicRuns = await this.getHistoricRuns(threadId);
|
|
1591
|
+
const allHistoricEvents = [];
|
|
1592
|
+
for (const run of historicRuns) {
|
|
1593
|
+
const events = JSON.parse(run.events);
|
|
1594
|
+
allHistoricEvents.push(...events);
|
|
1595
|
+
}
|
|
1596
|
+
const compactedEvents = compactEvents(allHistoricEvents);
|
|
1597
|
+
const emittedMessageIds = /* @__PURE__ */ new Set();
|
|
1598
|
+
for (const event of compactedEvents) {
|
|
1599
|
+
connectionSubject.next(event);
|
|
1600
|
+
if ("messageId" in event && typeof event.messageId === "string") {
|
|
1601
|
+
emittedMessageIds.add(event.messageId);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
1605
|
+
if (activeRunId) {
|
|
1606
|
+
const streamKey = redisKeys.stream(threadId, activeRunId);
|
|
1607
|
+
let lastId = "0-0";
|
|
1608
|
+
let consecutiveEmptyReads = 0;
|
|
1609
|
+
while (true) {
|
|
1610
|
+
try {
|
|
1611
|
+
const result = await this.redis.call(
|
|
1612
|
+
"XREAD",
|
|
1613
|
+
"BLOCK",
|
|
1614
|
+
"5000",
|
|
1615
|
+
"COUNT",
|
|
1616
|
+
"100",
|
|
1617
|
+
"STREAMS",
|
|
1618
|
+
streamKey,
|
|
1619
|
+
lastId
|
|
1620
|
+
);
|
|
1621
|
+
if (!result || result.length === 0) {
|
|
1622
|
+
consecutiveEmptyReads++;
|
|
1623
|
+
const exists = await this.redis.exists(streamKey);
|
|
1624
|
+
if (!exists) {
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
const stillActive = await this.redis.get(redisKeys.active(threadId));
|
|
1628
|
+
if (stillActive !== activeRunId) {
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
if (consecutiveEmptyReads > 3) {
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
consecutiveEmptyReads = 0;
|
|
1637
|
+
const [, messages] = result[0] || [null, []];
|
|
1638
|
+
for (const [id, fields] of messages || []) {
|
|
1639
|
+
lastId = id;
|
|
1640
|
+
let eventData = null;
|
|
1641
|
+
let eventType = null;
|
|
1642
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
1643
|
+
if (fields[i] === "data") {
|
|
1644
|
+
eventData = fields[i + 1] ?? null;
|
|
1645
|
+
} else if (fields[i] === "type") {
|
|
1646
|
+
eventType = fields[i + 1] ?? null;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (eventData) {
|
|
1650
|
+
const event = JSON.parse(eventData);
|
|
1651
|
+
if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
connectionSubject.next(event);
|
|
1655
|
+
if (eventType === EventType4.RUN_FINISHED || eventType === EventType4.RUN_ERROR) {
|
|
1656
|
+
connectionSubject.complete();
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
} catch {
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
connectionSubject.complete();
|
|
1667
|
+
};
|
|
1668
|
+
streamConnection().catch(() => connectionSubject.complete());
|
|
1669
|
+
return connectionSubject.asObservable();
|
|
1670
|
+
}
|
|
1671
|
+
async isRunning(request) {
|
|
1672
|
+
const { threadId } = request;
|
|
1673
|
+
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
1674
|
+
if (activeRunId) return true;
|
|
1675
|
+
const lockExists = await this.redis.exists(redisKeys.lock(threadId));
|
|
1676
|
+
if (lockExists) return true;
|
|
1677
|
+
const state = await this.db.selectFrom("run_state").where("thread_id", "=", threadId).selectAll().executeTakeFirst();
|
|
1678
|
+
return state?.is_running === 1;
|
|
1679
|
+
}
|
|
1680
|
+
async stop(request) {
|
|
1681
|
+
const { threadId } = request;
|
|
1682
|
+
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
1683
|
+
if (!activeRunId) {
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1686
|
+
const streamKey = redisKeys.stream(threadId, activeRunId);
|
|
1687
|
+
await this.redis.xadd(
|
|
1688
|
+
streamKey,
|
|
1689
|
+
"*",
|
|
1690
|
+
"type",
|
|
1691
|
+
EventType4.RUN_ERROR,
|
|
1692
|
+
"data",
|
|
1693
|
+
JSON.stringify({
|
|
1694
|
+
type: EventType4.RUN_ERROR,
|
|
1695
|
+
error: "Run stopped by user"
|
|
1696
|
+
})
|
|
1697
|
+
);
|
|
1698
|
+
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
1699
|
+
await this.setRunState(threadId, false);
|
|
1700
|
+
await this.redis.del(redisKeys.active(threadId));
|
|
1701
|
+
await this.redis.del(redisKeys.lock(threadId));
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
// Helper methods
|
|
1705
|
+
convertMessageToEvents(message) {
|
|
1706
|
+
const events = [];
|
|
1707
|
+
if ((message.role === "assistant" || message.role === "user" || message.role === "developer" || message.role === "system") && message.content) {
|
|
1708
|
+
const textStartEvent = {
|
|
1709
|
+
type: EventType4.TEXT_MESSAGE_START,
|
|
1710
|
+
messageId: message.id,
|
|
1711
|
+
role: message.role
|
|
1712
|
+
};
|
|
1713
|
+
events.push(textStartEvent);
|
|
1714
|
+
const textContentEvent = {
|
|
1715
|
+
type: EventType4.TEXT_MESSAGE_CONTENT,
|
|
1716
|
+
messageId: message.id,
|
|
1717
|
+
delta: message.content
|
|
1718
|
+
};
|
|
1719
|
+
events.push(textContentEvent);
|
|
1720
|
+
const textEndEvent = {
|
|
1721
|
+
type: EventType4.TEXT_MESSAGE_END,
|
|
1722
|
+
messageId: message.id
|
|
1723
|
+
};
|
|
1724
|
+
events.push(textEndEvent);
|
|
1725
|
+
}
|
|
1726
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
1727
|
+
for (const toolCall of message.toolCalls) {
|
|
1728
|
+
const toolStartEvent = {
|
|
1729
|
+
type: EventType4.TOOL_CALL_START,
|
|
1730
|
+
toolCallId: toolCall.id,
|
|
1731
|
+
toolCallName: toolCall.function.name,
|
|
1732
|
+
parentMessageId: message.id
|
|
1733
|
+
};
|
|
1734
|
+
events.push(toolStartEvent);
|
|
1735
|
+
const toolArgsEvent = {
|
|
1736
|
+
type: EventType4.TOOL_CALL_ARGS,
|
|
1737
|
+
toolCallId: toolCall.id,
|
|
1738
|
+
delta: toolCall.function.arguments
|
|
1739
|
+
};
|
|
1740
|
+
events.push(toolArgsEvent);
|
|
1741
|
+
const toolEndEvent = {
|
|
1742
|
+
type: EventType4.TOOL_CALL_END,
|
|
1743
|
+
toolCallId: toolCall.id
|
|
1744
|
+
};
|
|
1745
|
+
events.push(toolEndEvent);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (message.role === "tool" && message.toolCallId) {
|
|
1749
|
+
const toolResultEvent = {
|
|
1750
|
+
type: EventType4.TOOL_CALL_RESULT,
|
|
1751
|
+
messageId: message.id,
|
|
1752
|
+
toolCallId: message.toolCallId,
|
|
1753
|
+
content: message.content,
|
|
1754
|
+
role: "tool"
|
|
1755
|
+
};
|
|
1756
|
+
events.push(toolResultEvent);
|
|
1757
|
+
}
|
|
1758
|
+
return events;
|
|
1759
|
+
}
|
|
1760
|
+
async initializeSchema() {
|
|
1761
|
+
try {
|
|
1762
|
+
await this.db.schema.createTable("agent_runs").ifNotExists().addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()).addColumn("thread_id", "text", (col) => col.notNull()).addColumn("run_id", "text", (col) => col.notNull().unique()).addColumn("parent_run_id", "text").addColumn("events", "text", (col) => col.notNull()).addColumn("input", "text", (col) => col.notNull()).addColumn("created_at", "integer", (col) => col.notNull()).addColumn("version", "integer", (col) => col.notNull()).execute().catch(() => {
|
|
1763
|
+
});
|
|
1764
|
+
await this.db.schema.createTable("run_state").ifNotExists().addColumn("thread_id", "text", (col) => col.primaryKey()).addColumn("is_running", "integer", (col) => col.defaultTo(0)).addColumn("current_run_id", "text").addColumn("server_id", "text").addColumn("updated_at", "integer", (col) => col.notNull()).execute().catch(() => {
|
|
1765
|
+
});
|
|
1766
|
+
await this.db.schema.createTable("schema_version").ifNotExists().addColumn("version", "integer", (col) => col.primaryKey()).addColumn("applied_at", "integer", (col) => col.notNull()).execute().catch(() => {
|
|
1767
|
+
});
|
|
1768
|
+
await this.db.schema.createIndex("idx_thread_id").ifNotExists().on("agent_runs").column("thread_id").execute().catch(() => {
|
|
1769
|
+
});
|
|
1770
|
+
await this.db.schema.createIndex("idx_parent_run_id").ifNotExists().on("agent_runs").column("parent_run_id").execute().catch(() => {
|
|
1771
|
+
});
|
|
1772
|
+
const currentVersion = await this.db.selectFrom("schema_version").orderBy("version", "desc").limit(1).selectAll().executeTakeFirst();
|
|
1773
|
+
if (!currentVersion || currentVersion.version < SCHEMA_VERSION2) {
|
|
1774
|
+
await this.db.insertInto("schema_version").values({
|
|
1775
|
+
version: SCHEMA_VERSION2,
|
|
1776
|
+
applied_at: Date.now()
|
|
1777
|
+
}).onConflict(
|
|
1778
|
+
(oc) => oc.column("version").doUpdateSet({ applied_at: Date.now() })
|
|
1779
|
+
).execute();
|
|
1780
|
+
}
|
|
1781
|
+
} catch {
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
async storeRun(threadId, runId, events, input, parentRunId) {
|
|
1785
|
+
await this.db.insertInto("agent_runs").values({
|
|
1786
|
+
thread_id: threadId,
|
|
1787
|
+
run_id: runId,
|
|
1788
|
+
parent_run_id: parentRunId,
|
|
1789
|
+
events: JSON.stringify(events),
|
|
1790
|
+
input: JSON.stringify(input),
|
|
1791
|
+
created_at: Date.now(),
|
|
1792
|
+
version: SCHEMA_VERSION2
|
|
1793
|
+
}).execute();
|
|
1794
|
+
}
|
|
1795
|
+
async getHistoricRuns(threadId) {
|
|
1796
|
+
const rows = await this.db.selectFrom("agent_runs").where("thread_id", "=", threadId).orderBy("created_at", "asc").selectAll().execute();
|
|
1797
|
+
return rows.map((row) => ({
|
|
1798
|
+
id: Number(row.id),
|
|
1799
|
+
thread_id: row.thread_id,
|
|
1800
|
+
run_id: row.run_id,
|
|
1801
|
+
parent_run_id: row.parent_run_id,
|
|
1802
|
+
events: row.events,
|
|
1803
|
+
input: row.input,
|
|
1804
|
+
created_at: row.created_at,
|
|
1805
|
+
version: row.version
|
|
1806
|
+
}));
|
|
1807
|
+
}
|
|
1808
|
+
async setRunState(threadId, isRunning, runId) {
|
|
1809
|
+
await this.db.insertInto("run_state").values({
|
|
1810
|
+
thread_id: threadId,
|
|
1811
|
+
is_running: isRunning ? 1 : 0,
|
|
1812
|
+
current_run_id: runId ?? null,
|
|
1813
|
+
server_id: this.serverId,
|
|
1814
|
+
updated_at: Date.now()
|
|
1815
|
+
}).onConflict(
|
|
1816
|
+
(oc) => oc.column("thread_id").doUpdateSet({
|
|
1817
|
+
is_running: isRunning ? 1 : 0,
|
|
1818
|
+
current_run_id: runId ?? null,
|
|
1819
|
+
server_id: this.serverId,
|
|
1820
|
+
updated_at: Date.now()
|
|
1821
|
+
})
|
|
1822
|
+
).execute();
|
|
1823
|
+
}
|
|
1824
|
+
async close() {
|
|
1825
|
+
await this.db.destroy();
|
|
1826
|
+
this.redis.disconnect();
|
|
1827
|
+
this.redisSub.disconnect();
|
|
1828
|
+
}
|
|
1829
|
+
generateServerId() {
|
|
1830
|
+
return `server-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
export {
|
|
1834
|
+
CopilotRuntime,
|
|
1835
|
+
EnterpriseAgentRunner,
|
|
1836
|
+
InMemoryAgentRunner,
|
|
1837
|
+
SqliteAgentRunner,
|
|
1838
|
+
VERSION,
|
|
1839
|
+
createCopilotEndpoint
|
|
1840
|
+
};
|
|
1841
|
+
//# sourceMappingURL=index.mjs.map
|