@agi-cli/server 0.1.74 → 0.1.75
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/routes/session-messages.ts +19 -0
- package/src/runtime/message-service.ts +94 -152
- package/src/runtime/runner.ts +83 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.75",
|
|
4
4
|
"description": "HTTP API server for AGI CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"typecheck": "tsc --noEmit"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@agi-cli/sdk": "0.1.
|
|
33
|
-
"@agi-cli/database": "0.1.
|
|
32
|
+
"@agi-cli/sdk": "0.1.75",
|
|
33
|
+
"@agi-cli/database": "0.1.75",
|
|
34
34
|
"drizzle-orm": "^0.44.5",
|
|
35
35
|
"hono": "^4.9.9",
|
|
36
36
|
"zod": "^4.1.8"
|
|
@@ -85,6 +85,17 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
85
85
|
const db = await getDb(cfg.projectRoot);
|
|
86
86
|
const sessionId = c.req.param('id');
|
|
87
87
|
const body = await c.req.json().catch(() => ({}));
|
|
88
|
+
|
|
89
|
+
// DEBUG: Log received body
|
|
90
|
+
logger.info('[API] Received message request', {
|
|
91
|
+
sessionId,
|
|
92
|
+
hasContent: !!body?.content,
|
|
93
|
+
hasUserContext: !!body?.userContext,
|
|
94
|
+
userContext: body?.userContext
|
|
95
|
+
? `${String(body.userContext).substring(0, 50)}...`
|
|
96
|
+
: 'NONE',
|
|
97
|
+
});
|
|
98
|
+
|
|
88
99
|
// Load session to inherit its provider/model/agent by default
|
|
89
100
|
const sessionRows = await db
|
|
90
101
|
.select()
|
|
@@ -101,6 +112,14 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
101
112
|
const content = body?.content ?? '';
|
|
102
113
|
const userContext = body?.userContext;
|
|
103
114
|
|
|
115
|
+
// DEBUG: Log extracted userContext
|
|
116
|
+
logger.info('[API] Extracted userContext', {
|
|
117
|
+
userContext: userContext
|
|
118
|
+
? `${String(userContext).substring(0, 50)}...`
|
|
119
|
+
: 'NONE',
|
|
120
|
+
typeOf: typeof userContext,
|
|
121
|
+
});
|
|
122
|
+
|
|
104
123
|
// Validate model capabilities if tools are allowed for this agent
|
|
105
124
|
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
106
125
|
try {
|
|
@@ -37,6 +37,12 @@ export async function dispatchAssistantMessage(
|
|
|
37
37
|
oneShot,
|
|
38
38
|
userContext,
|
|
39
39
|
} = options;
|
|
40
|
+
|
|
41
|
+
// DEBUG: Log userContext in dispatch
|
|
42
|
+
debugLog(
|
|
43
|
+
`[MESSAGE_SERVICE] dispatchAssistantMessage called with userContext: ${userContext ? userContext.substring(0, 50) + '...' : 'NONE'}`,
|
|
44
|
+
);
|
|
45
|
+
|
|
40
46
|
const sessionId = session.id;
|
|
41
47
|
const now = Date.now();
|
|
42
48
|
const userMessageId = crypto.randomUUID();
|
|
@@ -100,6 +106,11 @@ export async function dispatchAssistantMessage(
|
|
|
100
106
|
payload: { id: assistantMessageId, role: 'assistant' },
|
|
101
107
|
});
|
|
102
108
|
|
|
109
|
+
// DEBUG: Log before enqueue
|
|
110
|
+
debugLog(
|
|
111
|
+
`[MESSAGE_SERVICE] Enqueuing assistant run with userContext: ${userContext ? userContext.substring(0, 50) + '...' : 'NONE'}`,
|
|
112
|
+
);
|
|
113
|
+
|
|
103
114
|
enqueueAssistantRun({
|
|
104
115
|
sessionId,
|
|
105
116
|
assistantMessageId,
|
|
@@ -128,21 +139,44 @@ function scheduleSessionTitle(args: {
|
|
|
128
139
|
db: DB;
|
|
129
140
|
sessionId: string;
|
|
130
141
|
content: unknown;
|
|
131
|
-
})
|
|
132
|
-
const { sessionId } = args;
|
|
133
|
-
|
|
134
|
-
titleInFlight.
|
|
135
|
-
|
|
142
|
+
}) {
|
|
143
|
+
const { cfg, db, sessionId, content } = args;
|
|
144
|
+
|
|
145
|
+
if (titleInFlight.has(sessionId) || titlePending.has(sessionId)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const processNext = () => {
|
|
150
|
+
if (titleQueue.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (titleActiveCount >= TITLE_CONCURRENCY_LIMIT) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const next = titleQueue.shift();
|
|
157
|
+
if (!next) return;
|
|
158
|
+
titleActiveCount++;
|
|
159
|
+
next();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const task = async () => {
|
|
163
|
+
titleInFlight.add(sessionId);
|
|
164
|
+
titlePending.delete(sessionId);
|
|
136
165
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Swallow title generation errors; they are non-blocking.
|
|
166
|
+
await generateSessionTitle({ cfg, db, sessionId, content });
|
|
167
|
+
} catch (err) {
|
|
168
|
+
debugLog('[TITLE_GEN] Title generation error:');
|
|
169
|
+
debugLog(err);
|
|
142
170
|
} finally {
|
|
143
171
|
titleInFlight.delete(sessionId);
|
|
172
|
+
titleActiveCount--;
|
|
173
|
+
processNext();
|
|
144
174
|
}
|
|
145
|
-
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
titlePending.add(sessionId);
|
|
178
|
+
titleQueue.push(task);
|
|
179
|
+
processNext();
|
|
146
180
|
}
|
|
147
181
|
|
|
148
182
|
function enqueueSessionTitle(args: {
|
|
@@ -151,72 +185,54 @@ function enqueueSessionTitle(args: {
|
|
|
151
185
|
sessionId: string;
|
|
152
186
|
content: unknown;
|
|
153
187
|
}) {
|
|
154
|
-
|
|
155
|
-
if (titlePending.has(sessionId) || titleInFlight.has(sessionId)) return;
|
|
156
|
-
titlePending.add(sessionId);
|
|
157
|
-
Promise.resolve()
|
|
158
|
-
.then(() => {
|
|
159
|
-
titlePending.delete(sessionId);
|
|
160
|
-
scheduleSessionTitle(args);
|
|
161
|
-
})
|
|
162
|
-
.catch(() => {
|
|
163
|
-
titlePending.delete(sessionId);
|
|
164
|
-
});
|
|
188
|
+
scheduleSessionTitle(args);
|
|
165
189
|
}
|
|
166
190
|
|
|
167
|
-
async function
|
|
191
|
+
async function generateSessionTitle(args: {
|
|
168
192
|
cfg: AGIConfig;
|
|
169
193
|
db: DB;
|
|
170
194
|
sessionId: string;
|
|
171
195
|
content: unknown;
|
|
172
|
-
}) {
|
|
196
|
+
}): Promise<void> {
|
|
173
197
|
const { cfg, db, sessionId, content } = args;
|
|
198
|
+
|
|
174
199
|
try {
|
|
175
|
-
const
|
|
200
|
+
const existingSession = await db
|
|
176
201
|
.select()
|
|
177
202
|
.from(sessions)
|
|
178
203
|
.where(eq(sessions.id, sessionId));
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let heuristic = '';
|
|
184
|
-
if (!alreadyHasTitle) {
|
|
185
|
-
heuristic = deriveTitle(String(content ?? ''));
|
|
186
|
-
if (heuristic) {
|
|
187
|
-
await db
|
|
188
|
-
.update(sessions)
|
|
189
|
-
.set({ title: heuristic })
|
|
190
|
-
.where(eq(sessions.id, sessionId));
|
|
191
|
-
publish({
|
|
192
|
-
type: 'session.updated',
|
|
193
|
-
sessionId,
|
|
194
|
-
payload: { title: heuristic },
|
|
195
|
-
});
|
|
196
|
-
}
|
|
204
|
+
|
|
205
|
+
if (!existingSession.length) {
|
|
206
|
+
debugLog('[TITLE_GEN] Session not found, aborting');
|
|
207
|
+
return;
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
const sess = existingSession[0];
|
|
211
|
+
if (sess.title && sess.title !== 'New Session') {
|
|
212
|
+
debugLog('[TITLE_GEN] Session already has a title, skipping');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const provider = sess.provider ?? cfg.defaults.provider;
|
|
217
|
+
const modelName = sess.model ?? cfg.defaults.model;
|
|
218
|
+
|
|
219
|
+
debugLog('[TITLE_GEN] Generating title for session');
|
|
220
|
+
debugLog(`[TITLE_GEN] Provider: ${provider}, Model: ${modelName}`);
|
|
202
221
|
|
|
203
|
-
|
|
204
|
-
debugLog(`[TITLE_GEN] Provider: ${providerId}, Model: ${modelId}`);
|
|
222
|
+
const model = await resolveModel(provider, modelName, cfg);
|
|
205
223
|
|
|
206
|
-
// Check if we need OAuth spoof prompt (same logic as runner)
|
|
207
224
|
const { getAuth } = await import('@agi-cli/sdk');
|
|
208
225
|
const { getProviderSpoofPrompt } = await import('./prompt.ts');
|
|
209
|
-
const auth = await getAuth(
|
|
226
|
+
const auth = await getAuth(provider, cfg.projectRoot);
|
|
210
227
|
const needsSpoof = auth?.type === 'oauth';
|
|
211
228
|
const spoofPrompt = needsSpoof
|
|
212
|
-
? getProviderSpoofPrompt(
|
|
229
|
+
? getProviderSpoofPrompt(provider)
|
|
213
230
|
: undefined;
|
|
214
231
|
|
|
215
|
-
debugLog(
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
debugLog(
|
|
233
|
+
`[TITLE_GEN] needsSpoof: ${needsSpoof}, spoofPrompt: ${spoofPrompt || 'NONE'}`,
|
|
234
|
+
);
|
|
218
235
|
|
|
219
|
-
const model = await resolveModel(providerId, modelId, cfg);
|
|
220
236
|
const promptText = String(content ?? '').slice(0, 2000);
|
|
221
237
|
|
|
222
238
|
const titlePrompt = [
|
|
@@ -281,123 +297,49 @@ async function updateSessionTitle(args: {
|
|
|
281
297
|
const sanitized = sanitizeTitle(modelTitle);
|
|
282
298
|
debugLog(`[TITLE_GEN] After sanitization: "${sanitized}"`);
|
|
283
299
|
|
|
284
|
-
if (!sanitized) {
|
|
285
|
-
debugLog('[TITLE_GEN]
|
|
300
|
+
if (!sanitized || sanitized === 'New Session') {
|
|
301
|
+
debugLog('[TITLE_GEN] Sanitized title is empty or default, aborting');
|
|
286
302
|
return;
|
|
287
303
|
}
|
|
288
304
|
|
|
289
|
-
modelTitle = sanitized;
|
|
290
|
-
|
|
291
|
-
const check = await db
|
|
292
|
-
.select()
|
|
293
|
-
.from(sessions)
|
|
294
|
-
.where(eq(sessions.id, sessionId));
|
|
295
|
-
if (!check.length) return;
|
|
296
|
-
const currentTitle = String(check[0].title ?? '').trim();
|
|
297
|
-
if (currentTitle && currentTitle !== heuristic) {
|
|
298
|
-
debugLog(
|
|
299
|
-
`[TITLE_GEN] Session already has different title: "${currentTitle}", skipping`,
|
|
300
|
-
);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
debugLog(`[TITLE_GEN] Setting final title: "${modelTitle}"`);
|
|
305
305
|
await db
|
|
306
306
|
.update(sessions)
|
|
307
|
-
.set({ title:
|
|
307
|
+
.set({ title: sanitized, updatedAt: Date.now() })
|
|
308
308
|
.where(eq(sessions.id, sessionId));
|
|
309
|
+
|
|
310
|
+
debugLog(`[TITLE_GEN] Setting final title: "${sanitized}"`);
|
|
311
|
+
|
|
309
312
|
publish({
|
|
310
313
|
type: 'session.updated',
|
|
311
314
|
sessionId,
|
|
312
|
-
payload: { title:
|
|
315
|
+
payload: { id: sessionId, title: sanitized },
|
|
313
316
|
});
|
|
314
317
|
} catch (err) {
|
|
315
|
-
debugLog('[TITLE_GEN]
|
|
318
|
+
debugLog('[TITLE_GEN] Error in generateSessionTitle:');
|
|
316
319
|
debugLog(err);
|
|
317
320
|
}
|
|
318
321
|
}
|
|
319
322
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async function sessionHasTitle(db: DB, sessionId: string): Promise<boolean> {
|
|
330
|
-
try {
|
|
331
|
-
const rows = await db
|
|
332
|
-
.select({ title: sessions.title })
|
|
333
|
-
.from(sessions)
|
|
334
|
-
.where(eq(sessions.id, sessionId))
|
|
335
|
-
.limit(1);
|
|
336
|
-
if (!rows.length) return false;
|
|
337
|
-
const title = rows[0]?.title;
|
|
338
|
-
return typeof title === 'string' && title.trim().length > 0;
|
|
339
|
-
} catch {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function acquireTitleSlot(): Promise<void> {
|
|
345
|
-
if (titleActiveCount < TITLE_CONCURRENCY_LIMIT) {
|
|
346
|
-
titleActiveCount += 1;
|
|
347
|
-
return Promise.resolve();
|
|
348
|
-
}
|
|
349
|
-
return new Promise((resolve) => {
|
|
350
|
-
titleQueue.push(() => {
|
|
351
|
-
titleActiveCount += 1;
|
|
352
|
-
resolve();
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function releaseTitleSlot(): void {
|
|
358
|
-
if (titleActiveCount > 0) titleActiveCount -= 1;
|
|
359
|
-
const next = titleQueue.shift();
|
|
360
|
-
if (next) {
|
|
361
|
-
next();
|
|
362
|
-
}
|
|
323
|
+
function sanitizeTitle(raw: string): string {
|
|
324
|
+
let s = raw.trim();
|
|
325
|
+
s = s.replace(/^["']|["']$/g, '');
|
|
326
|
+
s = s.replace(/[.!?]+$/, '');
|
|
327
|
+
s = s.replace(/\s+/g, ' ');
|
|
328
|
+
if (s.length > 80) s = s.slice(0, 80).trim();
|
|
329
|
+
return s;
|
|
363
330
|
}
|
|
364
331
|
|
|
365
|
-
async function touchSessionLastActive(args: {
|
|
332
|
+
async function touchSessionLastActive(args: {
|
|
333
|
+
db: DB;
|
|
334
|
+
sessionId: string;
|
|
335
|
+
}): Promise<void> {
|
|
366
336
|
const { db, sessionId } = args;
|
|
367
337
|
try {
|
|
368
338
|
await db
|
|
369
339
|
.update(sessions)
|
|
370
|
-
.set({
|
|
340
|
+
.set({ updatedAt: Date.now() })
|
|
371
341
|
.where(eq(sessions.id, sessionId));
|
|
372
|
-
} catch {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
function deriveTitle(text: string): string {
|
|
376
|
-
const cleaned = String(text || '')
|
|
377
|
-
.replace(/```[\s\S]*?```/g, ' ')
|
|
378
|
-
.replace(/`[^`]*`/g, ' ')
|
|
379
|
-
.replace(/\s+/g, ' ')
|
|
380
|
-
.trim();
|
|
381
|
-
if (!cleaned) return '';
|
|
382
|
-
const endIdx = (() => {
|
|
383
|
-
const punct = ['? ', '. ', '! ']
|
|
384
|
-
.map((p) => cleaned.indexOf(p))
|
|
385
|
-
.filter((i) => i > 0);
|
|
386
|
-
const idx = Math.min(...(punct.length ? punct : [cleaned.length]));
|
|
387
|
-
return Math.min(idx + 1, cleaned.length);
|
|
388
|
-
})();
|
|
389
|
-
const first = cleaned.slice(0, endIdx).trim();
|
|
390
|
-
const maxLen = 64;
|
|
391
|
-
const base = first.length > 8 ? first : cleaned;
|
|
392
|
-
const truncated =
|
|
393
|
-
base.length > maxLen ? `${base.slice(0, maxLen - 1).trimEnd()}…` : base;
|
|
394
|
-
return truncated;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function sanitizeTitle(s: string): string {
|
|
398
|
-
let t = s.trim();
|
|
399
|
-
t = t.replace(/^['"""''()[\\]]+|['"""''()[\\]]+$/g, '').trim();
|
|
400
|
-
t = t.replace(/[\\s\\-_:–—]+$/g, '').trim();
|
|
401
|
-
if (t.length > 64) t = `${t.slice(0, 63).trimEnd()}…`;
|
|
402
|
-
return t;
|
|
342
|
+
} catch (err) {
|
|
343
|
+
debugLog('[touchSessionLastActive] Error:', err);
|
|
344
|
+
}
|
|
403
345
|
}
|
package/src/runtime/runner.ts
CHANGED
|
@@ -122,7 +122,18 @@ async function runAssistant(opts: RunOpts) {
|
|
|
122
122
|
const history = await buildHistoryMessages(db, opts.sessionId);
|
|
123
123
|
historyTimer.end({ messages: history.length });
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
// FIX: For OAuth, we need to check if this is the first ASSISTANT message
|
|
126
|
+
// The user message is already in history by this point, so history.length will be > 0
|
|
127
|
+
// We need to add additionalSystemMessages on the first assistant turn
|
|
128
|
+
const isFirstMessage = !history.some((m) => m.role === 'assistant');
|
|
129
|
+
|
|
130
|
+
debugLog(`[RUNNER] isFirstMessage: ${isFirstMessage}`);
|
|
131
|
+
debugLog(`[RUNNER] userContext provided: ${opts.userContext ? 'YES' : 'NO'}`);
|
|
132
|
+
if (opts.userContext) {
|
|
133
|
+
debugLog(
|
|
134
|
+
`[RUNNER] userContext value: ${opts.userContext.substring(0, 100)}${opts.userContext.length > 100 ? '...' : ''}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
126
137
|
|
|
127
138
|
const systemTimer = time('runner:composeSystemPrompt');
|
|
128
139
|
const { getAuth } = await import('@agi-cli/sdk');
|
|
@@ -133,10 +144,14 @@ async function runAssistant(opts: RunOpts) {
|
|
|
133
144
|
? getProviderSpoofPrompt(opts.provider)
|
|
134
145
|
: undefined;
|
|
135
146
|
|
|
147
|
+
debugLog(`[RUNNER] needsSpoof (OAuth): ${needsSpoof}`);
|
|
148
|
+
debugLog(`[RUNNER] spoofPrompt: ${spoofPrompt || 'NONE'}`);
|
|
149
|
+
|
|
136
150
|
let system: string;
|
|
137
151
|
let additionalSystemMessages: Array<{ role: 'system'; content: string }> = [];
|
|
138
152
|
|
|
139
153
|
if (spoofPrompt) {
|
|
154
|
+
// OAuth mode: short spoof in system field, full instructions in messages array
|
|
140
155
|
system = spoofPrompt;
|
|
141
156
|
const fullPrompt = await composeSystemPrompt({
|
|
142
157
|
provider: opts.provider,
|
|
@@ -148,8 +163,25 @@ async function runAssistant(opts: RunOpts) {
|
|
|
148
163
|
includeProjectTree: isFirstMessage,
|
|
149
164
|
userContext: opts.userContext,
|
|
150
165
|
});
|
|
166
|
+
|
|
167
|
+
// FIX: Always add the system message for OAuth because:
|
|
168
|
+
// 1. System messages are NOT stored in the database
|
|
169
|
+
// 2. buildHistoryMessages only returns user/assistant messages
|
|
170
|
+
// 3. We need the full instructions on every turn
|
|
151
171
|
additionalSystemMessages = [{ role: 'system', content: fullPrompt }];
|
|
172
|
+
|
|
173
|
+
debugLog('[RUNNER] OAuth mode: additionalSystemMessages created');
|
|
174
|
+
debugLog(`[RUNNER] fullPrompt length: ${fullPrompt.length}`);
|
|
175
|
+
debugLog(
|
|
176
|
+
`[RUNNER] fullPrompt contains userContext: ${fullPrompt.includes('<user-provided-state-context>') ? 'YES' : 'NO'}`,
|
|
177
|
+
);
|
|
178
|
+
if (opts.userContext && fullPrompt.includes(opts.userContext)) {
|
|
179
|
+
debugLog('[RUNNER] ✅ userContext IS in fullPrompt');
|
|
180
|
+
} else if (opts.userContext) {
|
|
181
|
+
debugLog('[RUNNER] ❌ userContext NOT in fullPrompt!');
|
|
182
|
+
}
|
|
152
183
|
} else {
|
|
184
|
+
// API key mode: full instructions in system field
|
|
153
185
|
system = await composeSystemPrompt({
|
|
154
186
|
provider: opts.provider,
|
|
155
187
|
model: opts.model,
|
|
@@ -174,11 +206,29 @@ async function runAssistant(opts: RunOpts) {
|
|
|
174
206
|
'progress_update',
|
|
175
207
|
]);
|
|
176
208
|
const gated = allTools.filter((t) => allowedNames.has(t.name));
|
|
209
|
+
|
|
210
|
+
// FIX: For OAuth, ALWAYS prepend the system message because it's never in history
|
|
211
|
+
// For API key mode, only add on first message (when additionalSystemMessages is empty)
|
|
177
212
|
const messagesWithSystemInstructions = [
|
|
178
|
-
...
|
|
213
|
+
...additionalSystemMessages, // Always add for OAuth, empty for API key mode
|
|
179
214
|
...history,
|
|
180
215
|
];
|
|
181
216
|
|
|
217
|
+
debugLog(
|
|
218
|
+
`[RUNNER] messagesWithSystemInstructions length: ${messagesWithSystemInstructions.length}`,
|
|
219
|
+
);
|
|
220
|
+
debugLog(
|
|
221
|
+
`[RUNNER] additionalSystemMessages length: ${additionalSystemMessages.length}`,
|
|
222
|
+
);
|
|
223
|
+
if (additionalSystemMessages.length > 0) {
|
|
224
|
+
debugLog(
|
|
225
|
+
'[RUNNER] ✅ additionalSystemMessages ADDED to messagesWithSystemInstructions',
|
|
226
|
+
);
|
|
227
|
+
debugLog(
|
|
228
|
+
`[RUNNER] This happens on EVERY turn for OAuth (system messages not stored in DB)`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
182
232
|
const { sharedCtx, firstToolTimer, firstToolSeen } = await setupToolContext(
|
|
183
233
|
opts,
|
|
184
234
|
db,
|
|
@@ -259,9 +309,24 @@ async function runAssistant(opts: RunOpts) {
|
|
|
259
309
|
maxToolResults: 30,
|
|
260
310
|
});
|
|
261
311
|
|
|
312
|
+
debugLog(
|
|
313
|
+
`[RUNNER] After optimizeContext: ${contextOptimized.length} messages`,
|
|
314
|
+
);
|
|
315
|
+
|
|
262
316
|
// 2. Truncate history
|
|
263
317
|
const truncatedMessages = truncateHistory(contextOptimized, 20);
|
|
264
318
|
|
|
319
|
+
debugLog(
|
|
320
|
+
`[RUNNER] After truncateHistory: ${truncatedMessages.length} messages`,
|
|
321
|
+
);
|
|
322
|
+
if (truncatedMessages.length > 0 && truncatedMessages[0].role === 'system') {
|
|
323
|
+
debugLog('[RUNNER] ✅ First message is system message');
|
|
324
|
+
} else if (truncatedMessages.length > 0) {
|
|
325
|
+
debugLog(
|
|
326
|
+
`[RUNNER] ⚠️ First message is NOT system (it's ${truncatedMessages[0].role})`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
265
330
|
// 3. Add cache control
|
|
266
331
|
const { system: cachedSystem, messages: optimizedMessages } = addCacheControl(
|
|
267
332
|
opts.provider,
|
|
@@ -269,6 +334,13 @@ async function runAssistant(opts: RunOpts) {
|
|
|
269
334
|
truncatedMessages,
|
|
270
335
|
);
|
|
271
336
|
|
|
337
|
+
debugLog(
|
|
338
|
+
`[RUNNER] Final optimizedMessages: ${optimizedMessages.length} messages`,
|
|
339
|
+
);
|
|
340
|
+
debugLog(
|
|
341
|
+
`[RUNNER] cachedSystem (spoof): ${typeof cachedSystem === 'string' ? cachedSystem.substring(0, 100) : JSON.stringify(cachedSystem).substring(0, 100)}`,
|
|
342
|
+
);
|
|
343
|
+
|
|
272
344
|
try {
|
|
273
345
|
// @ts-expect-error this is fine 🔥
|
|
274
346
|
const result = streamText({
|
|
@@ -312,29 +384,23 @@ async function runAssistant(opts: RunOpts) {
|
|
|
312
384
|
await db
|
|
313
385
|
.update(messageParts)
|
|
314
386
|
.set({
|
|
315
|
-
content: JSON.stringify({
|
|
316
|
-
text: accumulated,
|
|
317
|
-
error: errorPayload.message,
|
|
318
|
-
}),
|
|
387
|
+
content: JSON.stringify({ error: errorPayload }),
|
|
319
388
|
})
|
|
320
|
-
.where(eq(messageParts.
|
|
389
|
+
.where(eq(messageParts.id, currentPartId));
|
|
390
|
+
|
|
321
391
|
publish({
|
|
322
|
-
type: 'error',
|
|
392
|
+
type: 'message.error',
|
|
323
393
|
sessionId: opts.sessionId,
|
|
324
394
|
payload: {
|
|
325
395
|
messageId: opts.assistantMessageId,
|
|
326
|
-
|
|
327
|
-
|
|
396
|
+
partId: currentPartId,
|
|
397
|
+
error: errorPayload,
|
|
328
398
|
},
|
|
329
399
|
});
|
|
330
400
|
throw error;
|
|
331
401
|
} finally {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
} catch {}
|
|
336
|
-
try {
|
|
337
|
-
await cleanupEmptyTextParts(opts, db);
|
|
338
|
-
} catch {}
|
|
402
|
+
unsubscribeFinish();
|
|
403
|
+
await cleanupEmptyTextParts(db, opts.assistantMessageId);
|
|
404
|
+
firstToolTimer.end({ seen: firstToolSeen });
|
|
339
405
|
}
|
|
340
406
|
}
|