@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
package/src/runtime/runner.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamText } from 'ai';
|
|
2
2
|
import { loadConfig } from '@agi-cli/sdk';
|
|
3
3
|
import { getDb } from '@agi-cli/database';
|
|
4
4
|
import { messageParts } from '@agi-cli/database/schema';
|
|
@@ -7,11 +7,8 @@ import { resolveModel } from './provider.ts';
|
|
|
7
7
|
import { resolveAgentConfig } from './agent-registry.ts';
|
|
8
8
|
import { composeSystemPrompt } from './prompt.ts';
|
|
9
9
|
import { discoverProjectTools } from '@agi-cli/sdk';
|
|
10
|
-
import {
|
|
11
|
-
import { publish, subscribe } from '../events/bus.ts';
|
|
12
|
-
import { debugLog, time } from './debug.ts';
|
|
10
|
+
import { publish } from '../events/bus.ts';
|
|
13
11
|
import { buildHistoryMessages } from './history-builder.ts';
|
|
14
|
-
import { toErrorPayload } from './error-handling.ts';
|
|
15
12
|
import { getMaxOutputTokens } from './token-utils.ts';
|
|
16
13
|
import {
|
|
17
14
|
type RunOpts,
|
|
@@ -22,16 +19,11 @@ import {
|
|
|
22
19
|
dequeueJob,
|
|
23
20
|
cleanupSession,
|
|
24
21
|
} from './session-queue.ts';
|
|
22
|
+
import { setupToolContext } from './tool-context-setup.ts';
|
|
25
23
|
import {
|
|
26
|
-
setupToolContext,
|
|
27
|
-
type RunnerToolContext,
|
|
28
|
-
} from './tool-context-setup.ts';
|
|
29
|
-
import {
|
|
30
|
-
updateSessionTokens,
|
|
31
24
|
updateSessionTokensIncremental,
|
|
32
25
|
updateMessageTokensIncremental,
|
|
33
26
|
completeAssistantMessage,
|
|
34
|
-
cleanupEmptyTextParts,
|
|
35
27
|
} from './db-operations.ts';
|
|
36
28
|
import {
|
|
37
29
|
createStepFinishHandler,
|
|
@@ -39,175 +31,38 @@ import {
|
|
|
39
31
|
createAbortHandler,
|
|
40
32
|
createFinishHandler,
|
|
41
33
|
} from './stream-handlers.ts';
|
|
34
|
+
import { addCacheControl } from './cache-optimizer.ts';
|
|
35
|
+
import { optimizeContext } from './context-optimizer.ts';
|
|
36
|
+
import { truncateHistory } from './history-truncator.ts';
|
|
42
37
|
|
|
43
38
|
/**
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
export function enqueueAssistantRun(opts: Omit<RunOpts, 'abortSignal'>) {
|
|
47
|
-
enqueueRun(opts, processQueue);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Aborts an active session.
|
|
52
|
-
*/
|
|
53
|
-
export function abortSession(sessionId: string) {
|
|
54
|
-
abortSessionQueue(sessionId);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Processes the queue of assistant runs for a session.
|
|
59
|
-
*/
|
|
60
|
-
async function processQueue(sessionId: string) {
|
|
61
|
-
const state = getRunnerState(sessionId);
|
|
62
|
-
if (!state) return;
|
|
63
|
-
if (state.running) return;
|
|
64
|
-
setRunning(sessionId, true);
|
|
65
|
-
|
|
66
|
-
while (state.queue.length > 0) {
|
|
67
|
-
const job = dequeueJob(sessionId);
|
|
68
|
-
if (!job) break;
|
|
69
|
-
try {
|
|
70
|
-
await runAssistant(job);
|
|
71
|
-
} catch (_err) {
|
|
72
|
-
// Swallow to keep the loop alive; event published by runner
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
setRunning(sessionId, false);
|
|
77
|
-
cleanupSession(sessionId);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Ensures the finish tool is called if not already observed.
|
|
82
|
-
*/
|
|
83
|
-
async function ensureFinishToolCalled(
|
|
84
|
-
finishObserved: boolean,
|
|
85
|
-
toolset: ReturnType<typeof adaptTools>,
|
|
86
|
-
sharedCtx: RunnerToolContext,
|
|
87
|
-
stepIndex: number,
|
|
88
|
-
) {
|
|
89
|
-
if (finishObserved || !toolset?.finish?.execute) return;
|
|
90
|
-
|
|
91
|
-
const finishInput = {} as const;
|
|
92
|
-
const callOptions = { input: finishInput } as const;
|
|
93
|
-
|
|
94
|
-
sharedCtx.stepIndex = stepIndex;
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await toolset.finish.onInputStart?.(callOptions as never);
|
|
98
|
-
} catch {}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
await toolset.finish.onInputAvailable?.(callOptions as never);
|
|
102
|
-
} catch {}
|
|
103
|
-
|
|
104
|
-
await toolset.finish.execute(finishInput, {} as never);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Main function to run the assistant for a given request.
|
|
39
|
+
* Main runner that executes the LLM streaming loop with tools
|
|
109
40
|
*/
|
|
110
|
-
async function runAssistant(opts: RunOpts) {
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const auth = await getAuth(opts.provider, cfg.projectRoot);
|
|
132
|
-
const needsSpoof = auth?.type === 'oauth';
|
|
133
|
-
const spoofPrompt = needsSpoof
|
|
134
|
-
? getProviderSpoofPrompt(opts.provider)
|
|
135
|
-
: undefined;
|
|
136
|
-
|
|
137
|
-
let system: string;
|
|
138
|
-
let additionalSystemMessages: Array<{ role: 'system'; content: string }> = [];
|
|
139
|
-
|
|
140
|
-
if (spoofPrompt) {
|
|
141
|
-
system = spoofPrompt;
|
|
142
|
-
const fullPrompt = await composeSystemPrompt({
|
|
143
|
-
provider: opts.provider,
|
|
144
|
-
model: opts.model,
|
|
145
|
-
projectRoot: cfg.projectRoot,
|
|
146
|
-
agentPrompt,
|
|
147
|
-
oneShot: opts.oneShot,
|
|
148
|
-
spoofPrompt: undefined,
|
|
149
|
-
includeProjectTree: isFirstMessage,
|
|
150
|
-
});
|
|
151
|
-
additionalSystemMessages = [{ role: 'system', content: fullPrompt }];
|
|
152
|
-
} else {
|
|
153
|
-
system = await composeSystemPrompt({
|
|
154
|
-
provider: opts.provider,
|
|
155
|
-
model: opts.model,
|
|
156
|
-
projectRoot: cfg.projectRoot,
|
|
157
|
-
agentPrompt,
|
|
158
|
-
oneShot: opts.oneShot,
|
|
159
|
-
spoofPrompt: undefined,
|
|
160
|
-
includeProjectTree: isFirstMessage,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
systemTimer.end();
|
|
164
|
-
debugLog('[system] composed prompt (provider+base+agent):');
|
|
165
|
-
debugLog(system);
|
|
166
|
-
|
|
167
|
-
const toolsTimer = time('runner:discoverTools');
|
|
168
|
-
const allTools = await discoverProjectTools(cfg.projectRoot);
|
|
169
|
-
toolsTimer.end({ count: allTools.length });
|
|
170
|
-
const allowedNames = new Set([
|
|
171
|
-
...(agentCfg.tools || []),
|
|
172
|
-
'finish',
|
|
173
|
-
'progress_update',
|
|
174
|
-
]);
|
|
175
|
-
const gated = allTools.filter((t) => allowedNames.has(t.name));
|
|
176
|
-
const messagesWithSystemInstructions = [
|
|
177
|
-
...(isFirstMessage ? additionalSystemMessages : []),
|
|
178
|
-
...history,
|
|
179
|
-
];
|
|
180
|
-
|
|
181
|
-
const { sharedCtx, firstToolTimer, firstToolSeen } = await setupToolContext(
|
|
182
|
-
opts,
|
|
183
|
-
db,
|
|
184
|
-
);
|
|
185
|
-
const toolset = adaptTools(gated, sharedCtx, opts.provider);
|
|
186
|
-
|
|
187
|
-
const modelTimer = time('runner:resolveModel');
|
|
188
|
-
const model = await resolveModel(opts.provider, opts.model, cfg);
|
|
189
|
-
modelTimer.end();
|
|
190
|
-
|
|
191
|
-
const maxOutputTokens = getMaxOutputTokens(opts.provider, opts.model);
|
|
192
|
-
|
|
193
|
-
let currentPartId = opts.assistantPartId;
|
|
41
|
+
export async function runAssistant(opts: RunOpts) {
|
|
42
|
+
const db = await getDb();
|
|
43
|
+
const config = await loadConfig();
|
|
44
|
+
const [provider, modelName] = opts.model.split('/', 2);
|
|
45
|
+
const model = resolveModel(provider, modelName);
|
|
46
|
+
|
|
47
|
+
// Build agent + system prompt
|
|
48
|
+
const agentConfig = resolveAgentConfig(opts.agent);
|
|
49
|
+
const availableTools = await discoverProjectTools(config.project.root);
|
|
50
|
+
const system = composeSystemPrompt(agentConfig, availableTools);
|
|
51
|
+
|
|
52
|
+
// Build message history
|
|
53
|
+
const history = await buildHistoryMessages(opts, db);
|
|
54
|
+
|
|
55
|
+
// Setup tool context
|
|
56
|
+
const toolContext = await setupToolContext(opts, db);
|
|
57
|
+
const { tools, sharedCtx } = toolContext;
|
|
58
|
+
|
|
59
|
+
// State
|
|
60
|
+
let currentPartId = sharedCtx.assistantPartId;
|
|
61
|
+
let stepIndex = sharedCtx.stepIndex;
|
|
194
62
|
let accumulated = '';
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
let finishObserved = false;
|
|
198
|
-
const unsubscribeFinish = subscribe(opts.sessionId, (evt) => {
|
|
199
|
-
if (evt.type !== 'tool.result') return;
|
|
200
|
-
try {
|
|
201
|
-
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
202
|
-
if (name === 'finish') finishObserved = true;
|
|
203
|
-
} catch {}
|
|
204
|
-
});
|
|
63
|
+
const abortController = new AbortController();
|
|
205
64
|
|
|
206
|
-
|
|
207
|
-
let firstDeltaSeen = false;
|
|
208
|
-
debugLog(`[streamText] Calling with maxOutputTokens: ${maxOutputTokens}`);
|
|
209
|
-
|
|
210
|
-
// State management helpers
|
|
65
|
+
// State getters/setters
|
|
211
66
|
const getCurrentPartId = () => currentPartId;
|
|
212
67
|
const getStepIndex = () => stepIndex;
|
|
213
68
|
const updateCurrentPartId = (id: string) => {
|
|
@@ -216,12 +71,10 @@ async function runAssistant(opts: RunOpts) {
|
|
|
216
71
|
const updateAccumulated = (text: string) => {
|
|
217
72
|
accumulated = text;
|
|
218
73
|
};
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
return stepIndex;
|
|
222
|
-
};
|
|
74
|
+
const getAccumulated = () => accumulated;
|
|
75
|
+
const incrementStepIndex = () => ++stepIndex;
|
|
223
76
|
|
|
224
|
-
//
|
|
77
|
+
// Handlers
|
|
225
78
|
const onStepFinish = createStepFinishHandler(
|
|
226
79
|
opts,
|
|
227
80
|
db,
|
|
@@ -235,104 +88,102 @@ async function runAssistant(opts: RunOpts) {
|
|
|
235
88
|
updateMessageTokensIncremental,
|
|
236
89
|
);
|
|
237
90
|
|
|
238
|
-
const onError = createErrorHandler(opts, db, getStepIndex, sharedCtx);
|
|
239
|
-
|
|
240
|
-
const onAbort = createAbortHandler(opts, db, getStepIndex, sharedCtx);
|
|
241
|
-
|
|
242
91
|
const onFinish = createFinishHandler(
|
|
243
92
|
opts,
|
|
244
93
|
db,
|
|
245
|
-
() => ensureFinishToolCalled(finishObserved, toolset, sharedCtx, stepIndex),
|
|
246
94
|
completeAssistantMessage,
|
|
95
|
+
getAccumulated,
|
|
96
|
+
abortController,
|
|
247
97
|
);
|
|
248
98
|
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
'./cache-optimizer.ts'
|
|
252
|
-
);
|
|
253
|
-
const { optimizeContext } = await import('./context-optimizer.ts');
|
|
99
|
+
const _onAbort = createAbortHandler(opts, db, abortController);
|
|
100
|
+
const onError = createErrorHandler(opts, db);
|
|
254
101
|
|
|
255
|
-
//
|
|
256
|
-
const contextOptimized = optimizeContext(
|
|
102
|
+
// Context optimization
|
|
103
|
+
const contextOptimized = optimizeContext(history, {
|
|
257
104
|
deduplicateFiles: true,
|
|
258
105
|
maxToolResults: 30,
|
|
259
106
|
});
|
|
260
107
|
|
|
261
|
-
//
|
|
108
|
+
// Truncate history
|
|
262
109
|
const truncatedMessages = truncateHistory(contextOptimized, 20);
|
|
263
110
|
|
|
264
|
-
//
|
|
111
|
+
// Add cache control
|
|
265
112
|
const { system: cachedSystem, messages: optimizedMessages } = addCacheControl(
|
|
266
|
-
opts.provider
|
|
113
|
+
opts.provider,
|
|
267
114
|
system,
|
|
268
115
|
truncatedMessages,
|
|
269
116
|
);
|
|
270
117
|
|
|
271
118
|
try {
|
|
272
|
-
const
|
|
119
|
+
const maxTokens = getMaxOutputTokens(provider, modelName);
|
|
120
|
+
const result = await streamText({
|
|
273
121
|
model,
|
|
274
|
-
|
|
275
|
-
...(cachedSystem ? { system: cachedSystem } : {}),
|
|
122
|
+
system: cachedSystem,
|
|
276
123
|
messages: optimizedMessages,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
124
|
+
tools,
|
|
125
|
+
maxSteps: 50,
|
|
126
|
+
maxTokens,
|
|
127
|
+
temperature: agentConfig.temperature ?? 0.7,
|
|
128
|
+
abortSignal: abortController.signal,
|
|
280
129
|
onStepFinish,
|
|
281
|
-
onError,
|
|
282
|
-
onAbort,
|
|
283
130
|
onFinish,
|
|
131
|
+
experimental_continueSteps: true,
|
|
284
132
|
});
|
|
285
133
|
|
|
134
|
+
// Process the stream
|
|
286
135
|
for await (const delta of result.textStream) {
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
firstDeltaSeen = true;
|
|
290
|
-
streamStartTimer.end();
|
|
291
|
-
}
|
|
136
|
+
if (abortController.signal.aborted) break;
|
|
137
|
+
|
|
292
138
|
accumulated += delta;
|
|
293
|
-
|
|
294
|
-
|
|
139
|
+
if (currentPartId) {
|
|
140
|
+
await db
|
|
141
|
+
.update(messageParts)
|
|
142
|
+
.set({ content: accumulated })
|
|
143
|
+
.where(eq(messageParts.id, currentPartId));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
publish('stream:text-delta', {
|
|
295
147
|
sessionId: opts.sessionId,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
},
|
|
148
|
+
messageId: opts.assistantMessageId,
|
|
149
|
+
assistantMessageId: opts.assistantMessageId,
|
|
150
|
+
stepIndex,
|
|
151
|
+
textDelta: delta,
|
|
152
|
+
fullText: accumulated,
|
|
302
153
|
});
|
|
303
|
-
await db
|
|
304
|
-
.update(messageParts)
|
|
305
|
-
.set({ content: JSON.stringify({ text: accumulated }) })
|
|
306
|
-
.where(eq(messageParts.id, currentPartId));
|
|
307
154
|
}
|
|
308
|
-
} catch (
|
|
309
|
-
|
|
310
|
-
await db
|
|
311
|
-
.update(messageParts)
|
|
312
|
-
.set({
|
|
313
|
-
content: JSON.stringify({
|
|
314
|
-
text: accumulated,
|
|
315
|
-
error: errorPayload.message,
|
|
316
|
-
}),
|
|
317
|
-
})
|
|
318
|
-
.where(eq(messageParts.messageId, opts.assistantMessageId));
|
|
319
|
-
publish({
|
|
320
|
-
type: 'error',
|
|
321
|
-
sessionId: opts.sessionId,
|
|
322
|
-
payload: {
|
|
323
|
-
messageId: opts.assistantMessageId,
|
|
324
|
-
error: errorPayload.message,
|
|
325
|
-
details: errorPayload.details,
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
throw error;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
await onError(err);
|
|
329
157
|
} finally {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
unsubscribeFinish();
|
|
333
|
-
} catch {}
|
|
334
|
-
try {
|
|
335
|
-
await cleanupEmptyTextParts(opts, db);
|
|
336
|
-
} catch {}
|
|
158
|
+
setRunning(opts.sessionId, false);
|
|
159
|
+
dequeueJob(opts.sessionId);
|
|
337
160
|
}
|
|
338
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Enqueues an assistant run
|
|
165
|
+
*/
|
|
166
|
+
export async function enqueueAssistantRun(opts: RunOpts) {
|
|
167
|
+
return enqueueRun(opts);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Aborts a running session
|
|
172
|
+
*/
|
|
173
|
+
export async function abortSession(sessionId: number) {
|
|
174
|
+
return abortSessionQueue(sessionId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Gets the current runner state for a session
|
|
179
|
+
*/
|
|
180
|
+
export function getSessionState(sessionId: number) {
|
|
181
|
+
return getRunnerState(sessionId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Cleanup session resources
|
|
186
|
+
*/
|
|
187
|
+
export function cleanupSessionResources(sessionId: number) {
|
|
188
|
+
return cleanupSession(sessionId);
|
|
189
|
+
}
|