@agi-cli/server 0.1.61 → 0.1.63
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 +47 -0
- package/src/routes/git.ts +514 -426
- package/src/runtime/cache-optimizer.ts +51 -29
- package/src/runtime/db-operations.ts +48 -43
- package/src/runtime/runner.ts +248 -99
- package/src/runtime/stream-handlers.ts +209 -175
|
@@ -8,26 +8,17 @@ 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
|
-
|
|
26
11
|
type StepFinishEvent = {
|
|
27
|
-
usage?:
|
|
12
|
+
usage?: {
|
|
13
|
+
inputTokens?: number;
|
|
14
|
+
outputTokens?: number;
|
|
15
|
+
totalTokens?: number;
|
|
16
|
+
cachedInputTokens?: number;
|
|
17
|
+
reasoningTokens?: number;
|
|
18
|
+
};
|
|
28
19
|
finishReason?: string;
|
|
29
20
|
response?: unknown;
|
|
30
|
-
experimental_providerMetadata?:
|
|
21
|
+
experimental_providerMetadata?: Record<string, any>;
|
|
31
22
|
};
|
|
32
23
|
|
|
33
24
|
type FinishEvent = {
|
|
@@ -51,19 +42,19 @@ export function createStepFinishHandler(
|
|
|
51
42
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
52
43
|
getCurrentPartId: () => string,
|
|
53
44
|
getStepIndex: () => number,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
sharedCtx: ToolAdapterContext,
|
|
46
|
+
updateCurrentPartId: (id: string) => void,
|
|
47
|
+
updateAccumulated: (text: string) => void,
|
|
57
48
|
incrementStepIndex: () => number,
|
|
58
49
|
updateSessionTokensIncrementalFn: (
|
|
59
|
-
usage:
|
|
60
|
-
providerMetadata:
|
|
50
|
+
usage: any,
|
|
51
|
+
providerMetadata: Record<string, any> | undefined,
|
|
61
52
|
opts: RunOpts,
|
|
62
53
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
63
54
|
) => Promise<void>,
|
|
64
55
|
updateMessageTokensIncrementalFn: (
|
|
65
|
-
usage:
|
|
66
|
-
providerMetadata:
|
|
56
|
+
usage: any,
|
|
57
|
+
providerMetadata: Record<string, any> | undefined,
|
|
67
58
|
opts: RunOpts,
|
|
68
59
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
69
60
|
) => Promise<void>,
|
|
@@ -78,11 +69,9 @@ export function createStepFinishHandler(
|
|
|
78
69
|
.update(messageParts)
|
|
79
70
|
.set({ completedAt: finishedAt })
|
|
80
71
|
.where(eq(messageParts.id, currentPartId));
|
|
81
|
-
} catch
|
|
82
|
-
console.error('[createStepFinishHandler] Failed to update part', err);
|
|
83
|
-
}
|
|
72
|
+
} catch {}
|
|
84
73
|
|
|
85
|
-
// Update
|
|
74
|
+
// Update token counts incrementally after each step
|
|
86
75
|
if (step.usage) {
|
|
87
76
|
try {
|
|
88
77
|
await updateSessionTokensIncrementalFn(
|
|
@@ -91,81 +80,126 @@ export function createStepFinishHandler(
|
|
|
91
80
|
opts,
|
|
92
81
|
db,
|
|
93
82
|
);
|
|
83
|
+
} catch {}
|
|
84
|
+
|
|
85
|
+
try {
|
|
94
86
|
await updateMessageTokensIncrementalFn(
|
|
95
87
|
step.usage,
|
|
96
88
|
step.experimental_providerMetadata,
|
|
97
89
|
opts,
|
|
98
90
|
db,
|
|
99
91
|
);
|
|
100
|
-
} catch
|
|
101
|
-
console.error('[createStepFinishHandler] Token update failed', err);
|
|
102
|
-
}
|
|
92
|
+
} catch {}
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
try {
|
|
96
|
+
publish({
|
|
97
|
+
type: 'finish-step',
|
|
98
|
+
sessionId: opts.sessionId,
|
|
99
|
+
payload: {
|
|
100
|
+
stepIndex,
|
|
101
|
+
usage: step.usage,
|
|
102
|
+
finishReason: step.finishReason,
|
|
103
|
+
response: step.response,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
if (step.usage) {
|
|
107
|
+
publish({
|
|
108
|
+
type: 'usage',
|
|
109
|
+
sessionId: opts.sessionId,
|
|
110
|
+
payload: { stepIndex, ...step.usage },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} catch {}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
try {
|
|
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 {}
|
|
116
137
|
};
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
/**
|
|
120
|
-
* Creates the
|
|
141
|
+
* Creates the onError handler for the stream
|
|
121
142
|
*/
|
|
122
|
-
export function
|
|
143
|
+
export function createErrorHandler(
|
|
123
144
|
opts: RunOpts,
|
|
124
145
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
opts: RunOpts,
|
|
128
|
-
db: Awaited<ReturnType<typeof getDb>>,
|
|
129
|
-
) => Promise<void>,
|
|
130
|
-
_getAccumulated: () => string,
|
|
131
|
-
_abortController: AbortController,
|
|
146
|
+
getStepIndex: () => number,
|
|
147
|
+
sharedCtx: ToolAdapterContext,
|
|
132
148
|
) {
|
|
133
|
-
return async (
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
return async (err: unknown) => {
|
|
150
|
+
const errorPayload = toErrorPayload(err);
|
|
151
|
+
const isApiError = APICallError.isInstance(err);
|
|
152
|
+
const stepIndex = getStepIndex();
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
// Create error part for UI display
|
|
155
|
+
const errorPartId = crypto.randomUUID();
|
|
156
|
+
await db.insert(messageParts).values({
|
|
157
|
+
id: errorPartId,
|
|
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
|
+
});
|
|
141
174
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
175
|
+
// Update message status
|
|
176
|
+
await db
|
|
177
|
+
.update(messages)
|
|
178
|
+
.set({
|
|
179
|
+
status: 'error',
|
|
180
|
+
error: errorPayload.message,
|
|
181
|
+
errorType: errorPayload.type,
|
|
182
|
+
errorDetails: JSON.stringify({
|
|
183
|
+
...errorPayload.details,
|
|
184
|
+
isApiError,
|
|
185
|
+
}),
|
|
186
|
+
isAborted: false,
|
|
187
|
+
})
|
|
188
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
152
189
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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,
|
|
190
|
+
// Publish enhanced error event
|
|
191
|
+
publish({
|
|
192
|
+
type: 'error',
|
|
193
|
+
sessionId: opts.sessionId,
|
|
194
|
+
payload: {
|
|
165
195
|
messageId: opts.assistantMessageId,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
196
|
+
partId: errorPartId,
|
|
197
|
+
error: errorPayload.message,
|
|
198
|
+
errorType: errorPayload.type,
|
|
199
|
+
details: errorPayload.details,
|
|
200
|
+
isAborted: false,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
169
203
|
};
|
|
170
204
|
}
|
|
171
205
|
|
|
@@ -175,116 +209,116 @@ export function createFinishHandler(
|
|
|
175
209
|
export function createAbortHandler(
|
|
176
210
|
opts: RunOpts,
|
|
177
211
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
178
|
-
|
|
212
|
+
getStepIndex: () => number,
|
|
213
|
+
sharedCtx: ToolAdapterContext,
|
|
179
214
|
) {
|
|
180
|
-
return async (
|
|
181
|
-
|
|
182
|
-
await db
|
|
183
|
-
.update(messages)
|
|
184
|
-
.set({ status: 'aborted', finishedAt: new Date() })
|
|
185
|
-
.where(eq(messages.id, opts.assistantMessageId));
|
|
215
|
+
return async ({ steps }: AbortEvent) => {
|
|
216
|
+
const stepIndex = getStepIndex();
|
|
186
217
|
|
|
187
|
-
|
|
188
|
-
|
|
218
|
+
// Create abort part for UI
|
|
219
|
+
const abortPartId = crypto.randomUUID();
|
|
220
|
+
await db.insert(messageParts).values({
|
|
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));
|
|
253
|
+
|
|
254
|
+
// Publish abort event
|
|
255
|
+
publish({
|
|
256
|
+
type: 'error',
|
|
257
|
+
sessionId: opts.sessionId,
|
|
258
|
+
payload: {
|
|
189
259
|
messageId: opts.assistantMessageId,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
260
|
+
partId: abortPartId,
|
|
261
|
+
error: 'Generation stopped by user',
|
|
262
|
+
errorType: 'abort',
|
|
263
|
+
isAborted: true,
|
|
264
|
+
stepsCompleted: steps.length,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
195
267
|
};
|
|
196
268
|
}
|
|
197
269
|
|
|
198
270
|
/**
|
|
199
|
-
* Creates the
|
|
271
|
+
* Creates the onFinish handler for the stream
|
|
200
272
|
*/
|
|
201
|
-
export function
|
|
273
|
+
export function createFinishHandler(
|
|
202
274
|
opts: RunOpts,
|
|
203
275
|
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>,
|
|
204
282
|
) {
|
|
205
|
-
return async (
|
|
206
|
-
console.error('[createErrorHandler] Stream error:', err);
|
|
207
|
-
|
|
283
|
+
return async (fin: FinishEvent) => {
|
|
208
284
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let errorStack: string | undefined;
|
|
285
|
+
await ensureFinishToolCalled();
|
|
286
|
+
} catch {}
|
|
212
287
|
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
}
|
|
288
|
+
// Note: Token updates are handled incrementally in onStepFinish
|
|
289
|
+
// Do NOT add fin.usage here as it would cause double-counting
|
|
224
290
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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,
|
|
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
|
-
}
|
|
291
|
+
try {
|
|
292
|
+
await completeAssistantMessageFn(fin, opts, db);
|
|
293
|
+
} catch {}
|
|
249
294
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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();
|
|
295
|
+
// Use session totals from DB for accurate cost calculation
|
|
296
|
+
const sessRows = await db
|
|
297
|
+
.select()
|
|
298
|
+
.from(messages)
|
|
299
|
+
.where(eq(messages.id, opts.assistantMessageId));
|
|
265
300
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
301
|
+
const usage = sessRows[0]
|
|
302
|
+
? {
|
|
303
|
+
inputTokens: Number(sessRows[0].promptTokens ?? 0),
|
|
304
|
+
outputTokens: Number(sessRows[0].completionTokens ?? 0),
|
|
305
|
+
totalTokens: Number(sessRows[0].totalTokens ?? 0),
|
|
306
|
+
}
|
|
307
|
+
: fin.usage;
|
|
269
308
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.update(messageParts)
|
|
274
|
-
.set({ content: accumulated })
|
|
275
|
-
.where(eq(messageParts.id, currentPartId));
|
|
276
|
-
}
|
|
309
|
+
const costUsd = usage
|
|
310
|
+
? estimateModelCostUsd(opts.provider, opts.model, usage)
|
|
311
|
+
: undefined;
|
|
277
312
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
313
|
+
publish({
|
|
314
|
+
type: 'message.completed',
|
|
315
|
+
sessionId: opts.sessionId,
|
|
316
|
+
payload: {
|
|
317
|
+
id: opts.assistantMessageId,
|
|
318
|
+
usage,
|
|
319
|
+
costUsd,
|
|
320
|
+
finishReason: fin.finishReason,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
289
323
|
};
|
|
290
324
|
}
|