@ottocode/server 0.1.265 → 0.1.266
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/auth/copilot.ts +699 -0
- package/src/routes/auth/oauth.ts +578 -0
- package/src/routes/auth/onboarding.ts +45 -0
- package/src/routes/auth/providers.ts +189 -0
- package/src/routes/auth/service.ts +167 -0
- package/src/routes/auth/state.ts +23 -0
- package/src/routes/auth/status.ts +203 -0
- package/src/routes/auth/wallet.ts +229 -0
- package/src/routes/auth.ts +12 -2080
- package/src/routes/config/models-service.ts +411 -0
- package/src/routes/config/models.ts +6 -426
- package/src/routes/config/providers-service.ts +237 -0
- package/src/routes/config/providers.ts +10 -242
- package/src/routes/files/handlers.ts +297 -0
- package/src/routes/files/service.ts +313 -0
- package/src/routes/files.ts +12 -608
- package/src/routes/git/commit-service.ts +207 -0
- package/src/routes/git/commit.ts +6 -220
- package/src/routes/git/remote-service.ts +116 -0
- package/src/routes/git/remote.ts +8 -115
- package/src/routes/git/staging-service.ts +111 -0
- package/src/routes/git/staging.ts +10 -205
- package/src/routes/mcp/auth.ts +338 -0
- package/src/routes/mcp/lifecycle.ts +263 -0
- package/src/routes/mcp/servers.ts +212 -0
- package/src/routes/mcp/service.ts +664 -0
- package/src/routes/mcp/state.ts +13 -0
- package/src/routes/mcp.ts +6 -1233
- package/src/routes/ottorouter/billing.ts +593 -0
- package/src/routes/ottorouter/service.ts +92 -0
- package/src/routes/ottorouter/topup.ts +301 -0
- package/src/routes/ottorouter/wallet.ts +370 -0
- package/src/routes/ottorouter.ts +6 -1319
- package/src/routes/research/service.ts +339 -0
- package/src/routes/research.ts +12 -390
- package/src/routes/sessions/crud.ts +563 -0
- package/src/routes/sessions/queue.ts +242 -0
- package/src/routes/sessions/retry.ts +121 -0
- package/src/routes/sessions/service.ts +768 -0
- package/src/routes/sessions/share.ts +434 -0
- package/src/routes/sessions.ts +8 -1977
- package/src/routes/skills/service.ts +221 -0
- package/src/routes/skills/spec.ts +309 -0
- package/src/routes/skills.ts +31 -909
- package/src/routes/terminals/service.ts +326 -0
- package/src/routes/terminals.ts +19 -295
- package/src/routes/tunnel/service.ts +217 -0
- package/src/routes/tunnel.ts +29 -219
- package/src/runtime/agent/registry-prompts.ts +147 -0
- package/src/runtime/agent/registry.ts +6 -124
- package/src/runtime/agent/runner-errors.ts +116 -0
- package/src/runtime/agent/runner-reminders.ts +45 -0
- package/src/runtime/agent/runner-setup-model.ts +75 -0
- package/src/runtime/agent/runner-setup-prompt.ts +185 -0
- package/src/runtime/agent/runner-setup-tools.ts +103 -0
- package/src/runtime/agent/runner-setup-utils.ts +21 -0
- package/src/runtime/agent/runner-setup.ts +54 -288
- package/src/runtime/agent/runner-telemetry.ts +112 -0
- package/src/runtime/agent/runner-text.ts +108 -0
- package/src/runtime/agent/runner-tool-observer.ts +86 -0
- package/src/runtime/agent/runner.ts +79 -378
- package/src/runtime/provider/custom.ts +73 -0
- package/src/runtime/provider/index.ts +2 -85
- package/src/runtime/provider/reasoning-builders.ts +280 -0
- package/src/runtime/provider/reasoning.ts +67 -264
- package/src/tools/adapter/events.ts +116 -0
- package/src/tools/adapter/execution.ts +160 -0
- package/src/tools/adapter/pending.ts +37 -0
- package/src/tools/adapter/persistence.ts +166 -0
- package/src/tools/adapter/results.ts +97 -0
- package/src/tools/adapter.ts +124 -451
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { subscribe } from '../../events/bus.ts';
|
|
2
|
+
import type { OttoEvent } from '../../events/types.ts';
|
|
3
|
+
import type { createTurnDumpCollector } from '../debug/turn-dump.ts';
|
|
4
|
+
|
|
5
|
+
export type RunnerToolObserverState = {
|
|
6
|
+
finishObserved: boolean;
|
|
7
|
+
toolActivityObserved: boolean;
|
|
8
|
+
trailingAssistantTextAfterTool: boolean;
|
|
9
|
+
endedWithToolActivity: boolean;
|
|
10
|
+
lastToolName?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type TurnDumpCollector = NonNullable<
|
|
14
|
+
ReturnType<typeof createTurnDumpCollector>
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
export function observeRunnerToolEvents(args: {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
dump: TurnDumpCollector | null;
|
|
20
|
+
getStepIndex: () => number;
|
|
21
|
+
onToolCall?: () => void;
|
|
22
|
+
}): { state: RunnerToolObserverState; unsubscribe: () => void } {
|
|
23
|
+
const state: RunnerToolObserverState = {
|
|
24
|
+
finishObserved: false,
|
|
25
|
+
toolActivityObserved: false,
|
|
26
|
+
trailingAssistantTextAfterTool: false,
|
|
27
|
+
endedWithToolActivity: false,
|
|
28
|
+
lastToolName: undefined,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const unsubscribe = subscribe(args.sessionId, (evt: OttoEvent) => {
|
|
32
|
+
if (evt.type === 'tool.call' || evt.type === 'tool.result') {
|
|
33
|
+
state.toolActivityObserved = true;
|
|
34
|
+
state.trailingAssistantTextAfterTool = false;
|
|
35
|
+
state.endedWithToolActivity = true;
|
|
36
|
+
try {
|
|
37
|
+
state.lastToolName = (
|
|
38
|
+
evt.payload as { name?: string } | undefined
|
|
39
|
+
)?.name;
|
|
40
|
+
} catch {
|
|
41
|
+
state.lastToolName = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (evt.type === 'tool.call') {
|
|
45
|
+
args.onToolCall?.();
|
|
46
|
+
if (args.dump) {
|
|
47
|
+
try {
|
|
48
|
+
const p = evt.payload as {
|
|
49
|
+
name?: string;
|
|
50
|
+
callId?: string;
|
|
51
|
+
args?: unknown;
|
|
52
|
+
};
|
|
53
|
+
args.dump.recordToolCall(
|
|
54
|
+
args.getStepIndex(),
|
|
55
|
+
p.name ?? '',
|
|
56
|
+
p.callId ?? '',
|
|
57
|
+
p.args,
|
|
58
|
+
);
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (evt.type === 'tool.result') {
|
|
63
|
+
if (args.dump) {
|
|
64
|
+
try {
|
|
65
|
+
const p = evt.payload as {
|
|
66
|
+
name?: string;
|
|
67
|
+
callId?: string;
|
|
68
|
+
result?: unknown;
|
|
69
|
+
};
|
|
70
|
+
args.dump.recordToolResult(
|
|
71
|
+
args.getStepIndex(),
|
|
72
|
+
p.name ?? '',
|
|
73
|
+
p.callId ?? '',
|
|
74
|
+
p.result,
|
|
75
|
+
);
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
80
|
+
if (name === 'finish') state.finishObserved = true;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { state, unsubscribe };
|
|
86
|
+
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { hasToolCall, streamText } from 'ai';
|
|
2
2
|
import { logger } from '@ottocode/sdk';
|
|
3
|
-
import
|
|
4
|
-
import { messageParts, sessions } from '@ottocode/database/schema';
|
|
5
|
-
import { eq } from 'drizzle-orm';
|
|
6
|
-
import { publish, subscribe } from '../../events/bus.ts';
|
|
7
|
-
import { time } from '../debug/index.ts';
|
|
8
|
-
import { toErrorPayload } from '../errors/handling.ts';
|
|
3
|
+
import { publish } from '../../events/bus.ts';
|
|
9
4
|
import {
|
|
10
5
|
type RunOpts,
|
|
11
6
|
setRunning,
|
|
@@ -24,10 +19,6 @@ import {
|
|
|
24
19
|
createAbortHandler,
|
|
25
20
|
createFinishHandler,
|
|
26
21
|
} from '../stream/handlers.ts';
|
|
27
|
-
import {
|
|
28
|
-
pruneSession,
|
|
29
|
-
shouldAutoCompactBeforeOverflow,
|
|
30
|
-
} from '../message/compaction.ts';
|
|
31
22
|
import { triggerDeferredTitleGeneration } from '../message/service.ts';
|
|
32
23
|
import { setupRunner } from './runner-setup.ts';
|
|
33
24
|
import {
|
|
@@ -46,6 +37,21 @@ import {
|
|
|
46
37
|
consumeOauthCodexTextDelta,
|
|
47
38
|
} from '../stream/text-guard.ts';
|
|
48
39
|
import { createTurnDumpCollector } from '../debug/turn-dump.ts';
|
|
40
|
+
import {
|
|
41
|
+
appendRunnerReminderMessages,
|
|
42
|
+
type RunnerMessage,
|
|
43
|
+
} from './runner-reminders.ts';
|
|
44
|
+
import {
|
|
45
|
+
createFirstOutputLatencyLogger,
|
|
46
|
+
logStreamRequestReady,
|
|
47
|
+
nowMs,
|
|
48
|
+
} from './runner-telemetry.ts';
|
|
49
|
+
import { handleRunnerTextDelta, type RunnerTextState } from './runner-text.ts';
|
|
50
|
+
import { observeRunnerToolEvents } from './runner-tool-observer.ts';
|
|
51
|
+
import {
|
|
52
|
+
handleRunnerError,
|
|
53
|
+
shouldPreemptivelyAutoCompact,
|
|
54
|
+
} from './runner-errors.ts';
|
|
49
55
|
|
|
50
56
|
export {
|
|
51
57
|
enqueueAssistantRun,
|
|
@@ -56,95 +62,6 @@ export {
|
|
|
56
62
|
getRunnerState,
|
|
57
63
|
} from '../session/queue.ts';
|
|
58
64
|
|
|
59
|
-
const DEFAULT_TRACED_TOOL_INPUTS = new Set([
|
|
60
|
-
'write',
|
|
61
|
-
'edit',
|
|
62
|
-
'multiedit',
|
|
63
|
-
'copy_into',
|
|
64
|
-
'apply_patch',
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
function shouldTraceToolInput(name: string): boolean {
|
|
68
|
-
void DEFAULT_TRACED_TOOL_INPUTS;
|
|
69
|
-
void name;
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function summarizeTraceValue(value: unknown, max = 160): string {
|
|
74
|
-
try {
|
|
75
|
-
const json = JSON.stringify(value);
|
|
76
|
-
if (typeof json === 'string') {
|
|
77
|
-
return json.length > max ? `${json.slice(0, max)}…` : json;
|
|
78
|
-
}
|
|
79
|
-
} catch {}
|
|
80
|
-
const fallback = String(value);
|
|
81
|
-
return fallback.length > max ? `${fallback.slice(0, max)}…` : fallback;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function nowMs(): number {
|
|
85
|
-
const perf = globalThis.performance;
|
|
86
|
-
if (perf && typeof perf.now === 'function') return perf.now();
|
|
87
|
-
return Date.now();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function approximateMessageChars(
|
|
91
|
-
messages: Array<{ role: string; content: string | Array<unknown> }>,
|
|
92
|
-
): number {
|
|
93
|
-
let total = 0;
|
|
94
|
-
for (const message of messages) {
|
|
95
|
-
total += message.role.length;
|
|
96
|
-
if (typeof message.content === 'string') {
|
|
97
|
-
total += message.content.length;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
total += JSON.stringify(message.content).length;
|
|
102
|
-
} catch {}
|
|
103
|
-
}
|
|
104
|
-
return total;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function summarizeToolShape(tools: Record<string, unknown>) {
|
|
108
|
-
const names = Object.keys(tools);
|
|
109
|
-
const entries = names.map((name) => {
|
|
110
|
-
const toolValue = tools[name];
|
|
111
|
-
let approxChars = 0;
|
|
112
|
-
try {
|
|
113
|
-
approxChars = JSON.stringify(toolValue).length;
|
|
114
|
-
} catch {}
|
|
115
|
-
return { name, approxChars };
|
|
116
|
-
});
|
|
117
|
-
entries.sort((a, b) => b.approxChars - a.approxChars);
|
|
118
|
-
return {
|
|
119
|
-
toolNames: names,
|
|
120
|
-
toolSchemaCharsApprox: entries.reduce(
|
|
121
|
-
(total, entry) => total + entry.approxChars,
|
|
122
|
-
0,
|
|
123
|
-
),
|
|
124
|
-
largestTools: entries.slice(0, 8),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function shouldPreemptivelyAutoCompact(
|
|
129
|
-
db: Awaited<ReturnType<typeof getDb>>,
|
|
130
|
-
opts: RunOpts,
|
|
131
|
-
threshold: number | null | undefined,
|
|
132
|
-
): Promise<boolean> {
|
|
133
|
-
const sessionRows = await db
|
|
134
|
-
.select({ currentContextTokens: sessions.currentContextTokens })
|
|
135
|
-
.from(sessions)
|
|
136
|
-
.where(eq(sessions.id, opts.sessionId))
|
|
137
|
-
.limit(1);
|
|
138
|
-
|
|
139
|
-
return shouldAutoCompactBeforeOverflow({
|
|
140
|
-
autoCompactThresholdTokens: threshold,
|
|
141
|
-
currentContextTokens: sessionRows[0]?.currentContextTokens ?? 0,
|
|
142
|
-
estimatedInputTokens: opts.estimatedInputTokens ?? 0,
|
|
143
|
-
isCompactCommand: opts.isCompactCommand,
|
|
144
|
-
compactionRetries: opts.compactionRetries,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
65
|
export async function runSessionLoop(sessionId: string) {
|
|
149
66
|
setRunning(sessionId, true);
|
|
150
67
|
|
|
@@ -225,41 +142,16 @@ async function runAssistant(opts: RunOpts) {
|
|
|
225
142
|
|
|
226
143
|
const isFirstMessage = !history.some((m) => m.role === 'assistant');
|
|
227
144
|
|
|
228
|
-
const messagesWithSystemInstructions:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'[system-reminder] Continuing an existing session. Execute directly, use tools as needed, and call `finish` at the end. For simple questions, your answer IS the response — do not add a "Summary:" recap.',
|
|
239
|
-
});
|
|
240
|
-
} else {
|
|
241
|
-
messagesWithSystemInstructions.push({
|
|
242
|
-
role: 'user',
|
|
243
|
-
content:
|
|
244
|
-
'<system-reminder>Continuing an existing session. Answer or complete the work directly, then call `finish`. For simple questions, your answer IS the response — do NOT add a labeled "Summary:" line or recap trivial replies.</system-reminder>',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if ((opts.continuationCount ?? 0) > 0) {
|
|
249
|
-
if (isOpenAIOAuth) {
|
|
250
|
-
messagesWithSystemInstructions.push({
|
|
251
|
-
role: 'system',
|
|
252
|
-
content:
|
|
253
|
-
'[system-reminder] Your previous response stopped mid-task. Resume from where you left off and complete the actual work — not a plan-only update.',
|
|
254
|
-
});
|
|
255
|
-
} else {
|
|
256
|
-
messagesWithSystemInstructions.push({
|
|
257
|
-
role: 'user',
|
|
258
|
-
content:
|
|
259
|
-
'<system-reminder>Your previous response stopped before calling `finish`. Resume from where you left off, do the actual work (no plan-only updates), then stream a summary and call `finish`.</system-reminder>',
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
}
|
|
145
|
+
const messagesWithSystemInstructions: RunnerMessage[] = [
|
|
146
|
+
...additionalSystemMessages,
|
|
147
|
+
...history,
|
|
148
|
+
];
|
|
149
|
+
appendRunnerReminderMessages({
|
|
150
|
+
messages: messagesWithSystemInstructions,
|
|
151
|
+
isFirstMessage,
|
|
152
|
+
isOpenAIOAuth,
|
|
153
|
+
continuationCount: opts.continuationCount,
|
|
154
|
+
});
|
|
263
155
|
|
|
264
156
|
const dump = createTurnDumpCollector({
|
|
265
157
|
sessionId: opts.sessionId,
|
|
@@ -286,98 +178,34 @@ async function runAssistant(opts: RunOpts) {
|
|
|
286
178
|
});
|
|
287
179
|
}
|
|
288
180
|
|
|
289
|
-
let _finishObserved = false;
|
|
290
|
-
let _toolActivityObserved = false;
|
|
291
|
-
let _trailingAssistantTextAfterTool = false;
|
|
292
|
-
let _endedWithToolActivity = false;
|
|
293
|
-
let _lastToolName: string | undefined;
|
|
294
181
|
let _abortedByUser = false;
|
|
295
182
|
let titleGenerationTriggered = false;
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
_lastToolName = (evt.payload as { name?: string } | undefined)?.name;
|
|
303
|
-
} catch {
|
|
304
|
-
_lastToolName = undefined;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (evt.type === 'tool.call') {
|
|
308
|
-
triggerTitleGenerationWhenReady();
|
|
309
|
-
if (dump) {
|
|
310
|
-
try {
|
|
311
|
-
const p = evt.payload as {
|
|
312
|
-
name?: string;
|
|
313
|
-
callId?: string;
|
|
314
|
-
args?: unknown;
|
|
315
|
-
};
|
|
316
|
-
dump.recordToolCall(stepIndex, p.name ?? '', p.callId ?? '', p.args);
|
|
317
|
-
} catch {}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (evt.type === 'tool.result') {
|
|
321
|
-
if (dump) {
|
|
322
|
-
try {
|
|
323
|
-
const p = evt.payload as {
|
|
324
|
-
name?: string;
|
|
325
|
-
callId?: string;
|
|
326
|
-
result?: unknown;
|
|
327
|
-
};
|
|
328
|
-
dump.recordToolResult(
|
|
329
|
-
stepIndex,
|
|
330
|
-
p.name ?? '',
|
|
331
|
-
p.callId ?? '',
|
|
332
|
-
p.result,
|
|
333
|
-
);
|
|
334
|
-
} catch {}
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
338
|
-
if (name === 'finish') _finishObserved = true;
|
|
339
|
-
} catch {}
|
|
340
|
-
}
|
|
183
|
+
const logFirstOutputLatency = createFirstOutputLatencyLogger({
|
|
184
|
+
opts,
|
|
185
|
+
runStartedAt,
|
|
186
|
+
queueWaitMs,
|
|
187
|
+
timings,
|
|
341
188
|
});
|
|
342
189
|
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
streamStartTimer.end({ kind, queueWaitMs, setupMs: timings.totalMs });
|
|
350
|
-
logger.info('[latency] first output', {
|
|
351
|
-
sessionId: opts.sessionId,
|
|
352
|
-
messageId: opts.assistantMessageId,
|
|
353
|
-
agent: opts.agent,
|
|
354
|
-
provider: opts.provider,
|
|
355
|
-
model: opts.model,
|
|
356
|
-
kind,
|
|
357
|
-
queueWaitMs,
|
|
358
|
-
firstOutputMs,
|
|
359
|
-
setupMs: timings.totalMs,
|
|
360
|
-
totalSinceEnqueueMs: queueWaitMs + firstOutputMs,
|
|
361
|
-
timings,
|
|
362
|
-
});
|
|
190
|
+
const textState: RunnerTextState = {
|
|
191
|
+
currentPartId: null,
|
|
192
|
+
accumulated: '',
|
|
193
|
+
latestAssistantText: '',
|
|
194
|
+
lastTextDeltaStepIndex: null,
|
|
195
|
+
firstPublishedDeltaSeen: false,
|
|
363
196
|
};
|
|
364
|
-
|
|
365
|
-
let currentPartId: string | null = null;
|
|
366
|
-
let accumulated = '';
|
|
367
|
-
let latestAssistantText = '';
|
|
368
|
-
let lastTextDeltaStepIndex: number | null = null;
|
|
369
197
|
let stepIndex = 0;
|
|
370
198
|
const oauthTextGuard = isOpenAIOAuth
|
|
371
199
|
? createOauthCodexTextGuardState()
|
|
372
200
|
: null;
|
|
373
201
|
|
|
374
|
-
const getCurrentPartId = () => currentPartId;
|
|
202
|
+
const getCurrentPartId = () => textState.currentPartId;
|
|
375
203
|
const getStepIndex = () => stepIndex;
|
|
376
204
|
const updateCurrentPartId = (id: string | null) => {
|
|
377
|
-
currentPartId = id;
|
|
205
|
+
textState.currentPartId = id;
|
|
378
206
|
};
|
|
379
207
|
const updateAccumulated = (text: string) => {
|
|
380
|
-
accumulated = text;
|
|
208
|
+
textState.accumulated = text;
|
|
381
209
|
};
|
|
382
210
|
const incrementStepIndex = () => {
|
|
383
211
|
stepIndex += 1;
|
|
@@ -399,6 +227,13 @@ async function runAssistant(opts: RunOpts) {
|
|
|
399
227
|
sessionId: opts.sessionId,
|
|
400
228
|
});
|
|
401
229
|
};
|
|
230
|
+
const toolObserver = observeRunnerToolEvents({
|
|
231
|
+
sessionId: opts.sessionId,
|
|
232
|
+
dump,
|
|
233
|
+
getStepIndex,
|
|
234
|
+
onToolCall: triggerTitleGenerationWhenReady,
|
|
235
|
+
});
|
|
236
|
+
const unsubscribeFinish = toolObserver.unsubscribe;
|
|
402
237
|
|
|
403
238
|
const reasoningStates = new Map<string, ReasoningState>();
|
|
404
239
|
|
|
@@ -452,26 +287,13 @@ async function runAssistant(opts: RunOpts) {
|
|
|
452
287
|
const stopWhenCondition = isCopilotResponsesApi
|
|
453
288
|
? undefined
|
|
454
289
|
: hasToolCall('finish');
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
messageId: opts.assistantMessageId,
|
|
459
|
-
agent: opts.agent,
|
|
460
|
-
provider: opts.provider,
|
|
461
|
-
model: opts.model,
|
|
290
|
+
logStreamRequestReady({
|
|
291
|
+
opts,
|
|
292
|
+
setup,
|
|
462
293
|
queueWaitMs,
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
toolCount: Object.keys(toolset).length,
|
|
466
|
-
toolNames: toolShape.toolNames,
|
|
467
|
-
toolSchemaCharsApprox: toolShape.toolSchemaCharsApprox,
|
|
468
|
-
largestTools: toolShape.largestTools,
|
|
294
|
+
messages: messagesWithSystemInstructions,
|
|
295
|
+
toolset: toolset as Record<string, unknown>,
|
|
469
296
|
hasPrepareStep: Boolean(prepareStep),
|
|
470
|
-
providerOptionsKeys: Object.keys(providerOptions),
|
|
471
|
-
systemPromptChars: system.length,
|
|
472
|
-
messageCharsApprox: approximateMessageChars(messagesWithSystemInstructions),
|
|
473
|
-
additionalSystemMessages: additionalSystemMessages.length,
|
|
474
|
-
historyMessages: history.length,
|
|
475
297
|
});
|
|
476
298
|
|
|
477
299
|
try {
|
|
@@ -516,9 +338,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
516
338
|
model: opts.model,
|
|
517
339
|
invokeMs: nowMs() - streamInvocationStartedAt,
|
|
518
340
|
});
|
|
519
|
-
const tracedToolInputNamesById = new Map<string, string>();
|
|
520
341
|
let firstFullStreamPartSeen = false;
|
|
521
|
-
let firstPublishedDeltaSeen = false;
|
|
522
342
|
|
|
523
343
|
for await (const part of result.fullStream) {
|
|
524
344
|
if (!part) continue;
|
|
@@ -538,38 +358,22 @@ async function runAssistant(opts: RunOpts) {
|
|
|
538
358
|
}
|
|
539
359
|
|
|
540
360
|
if (part.type === 'tool-input-start') {
|
|
541
|
-
if (shouldTraceToolInput(part.toolName)) {
|
|
542
|
-
tracedToolInputNamesById.set(part.id, part.toolName);
|
|
543
|
-
}
|
|
544
361
|
continue;
|
|
545
362
|
}
|
|
546
363
|
|
|
547
364
|
if (part.type === 'tool-input-delta') {
|
|
548
|
-
const toolName = tracedToolInputNamesById.get(part.id);
|
|
549
|
-
if (toolName) void summarizeTraceValue(part.delta);
|
|
550
365
|
continue;
|
|
551
366
|
}
|
|
552
367
|
|
|
553
368
|
if (part.type === 'tool-input-end') {
|
|
554
|
-
const toolName = tracedToolInputNamesById.get(part.id);
|
|
555
|
-
if (toolName) {
|
|
556
|
-
tracedToolInputNamesById.delete(part.id);
|
|
557
|
-
}
|
|
558
369
|
continue;
|
|
559
370
|
}
|
|
560
371
|
|
|
561
372
|
if (part.type === 'tool-call') {
|
|
562
|
-
if (shouldTraceToolInput(part.toolName)) {
|
|
563
|
-
tracedToolInputNamesById.delete(part.toolCallId);
|
|
564
|
-
void summarizeTraceValue(part.input);
|
|
565
|
-
}
|
|
566
373
|
continue;
|
|
567
374
|
}
|
|
568
375
|
|
|
569
376
|
if (part.type === 'tool-result') {
|
|
570
|
-
if (shouldTraceToolInput(part.toolName)) {
|
|
571
|
-
void summarizeTraceValue(part.output);
|
|
572
|
-
}
|
|
573
377
|
continue;
|
|
574
378
|
}
|
|
575
379
|
|
|
@@ -582,73 +386,21 @@ async function runAssistant(opts: RunOpts) {
|
|
|
582
386
|
: rawDelta;
|
|
583
387
|
if (!delta) continue;
|
|
584
388
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (!currentPartId && !accumulated.trim()) {
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
logFirstOutputLatency('text');
|
|
606
|
-
|
|
607
|
-
if (!currentPartId) {
|
|
608
|
-
currentPartId = crypto.randomUUID();
|
|
609
|
-
sharedCtx.assistantPartId = currentPartId;
|
|
610
|
-
await db.insert(messageParts).values({
|
|
611
|
-
id: currentPartId,
|
|
612
|
-
messageId: opts.assistantMessageId,
|
|
613
|
-
index: await sharedCtx.nextIndex(),
|
|
614
|
-
stepIndex: null,
|
|
615
|
-
type: 'text',
|
|
616
|
-
content: JSON.stringify({ text: accumulated }),
|
|
617
|
-
agent: opts.agent,
|
|
618
|
-
provider: opts.provider,
|
|
619
|
-
model: opts.model,
|
|
620
|
-
startedAt: Date.now(),
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
publish({
|
|
625
|
-
type: 'message.part.delta',
|
|
626
|
-
sessionId: opts.sessionId,
|
|
627
|
-
payload: {
|
|
628
|
-
messageId: opts.assistantMessageId,
|
|
629
|
-
partId: currentPartId,
|
|
630
|
-
stepIndex,
|
|
631
|
-
delta,
|
|
632
|
-
},
|
|
389
|
+
await handleRunnerTextDelta({
|
|
390
|
+
delta,
|
|
391
|
+
state: textState,
|
|
392
|
+
toolObserver: toolObserver.state,
|
|
393
|
+
opts,
|
|
394
|
+
db,
|
|
395
|
+
sharedCtx,
|
|
396
|
+
stepIndex,
|
|
397
|
+
dump,
|
|
398
|
+
firstToolSeen,
|
|
399
|
+
logFirstOutputLatency,
|
|
400
|
+
runStartedAt,
|
|
401
|
+
queueWaitMs,
|
|
402
|
+
setupMs: timings.totalMs,
|
|
633
403
|
});
|
|
634
|
-
if (!firstPublishedDeltaSeen) {
|
|
635
|
-
firstPublishedDeltaSeen = true;
|
|
636
|
-
logger.info('[latency] first published delta', {
|
|
637
|
-
sessionId: opts.sessionId,
|
|
638
|
-
messageId: opts.assistantMessageId,
|
|
639
|
-
agent: opts.agent,
|
|
640
|
-
provider: opts.provider,
|
|
641
|
-
model: opts.model,
|
|
642
|
-
sinceRunStartMs: nowMs() - runStartedAt,
|
|
643
|
-
queueWaitMs,
|
|
644
|
-
setupMs: timings.totalMs,
|
|
645
|
-
deltaPreview: delta.length > 80 ? `${delta.slice(0, 80)}…` : delta,
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
await db
|
|
649
|
-
.update(messageParts)
|
|
650
|
-
.set({ content: JSON.stringify({ text: accumulated }) })
|
|
651
|
-
.where(eq(messageParts.id, currentPartId));
|
|
652
404
|
continue;
|
|
653
405
|
}
|
|
654
406
|
|
|
@@ -690,7 +442,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
690
442
|
}
|
|
691
443
|
|
|
692
444
|
const fs = firstToolSeen();
|
|
693
|
-
if (!fs && !
|
|
445
|
+
if (!fs && !toolObserver.state.finishObserved) {
|
|
694
446
|
publish({
|
|
695
447
|
type: 'finish-step',
|
|
696
448
|
sessionId: opts.sessionId,
|
|
@@ -717,10 +469,11 @@ async function runAssistant(opts: RunOpts) {
|
|
|
717
469
|
}
|
|
718
470
|
|
|
719
471
|
if (dump) {
|
|
720
|
-
const finalTextSnapshot =
|
|
472
|
+
const finalTextSnapshot =
|
|
473
|
+
textState.latestAssistantText || textState.accumulated;
|
|
721
474
|
if (finalTextSnapshot.length > 0) {
|
|
722
475
|
dump.recordTextDelta(
|
|
723
|
-
lastTextDeltaStepIndex ?? stepIndex,
|
|
476
|
+
textState.lastTextDeltaStepIndex ?? stepIndex,
|
|
724
477
|
finalTextSnapshot,
|
|
725
478
|
{ force: true },
|
|
726
479
|
);
|
|
@@ -728,74 +481,22 @@ async function runAssistant(opts: RunOpts) {
|
|
|
728
481
|
dump.recordStreamEnd({
|
|
729
482
|
finishReason: streamFinishReason,
|
|
730
483
|
rawFinishReason: streamRawFinishReason,
|
|
731
|
-
finishObserved:
|
|
484
|
+
finishObserved: toolObserver.state.finishObserved,
|
|
732
485
|
aborted: _abortedByUser,
|
|
733
486
|
});
|
|
734
487
|
}
|
|
735
488
|
} catch (err) {
|
|
736
489
|
unsubscribeFinish();
|
|
737
490
|
dump?.recordError(err);
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const isPromptTooLong =
|
|
747
|
-
combinedError.includes('prompt is too long') ||
|
|
748
|
-
combinedError.includes('maximum context length') ||
|
|
749
|
-
combinedError.includes('too many tokens') ||
|
|
750
|
-
combinedError.includes('context_length_exceeded') ||
|
|
751
|
-
combinedError.includes('request too large') ||
|
|
752
|
-
combinedError.includes('exceeds the model') ||
|
|
753
|
-
combinedError.includes('input is too long') ||
|
|
754
|
-
errorCode === 'context_length_exceeded' ||
|
|
755
|
-
apiErrorType === 'invalid_request_error';
|
|
756
|
-
|
|
757
|
-
if (isPromptTooLong && !opts.isCompactCommand) {
|
|
758
|
-
try {
|
|
759
|
-
const pruneResult = await pruneSession(db, opts.sessionId);
|
|
760
|
-
void pruneResult;
|
|
761
|
-
|
|
762
|
-
publish({
|
|
763
|
-
type: 'error',
|
|
764
|
-
sessionId: opts.sessionId,
|
|
765
|
-
payload: {
|
|
766
|
-
...payload,
|
|
767
|
-
message: `Context too large. Auto-compacted old tool results. Please retry your message.`,
|
|
768
|
-
name: 'ContextOverflow',
|
|
769
|
-
},
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
try {
|
|
773
|
-
await completeAssistantMessage({}, opts, db);
|
|
774
|
-
} catch {}
|
|
775
|
-
return;
|
|
776
|
-
} catch {}
|
|
777
|
-
}
|
|
778
|
-
publish({
|
|
779
|
-
type: 'error',
|
|
780
|
-
sessionId: opts.sessionId,
|
|
781
|
-
payload,
|
|
491
|
+
const outcome = await handleRunnerError({
|
|
492
|
+
err,
|
|
493
|
+
opts,
|
|
494
|
+
db,
|
|
495
|
+
completeAssistantMessage,
|
|
496
|
+
updateSessionTokensIncremental,
|
|
497
|
+
updateMessageTokensIncremental,
|
|
782
498
|
});
|
|
783
|
-
|
|
784
|
-
try {
|
|
785
|
-
await updateSessionTokensIncremental(
|
|
786
|
-
{ inputTokens: 0, outputTokens: 0 },
|
|
787
|
-
undefined,
|
|
788
|
-
opts,
|
|
789
|
-
db,
|
|
790
|
-
);
|
|
791
|
-
await updateMessageTokensIncremental(
|
|
792
|
-
{ inputTokens: 0, outputTokens: 0 },
|
|
793
|
-
undefined,
|
|
794
|
-
opts,
|
|
795
|
-
db,
|
|
796
|
-
);
|
|
797
|
-
await completeAssistantMessage({}, opts, db);
|
|
798
|
-
} catch {}
|
|
499
|
+
if (outcome === 'handled') return;
|
|
799
500
|
throw err;
|
|
800
501
|
} finally {
|
|
801
502
|
if (dump) {
|