@agi-cli/server 0.1.58 → 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/index.ts +1 -1
- package/src/openapi/spec.ts +641 -0
- package/src/runtime/agent-registry.ts +3 -2
- package/src/runtime/cache-optimizer.ts +93 -0
- package/src/runtime/context-optimizer.ts +206 -0
- package/src/runtime/db-operations.ts +173 -39
- package/src/runtime/history-truncator.ts +26 -0
- package/src/runtime/runner.ts +116 -240
- package/src/runtime/session-manager.ts +2 -0
- package/src/runtime/stream-handlers.ts +199 -184
- package/src/tools/adapter.ts +261 -173
|
@@ -8,10 +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
|
+
interface ProviderMetadata {
|
|
12
|
+
openai?: {
|
|
13
|
+
cachedPromptTokens?: number;
|
|
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
|
+
|
|
11
26
|
type StepFinishEvent = {
|
|
12
|
-
usage?:
|
|
27
|
+
usage?: UsageData;
|
|
13
28
|
finishReason?: string;
|
|
14
29
|
response?: unknown;
|
|
30
|
+
experimental_providerMetadata?: ProviderMetadata;
|
|
15
31
|
};
|
|
16
32
|
|
|
17
33
|
type FinishEvent = {
|
|
@@ -35,10 +51,22 @@ export function createStepFinishHandler(
|
|
|
35
51
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
36
52
|
getCurrentPartId: () => string,
|
|
37
53
|
getStepIndex: () => number,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
_sharedCtx: ToolAdapterContext,
|
|
55
|
+
_updateCurrentPartId: (id: string) => void,
|
|
56
|
+
_updateAccumulated: (text: string) => void,
|
|
41
57
|
incrementStepIndex: () => number,
|
|
58
|
+
updateSessionTokensIncrementalFn: (
|
|
59
|
+
usage: UsageData,
|
|
60
|
+
providerMetadata: ProviderMetadata | undefined,
|
|
61
|
+
opts: RunOpts,
|
|
62
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
63
|
+
) => Promise<void>,
|
|
64
|
+
updateMessageTokensIncrementalFn: (
|
|
65
|
+
usage: UsageData,
|
|
66
|
+
providerMetadata: ProviderMetadata | undefined,
|
|
67
|
+
opts: RunOpts,
|
|
68
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
69
|
+
) => Promise<void>,
|
|
42
70
|
) {
|
|
43
71
|
return async (step: StepFinishEvent) => {
|
|
44
72
|
const finishedAt = Date.now();
|
|
@@ -50,116 +78,94 @@ export function createStepFinishHandler(
|
|
|
50
78
|
.update(messageParts)
|
|
51
79
|
.set({ completedAt: finishedAt })
|
|
52
80
|
.where(eq(messageParts.id, currentPartId));
|
|
53
|
-
} catch {
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('[createStepFinishHandler] Failed to update part', err);
|
|
83
|
+
}
|
|
54
84
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
// Update tokens incrementally
|
|
86
|
+
if (step.usage) {
|
|
87
|
+
try {
|
|
88
|
+
await updateSessionTokensIncrementalFn(
|
|
89
|
+
step.usage,
|
|
90
|
+
step.experimental_providerMetadata,
|
|
91
|
+
opts,
|
|
92
|
+
db,
|
|
93
|
+
);
|
|
94
|
+
await updateMessageTokensIncrementalFn(
|
|
95
|
+
step.usage,
|
|
96
|
+
step.experimental_providerMetadata,
|
|
97
|
+
opts,
|
|
98
|
+
db,
|
|
99
|
+
);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[createStepFinishHandler] Token update failed', err);
|
|
72
102
|
}
|
|
73
|
-
}
|
|
103
|
+
}
|
|
74
104
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
content: JSON.stringify({ text: '' }),
|
|
87
|
-
agent: opts.agent,
|
|
88
|
-
provider: opts.provider,
|
|
89
|
-
model: opts.model,
|
|
90
|
-
startedAt: nowTs,
|
|
91
|
-
});
|
|
92
|
-
updateCurrentPartId(newPartId);
|
|
93
|
-
sharedCtx.assistantPartId = newPartId;
|
|
94
|
-
sharedCtx.stepIndex = newStepIndex;
|
|
95
|
-
updateAccumulated('');
|
|
96
|
-
} 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
|
+
|
|
115
|
+
incrementStepIndex();
|
|
97
116
|
};
|
|
98
117
|
}
|
|
99
118
|
|
|
100
119
|
/**
|
|
101
|
-
* Creates the
|
|
120
|
+
* Creates the onFinish handler for the stream
|
|
102
121
|
*/
|
|
103
|
-
export function
|
|
122
|
+
export function createFinishHandler(
|
|
104
123
|
opts: RunOpts,
|
|
105
124
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
completeAssistantMessageFn: (
|
|
126
|
+
fin: FinishEvent,
|
|
127
|
+
opts: RunOpts,
|
|
128
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
129
|
+
) => Promise<void>,
|
|
130
|
+
_getAccumulated: () => string,
|
|
131
|
+
_abortController: AbortController,
|
|
108
132
|
) {
|
|
109
|
-
return async (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const stepIndex = getStepIndex();
|
|
133
|
+
return async (fin: FinishEvent) => {
|
|
134
|
+
try {
|
|
135
|
+
await completeAssistantMessageFn(fin, opts, db);
|
|
113
136
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
messageId: opts.assistantMessageId,
|
|
119
|
-
index: await sharedCtx.nextIndex(),
|
|
120
|
-
stepIndex,
|
|
121
|
-
type: 'error',
|
|
122
|
-
content: JSON.stringify({
|
|
123
|
-
message: errorPayload.message,
|
|
124
|
-
type: errorPayload.type,
|
|
125
|
-
details: errorPayload.details,
|
|
126
|
-
isAborted: false,
|
|
127
|
-
}),
|
|
128
|
-
agent: opts.agent,
|
|
129
|
-
provider: opts.provider,
|
|
130
|
-
model: opts.model,
|
|
131
|
-
startedAt: Date.now(),
|
|
132
|
-
completedAt: Date.now(),
|
|
133
|
-
});
|
|
137
|
+
const msgRows = await db
|
|
138
|
+
.select()
|
|
139
|
+
.from(messages)
|
|
140
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
134
141
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}),
|
|
146
|
-
isAborted: false,
|
|
147
|
-
})
|
|
148
|
-
.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
|
+
}
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
type: 'error',
|
|
153
|
-
sessionId: opts.sessionId,
|
|
154
|
-
payload: {
|
|
153
|
+
publish('stream:finished', {
|
|
154
|
+
sessionId: opts.sessionId,
|
|
155
155
|
messageId: opts.assistantMessageId,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
169
|
};
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -169,107 +175,116 @@ export function createErrorHandler(
|
|
|
169
175
|
export function createAbortHandler(
|
|
170
176
|
opts: RunOpts,
|
|
171
177
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
172
|
-
|
|
173
|
-
sharedCtx: ToolAdapterContext,
|
|
178
|
+
_abortController: AbortController,
|
|
174
179
|
) {
|
|
175
|
-
return async (
|
|
176
|
-
|
|
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));
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
type: 'abort',
|
|
189
|
-
isAborted: true,
|
|
190
|
-
stepsCompleted: steps.length,
|
|
191
|
-
}),
|
|
192
|
-
agent: opts.agent,
|
|
193
|
-
provider: opts.provider,
|
|
194
|
-
model: opts.model,
|
|
195
|
-
startedAt: Date.now(),
|
|
196
|
-
completedAt: Date.now(),
|
|
197
|
-
});
|
|
187
|
+
publish('stream:aborted', {
|
|
188
|
+
sessionId: opts.sessionId,
|
|
189
|
+
messageId: opts.assistantMessageId,
|
|
190
|
+
assistantMessageId: opts.assistantMessageId,
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error('[createAbortHandler] Error in onAbort', err);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
abortedAt: Date.now(),
|
|
209
|
-
}),
|
|
210
|
-
isAborted: true,
|
|
211
|
-
})
|
|
212
|
-
.where(eq(messages.id, opts.assistantMessageId));
|
|
198
|
+
/**
|
|
199
|
+
* Creates the error handler for the stream
|
|
200
|
+
*/
|
|
201
|
+
export function createErrorHandler(
|
|
202
|
+
opts: RunOpts,
|
|
203
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
204
|
+
) {
|
|
205
|
+
return async (err: unknown) => {
|
|
206
|
+
console.error('[createErrorHandler] Stream error:', err);
|
|
213
207
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
208
|
+
try {
|
|
209
|
+
let errorMessage = 'Unknown error';
|
|
210
|
+
let errorType = 'UNKNOWN_ERROR';
|
|
211
|
+
let errorStack: string | undefined;
|
|
212
|
+
|
|
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
|
+
}
|
|
224
|
+
|
|
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));
|
|
233
|
+
|
|
234
|
+
publish('stream:error', {
|
|
235
|
+
sessionId: opts.sessionId,
|
|
219
236
|
messageId: opts.assistantMessageId,
|
|
220
|
-
|
|
221
|
-
error:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
+
}
|
|
227
247
|
};
|
|
228
248
|
}
|
|
229
249
|
|
|
230
250
|
/**
|
|
231
|
-
* Creates the
|
|
251
|
+
* Creates the text delta handler for the stream
|
|
232
252
|
*/
|
|
233
|
-
export function
|
|
253
|
+
export function createTextHandler(
|
|
234
254
|
opts: RunOpts,
|
|
235
255
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
) => Promise<void>,
|
|
242
|
-
completeAssistantMessageFn: (
|
|
243
|
-
fin: FinishEvent,
|
|
244
|
-
opts: RunOpts,
|
|
245
|
-
db: Awaited<ReturnType<typeof getDb>>,
|
|
246
|
-
) => Promise<void>,
|
|
256
|
+
getCurrentPartId: () => string,
|
|
257
|
+
getStepIndex: () => number,
|
|
258
|
+
_updateCurrentPartId: (id: string) => void,
|
|
259
|
+
updateAccumulated: (text: string) => void,
|
|
260
|
+
getAccumulated: () => string,
|
|
247
261
|
) {
|
|
248
|
-
return async (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} catch {}
|
|
262
|
+
return async (textDelta: string) => {
|
|
263
|
+
const currentPartId = getCurrentPartId();
|
|
264
|
+
const stepIndex = getStepIndex();
|
|
252
265
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
266
|
+
// Accumulate the text
|
|
267
|
+
const accumulated = getAccumulated() + textDelta;
|
|
268
|
+
updateAccumulated(accumulated);
|
|
256
269
|
|
|
257
270
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
if (currentPartId) {
|
|
272
|
+
await db
|
|
273
|
+
.update(messageParts)
|
|
274
|
+
.set({ content: accumulated })
|
|
275
|
+
.where(eq(messageParts.id, currentPartId));
|
|
276
|
+
}
|
|
260
277
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
},
|
|
273
|
-
});
|
|
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
|
+
}
|
|
274
289
|
};
|
|
275
290
|
}
|