@copilotkitnext/runtime 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/.turbo/turbo-build.log +0 -23
- package/.turbo/turbo-check-types.log +0 -4
- package/.turbo/turbo-lint.log +0 -56
- package/.turbo/turbo-test$colon$coverage.log +0 -149
- package/.turbo/turbo-test.log +0 -108
- package/src/__tests__/get-runtime-info.test.ts +0 -117
- package/src/__tests__/handle-run.test.ts +0 -69
- package/src/__tests__/handle-transcribe.test.ts +0 -289
- package/src/__tests__/in-process-agent-runner-messages.test.ts +0 -599
- package/src/__tests__/in-process-agent-runner.test.ts +0 -726
- package/src/__tests__/middleware.test.ts +0 -432
- package/src/__tests__/routing.test.ts +0 -257
- package/src/endpoint.ts +0 -150
- package/src/handler.ts +0 -3
- package/src/handlers/get-runtime-info.ts +0 -50
- package/src/handlers/handle-connect.ts +0 -144
- package/src/handlers/handle-run.ts +0 -156
- package/src/handlers/handle-transcribe.ts +0 -126
- package/src/index.ts +0 -8
- package/src/middleware.ts +0 -232
- package/src/runner/__tests__/enterprise-runner.test.ts +0 -992
- package/src/runner/__tests__/event-compaction.test.ts +0 -253
- package/src/runner/__tests__/in-memory-runner.test.ts +0 -483
- package/src/runner/__tests__/sqlite-runner.test.ts +0 -975
- package/src/runner/agent-runner.ts +0 -27
- package/src/runner/enterprise.ts +0 -653
- package/src/runner/event-compaction.ts +0 -250
- package/src/runner/in-memory.ts +0 -328
- package/src/runner/index.ts +0 -0
- package/src/runner/sqlite.ts +0 -481
- package/src/runtime.ts +0 -53
- package/src/transcription-service/transcription-service-openai.ts +0 -29
- package/src/transcription-service/transcription-service.ts +0 -11
- package/tsconfig.json +0 -13
- package/tsup.config.ts +0 -11
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { AbstractAgent, BaseEvent, RunAgentInput } from "@ag-ui/client";
|
|
2
|
-
import { Observable } from "rxjs";
|
|
3
|
-
|
|
4
|
-
export interface AgentRunnerRunRequest {
|
|
5
|
-
threadId: string;
|
|
6
|
-
agent: AbstractAgent;
|
|
7
|
-
input: RunAgentInput;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface AgentRunnerConnectRequest {
|
|
11
|
-
threadId: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AgentRunnerIsRunningRequest {
|
|
15
|
-
threadId: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AgentRunnerStopRequest {
|
|
19
|
-
threadId: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export abstract class AgentRunner {
|
|
23
|
-
abstract run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
|
|
24
|
-
abstract connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
|
|
25
|
-
abstract isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
|
|
26
|
-
abstract stop(request: AgentRunnerStopRequest): Promise<boolean | undefined>;
|
|
27
|
-
}
|
package/src/runner/enterprise.ts
DELETED
|
@@ -1,653 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AgentRunner,
|
|
3
|
-
AgentRunnerConnectRequest,
|
|
4
|
-
AgentRunnerIsRunningRequest,
|
|
5
|
-
AgentRunnerRunRequest,
|
|
6
|
-
type AgentRunnerStopRequest,
|
|
7
|
-
} from "./agent-runner";
|
|
8
|
-
import { Observable, ReplaySubject } from "rxjs";
|
|
9
|
-
import {
|
|
10
|
-
BaseEvent,
|
|
11
|
-
RunAgentInput,
|
|
12
|
-
Message,
|
|
13
|
-
EventType,
|
|
14
|
-
TextMessageStartEvent,
|
|
15
|
-
TextMessageContentEvent,
|
|
16
|
-
TextMessageEndEvent,
|
|
17
|
-
ToolCallStartEvent,
|
|
18
|
-
ToolCallArgsEvent,
|
|
19
|
-
ToolCallEndEvent,
|
|
20
|
-
ToolCallResultEvent,
|
|
21
|
-
} from "@ag-ui/client";
|
|
22
|
-
import { compactEvents } from "./event-compaction";
|
|
23
|
-
import { Kysely, Generated } from "kysely";
|
|
24
|
-
import { Redis } from "ioredis";
|
|
25
|
-
|
|
26
|
-
const SCHEMA_VERSION = 1;
|
|
27
|
-
|
|
28
|
-
interface AgentDatabase {
|
|
29
|
-
agent_runs: {
|
|
30
|
-
id: Generated<number>;
|
|
31
|
-
thread_id: string;
|
|
32
|
-
run_id: string;
|
|
33
|
-
parent_run_id: string | null;
|
|
34
|
-
events: string;
|
|
35
|
-
input: string;
|
|
36
|
-
created_at: number;
|
|
37
|
-
version: number;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
run_state: {
|
|
41
|
-
thread_id: string;
|
|
42
|
-
is_running: number;
|
|
43
|
-
current_run_id: string | null;
|
|
44
|
-
server_id: string | null;
|
|
45
|
-
updated_at: number;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
schema_version: {
|
|
49
|
-
version: number;
|
|
50
|
-
applied_at: number;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface AgentRunRecord {
|
|
55
|
-
id: number;
|
|
56
|
-
thread_id: string;
|
|
57
|
-
run_id: string;
|
|
58
|
-
parent_run_id: string | null;
|
|
59
|
-
events: string;
|
|
60
|
-
input: string;
|
|
61
|
-
created_at: number;
|
|
62
|
-
version: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const redisKeys = {
|
|
66
|
-
stream: (threadId: string, runId: string) => `stream:${threadId}:${runId}`,
|
|
67
|
-
active: (threadId: string) => `active:${threadId}`,
|
|
68
|
-
lock: (threadId: string) => `lock:${threadId}`,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export interface EnterpriseAgentRunnerOptions {
|
|
72
|
-
kysely: Kysely<AgentDatabase>;
|
|
73
|
-
redis: Redis;
|
|
74
|
-
redisSub?: Redis;
|
|
75
|
-
streamRetentionMs?: number;
|
|
76
|
-
streamActiveTTLMs?: number;
|
|
77
|
-
lockTTLMs?: number;
|
|
78
|
-
serverId?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export class EnterpriseAgentRunner extends AgentRunner {
|
|
82
|
-
private db: Kysely<AgentDatabase>;
|
|
83
|
-
public redis: Redis;
|
|
84
|
-
public redisSub: Redis;
|
|
85
|
-
private serverId: string;
|
|
86
|
-
private streamRetentionMs: number;
|
|
87
|
-
private streamActiveTTLMs: number;
|
|
88
|
-
private lockTTLMs: number;
|
|
89
|
-
|
|
90
|
-
constructor(options: EnterpriseAgentRunnerOptions) {
|
|
91
|
-
super();
|
|
92
|
-
this.db = options.kysely;
|
|
93
|
-
this.redis = options.redis;
|
|
94
|
-
this.redisSub = options.redisSub || options.redis.duplicate();
|
|
95
|
-
this.serverId = options.serverId || this.generateServerId();
|
|
96
|
-
this.streamRetentionMs = options.streamRetentionMs ?? 3600000; // 1 hour
|
|
97
|
-
this.streamActiveTTLMs = options.streamActiveTTLMs ?? 300000; // 5 minutes
|
|
98
|
-
this.lockTTLMs = options.lockTTLMs ?? 300000; // 5 minutes
|
|
99
|
-
|
|
100
|
-
this.initializeSchema();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
run(request: AgentRunnerRunRequest): Observable<BaseEvent> {
|
|
104
|
-
const runSubject = new ReplaySubject<BaseEvent>(Infinity);
|
|
105
|
-
|
|
106
|
-
const executeRun = async () => {
|
|
107
|
-
const { threadId, input, agent } = request;
|
|
108
|
-
const runId = input.runId;
|
|
109
|
-
const streamKey = redisKeys.stream(threadId, runId);
|
|
110
|
-
|
|
111
|
-
// Check if thread already running (do this check synchronously for consistency with SQLite)
|
|
112
|
-
// For now we'll just check after, but in production you might want a sync check
|
|
113
|
-
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
114
|
-
if (activeRunId) {
|
|
115
|
-
throw new Error("Thread already running");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Acquire distributed lock
|
|
119
|
-
const lockAcquired = await this.redis.set(
|
|
120
|
-
redisKeys.lock(threadId),
|
|
121
|
-
this.serverId,
|
|
122
|
-
'PX', this.lockTTLMs,
|
|
123
|
-
'NX'
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
if (!lockAcquired) {
|
|
127
|
-
throw new Error("Thread already running");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Mark as active
|
|
131
|
-
await this.redis.setex(
|
|
132
|
-
redisKeys.active(threadId),
|
|
133
|
-
Math.floor(this.lockTTLMs / 1000),
|
|
134
|
-
runId
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// Update database state
|
|
138
|
-
await this.setRunState(threadId, true, runId);
|
|
139
|
-
|
|
140
|
-
// Track events and message IDs
|
|
141
|
-
const currentRunEvents: BaseEvent[] = [];
|
|
142
|
-
const seenMessageIds = new Set<string>();
|
|
143
|
-
|
|
144
|
-
// Get historic message IDs
|
|
145
|
-
const historicRuns = await this.getHistoricRuns(threadId);
|
|
146
|
-
const historicMessageIds = new Set<string>();
|
|
147
|
-
for (const run of historicRuns) {
|
|
148
|
-
const events = JSON.parse(run.events) as BaseEvent[];
|
|
149
|
-
for (const event of events) {
|
|
150
|
-
if ('messageId' in event && typeof event.messageId === 'string') {
|
|
151
|
-
historicMessageIds.add(event.messageId);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const parentRunId = historicRuns[historicRuns.length - 1]?.run_id ?? null;
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
await agent.runAgent(input, {
|
|
160
|
-
onEvent: async ({ event }) => {
|
|
161
|
-
// Emit to run() caller
|
|
162
|
-
runSubject.next(event);
|
|
163
|
-
|
|
164
|
-
// Collect for database
|
|
165
|
-
currentRunEvents.push(event);
|
|
166
|
-
|
|
167
|
-
// Stream to Redis for connect() subscribers
|
|
168
|
-
await this.redis.xadd(
|
|
169
|
-
streamKey,
|
|
170
|
-
'MAXLEN', '~', '10000',
|
|
171
|
-
'*',
|
|
172
|
-
'type', event.type,
|
|
173
|
-
'data', JSON.stringify(event)
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// Refresh TTL with sliding window during active writes
|
|
177
|
-
await this.redis.pexpire(streamKey, this.streamActiveTTLMs);
|
|
178
|
-
|
|
179
|
-
// Check for completion events
|
|
180
|
-
if (event.type === EventType.RUN_FINISHED ||
|
|
181
|
-
event.type === EventType.RUN_ERROR) {
|
|
182
|
-
// Switch to retention TTL for late readers
|
|
183
|
-
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
onNewMessage: ({ message }) => {
|
|
188
|
-
if (!seenMessageIds.has(message.id)) {
|
|
189
|
-
seenMessageIds.add(message.id);
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
onRunStartedEvent: async () => {
|
|
194
|
-
// Process input messages
|
|
195
|
-
if (input.messages) {
|
|
196
|
-
for (const message of input.messages) {
|
|
197
|
-
if (!seenMessageIds.has(message.id)) {
|
|
198
|
-
seenMessageIds.add(message.id);
|
|
199
|
-
const events = this.convertMessageToEvents(message);
|
|
200
|
-
const isNewMessage = !historicMessageIds.has(message.id);
|
|
201
|
-
|
|
202
|
-
for (const event of events) {
|
|
203
|
-
// Stream to Redis for context
|
|
204
|
-
await this.redis.xadd(
|
|
205
|
-
streamKey,
|
|
206
|
-
'MAXLEN', '~', '10000',
|
|
207
|
-
'*',
|
|
208
|
-
'type', event.type,
|
|
209
|
-
'data', JSON.stringify(event)
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
if (isNewMessage) {
|
|
213
|
-
currentRunEvents.push(event);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Refresh TTL
|
|
221
|
-
await this.redis.pexpire(streamKey, this.streamActiveTTLMs);
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Store to database
|
|
226
|
-
const compactedEvents = compactEvents(currentRunEvents);
|
|
227
|
-
await this.storeRun(threadId, runId, compactedEvents, input, parentRunId);
|
|
228
|
-
|
|
229
|
-
} finally {
|
|
230
|
-
// Clean up (even on error)
|
|
231
|
-
await this.setRunState(threadId, false);
|
|
232
|
-
await this.redis.del(redisKeys.active(threadId));
|
|
233
|
-
await this.redis.del(redisKeys.lock(threadId));
|
|
234
|
-
|
|
235
|
-
// Ensure stream has retention TTL for late readers
|
|
236
|
-
const exists = await this.redis.exists(streamKey);
|
|
237
|
-
if (exists) {
|
|
238
|
-
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
runSubject.complete();
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
executeRun().catch((error) => {
|
|
246
|
-
runSubject.error(error);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
return runSubject.asObservable();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {
|
|
253
|
-
const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);
|
|
254
|
-
|
|
255
|
-
const streamConnection = async () => {
|
|
256
|
-
const { threadId } = request;
|
|
257
|
-
|
|
258
|
-
// Load and emit historic runs from database
|
|
259
|
-
const historicRuns = await this.getHistoricRuns(threadId);
|
|
260
|
-
const allHistoricEvents: BaseEvent[] = [];
|
|
261
|
-
|
|
262
|
-
for (const run of historicRuns) {
|
|
263
|
-
const events = JSON.parse(run.events) as BaseEvent[];
|
|
264
|
-
allHistoricEvents.push(...events);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Compact and emit historic events
|
|
268
|
-
const compactedEvents = compactEvents(allHistoricEvents);
|
|
269
|
-
const emittedMessageIds = new Set<string>();
|
|
270
|
-
|
|
271
|
-
for (const event of compactedEvents) {
|
|
272
|
-
connectionSubject.next(event);
|
|
273
|
-
if ('messageId' in event && typeof event.messageId === 'string') {
|
|
274
|
-
emittedMessageIds.add(event.messageId);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Check for active run
|
|
279
|
-
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
280
|
-
|
|
281
|
-
if (activeRunId) {
|
|
282
|
-
// Tail the run-specific Redis stream
|
|
283
|
-
const streamKey = redisKeys.stream(threadId, activeRunId);
|
|
284
|
-
let lastId = '0-0';
|
|
285
|
-
let consecutiveEmptyReads = 0;
|
|
286
|
-
|
|
287
|
-
while (true) {
|
|
288
|
-
try {
|
|
289
|
-
// Read with blocking using call method for better compatibility
|
|
290
|
-
const result = await this.redis.call(
|
|
291
|
-
'XREAD',
|
|
292
|
-
'BLOCK', '5000',
|
|
293
|
-
'COUNT', '100',
|
|
294
|
-
'STREAMS', streamKey, lastId
|
|
295
|
-
) as [string, [string, string[]][]][] | null;
|
|
296
|
-
|
|
297
|
-
if (!result || result.length === 0) {
|
|
298
|
-
consecutiveEmptyReads++;
|
|
299
|
-
|
|
300
|
-
// Check if stream still exists
|
|
301
|
-
const exists = await this.redis.exists(streamKey);
|
|
302
|
-
if (!exists) {
|
|
303
|
-
// Stream expired, we're done
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Check if thread still active
|
|
308
|
-
const stillActive = await this.redis.get(redisKeys.active(threadId));
|
|
309
|
-
if (stillActive !== activeRunId) {
|
|
310
|
-
// Different run started or thread stopped
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// After multiple empty reads, assume completion
|
|
315
|
-
if (consecutiveEmptyReads > 3) {
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
consecutiveEmptyReads = 0;
|
|
323
|
-
const [, messages] = result[0] || [null, []];
|
|
324
|
-
|
|
325
|
-
for (const [id, fields] of messages || []) {
|
|
326
|
-
lastId = id;
|
|
327
|
-
|
|
328
|
-
// Extract event data (fields is array: [key, value, key, value, ...])
|
|
329
|
-
let eventData: string | null = null;
|
|
330
|
-
let eventType: string | null = null;
|
|
331
|
-
|
|
332
|
-
for (let i = 0; i < fields.length; i += 2) {
|
|
333
|
-
if (fields[i] === 'data') {
|
|
334
|
-
eventData = fields[i + 1] ?? null;
|
|
335
|
-
} else if (fields[i] === 'type') {
|
|
336
|
-
eventType = fields[i + 1] ?? null;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (eventData) {
|
|
341
|
-
const event = JSON.parse(eventData) as BaseEvent;
|
|
342
|
-
|
|
343
|
-
// Skip already emitted messages
|
|
344
|
-
if ('messageId' in event &&
|
|
345
|
-
typeof event.messageId === 'string' &&
|
|
346
|
-
emittedMessageIds.has(event.messageId)) {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
connectionSubject.next(event);
|
|
351
|
-
|
|
352
|
-
// Check for completion events
|
|
353
|
-
if (eventType === EventType.RUN_FINISHED ||
|
|
354
|
-
eventType === EventType.RUN_ERROR) {
|
|
355
|
-
connectionSubject.complete();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
// Redis error, complete the stream
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
connectionSubject.complete();
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
streamConnection().catch(() => connectionSubject.complete());
|
|
371
|
-
return connectionSubject.asObservable();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {
|
|
375
|
-
const { threadId } = request;
|
|
376
|
-
|
|
377
|
-
// Check Redis first for speed
|
|
378
|
-
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
379
|
-
if (activeRunId) return true;
|
|
380
|
-
|
|
381
|
-
// Check lock
|
|
382
|
-
const lockExists = await this.redis.exists(redisKeys.lock(threadId));
|
|
383
|
-
if (lockExists) return true;
|
|
384
|
-
|
|
385
|
-
// Fallback to database
|
|
386
|
-
const state = await this.db
|
|
387
|
-
.selectFrom('run_state')
|
|
388
|
-
.where('thread_id', '=', threadId)
|
|
389
|
-
.selectAll()
|
|
390
|
-
.executeTakeFirst();
|
|
391
|
-
|
|
392
|
-
return state?.is_running === 1;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async stop(request: AgentRunnerStopRequest): Promise<boolean> {
|
|
396
|
-
const { threadId } = request;
|
|
397
|
-
|
|
398
|
-
// Get active run ID
|
|
399
|
-
const activeRunId = await this.redis.get(redisKeys.active(threadId));
|
|
400
|
-
if (!activeRunId) {
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Add RUN_ERROR event to stream
|
|
405
|
-
const streamKey = redisKeys.stream(threadId, activeRunId);
|
|
406
|
-
await this.redis.xadd(
|
|
407
|
-
streamKey,
|
|
408
|
-
'*',
|
|
409
|
-
'type', EventType.RUN_ERROR,
|
|
410
|
-
'data', JSON.stringify({
|
|
411
|
-
type: EventType.RUN_ERROR,
|
|
412
|
-
error: 'Run stopped by user'
|
|
413
|
-
})
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
// Set retention TTL
|
|
417
|
-
await this.redis.pexpire(streamKey, this.streamRetentionMs);
|
|
418
|
-
|
|
419
|
-
// Clean up
|
|
420
|
-
await this.setRunState(threadId, false);
|
|
421
|
-
await this.redis.del(redisKeys.active(threadId));
|
|
422
|
-
await this.redis.del(redisKeys.lock(threadId));
|
|
423
|
-
|
|
424
|
-
return true;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Helper methods
|
|
428
|
-
private convertMessageToEvents(message: Message): BaseEvent[] {
|
|
429
|
-
const events: BaseEvent[] = [];
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
(message.role === "assistant" ||
|
|
433
|
-
message.role === "user" ||
|
|
434
|
-
message.role === "developer" ||
|
|
435
|
-
message.role === "system") &&
|
|
436
|
-
message.content
|
|
437
|
-
) {
|
|
438
|
-
const textStartEvent: TextMessageStartEvent = {
|
|
439
|
-
type: EventType.TEXT_MESSAGE_START,
|
|
440
|
-
messageId: message.id,
|
|
441
|
-
role: message.role,
|
|
442
|
-
};
|
|
443
|
-
events.push(textStartEvent);
|
|
444
|
-
|
|
445
|
-
const textContentEvent: TextMessageContentEvent = {
|
|
446
|
-
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
447
|
-
messageId: message.id,
|
|
448
|
-
delta: message.content,
|
|
449
|
-
};
|
|
450
|
-
events.push(textContentEvent);
|
|
451
|
-
|
|
452
|
-
const textEndEvent: TextMessageEndEvent = {
|
|
453
|
-
type: EventType.TEXT_MESSAGE_END,
|
|
454
|
-
messageId: message.id,
|
|
455
|
-
};
|
|
456
|
-
events.push(textEndEvent);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (message.role === "assistant" && message.toolCalls) {
|
|
460
|
-
for (const toolCall of message.toolCalls) {
|
|
461
|
-
const toolStartEvent: ToolCallStartEvent = {
|
|
462
|
-
type: EventType.TOOL_CALL_START,
|
|
463
|
-
toolCallId: toolCall.id,
|
|
464
|
-
toolCallName: toolCall.function.name,
|
|
465
|
-
parentMessageId: message.id,
|
|
466
|
-
};
|
|
467
|
-
events.push(toolStartEvent);
|
|
468
|
-
|
|
469
|
-
const toolArgsEvent: ToolCallArgsEvent = {
|
|
470
|
-
type: EventType.TOOL_CALL_ARGS,
|
|
471
|
-
toolCallId: toolCall.id,
|
|
472
|
-
delta: toolCall.function.arguments,
|
|
473
|
-
};
|
|
474
|
-
events.push(toolArgsEvent);
|
|
475
|
-
|
|
476
|
-
const toolEndEvent: ToolCallEndEvent = {
|
|
477
|
-
type: EventType.TOOL_CALL_END,
|
|
478
|
-
toolCallId: toolCall.id,
|
|
479
|
-
};
|
|
480
|
-
events.push(toolEndEvent);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (message.role === "tool" && message.toolCallId) {
|
|
485
|
-
const toolResultEvent: ToolCallResultEvent = {
|
|
486
|
-
type: EventType.TOOL_CALL_RESULT,
|
|
487
|
-
messageId: message.id,
|
|
488
|
-
toolCallId: message.toolCallId,
|
|
489
|
-
content: message.content,
|
|
490
|
-
role: "tool",
|
|
491
|
-
};
|
|
492
|
-
events.push(toolResultEvent);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return events;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private async initializeSchema(): Promise<void> {
|
|
499
|
-
try {
|
|
500
|
-
// Create agent_runs table
|
|
501
|
-
await this.db.schema
|
|
502
|
-
.createTable('agent_runs')
|
|
503
|
-
.ifNotExists()
|
|
504
|
-
.addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
|
|
505
|
-
.addColumn('thread_id', 'text', (col) => col.notNull())
|
|
506
|
-
.addColumn('run_id', 'text', (col) => col.notNull().unique())
|
|
507
|
-
.addColumn('parent_run_id', 'text')
|
|
508
|
-
.addColumn('events', 'text', (col) => col.notNull())
|
|
509
|
-
.addColumn('input', 'text', (col) => col.notNull())
|
|
510
|
-
.addColumn('created_at', 'integer', (col) => col.notNull())
|
|
511
|
-
.addColumn('version', 'integer', (col) => col.notNull())
|
|
512
|
-
.execute()
|
|
513
|
-
.catch(() => {}); // Ignore if already exists
|
|
514
|
-
|
|
515
|
-
// Create run_state table
|
|
516
|
-
await this.db.schema
|
|
517
|
-
.createTable('run_state')
|
|
518
|
-
.ifNotExists()
|
|
519
|
-
.addColumn('thread_id', 'text', (col) => col.primaryKey())
|
|
520
|
-
.addColumn('is_running', 'integer', (col) => col.defaultTo(0))
|
|
521
|
-
.addColumn('current_run_id', 'text')
|
|
522
|
-
.addColumn('server_id', 'text')
|
|
523
|
-
.addColumn('updated_at', 'integer', (col) => col.notNull())
|
|
524
|
-
.execute()
|
|
525
|
-
.catch(() => {}); // Ignore if already exists
|
|
526
|
-
|
|
527
|
-
// Create schema_version table
|
|
528
|
-
await this.db.schema
|
|
529
|
-
.createTable('schema_version')
|
|
530
|
-
.ifNotExists()
|
|
531
|
-
.addColumn('version', 'integer', (col) => col.primaryKey())
|
|
532
|
-
.addColumn('applied_at', 'integer', (col) => col.notNull())
|
|
533
|
-
.execute()
|
|
534
|
-
.catch(() => {}); // Ignore if already exists
|
|
535
|
-
|
|
536
|
-
// Create indexes
|
|
537
|
-
await this.db.schema
|
|
538
|
-
.createIndex('idx_thread_id')
|
|
539
|
-
.ifNotExists()
|
|
540
|
-
.on('agent_runs')
|
|
541
|
-
.column('thread_id')
|
|
542
|
-
.execute()
|
|
543
|
-
.catch(() => {});
|
|
544
|
-
|
|
545
|
-
await this.db.schema
|
|
546
|
-
.createIndex('idx_parent_run_id')
|
|
547
|
-
.ifNotExists()
|
|
548
|
-
.on('agent_runs')
|
|
549
|
-
.column('parent_run_id')
|
|
550
|
-
.execute()
|
|
551
|
-
.catch(() => {});
|
|
552
|
-
|
|
553
|
-
// Check and set schema version
|
|
554
|
-
const currentVersion = await this.db
|
|
555
|
-
.selectFrom('schema_version')
|
|
556
|
-
.orderBy('version', 'desc')
|
|
557
|
-
.limit(1)
|
|
558
|
-
.selectAll()
|
|
559
|
-
.executeTakeFirst();
|
|
560
|
-
|
|
561
|
-
if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
|
|
562
|
-
await this.db
|
|
563
|
-
.insertInto('schema_version')
|
|
564
|
-
.values({
|
|
565
|
-
version: SCHEMA_VERSION,
|
|
566
|
-
applied_at: Date.now()
|
|
567
|
-
})
|
|
568
|
-
.onConflict((oc) => oc
|
|
569
|
-
.column('version')
|
|
570
|
-
.doUpdateSet({ applied_at: Date.now() })
|
|
571
|
-
)
|
|
572
|
-
.execute();
|
|
573
|
-
}
|
|
574
|
-
} catch {
|
|
575
|
-
// Schema initialization might fail if DB is closed, ignore
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
private async storeRun(
|
|
580
|
-
threadId: string,
|
|
581
|
-
runId: string,
|
|
582
|
-
events: BaseEvent[],
|
|
583
|
-
input: RunAgentInput,
|
|
584
|
-
parentRunId: string | null
|
|
585
|
-
): Promise<void> {
|
|
586
|
-
await this.db.insertInto('agent_runs')
|
|
587
|
-
.values({
|
|
588
|
-
thread_id: threadId,
|
|
589
|
-
run_id: runId,
|
|
590
|
-
parent_run_id: parentRunId,
|
|
591
|
-
events: JSON.stringify(events),
|
|
592
|
-
input: JSON.stringify(input),
|
|
593
|
-
created_at: Date.now(),
|
|
594
|
-
version: SCHEMA_VERSION
|
|
595
|
-
})
|
|
596
|
-
.execute();
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
private async getHistoricRuns(threadId: string): Promise<AgentRunRecord[]> {
|
|
600
|
-
const rows = await this.db
|
|
601
|
-
.selectFrom('agent_runs')
|
|
602
|
-
.where('thread_id', '=', threadId)
|
|
603
|
-
.orderBy('created_at', 'asc')
|
|
604
|
-
.selectAll()
|
|
605
|
-
.execute();
|
|
606
|
-
|
|
607
|
-
return rows.map(row => ({
|
|
608
|
-
id: Number(row.id),
|
|
609
|
-
thread_id: row.thread_id,
|
|
610
|
-
run_id: row.run_id,
|
|
611
|
-
parent_run_id: row.parent_run_id,
|
|
612
|
-
events: row.events,
|
|
613
|
-
input: row.input,
|
|
614
|
-
created_at: row.created_at,
|
|
615
|
-
version: row.version
|
|
616
|
-
}));
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
private async setRunState(
|
|
620
|
-
threadId: string,
|
|
621
|
-
isRunning: boolean,
|
|
622
|
-
runId?: string
|
|
623
|
-
): Promise<void> {
|
|
624
|
-
await this.db.insertInto('run_state')
|
|
625
|
-
.values({
|
|
626
|
-
thread_id: threadId,
|
|
627
|
-
is_running: isRunning ? 1 : 0,
|
|
628
|
-
current_run_id: runId ?? null,
|
|
629
|
-
server_id: this.serverId,
|
|
630
|
-
updated_at: Date.now()
|
|
631
|
-
})
|
|
632
|
-
.onConflict((oc) => oc
|
|
633
|
-
.column('thread_id')
|
|
634
|
-
.doUpdateSet({
|
|
635
|
-
is_running: isRunning ? 1 : 0,
|
|
636
|
-
current_run_id: runId ?? null,
|
|
637
|
-
server_id: this.serverId,
|
|
638
|
-
updated_at: Date.now()
|
|
639
|
-
})
|
|
640
|
-
)
|
|
641
|
-
.execute();
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
async close(): Promise<void> {
|
|
645
|
-
await this.db.destroy();
|
|
646
|
-
this.redis.disconnect();
|
|
647
|
-
this.redisSub.disconnect();
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
private generateServerId(): string {
|
|
651
|
-
return `server-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
652
|
-
}
|
|
653
|
-
}
|