@agi-cli/server 0.1.59 → 0.1.61
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/package.json +3 -3
- package/src/openapi/spec.ts +641 -0
- package/src/runtime/cache-optimizer.ts +29 -51
- package/src/runtime/context-optimizer.ts +20 -6
- package/src/runtime/db-operations.ts +43 -48
- package/src/runtime/history-truncator.ts +26 -0
- package/src/runtime/runner.ts +99 -248
- package/src/runtime/stream-handlers.ts +175 -209
|
@@ -8,17 +8,26 @@ import { toErrorPayload } from './error-handling.ts';
|
|
|
8
8
|
import type { RunOpts } from './session-queue.ts';
|
|
9
9
|
import type { ToolAdapterContext } from '../tools/adapter.ts';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
outputTokens?: number;
|
|
15
|
-
totalTokens?: number;
|
|
16
|
-
cachedInputTokens?: number;
|
|
17
|
-
reasoningTokens?: number;
|
|
11
|
+
interface ProviderMetadata {
|
|
12
|
+
openai?: {
|
|
13
|
+
cachedPromptTokens?: number;
|
|
18
14
|
};
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface UsageData {
|
|
19
|
+
inputTokens?: number;
|
|
20
|
+
outputTokens?: number;
|
|
21
|
+
totalTokens?: number;
|
|
22
|
+
cachedInputTokens?: number;
|
|
23
|
+
reasoningTokens?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type StepFinishEvent = {
|
|
27
|
+
usage?: UsageData;
|
|
19
28
|
finishReason?: string;
|
|
20
29
|
response?: unknown;
|
|
21
|
-
experimental_providerMetadata?:
|
|
30
|
+
experimental_providerMetadata?: ProviderMetadata;
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
type FinishEvent = {
|
|
@@ -42,19 +51,19 @@ export function createStepFinishHandler(
|
|
|
42
51
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
43
52
|
getCurrentPartId: () => string,
|
|
44
53
|
getStepIndex: () => number,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
_sharedCtx: ToolAdapterContext,
|
|
55
|
+
_updateCurrentPartId: (id: string) => void,
|
|
56
|
+
_updateAccumulated: (text: string) => void,
|
|
48
57
|
incrementStepIndex: () => number,
|
|
49
58
|
updateSessionTokensIncrementalFn: (
|
|
50
|
-
usage:
|
|
51
|
-
providerMetadata:
|
|
59
|
+
usage: UsageData,
|
|
60
|
+
providerMetadata: ProviderMetadata | undefined,
|
|
52
61
|
opts: RunOpts,
|
|
53
62
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
54
63
|
) => Promise<void>,
|
|
55
64
|
updateMessageTokensIncrementalFn: (
|
|
56
|
-
usage:
|
|
57
|
-
providerMetadata:
|
|
65
|
+
usage: UsageData,
|
|
66
|
+
providerMetadata: ProviderMetadata | undefined,
|
|
58
67
|
opts: RunOpts,
|
|
59
68
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
60
69
|
) => Promise<void>,
|
|
@@ -69,9 +78,11 @@ export function createStepFinishHandler(
|
|
|
69
78
|
.update(messageParts)
|
|
70
79
|
.set({ completedAt: finishedAt })
|
|
71
80
|
.where(eq(messageParts.id, currentPartId));
|
|
72
|
-
} catch {
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('[createStepFinishHandler] Failed to update part', err);
|
|
83
|
+
}
|
|
73
84
|
|
|
74
|
-
// Update
|
|
85
|
+
// Update tokens incrementally
|
|
75
86
|
if (step.usage) {
|
|
76
87
|
try {
|
|
77
88
|
await updateSessionTokensIncrementalFn(
|
|
@@ -80,126 +91,81 @@ export function createStepFinishHandler(
|
|
|
80
91
|
opts,
|
|
81
92
|
db,
|
|
82
93
|
);
|
|
83
|
-
} catch {}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
94
|
await updateMessageTokensIncrementalFn(
|
|
87
95
|
step.usage,
|
|
88
96
|
step.experimental_providerMetadata,
|
|
89
97
|
opts,
|
|
90
98
|
db,
|
|
91
99
|
);
|
|
92
|
-
} catch {
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[createStepFinishHandler] Token update failed', err);
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
if (step.usage) {
|
|
107
|
-
publish({
|
|
108
|
-
type: 'usage',
|
|
109
|
-
sessionId: opts.sessionId,
|
|
110
|
-
payload: { stepIndex, ...step.usage },
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
} catch {}
|
|
105
|
+
// Publish step-finished event
|
|
106
|
+
publish('stream:step-finished', {
|
|
107
|
+
sessionId: opts.sessionId,
|
|
108
|
+
messageId: opts.assistantMessageId,
|
|
109
|
+
assistantMessageId: opts.assistantMessageId,
|
|
110
|
+
stepIndex,
|
|
111
|
+
finishReason: step.finishReason,
|
|
112
|
+
usage: step.usage,
|
|
113
|
+
});
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
const newStepIndex = incrementStepIndex();
|
|
117
|
-
const newPartId = crypto.randomUUID();
|
|
118
|
-
const index = await sharedCtx.nextIndex();
|
|
119
|
-
const nowTs = Date.now();
|
|
120
|
-
await db.insert(messageParts).values({
|
|
121
|
-
id: newPartId,
|
|
122
|
-
messageId: opts.assistantMessageId,
|
|
123
|
-
index,
|
|
124
|
-
stepIndex: newStepIndex,
|
|
125
|
-
type: 'text',
|
|
126
|
-
content: JSON.stringify({ text: '' }),
|
|
127
|
-
agent: opts.agent,
|
|
128
|
-
provider: opts.provider,
|
|
129
|
-
model: opts.model,
|
|
130
|
-
startedAt: nowTs,
|
|
131
|
-
});
|
|
132
|
-
updateCurrentPartId(newPartId);
|
|
133
|
-
sharedCtx.assistantPartId = newPartId;
|
|
134
|
-
sharedCtx.stepIndex = newStepIndex;
|
|
135
|
-
updateAccumulated('');
|
|
136
|
-
} catch {}
|
|
115
|
+
incrementStepIndex();
|
|
137
116
|
};
|
|
138
117
|
}
|
|
139
118
|
|
|
140
119
|
/**
|
|
141
|
-
* Creates the
|
|
120
|
+
* Creates the onFinish handler for the stream
|
|
142
121
|
*/
|
|
143
|
-
export function
|
|
122
|
+
export function createFinishHandler(
|
|
144
123
|
opts: RunOpts,
|
|
145
124
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
146
|
-
|
|
147
|
-
|
|
125
|
+
completeAssistantMessageFn: (
|
|
126
|
+
fin: FinishEvent,
|
|
127
|
+
opts: RunOpts,
|
|
128
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
129
|
+
) => Promise<void>,
|
|
130
|
+
_getAccumulated: () => string,
|
|
131
|
+
_abortController: AbortController,
|
|
148
132
|
) {
|
|
149
|
-
return async (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const stepIndex = getStepIndex();
|
|
133
|
+
return async (fin: FinishEvent) => {
|
|
134
|
+
try {
|
|
135
|
+
await completeAssistantMessageFn(fin, opts, db);
|
|
153
136
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
messageId: opts.assistantMessageId,
|
|
159
|
-
index: await sharedCtx.nextIndex(),
|
|
160
|
-
stepIndex,
|
|
161
|
-
type: 'error',
|
|
162
|
-
content: JSON.stringify({
|
|
163
|
-
message: errorPayload.message,
|
|
164
|
-
type: errorPayload.type,
|
|
165
|
-
details: errorPayload.details,
|
|
166
|
-
isAborted: false,
|
|
167
|
-
}),
|
|
168
|
-
agent: opts.agent,
|
|
169
|
-
provider: opts.provider,
|
|
170
|
-
model: opts.model,
|
|
171
|
-
startedAt: Date.now(),
|
|
172
|
-
completedAt: Date.now(),
|
|
173
|
-
});
|
|
137
|
+
const msgRows = await db
|
|
138
|
+
.select()
|
|
139
|
+
.from(messages)
|
|
140
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
174
141
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}),
|
|
186
|
-
isAborted: false,
|
|
187
|
-
})
|
|
188
|
-
.where(eq(messages.id, opts.assistantMessageId));
|
|
142
|
+
let estimatedCost = 0;
|
|
143
|
+
if (msgRows.length > 0 && msgRows[0]) {
|
|
144
|
+
const msg = msgRows[0];
|
|
145
|
+
estimatedCost = estimateModelCostUsd(
|
|
146
|
+
opts.provider,
|
|
147
|
+
opts.model,
|
|
148
|
+
Number(msg.promptTokens ?? 0),
|
|
149
|
+
Number(msg.completionTokens ?? 0),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
189
152
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
type: 'error',
|
|
193
|
-
sessionId: opts.sessionId,
|
|
194
|
-
payload: {
|
|
153
|
+
publish('stream:finished', {
|
|
154
|
+
sessionId: opts.sessionId,
|
|
195
155
|
messageId: opts.assistantMessageId,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
156
|
+
assistantMessageId: opts.assistantMessageId,
|
|
157
|
+
usage: fin.usage,
|
|
158
|
+
finishReason: fin.finishReason,
|
|
159
|
+
estimatedCost,
|
|
160
|
+
});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error('[createFinishHandler] Error in onFinish', err);
|
|
163
|
+
publish('stream:error', {
|
|
164
|
+
sessionId: opts.sessionId,
|
|
165
|
+
messageId: opts.assistantMessageId,
|
|
166
|
+
error: toErrorPayload(err),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
203
169
|
};
|
|
204
170
|
}
|
|
205
171
|
|
|
@@ -209,116 +175,116 @@ export function createErrorHandler(
|
|
|
209
175
|
export function createAbortHandler(
|
|
210
176
|
opts: RunOpts,
|
|
211
177
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
212
|
-
|
|
213
|
-
sharedCtx: ToolAdapterContext,
|
|
178
|
+
_abortController: AbortController,
|
|
214
179
|
) {
|
|
215
|
-
return async (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
id: abortPartId,
|
|
222
|
-
messageId: opts.assistantMessageId,
|
|
223
|
-
index: await sharedCtx.nextIndex(),
|
|
224
|
-
stepIndex,
|
|
225
|
-
type: 'error',
|
|
226
|
-
content: JSON.stringify({
|
|
227
|
-
message: 'Generation stopped by user',
|
|
228
|
-
type: 'abort',
|
|
229
|
-
isAborted: true,
|
|
230
|
-
stepsCompleted: steps.length,
|
|
231
|
-
}),
|
|
232
|
-
agent: opts.agent,
|
|
233
|
-
provider: opts.provider,
|
|
234
|
-
model: opts.model,
|
|
235
|
-
startedAt: Date.now(),
|
|
236
|
-
completedAt: Date.now(),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// Store abort info
|
|
240
|
-
await db
|
|
241
|
-
.update(messages)
|
|
242
|
-
.set({
|
|
243
|
-
status: 'error',
|
|
244
|
-
error: 'Generation stopped by user',
|
|
245
|
-
errorType: 'abort',
|
|
246
|
-
errorDetails: JSON.stringify({
|
|
247
|
-
stepsCompleted: steps.length,
|
|
248
|
-
abortedAt: Date.now(),
|
|
249
|
-
}),
|
|
250
|
-
isAborted: true,
|
|
251
|
-
})
|
|
252
|
-
.where(eq(messages.id, opts.assistantMessageId));
|
|
180
|
+
return async (_event: AbortEvent) => {
|
|
181
|
+
try {
|
|
182
|
+
await db
|
|
183
|
+
.update(messages)
|
|
184
|
+
.set({ status: 'aborted', finishedAt: new Date() })
|
|
185
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
253
186
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
type: 'error',
|
|
257
|
-
sessionId: opts.sessionId,
|
|
258
|
-
payload: {
|
|
187
|
+
publish('stream:aborted', {
|
|
188
|
+
sessionId: opts.sessionId,
|
|
259
189
|
messageId: opts.assistantMessageId,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
},
|
|
266
|
-
});
|
|
190
|
+
assistantMessageId: opts.assistantMessageId,
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error('[createAbortHandler] Error in onAbort', err);
|
|
194
|
+
}
|
|
267
195
|
};
|
|
268
196
|
}
|
|
269
197
|
|
|
270
198
|
/**
|
|
271
|
-
* Creates the
|
|
199
|
+
* Creates the error handler for the stream
|
|
272
200
|
*/
|
|
273
|
-
export function
|
|
201
|
+
export function createErrorHandler(
|
|
274
202
|
opts: RunOpts,
|
|
275
203
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
276
|
-
ensureFinishToolCalled: () => Promise<void>,
|
|
277
|
-
completeAssistantMessageFn: (
|
|
278
|
-
fin: FinishEvent,
|
|
279
|
-
opts: RunOpts,
|
|
280
|
-
db: Awaited<ReturnType<typeof getDb>>,
|
|
281
|
-
) => Promise<void>,
|
|
282
204
|
) {
|
|
283
|
-
return async (
|
|
205
|
+
return async (err: unknown) => {
|
|
206
|
+
console.error('[createErrorHandler] Stream error:', err);
|
|
207
|
+
|
|
284
208
|
try {
|
|
285
|
-
|
|
286
|
-
|
|
209
|
+
let errorMessage = 'Unknown error';
|
|
210
|
+
let errorType = 'UNKNOWN_ERROR';
|
|
211
|
+
let errorStack: string | undefined;
|
|
287
212
|
|
|
288
|
-
|
|
289
|
-
|
|
213
|
+
if (err instanceof APICallError) {
|
|
214
|
+
errorMessage = err.message;
|
|
215
|
+
errorType = 'API_CALL_ERROR';
|
|
216
|
+
errorStack = err.stack;
|
|
217
|
+
} else if (err instanceof Error) {
|
|
218
|
+
errorMessage = err.message;
|
|
219
|
+
errorType = err.name || 'ERROR';
|
|
220
|
+
errorStack = err.stack;
|
|
221
|
+
} else if (typeof err === 'string') {
|
|
222
|
+
errorMessage = err;
|
|
223
|
+
}
|
|
290
224
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
225
|
+
await db
|
|
226
|
+
.update(messages)
|
|
227
|
+
.set({
|
|
228
|
+
status: 'error',
|
|
229
|
+
finishedAt: new Date(),
|
|
230
|
+
error: errorMessage,
|
|
231
|
+
})
|
|
232
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
294
233
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
234
|
+
publish('stream:error', {
|
|
235
|
+
sessionId: opts.sessionId,
|
|
236
|
+
messageId: opts.assistantMessageId,
|
|
237
|
+
assistantMessageId: opts.assistantMessageId,
|
|
238
|
+
error: {
|
|
239
|
+
message: errorMessage,
|
|
240
|
+
type: errorType,
|
|
241
|
+
stack: errorStack,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
} catch (dbErr) {
|
|
245
|
+
console.error('[createErrorHandler] Failed to save error to DB', dbErr);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
300
249
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Creates the text delta handler for the stream
|
|
252
|
+
*/
|
|
253
|
+
export function createTextHandler(
|
|
254
|
+
opts: RunOpts,
|
|
255
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
256
|
+
getCurrentPartId: () => string,
|
|
257
|
+
getStepIndex: () => number,
|
|
258
|
+
_updateCurrentPartId: (id: string) => void,
|
|
259
|
+
updateAccumulated: (text: string) => void,
|
|
260
|
+
getAccumulated: () => string,
|
|
261
|
+
) {
|
|
262
|
+
return async (textDelta: string) => {
|
|
263
|
+
const currentPartId = getCurrentPartId();
|
|
264
|
+
const stepIndex = getStepIndex();
|
|
308
265
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
266
|
+
// Accumulate the text
|
|
267
|
+
const accumulated = getAccumulated() + textDelta;
|
|
268
|
+
updateAccumulated(accumulated);
|
|
312
269
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
270
|
+
try {
|
|
271
|
+
if (currentPartId) {
|
|
272
|
+
await db
|
|
273
|
+
.update(messageParts)
|
|
274
|
+
.set({ content: accumulated })
|
|
275
|
+
.where(eq(messageParts.id, currentPartId));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
publish('stream:text-delta', {
|
|
279
|
+
sessionId: opts.sessionId,
|
|
280
|
+
messageId: opts.assistantMessageId,
|
|
281
|
+
assistantMessageId: opts.assistantMessageId,
|
|
282
|
+
stepIndex,
|
|
283
|
+
textDelta,
|
|
284
|
+
fullText: accumulated,
|
|
285
|
+
});
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error('[createTextHandler] Error updating text part', err);
|
|
288
|
+
}
|
|
323
289
|
};
|
|
324
290
|
}
|