@ottocode/server 0.1.234 → 0.1.236
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/paths/config.ts +94 -0
- package/src/routes/config/debug.ts +39 -0
- package/src/routes/config/index.ts +2 -0
- package/src/routes/config/utils.ts +3 -16
- package/src/routes/git/commit.ts +0 -3
- package/src/routes/terminals.ts +0 -21
- package/src/routes/tunnel.ts +0 -5
- package/src/runtime/agent/mcp-prepare-step.ts +1 -7
- package/src/runtime/agent/registry.ts +1 -8
- package/src/runtime/agent/runner-setup.ts +73 -49
- package/src/runtime/agent/runner.ts +20 -194
- package/src/runtime/debug/index.ts +3 -91
- package/src/runtime/debug/state.ts +5 -61
- package/src/runtime/debug/turn-dump.ts +0 -4
- package/src/runtime/message/compaction-auto.ts +1 -21
- package/src/runtime/message/compaction-mark.ts +75 -27
- package/src/runtime/message/compaction-prune.ts +0 -3
- package/src/runtime/message/history-builder.ts +2 -23
- package/src/runtime/message/service.ts +22 -64
- package/src/runtime/message/tool-history-tracker.ts +0 -3
- package/src/runtime/prompt/builder.ts +0 -2
- package/src/runtime/provider/oauth-adapter.ts +5 -5
- package/src/runtime/stream/error-handler.ts +1 -31
- package/src/runtime/stream/finish-handler.ts +3 -20
- package/src/runtime/stream/step-finish.ts +5 -26
- package/src/runtime/tools/approval.ts +0 -18
- package/src/runtime/utils/token.ts +1 -10
- package/src/tools/adapter.ts +23 -22
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import type { getDb } from '@ottocode/database';
|
|
2
2
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
3
3
|
import { eq, asc, and, lt } from 'drizzle-orm';
|
|
4
|
-
import { debugLog } from '../debug/index.ts';
|
|
5
4
|
import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
|
|
6
5
|
|
|
7
6
|
const PROTECTED_TOOLS = ['skill'];
|
|
8
7
|
|
|
8
|
+
type PartInfo = {
|
|
9
|
+
id: string;
|
|
10
|
+
tokens: number;
|
|
11
|
+
toolCallId: string | null;
|
|
12
|
+
type: 'tool_call' | 'tool_result';
|
|
13
|
+
index: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type CompactUnit = {
|
|
17
|
+
partIds: string[];
|
|
18
|
+
tokens: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
9
21
|
export async function markSessionCompacted(
|
|
10
22
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
11
23
|
sessionId: string,
|
|
12
24
|
compactMessageId: string,
|
|
13
25
|
): Promise<{ compacted: number; saved: number }> {
|
|
14
|
-
debugLog(`[compaction] Marking session ${sessionId} as compacted`);
|
|
15
|
-
|
|
16
26
|
const compactMsg = await db
|
|
17
27
|
.select()
|
|
18
28
|
.from(messages)
|
|
@@ -20,7 +30,6 @@ export async function markSessionCompacted(
|
|
|
20
30
|
.limit(1);
|
|
21
31
|
|
|
22
32
|
if (!compactMsg.length) {
|
|
23
|
-
debugLog('[compaction] Compact message not found');
|
|
24
33
|
return { compacted: 0, saved: 0 };
|
|
25
34
|
}
|
|
26
35
|
|
|
@@ -37,8 +46,7 @@ export async function markSessionCompacted(
|
|
|
37
46
|
)
|
|
38
47
|
.orderBy(asc(messages.createdAt));
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
const allToolParts: PartInfo[] = [];
|
|
49
|
+
const allCompactUnits: CompactUnit[] = [];
|
|
42
50
|
let totalToolTokens = 0;
|
|
43
51
|
|
|
44
52
|
for (const msg of oldMessages) {
|
|
@@ -48,6 +56,8 @@ export async function markSessionCompacted(
|
|
|
48
56
|
.where(eq(messageParts.messageId, msg.id))
|
|
49
57
|
.orderBy(asc(messageParts.index));
|
|
50
58
|
|
|
59
|
+
const eligibleParts: PartInfo[] = [];
|
|
60
|
+
|
|
51
61
|
for (const part of parts) {
|
|
52
62
|
if (part.type !== 'tool_call' && part.type !== 'tool_result') continue;
|
|
53
63
|
if (part.toolName && PROTECTED_TOOLS.includes(part.toolName)) continue;
|
|
@@ -69,43 +79,81 @@ export async function markSessionCompacted(
|
|
|
69
79
|
|
|
70
80
|
const tokens = estimateTokens(contentStr);
|
|
71
81
|
totalToolTokens += tokens;
|
|
72
|
-
|
|
82
|
+
eligibleParts.push({
|
|
83
|
+
id: part.id,
|
|
84
|
+
tokens,
|
|
85
|
+
toolCallId: part.toolCallId,
|
|
86
|
+
type: part.type,
|
|
87
|
+
index: part.index,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const pairedCallIds = new Set<string>();
|
|
92
|
+
const callsById = new Map<string, PartInfo[]>();
|
|
93
|
+
const resultsById = new Map<string, PartInfo[]>();
|
|
94
|
+
|
|
95
|
+
for (const part of eligibleParts) {
|
|
96
|
+
if (!part.toolCallId) continue;
|
|
97
|
+
const bucket = part.type === 'tool_call' ? callsById : resultsById;
|
|
98
|
+
const items = bucket.get(part.toolCallId) ?? [];
|
|
99
|
+
items.push(part);
|
|
100
|
+
bucket.set(part.toolCallId, items);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [toolCallId, callParts] of callsById) {
|
|
104
|
+
const resultParts = resultsById.get(toolCallId);
|
|
105
|
+
if (!resultParts?.length) continue;
|
|
106
|
+
|
|
107
|
+
const pairParts = [...callParts, ...resultParts].sort(
|
|
108
|
+
(a, b) => a.index - b.index,
|
|
109
|
+
);
|
|
110
|
+
pairedCallIds.add(toolCallId);
|
|
111
|
+
allCompactUnits.push({
|
|
112
|
+
partIds: pairParts.map((part) => part.id),
|
|
113
|
+
tokens: pairParts.reduce((sum, part) => sum + part.tokens, 0),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const part of eligibleParts) {
|
|
118
|
+
if (part.toolCallId && pairedCallIds.has(part.toolCallId)) continue;
|
|
119
|
+
allCompactUnits.push({
|
|
120
|
+
partIds: [part.id],
|
|
121
|
+
tokens: part.tokens,
|
|
122
|
+
});
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
125
|
|
|
76
126
|
const tokensToFree = Math.max(0, totalToolTokens - PRUNE_PROTECT);
|
|
77
127
|
|
|
78
|
-
const toCompact:
|
|
128
|
+
const toCompact: CompactUnit[] = [];
|
|
79
129
|
let freedTokens = 0;
|
|
80
130
|
|
|
81
|
-
for (const
|
|
131
|
+
for (const unit of allCompactUnits) {
|
|
82
132
|
if (freedTokens >= tokensToFree) break;
|
|
83
|
-
freedTokens +=
|
|
84
|
-
toCompact.push(
|
|
133
|
+
freedTokens += unit.tokens;
|
|
134
|
+
toCompact.push(unit);
|
|
85
135
|
}
|
|
86
136
|
|
|
87
|
-
debugLog(
|
|
88
|
-
`[compaction] Found ${toCompact.length} parts to compact (oldest first), saving ~${freedTokens} tokens`,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
137
|
if (toCompact.length > 0) {
|
|
92
138
|
const compactedAt = Date.now();
|
|
93
139
|
|
|
94
|
-
for (const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`[compaction] Failed to mark part ${part.id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
103
|
-
);
|
|
140
|
+
for (const unit of toCompact) {
|
|
141
|
+
for (const partId of unit.partIds) {
|
|
142
|
+
try {
|
|
143
|
+
await db
|
|
144
|
+
.update(messageParts)
|
|
145
|
+
.set({ compactedAt })
|
|
146
|
+
.where(eq(messageParts.id, partId));
|
|
147
|
+
} catch {}
|
|
104
148
|
}
|
|
105
149
|
}
|
|
106
150
|
|
|
107
|
-
|
|
151
|
+
const compactedParts = toCompact.reduce(
|
|
152
|
+
(sum, unit) => sum + unit.partIds.length,
|
|
153
|
+
0,
|
|
154
|
+
);
|
|
155
|
+
return { compacted: compactedParts, saved: freedTokens };
|
|
108
156
|
}
|
|
109
157
|
|
|
110
|
-
return { compacted:
|
|
158
|
+
return { compacted: 0, saved: freedTokens };
|
|
111
159
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { getDb } from '@ottocode/database';
|
|
2
2
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
3
3
|
import { eq, desc } from 'drizzle-orm';
|
|
4
|
-
import { debugLog } from '../debug/index.ts';
|
|
5
4
|
import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
|
|
6
5
|
|
|
7
6
|
const PROTECTED_TOOLS = ['skill'];
|
|
@@ -10,8 +9,6 @@ export async function pruneSession(
|
|
|
10
9
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
11
10
|
sessionId: string,
|
|
12
11
|
): Promise<{ pruned: number; saved: number }> {
|
|
13
|
-
debugLog(`[compaction] Auto-pruning session ${sessionId}`);
|
|
14
|
-
|
|
15
12
|
const allMessages = await db
|
|
16
13
|
.select()
|
|
17
14
|
.from(messages)
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
import type { getDb } from '@ottocode/database';
|
|
9
9
|
import { messages, messageParts } from '@ottocode/database/schema';
|
|
10
10
|
import { eq, asc } from 'drizzle-orm';
|
|
11
|
-
import { debugLog } from '../debug/index.ts';
|
|
12
11
|
import { ToolHistoryTracker } from './tool-history-tracker.ts';
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -43,15 +42,8 @@ export async function buildHistoryMessages(
|
|
|
43
42
|
m.status !== 'error'
|
|
44
43
|
) {
|
|
45
44
|
if (parts.length === 0) {
|
|
46
|
-
debugLog(
|
|
47
|
-
`[buildHistoryMessages] Skipping empty assistant message ${m.id} with status ${m.status}`,
|
|
48
|
-
);
|
|
49
45
|
continue;
|
|
50
46
|
}
|
|
51
|
-
|
|
52
|
-
debugLog(
|
|
53
|
-
`[buildHistoryMessages] Including non-complete assistant message ${m.id} (status: ${m.status}) with ${parts.length} parts to preserve context`,
|
|
54
|
-
);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
if (m.role === 'user') {
|
|
@@ -179,9 +171,6 @@ export async function buildHistoryMessages(
|
|
|
179
171
|
let result = toolResultsById.get(obj.callId);
|
|
180
172
|
|
|
181
173
|
if (!result) {
|
|
182
|
-
debugLog(
|
|
183
|
-
`[buildHistoryMessages] Synthesizing error result for incomplete tool call ${obj.name}#${obj.callId}`,
|
|
184
|
-
);
|
|
185
174
|
result = {
|
|
186
175
|
name: obj.name,
|
|
187
176
|
callId: obj.callId,
|
|
@@ -265,16 +254,6 @@ async function _logPendingToolParts(
|
|
|
265
254
|
}
|
|
266
255
|
} catch {}
|
|
267
256
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
`[buildHistoryMessages] Pending tool calls for assistant message ${messageId}: ${pendingCalls.join(', ')}`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
} catch (err) {
|
|
274
|
-
debugLog(
|
|
275
|
-
`[buildHistoryMessages] Failed to inspect pending tool calls for ${messageId}: ${
|
|
276
|
-
err instanceof Error ? err.message : String(err)
|
|
277
|
-
}`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
257
|
+
void pendingCalls;
|
|
258
|
+
} catch {}
|
|
280
259
|
}
|
|
@@ -9,10 +9,10 @@ import { runSessionLoop } from '../agent/runner.ts';
|
|
|
9
9
|
import { resolveModel } from '../provider/index.ts';
|
|
10
10
|
import {
|
|
11
11
|
getFastModelForAuth,
|
|
12
|
+
logger,
|
|
12
13
|
type ProviderId,
|
|
13
14
|
type ReasoningLevel,
|
|
14
15
|
} from '@ottocode/sdk';
|
|
15
|
-
import { debugLog } from '../debug/index.ts';
|
|
16
16
|
import { isCompactCommand, buildCompactionContext } from './compaction.ts';
|
|
17
17
|
import { detectOAuth, adaptSimpleCall } from '../provider/oauth-adapter.ts';
|
|
18
18
|
|
|
@@ -59,13 +59,17 @@ export async function dispatchAssistantMessage(
|
|
|
59
59
|
files,
|
|
60
60
|
} = options;
|
|
61
61
|
|
|
62
|
-
debugLog(
|
|
63
|
-
`[MESSAGE_SERVICE] dispatchAssistantMessage called with userContext: ${userContext ? `${userContext.substring(0, 50)}...` : 'NONE'}`,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
62
|
const sessionId = session.id;
|
|
67
63
|
const now = Date.now();
|
|
68
64
|
const userMessageId = crypto.randomUUID();
|
|
65
|
+
logger.debug('[agent] dispatching assistant message', {
|
|
66
|
+
sessionId,
|
|
67
|
+
agent,
|
|
68
|
+
provider,
|
|
69
|
+
model,
|
|
70
|
+
oneShot: Boolean(oneShot),
|
|
71
|
+
hasUserContext: Boolean(userContext),
|
|
72
|
+
});
|
|
69
73
|
|
|
70
74
|
await db.insert(messages).values({
|
|
71
75
|
id: userMessageId,
|
|
@@ -163,15 +167,10 @@ export async function dispatchAssistantMessage(
|
|
|
163
167
|
},
|
|
164
168
|
});
|
|
165
169
|
|
|
166
|
-
debugLog(
|
|
167
|
-
`[MESSAGE_SERVICE] Enqueuing assistant run with userContext: ${userContext ? `${userContext.substring(0, 50)}...` : 'NONE'}`,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
170
|
const isCompact = isCompactCommand(content);
|
|
171
171
|
let compactionContext: string | undefined;
|
|
172
172
|
|
|
173
173
|
if (isCompact) {
|
|
174
|
-
debugLog('[MESSAGE_SERVICE] Detected /compact command, building context');
|
|
175
174
|
const { getModelLimits } = await import('./compaction.ts');
|
|
176
175
|
const limits = getModelLimits(provider, model);
|
|
177
176
|
const contextTokenLimit = limits
|
|
@@ -182,9 +181,6 @@ export async function dispatchAssistantMessage(
|
|
|
182
181
|
sessionId,
|
|
183
182
|
contextTokenLimit,
|
|
184
183
|
);
|
|
185
|
-
debugLog(
|
|
186
|
-
`[message-service] /compact context length: ${compactionContext.length}, limit: ${contextTokenLimit} tokens`,
|
|
187
|
-
);
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
const toolApprovalMode = cfg.defaults.toolApproval ?? 'dangerous';
|
|
@@ -207,6 +203,14 @@ export async function dispatchAssistantMessage(
|
|
|
207
203
|
},
|
|
208
204
|
runSessionLoop,
|
|
209
205
|
);
|
|
206
|
+
logger.debug('[agent] assistant run enqueued', {
|
|
207
|
+
sessionId,
|
|
208
|
+
assistantMessageId,
|
|
209
|
+
agent,
|
|
210
|
+
provider,
|
|
211
|
+
model,
|
|
212
|
+
isCompactCommand: isCompact,
|
|
213
|
+
});
|
|
210
214
|
|
|
211
215
|
void touchSessionLastActive({ db, sessionId });
|
|
212
216
|
|
|
@@ -249,9 +253,7 @@ function scheduleSessionTitle(args: {
|
|
|
249
253
|
titlePending.delete(sessionId);
|
|
250
254
|
try {
|
|
251
255
|
await generateSessionTitle({ cfg, db, sessionId, content });
|
|
252
|
-
} catch
|
|
253
|
-
debugLog('[TITLE_GEN] Title generation error:');
|
|
254
|
-
debugLog(err);
|
|
256
|
+
} catch {
|
|
255
257
|
} finally {
|
|
256
258
|
titleInFlight.delete(sessionId);
|
|
257
259
|
titleActiveCount--;
|
|
@@ -288,34 +290,24 @@ async function generateSessionTitle(args: {
|
|
|
288
290
|
.where(eq(sessions.id, sessionId));
|
|
289
291
|
|
|
290
292
|
if (!existingSession.length) {
|
|
291
|
-
debugLog('[TITLE_GEN] Session not found, aborting');
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
const sess = existingSession[0];
|
|
296
297
|
if (sess.title && sess.title !== 'New Session') {
|
|
297
|
-
debugLog('[TITLE_GEN] Session already has a title, skipping');
|
|
298
298
|
return;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
const provider = (sess.provider ?? cfg.defaults.provider) as ProviderId;
|
|
302
302
|
const modelName = sess.model ?? cfg.defaults.model;
|
|
303
303
|
|
|
304
|
-
debugLog('[TITLE_GEN] Generating title for session');
|
|
305
|
-
debugLog(`[TITLE_GEN] Provider: ${provider}, Model: ${modelName}`);
|
|
306
|
-
|
|
307
304
|
const { getAuth } = await import('@ottocode/sdk');
|
|
308
305
|
const auth = await getAuth(provider, cfg.projectRoot);
|
|
309
306
|
const oauth = detectOAuth(provider, auth);
|
|
310
307
|
|
|
311
308
|
const titleModel = getFastModelForAuth(provider, auth?.type) ?? modelName;
|
|
312
|
-
debugLog(`[TITLE_GEN] Using title model: ${titleModel}`);
|
|
313
309
|
const model = await resolveModel(provider, titleModel, cfg);
|
|
314
310
|
|
|
315
|
-
debugLog(
|
|
316
|
-
`[TITLE_GEN] oauth: needsSpoof=${oauth.needsSpoof}, isOpenAIOAuth=${oauth.isOpenAIOAuth}`,
|
|
317
|
-
);
|
|
318
|
-
|
|
319
311
|
const promptText = String(content ?? '').slice(0, 2000);
|
|
320
312
|
|
|
321
313
|
const titleInstructions = `Generate a brief title (6-8 words) summarizing what the user wants to do.
|
|
@@ -330,10 +322,6 @@ Output ONLY the title, nothing else.`;
|
|
|
330
322
|
userContent: promptText,
|
|
331
323
|
});
|
|
332
324
|
|
|
333
|
-
debugLog(
|
|
334
|
-
`[TITLE_GEN] mode=${adapted.forceStream ? 'openai-oauth' : oauth.needsSpoof ? 'spoof' : 'api-key'}`,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
325
|
let modelTitle = '';
|
|
338
326
|
try {
|
|
339
327
|
if (adapted.forceStream || oauth.needsSpoof) {
|
|
@@ -348,7 +336,6 @@ Output ONLY the title, nothing else.`;
|
|
|
348
336
|
}
|
|
349
337
|
modelTitle = modelTitle.trim();
|
|
350
338
|
} else {
|
|
351
|
-
debugLog('[TITLE_GEN] Using generateText...');
|
|
352
339
|
const out = await generateText({
|
|
353
340
|
model,
|
|
354
341
|
system: adapted.system,
|
|
@@ -356,24 +343,15 @@ Output ONLY the title, nothing else.`;
|
|
|
356
343
|
});
|
|
357
344
|
modelTitle = (out?.text || '').trim();
|
|
358
345
|
}
|
|
359
|
-
|
|
360
|
-
debugLog('[TITLE_GEN] Raw response from model:');
|
|
361
|
-
debugLog(`[TITLE_GEN] "${modelTitle}"`);
|
|
362
|
-
} catch (err) {
|
|
363
|
-
debugLog('[TITLE_GEN] Error generating title:');
|
|
364
|
-
debugLog(err);
|
|
365
|
-
}
|
|
346
|
+
} catch {}
|
|
366
347
|
|
|
367
348
|
if (!modelTitle) {
|
|
368
|
-
debugLog('[TITLE_GEN] No title returned, aborting');
|
|
369
349
|
return;
|
|
370
350
|
}
|
|
371
351
|
|
|
372
352
|
const sanitized = sanitizeTitle(modelTitle);
|
|
373
|
-
debugLog(`[TITLE_GEN] After sanitization: "${sanitized}"`);
|
|
374
353
|
|
|
375
354
|
if (!sanitized || sanitized === 'New Session') {
|
|
376
|
-
debugLog('[TITLE_GEN] Sanitized title is empty or default, aborting');
|
|
377
355
|
return;
|
|
378
356
|
}
|
|
379
357
|
|
|
@@ -382,17 +360,12 @@ Output ONLY the title, nothing else.`;
|
|
|
382
360
|
.set({ title: sanitized, lastActiveAt: Date.now() })
|
|
383
361
|
.where(eq(sessions.id, sessionId));
|
|
384
362
|
|
|
385
|
-
debugLog(`[TITLE_GEN] Setting final title: "${sanitized}"`);
|
|
386
|
-
|
|
387
363
|
publish({
|
|
388
364
|
type: 'session.updated',
|
|
389
365
|
sessionId,
|
|
390
366
|
payload: { id: sessionId, title: sanitized },
|
|
391
367
|
});
|
|
392
|
-
} catch
|
|
393
|
-
debugLog('[TITLE_GEN] Error in generateSessionTitle:');
|
|
394
|
-
debugLog(err);
|
|
395
|
-
}
|
|
368
|
+
} catch {}
|
|
396
369
|
}
|
|
397
370
|
|
|
398
371
|
function sanitizeTitle(raw: string): string {
|
|
@@ -424,9 +397,7 @@ async function touchSessionLastActive(args: {
|
|
|
424
397
|
.set({ lastActiveAt: Date.now() })
|
|
425
398
|
.where(eq(sessions.id, sessionId))
|
|
426
399
|
.run();
|
|
427
|
-
} catch
|
|
428
|
-
debugLog('[touchSessionLastActive] Error:', err);
|
|
429
|
-
}
|
|
400
|
+
} catch {}
|
|
430
401
|
}
|
|
431
402
|
|
|
432
403
|
export async function triggerDeferredTitleGeneration(args: {
|
|
@@ -445,9 +416,6 @@ export async function triggerDeferredTitleGeneration(args: {
|
|
|
445
416
|
.limit(1);
|
|
446
417
|
|
|
447
418
|
if (!userMessages.length || userMessages[0].role !== 'user') {
|
|
448
|
-
debugLog(
|
|
449
|
-
'[TITLE_GEN] No user message found for deferred title generation',
|
|
450
|
-
);
|
|
451
419
|
return;
|
|
452
420
|
}
|
|
453
421
|
|
|
@@ -459,9 +427,6 @@ export async function triggerDeferredTitleGeneration(args: {
|
|
|
459
427
|
.limit(1);
|
|
460
428
|
|
|
461
429
|
if (!parts.length) {
|
|
462
|
-
debugLog(
|
|
463
|
-
'[TITLE_GEN] No message parts found for deferred title generation',
|
|
464
|
-
);
|
|
465
430
|
return;
|
|
466
431
|
}
|
|
467
432
|
|
|
@@ -470,19 +435,12 @@ export async function triggerDeferredTitleGeneration(args: {
|
|
|
470
435
|
const parsed = JSON.parse(parts[0].content ?? '{}');
|
|
471
436
|
content = String(parsed.text ?? '');
|
|
472
437
|
} catch {
|
|
473
|
-
debugLog('[TITLE_GEN] Failed to parse message part content');
|
|
474
438
|
return;
|
|
475
439
|
}
|
|
476
440
|
|
|
477
441
|
if (!content) {
|
|
478
|
-
debugLog('[TITLE_GEN] Empty content for deferred title generation');
|
|
479
442
|
return;
|
|
480
443
|
}
|
|
481
|
-
|
|
482
|
-
debugLog('[TITLE_GEN] Triggering deferred title generation');
|
|
483
444
|
enqueueSessionTitle({ cfg, db, sessionId, content });
|
|
484
|
-
} catch
|
|
485
|
-
debugLog('[TITLE_GEN] Error in triggerDeferredTitleGeneration:');
|
|
486
|
-
debugLog(err);
|
|
487
|
-
}
|
|
445
|
+
} catch {}
|
|
488
446
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { debugLog } from '../debug/index.ts';
|
|
2
|
-
|
|
3
1
|
type ToolResultPart = {
|
|
4
2
|
type: string;
|
|
5
3
|
state?: string;
|
|
@@ -61,7 +59,6 @@ export class ToolHistoryTracker {
|
|
|
61
59
|
.callProviderMetadata;
|
|
62
60
|
delete (entry.part as { providerMetadata?: unknown }).providerMetadata;
|
|
63
61
|
entry.summarized = true;
|
|
64
|
-
debugLog(`[history] summarized tool output -> ${entry.summary}`);
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { providerBasePrompt } from '@ottocode/sdk';
|
|
2
|
-
import { debugLog } from '../debug/index.ts';
|
|
3
2
|
import { composeEnvironmentAndInstructions } from '../context/environment.ts';
|
|
4
3
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
5
4
|
import BASE_PROMPT from '@ottocode/sdk/prompts/base.txt' with { type: 'text' };
|
|
@@ -163,7 +162,6 @@ export async function composeSystemPrompt(options: {
|
|
|
163
162
|
|
|
164
163
|
const composed = parts.filter(Boolean).join('\n\n').trim();
|
|
165
164
|
if (composed) {
|
|
166
|
-
debugLog(`[system] pieces: ${dedupeComponents(components).join(', ')}`);
|
|
167
165
|
return {
|
|
168
166
|
prompt: composed,
|
|
169
167
|
components: dedupeComponents(components),
|
|
@@ -88,11 +88,11 @@ export function detectOAuth(
|
|
|
88
88
|
const CODEX_INSTRUCTIONS =
|
|
89
89
|
'You are a coding agent. Follow all developer messages. Use tools to complete tasks.';
|
|
90
90
|
|
|
91
|
-
export function buildCodexProviderOptions() {
|
|
91
|
+
export function buildCodexProviderOptions(instructions?: string) {
|
|
92
92
|
return {
|
|
93
93
|
openai: {
|
|
94
94
|
store: false as const,
|
|
95
|
-
instructions: CODEX_INSTRUCTIONS,
|
|
95
|
+
instructions: instructions?.trim() || CODEX_INSTRUCTIONS,
|
|
96
96
|
parallelToolCalls: false,
|
|
97
97
|
},
|
|
98
98
|
};
|
|
@@ -145,7 +145,7 @@ export function adaptSimpleCall(
|
|
|
145
145
|
content: input.userContent,
|
|
146
146
|
},
|
|
147
147
|
],
|
|
148
|
-
providerOptions: buildCodexProviderOptions(),
|
|
148
|
+
providerOptions: buildCodexProviderOptions(input.instructions),
|
|
149
149
|
forceStream: true,
|
|
150
150
|
};
|
|
151
151
|
}
|
|
@@ -229,9 +229,9 @@ export function adaptRunnerCall(
|
|
|
229
229
|
return {
|
|
230
230
|
system: '',
|
|
231
231
|
systemComponents: composed.components,
|
|
232
|
-
additionalSystemMessages: [
|
|
232
|
+
additionalSystemMessages: [],
|
|
233
233
|
maxOutputTokens: undefined,
|
|
234
|
-
providerOptions: buildCodexProviderOptions(),
|
|
234
|
+
providerOptions: buildCodexProviderOptions(composed.prompt),
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -7,7 +7,6 @@ import { toErrorPayload } from '../errors/handling.ts';
|
|
|
7
7
|
import type { RunOpts } from '../session/queue.ts';
|
|
8
8
|
import type { ToolAdapterContext } from '../../tools/adapter.ts';
|
|
9
9
|
import { pruneSession, performAutoCompaction } from '../message/compaction.ts';
|
|
10
|
-
import { debugLog } from '../debug/index.ts';
|
|
11
10
|
import { enqueueAssistantRun } from '../session/queue.ts';
|
|
12
11
|
import { clearPendingTopup } from '../topup/manager.ts';
|
|
13
12
|
|
|
@@ -77,7 +76,6 @@ export function createErrorHandler(
|
|
|
77
76
|
|
|
78
77
|
// Handle fiat payment selected - this is not an error, just a signal to pause
|
|
79
78
|
if (isFiatSelected) {
|
|
80
|
-
debugLog('[stream-handlers] Fiat payment selected, pausing request');
|
|
81
79
|
clearPendingTopup(opts.sessionId);
|
|
82
80
|
|
|
83
81
|
// Add a helpful message part telling user to complete payment
|
|
@@ -171,20 +169,9 @@ export function createErrorHandler(
|
|
|
171
169
|
errorCode === 'context_length_exceeded' ||
|
|
172
170
|
errorType === 'invalid_request_error';
|
|
173
171
|
|
|
174
|
-
debugLog(
|
|
175
|
-
`[stream-handlers] isPromptTooLong: ${isPromptTooLong}, errorCode: ${errorCode}, errorType: ${errorType}`,
|
|
176
|
-
);
|
|
177
|
-
|
|
178
172
|
if (isPromptTooLong && !opts.isCompactCommand) {
|
|
179
|
-
debugLog(
|
|
180
|
-
'[stream-handlers] Prompt too long detected, auto-compacting...',
|
|
181
|
-
);
|
|
182
|
-
|
|
183
173
|
const retries = opts.compactionRetries ?? 0;
|
|
184
174
|
if (retries >= 2) {
|
|
185
|
-
debugLog(
|
|
186
|
-
'[stream-handlers] Compaction retry limit reached, surfacing error',
|
|
187
|
-
);
|
|
188
175
|
} else {
|
|
189
176
|
await db
|
|
190
177
|
.update(messages)
|
|
@@ -243,25 +230,12 @@ export function createErrorHandler(
|
|
|
243
230
|
opts.model,
|
|
244
231
|
);
|
|
245
232
|
if (compactResult.success) {
|
|
246
|
-
debugLog(
|
|
247
|
-
`[stream-handlers] Auto-compaction succeeded: ${compactResult.summary?.slice(0, 100)}...`,
|
|
248
|
-
);
|
|
249
233
|
compactionSucceeded = true;
|
|
250
234
|
} else {
|
|
251
|
-
debugLog(
|
|
252
|
-
`[stream-handlers] Auto-compaction failed: ${compactResult.error}, falling back to prune`,
|
|
253
|
-
);
|
|
254
235
|
const pruneResult = await pruneSession(db, opts.sessionId);
|
|
255
|
-
debugLog(
|
|
256
|
-
`[stream-handlers] Fallback pruned ${pruneResult.pruned} parts, saved ~${pruneResult.saved} tokens`,
|
|
257
|
-
);
|
|
258
236
|
compactionSucceeded = pruneResult.pruned > 0;
|
|
259
237
|
}
|
|
260
|
-
} catch
|
|
261
|
-
debugLog(
|
|
262
|
-
`[stream-handlers] Auto-compact error: ${compactErr instanceof Error ? compactErr.message : String(compactErr)}`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
238
|
+
} catch {}
|
|
265
239
|
|
|
266
240
|
await db
|
|
267
241
|
.update(messages)
|
|
@@ -278,7 +252,6 @@ export function createErrorHandler(
|
|
|
278
252
|
});
|
|
279
253
|
|
|
280
254
|
if (compactionSucceeded && retryCallback) {
|
|
281
|
-
debugLog('[stream-handlers] Triggering retry after compaction...');
|
|
282
255
|
const retryMessageId = crypto.randomUUID();
|
|
283
256
|
await db.insert(messages).values({
|
|
284
257
|
id: retryMessageId,
|
|
@@ -315,9 +288,6 @@ export function createErrorHandler(
|
|
|
315
288
|
}
|
|
316
289
|
|
|
317
290
|
if (compactionSucceeded) {
|
|
318
|
-
debugLog(
|
|
319
|
-
'[stream-handlers] No retryCallback provided, cannot auto-retry',
|
|
320
|
-
);
|
|
321
291
|
return;
|
|
322
292
|
}
|
|
323
293
|
}
|
|
@@ -5,7 +5,6 @@ import { publish } from '../../events/bus.ts';
|
|
|
5
5
|
import { estimateModelCostUsd } from '@ottocode/sdk';
|
|
6
6
|
import type { RunOpts } from '../session/queue.ts';
|
|
7
7
|
import { markSessionCompacted } from '../message/compaction.ts';
|
|
8
|
-
import { debugLog } from '../debug/index.ts';
|
|
9
8
|
import type { FinishEvent } from './types.ts';
|
|
10
9
|
import {
|
|
11
10
|
normalizeUsage,
|
|
@@ -24,11 +23,7 @@ export function createFinishHandler(
|
|
|
24
23
|
return async (fin: FinishEvent) => {
|
|
25
24
|
try {
|
|
26
25
|
await completeAssistantMessageFn(fin, opts, db);
|
|
27
|
-
} catch
|
|
28
|
-
debugLog(
|
|
29
|
-
`[finish-handler] completeAssistantMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
26
|
+
} catch {}
|
|
32
27
|
|
|
33
28
|
if (opts.isCompactCommand && fin.finishReason !== 'error') {
|
|
34
29
|
const assistantParts = await db
|
|
@@ -40,27 +35,15 @@ export function createFinishHandler(
|
|
|
40
35
|
);
|
|
41
36
|
|
|
42
37
|
if (!hasTextContent) {
|
|
43
|
-
debugLog(
|
|
44
|
-
'[stream-handlers] /compact finished but no summary generated, skipping compaction marking',
|
|
45
|
-
);
|
|
46
38
|
} else {
|
|
47
39
|
try {
|
|
48
|
-
debugLog(
|
|
49
|
-
`[stream-handlers] /compact complete, marking session compacted`,
|
|
50
|
-
);
|
|
51
40
|
const result = await markSessionCompacted(
|
|
52
41
|
db,
|
|
53
42
|
opts.sessionId,
|
|
54
43
|
opts.assistantMessageId,
|
|
55
44
|
);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
debugLog(
|
|
61
|
-
`[stream-handlers] Compaction failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
45
|
+
void result;
|
|
46
|
+
} catch {}
|
|
64
47
|
}
|
|
65
48
|
}
|
|
66
49
|
|