@ottocode/server 0.1.228 → 0.1.230
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/ask.ts +11 -0
- package/src/openapi/paths/config.ts +15 -0
- package/src/openapi/paths/messages.ts +6 -0
- package/src/openapi/schemas.ts +5 -0
- package/src/routes/ask.ts +8 -0
- package/src/routes/config/defaults.ts +9 -1
- package/src/routes/config/main.ts +1 -0
- package/src/routes/session-messages.ts +6 -1
- package/src/routes/sessions.ts +4 -1
- package/src/runtime/agent/runner-setup.ts +43 -34
- package/src/runtime/agent/runner.ts +171 -8
- package/src/runtime/ask/service.ts +16 -0
- package/src/runtime/debug/turn-dump.ts +330 -0
- package/src/runtime/message/history-builder.ts +99 -91
- package/src/runtime/message/service.ts +8 -1
- package/src/runtime/prompt/builder.ts +8 -6
- package/src/runtime/provider/reasoning.ts +291 -0
- package/src/runtime/session/queue.ts +2 -0
- package/src/tools/adapter.ts +84 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.230",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/sdk": "0.1.
|
|
53
|
-
"@ottocode/database": "0.1.
|
|
52
|
+
"@ottocode/sdk": "0.1.230",
|
|
53
|
+
"@ottocode/database": "0.1.230",
|
|
54
54
|
"drizzle-orm": "^0.44.5",
|
|
55
55
|
"hono": "^4.9.9",
|
|
56
56
|
"zod": "^4.3.6"
|
package/src/openapi/paths/ask.ts
CHANGED
|
@@ -35,6 +35,17 @@ export const askPaths = {
|
|
|
35
35
|
description:
|
|
36
36
|
'Optional model override for the selected provider.',
|
|
37
37
|
},
|
|
38
|
+
reasoningText: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
description:
|
|
41
|
+
'Enable extended thinking / reasoning for models that support it.',
|
|
42
|
+
},
|
|
43
|
+
reasoningLevel: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max', 'xhigh'],
|
|
46
|
+
description:
|
|
47
|
+
'Optional reasoning intensity override for supported providers/models.',
|
|
48
|
+
},
|
|
38
49
|
sessionId: {
|
|
39
50
|
type: 'string',
|
|
40
51
|
description: 'Send the prompt to a specific session.',
|
|
@@ -179,6 +179,10 @@ export const configPaths = {
|
|
|
179
179
|
provider: { type: 'string' },
|
|
180
180
|
model: { type: 'string' },
|
|
181
181
|
reasoningText: { type: 'boolean' },
|
|
182
|
+
reasoningLevel: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max', 'xhigh'],
|
|
185
|
+
},
|
|
182
186
|
scope: {
|
|
183
187
|
type: 'string',
|
|
184
188
|
enum: ['global', 'local'],
|
|
@@ -205,6 +209,17 @@ export const configPaths = {
|
|
|
205
209
|
provider: { type: 'string' },
|
|
206
210
|
model: { type: 'string' },
|
|
207
211
|
reasoningText: { type: 'boolean' },
|
|
212
|
+
reasoningLevel: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
enum: [
|
|
215
|
+
'minimal',
|
|
216
|
+
'low',
|
|
217
|
+
'medium',
|
|
218
|
+
'high',
|
|
219
|
+
'max',
|
|
220
|
+
'xhigh',
|
|
221
|
+
],
|
|
222
|
+
},
|
|
208
223
|
},
|
|
209
224
|
required: ['agent', 'provider', 'model'],
|
|
210
225
|
},
|
|
@@ -72,6 +72,12 @@ export const messagesPaths = {
|
|
|
72
72
|
description:
|
|
73
73
|
'Enable extended thinking / reasoning for models that support it.',
|
|
74
74
|
},
|
|
75
|
+
reasoningLevel: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max', 'xhigh'],
|
|
78
|
+
description:
|
|
79
|
+
'Reasoning intensity level for providers/models that support it.',
|
|
80
|
+
},
|
|
75
81
|
},
|
|
76
82
|
},
|
|
77
83
|
},
|
package/src/openapi/schemas.ts
CHANGED
|
@@ -73,6 +73,7 @@ export const schemas = {
|
|
|
73
73
|
additionalProperties: { type: 'integer' },
|
|
74
74
|
nullable: true,
|
|
75
75
|
},
|
|
76
|
+
isRunning: { type: 'boolean' },
|
|
76
77
|
},
|
|
77
78
|
required: ['id', 'agent', 'provider', 'model', 'projectPath', 'createdAt'],
|
|
78
79
|
},
|
|
@@ -196,6 +197,10 @@ export const schemas = {
|
|
|
196
197
|
provider: { $ref: '#/components/schemas/Provider' },
|
|
197
198
|
model: { type: 'string' },
|
|
198
199
|
reasoningText: { type: 'boolean' },
|
|
200
|
+
reasoningLevel: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max', 'xhigh'],
|
|
203
|
+
},
|
|
199
204
|
},
|
|
200
205
|
required: ['agent', 'provider', 'model'],
|
|
201
206
|
},
|
package/src/routes/ask.ts
CHANGED
|
@@ -79,6 +79,14 @@ export function registerAskRoutes(app: Hono) {
|
|
|
79
79
|
agent: typeof body.agent === 'string' ? body.agent : undefined,
|
|
80
80
|
provider: typeof body.provider === 'string' ? body.provider : undefined,
|
|
81
81
|
model: typeof body.model === 'string' ? body.model : undefined,
|
|
82
|
+
reasoningText:
|
|
83
|
+
typeof body.reasoningText === 'boolean'
|
|
84
|
+
? body.reasoningText
|
|
85
|
+
: undefined,
|
|
86
|
+
reasoningLevel:
|
|
87
|
+
typeof body.reasoningLevel === 'string'
|
|
88
|
+
? (body.reasoningLevel as AskServerRequest['reasoningLevel'])
|
|
89
|
+
: undefined,
|
|
82
90
|
sessionId:
|
|
83
91
|
typeof body.sessionId === 'string' ? body.sessionId : undefined,
|
|
84
92
|
last: Boolean(body.last),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
setConfig,
|
|
4
|
+
loadConfig,
|
|
5
|
+
type ProviderId,
|
|
6
|
+
type ReasoningLevel,
|
|
7
|
+
} from '@ottocode/sdk';
|
|
3
8
|
import { logger } from '@ottocode/sdk';
|
|
4
9
|
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
5
10
|
|
|
@@ -14,6 +19,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
14
19
|
toolApproval?: 'auto' | 'dangerous' | 'all';
|
|
15
20
|
guidedMode?: boolean;
|
|
16
21
|
reasoningText?: boolean;
|
|
22
|
+
reasoningLevel?: ReasoningLevel;
|
|
17
23
|
theme?: string;
|
|
18
24
|
scope?: 'global' | 'local';
|
|
19
25
|
}>();
|
|
@@ -26,6 +32,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
26
32
|
toolApproval: 'auto' | 'dangerous' | 'all';
|
|
27
33
|
guidedMode: boolean;
|
|
28
34
|
reasoningText: boolean;
|
|
35
|
+
reasoningLevel: ReasoningLevel;
|
|
29
36
|
theme: string;
|
|
30
37
|
}> = {};
|
|
31
38
|
|
|
@@ -36,6 +43,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
36
43
|
if (body.guidedMode !== undefined) updates.guidedMode = body.guidedMode;
|
|
37
44
|
if (body.reasoningText !== undefined)
|
|
38
45
|
updates.reasoningText = body.reasoningText;
|
|
46
|
+
if (body.reasoningLevel) updates.reasoningLevel = body.reasoningLevel;
|
|
39
47
|
if (body.theme) updates.theme = body.theme;
|
|
40
48
|
|
|
41
49
|
await setConfig(scope, updates, projectRoot);
|
|
@@ -61,6 +61,7 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
61
61
|
) as 'auto' | 'dangerous' | 'all',
|
|
62
62
|
guidedMode: cfg.defaults.guidedMode ?? false,
|
|
63
63
|
reasoningText: cfg.defaults.reasoningText ?? true,
|
|
64
|
+
reasoningLevel: cfg.defaults.reasoningLevel ?? 'high',
|
|
64
65
|
theme: cfg.defaults.theme,
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
import { loadConfig } from '@ottocode/sdk';
|
|
2
|
+
import { loadConfig, type ReasoningLevel } from '@ottocode/sdk';
|
|
3
3
|
import { getDb } from '@ottocode/database';
|
|
4
4
|
import { messages, messageParts, sessions } from '@ottocode/database/schema';
|
|
5
5
|
import { eq, inArray } from 'drizzle-orm';
|
|
@@ -124,6 +124,10 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
124
124
|
|
|
125
125
|
const reasoning =
|
|
126
126
|
body?.reasoningText ?? cfg.defaults.reasoningText ?? false;
|
|
127
|
+
const reasoningLevel =
|
|
128
|
+
(body?.reasoningLevel as ReasoningLevel | undefined) ??
|
|
129
|
+
cfg.defaults.reasoningLevel ??
|
|
130
|
+
'high';
|
|
127
131
|
|
|
128
132
|
// Validate model capabilities if tools are allowed for this agent
|
|
129
133
|
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
@@ -158,6 +162,7 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
158
162
|
oneShot: Boolean(body?.oneShot),
|
|
159
163
|
userContext,
|
|
160
164
|
reasoningText: reasoning,
|
|
165
|
+
reasoningLevel,
|
|
161
166
|
images,
|
|
162
167
|
files,
|
|
163
168
|
});
|
package/src/routes/sessions.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { resolveAgentConfig } from '../runtime/agent/registry.ts';
|
|
|
15
15
|
import { createSession as createSessionRow } from '../runtime/session/manager.ts';
|
|
16
16
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
17
17
|
import { logger } from '@ottocode/sdk';
|
|
18
|
+
import { getRunnerState } from '../runtime/session/queue.ts';
|
|
18
19
|
|
|
19
20
|
export function registerSessionsRoutes(app: Hono) {
|
|
20
21
|
// List sessions
|
|
@@ -53,7 +54,9 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
53
54
|
} catch {}
|
|
54
55
|
}
|
|
55
56
|
const { toolCountsJson: _toolCountsJson, ...rest } = r;
|
|
56
|
-
|
|
57
|
+
const isRunning = getRunnerState(r.id)?.running ?? false;
|
|
58
|
+
const base = counts ? { ...rest, toolCounts: counts } : rest;
|
|
59
|
+
return { ...base, isRunning };
|
|
57
60
|
});
|
|
58
61
|
return c.json({
|
|
59
62
|
items: normalized,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadConfig
|
|
1
|
+
import { loadConfig } from '@ottocode/sdk';
|
|
2
2
|
import { wrapLanguageModel } from 'ai';
|
|
3
3
|
import { devToolsMiddleware } from '@ai-sdk/devtools';
|
|
4
4
|
import { getDb } from '@ottocode/database';
|
|
@@ -18,6 +18,7 @@ import { getMaxOutputTokens } from '../utils/token.ts';
|
|
|
18
18
|
import { setupToolContext } from '../tools/setup.ts';
|
|
19
19
|
import { getCompactionSystemPrompt } from '../message/compaction.ts';
|
|
20
20
|
import { detectOAuth, adaptRunnerCall } from '../provider/oauth-adapter.ts';
|
|
21
|
+
import { buildReasoningConfig } from '../provider/reasoning.ts';
|
|
21
22
|
import type { RunOpts } from '../session/queue.ts';
|
|
22
23
|
import type { ToolAdapterContext } from '../../tools/adapter.ts';
|
|
23
24
|
|
|
@@ -44,7 +45,32 @@ export interface SetupResult {
|
|
|
44
45
|
mcpToolsRecord: Record<string, Tool>;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
export function mergeProviderOptions(
|
|
49
|
+
base: Record<string, unknown>,
|
|
50
|
+
incoming: Record<string, unknown>,
|
|
51
|
+
): Record<string, unknown> {
|
|
52
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
53
|
+
const existing = base[key];
|
|
54
|
+
if (
|
|
55
|
+
existing &&
|
|
56
|
+
typeof existing === 'object' &&
|
|
57
|
+
!Array.isArray(existing) &&
|
|
58
|
+
value &&
|
|
59
|
+
typeof value === 'object' &&
|
|
60
|
+
!Array.isArray(value)
|
|
61
|
+
) {
|
|
62
|
+
base[key] = {
|
|
63
|
+
...(existing as Record<string, unknown>),
|
|
64
|
+
...(value as Record<string, unknown>),
|
|
65
|
+
};
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
base[key] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
48
74
|
|
|
49
75
|
export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
50
76
|
const cfgTimer = time('runner:loadConfig+db');
|
|
@@ -218,38 +244,21 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
218
244
|
};
|
|
219
245
|
}
|
|
220
246
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
...((providerOptions.openai as Record<string, unknown>) || {}),
|
|
237
|
-
reasoningEffort: 'high',
|
|
238
|
-
reasoningSummary: 'auto',
|
|
239
|
-
};
|
|
240
|
-
} else if (underlyingProvider === 'google') {
|
|
241
|
-
const isGemini3 = opts.model.includes('gemini-3');
|
|
242
|
-
providerOptions.google = {
|
|
243
|
-
thinkingConfig: isGemini3
|
|
244
|
-
? { thinkingLevel: 'high', includeThoughts: true }
|
|
245
|
-
: { thinkingBudget: THINKING_BUDGET },
|
|
246
|
-
};
|
|
247
|
-
} else if (underlyingProvider === 'openai-compatible') {
|
|
248
|
-
providerOptions.openaiCompatible = {
|
|
249
|
-
reasoningEffort: 'high',
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
}
|
|
247
|
+
const reasoningConfig = buildReasoningConfig({
|
|
248
|
+
provider: opts.provider,
|
|
249
|
+
model: opts.model,
|
|
250
|
+
reasoningText: opts.reasoningText,
|
|
251
|
+
reasoningLevel: opts.reasoningLevel,
|
|
252
|
+
maxOutputTokens,
|
|
253
|
+
});
|
|
254
|
+
mergeProviderOptions(providerOptions, reasoningConfig.providerOptions);
|
|
255
|
+
effectiveMaxOutputTokens = reasoningConfig.effectiveMaxOutputTokens;
|
|
256
|
+
debugLog(
|
|
257
|
+
`[RUNNER] reasoning enabled for ${opts.provider}/${opts.model}: ${reasoningConfig.enabled}, level: ${opts.reasoningLevel ?? 'default'}`,
|
|
258
|
+
);
|
|
259
|
+
debugLog(
|
|
260
|
+
`[RUNNER] final providerOptions: ${JSON.stringify(providerOptions)}`,
|
|
261
|
+
);
|
|
253
262
|
|
|
254
263
|
return {
|
|
255
264
|
cfg,
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
consumeOauthCodexTextDelta,
|
|
43
43
|
} from '../stream/text-guard.ts';
|
|
44
44
|
import { decideOauthCodexContinuation } from './oauth-codex-continuation.ts';
|
|
45
|
+
import { createTurnDumpCollector } from '../debug/turn-dump.ts';
|
|
45
46
|
|
|
46
47
|
export {
|
|
47
48
|
enqueueAssistantRun,
|
|
@@ -52,6 +53,33 @@ export {
|
|
|
52
53
|
getRunnerState,
|
|
53
54
|
} from '../session/queue.ts';
|
|
54
55
|
|
|
56
|
+
const DEFAULT_TRACED_TOOL_INPUTS = new Set(['write', 'apply_patch']);
|
|
57
|
+
|
|
58
|
+
function shouldTraceToolInput(name: string): boolean {
|
|
59
|
+
const raw = process.env.OTTO_DEBUG_TOOL_INPUT?.trim();
|
|
60
|
+
if (!raw) return false;
|
|
61
|
+
const normalized = raw.toLowerCase();
|
|
62
|
+
if (['1', 'true', 'yes', 'on', 'all'].includes(normalized)) {
|
|
63
|
+
return DEFAULT_TRACED_TOOL_INPUTS.has(name);
|
|
64
|
+
}
|
|
65
|
+
const tokens = raw
|
|
66
|
+
.split(/[\s,]+/)
|
|
67
|
+
.map((token) => token.trim().toLowerCase())
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
return tokens.includes('all') || tokens.includes(name.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function summarizeTraceValue(value: unknown, max = 160): string {
|
|
73
|
+
try {
|
|
74
|
+
const json = JSON.stringify(value);
|
|
75
|
+
if (typeof json === 'string') {
|
|
76
|
+
return json.length > max ? `${json.slice(0, max)}…` : json;
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
const fallback = String(value);
|
|
80
|
+
return fallback.length > max ? `${fallback.slice(0, max)}…` : fallback;
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
export async function runSessionLoop(sessionId: string) {
|
|
56
84
|
setRunning(sessionId, true);
|
|
57
85
|
|
|
@@ -179,6 +207,31 @@ async function runAssistant(opts: RunOpts) {
|
|
|
179
207
|
`[RUNNER] messagesWithSystemInstructions length: ${messagesWithSystemInstructions.length}`,
|
|
180
208
|
);
|
|
181
209
|
|
|
210
|
+
const dump = createTurnDumpCollector({
|
|
211
|
+
sessionId: opts.sessionId,
|
|
212
|
+
messageId: opts.assistantMessageId,
|
|
213
|
+
provider: opts.provider,
|
|
214
|
+
model: opts.model,
|
|
215
|
+
agent: opts.agent,
|
|
216
|
+
continuationCount: opts.continuationCount,
|
|
217
|
+
});
|
|
218
|
+
if (dump) {
|
|
219
|
+
dump.setSystemPrompt(system, setup.systemComponents);
|
|
220
|
+
dump.setAdditionalSystemMessages(
|
|
221
|
+
additionalSystemMessages as Array<{ role: string; content: string }>,
|
|
222
|
+
);
|
|
223
|
+
dump.setHistory(history as Array<{ role: string; content: unknown }>);
|
|
224
|
+
dump.setFinalMessages(messagesWithSystemInstructions);
|
|
225
|
+
dump.setTools(toolset);
|
|
226
|
+
dump.setModelConfig({
|
|
227
|
+
maxOutputTokens: setup.maxOutputTokens,
|
|
228
|
+
effectiveMaxOutputTokens,
|
|
229
|
+
providerOptions,
|
|
230
|
+
isOpenAIOAuth,
|
|
231
|
+
needsSpoof: setup.needsSpoof,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
182
235
|
let _finishObserved = false;
|
|
183
236
|
let _toolActivityObserved = false;
|
|
184
237
|
let _trailingAssistantTextAfterTool = false;
|
|
@@ -199,15 +252,41 @@ async function runAssistant(opts: RunOpts) {
|
|
|
199
252
|
}
|
|
200
253
|
if (evt.type === 'tool.call') {
|
|
201
254
|
triggerTitleGenerationWhenReady();
|
|
255
|
+
if (dump) {
|
|
256
|
+
try {
|
|
257
|
+
const p = evt.payload as {
|
|
258
|
+
name?: string;
|
|
259
|
+
callId?: string;
|
|
260
|
+
args?: unknown;
|
|
261
|
+
};
|
|
262
|
+
dump.recordToolCall(stepIndex, p.name ?? '', p.callId ?? '', p.args);
|
|
263
|
+
} catch {}
|
|
264
|
+
}
|
|
202
265
|
}
|
|
203
|
-
if (evt.type
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
266
|
+
if (evt.type === 'tool.result') {
|
|
267
|
+
if (dump) {
|
|
268
|
+
try {
|
|
269
|
+
const p = evt.payload as {
|
|
270
|
+
name?: string;
|
|
271
|
+
callId?: string;
|
|
272
|
+
result?: unknown;
|
|
273
|
+
};
|
|
274
|
+
dump.recordToolResult(
|
|
275
|
+
stepIndex,
|
|
276
|
+
p.name ?? '',
|
|
277
|
+
p.callId ?? '',
|
|
278
|
+
p.result,
|
|
279
|
+
);
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
284
|
+
if (name === 'finish') _finishObserved = true;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
debugLog(
|
|
287
|
+
`[RUNNER] finish observer error: ${err instanceof Error ? err.message : String(err)}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
211
290
|
}
|
|
212
291
|
});
|
|
213
292
|
|
|
@@ -217,6 +296,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
217
296
|
let currentPartId: string | null = null;
|
|
218
297
|
let accumulated = '';
|
|
219
298
|
let latestAssistantText = '';
|
|
299
|
+
let lastTextDeltaStepIndex: number | null = null;
|
|
220
300
|
let stepIndex = 0;
|
|
221
301
|
const oauthTextGuard = isOpenAIOAuth
|
|
222
302
|
? createOauthCodexTextGuardState()
|
|
@@ -313,10 +393,61 @@ async function runAssistant(opts: RunOpts) {
|
|
|
313
393
|
onFinish: onFinish as any,
|
|
314
394
|
// biome-ignore lint/suspicious/noExplicitAny: AI SDK streamText options type
|
|
315
395
|
} as any);
|
|
396
|
+
const tracedToolInputNamesById = new Map<string, string>();
|
|
316
397
|
|
|
317
398
|
for await (const part of result.fullStream) {
|
|
318
399
|
if (!part) continue;
|
|
319
400
|
|
|
401
|
+
if (part.type === 'tool-input-start') {
|
|
402
|
+
if (shouldTraceToolInput(part.toolName)) {
|
|
403
|
+
tracedToolInputNamesById.set(part.id, part.toolName);
|
|
404
|
+
debugLog(
|
|
405
|
+
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-start tool=${part.toolName} callId=${part.id}`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (part.type === 'tool-input-delta') {
|
|
412
|
+
const toolName = tracedToolInputNamesById.get(part.id);
|
|
413
|
+
if (toolName) {
|
|
414
|
+
debugLog(
|
|
415
|
+
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-delta tool=${toolName} callId=${part.id} delta=${summarizeTraceValue(part.delta)}`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (part.type === 'tool-input-end') {
|
|
422
|
+
const toolName = tracedToolInputNamesById.get(part.id);
|
|
423
|
+
if (toolName) {
|
|
424
|
+
debugLog(
|
|
425
|
+
`[TOOL_INPUT_TRACE][runner] fullStream tool-input-end tool=${toolName} callId=${part.id}`,
|
|
426
|
+
);
|
|
427
|
+
tracedToolInputNamesById.delete(part.id);
|
|
428
|
+
}
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (part.type === 'tool-call') {
|
|
433
|
+
if (shouldTraceToolInput(part.toolName)) {
|
|
434
|
+
tracedToolInputNamesById.delete(part.toolCallId);
|
|
435
|
+
debugLog(
|
|
436
|
+
`[TOOL_INPUT_TRACE][runner] fullStream tool-call tool=${part.toolName} callId=${part.toolCallId} input=${summarizeTraceValue(part.input)}`,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (part.type === 'tool-result') {
|
|
443
|
+
if (shouldTraceToolInput(part.toolName)) {
|
|
444
|
+
debugLog(
|
|
445
|
+
`[TOOL_INPUT_TRACE][runner] fullStream tool-result tool=${part.toolName} callId=${part.toolCallId} output=${summarizeTraceValue(part.output)}`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
320
451
|
if (part.type === 'text-delta') {
|
|
321
452
|
const rawDelta = part.text;
|
|
322
453
|
if (!rawDelta) continue;
|
|
@@ -330,6 +461,10 @@ async function runAssistant(opts: RunOpts) {
|
|
|
330
461
|
if (accumulated.trim()) {
|
|
331
462
|
latestAssistantText = accumulated;
|
|
332
463
|
}
|
|
464
|
+
if (accumulated.length > 0) {
|
|
465
|
+
lastTextDeltaStepIndex = stepIndex;
|
|
466
|
+
}
|
|
467
|
+
dump?.recordTextDelta(stepIndex, accumulated);
|
|
333
468
|
if (
|
|
334
469
|
(delta.trim().length > 0 && _toolActivityObserved) ||
|
|
335
470
|
(delta.trim().length > 0 && firstToolSeen())
|
|
@@ -450,6 +585,23 @@ async function runAssistant(opts: RunOpts) {
|
|
|
450
585
|
`[RUNNER] Stream finished. finishSeen=${_finishObserved}, firstToolSeen=${fs}, trailingAssistantTextAfterTool=${_trailingAssistantTextAfterTool}, finishReason=${streamFinishReason}, rawFinishReason=${streamRawFinishReason}`,
|
|
451
586
|
);
|
|
452
587
|
|
|
588
|
+
if (dump) {
|
|
589
|
+
const finalTextSnapshot = latestAssistantText || accumulated;
|
|
590
|
+
if (finalTextSnapshot.length > 0) {
|
|
591
|
+
dump.recordTextDelta(
|
|
592
|
+
lastTextDeltaStepIndex ?? stepIndex,
|
|
593
|
+
finalTextSnapshot,
|
|
594
|
+
{ force: true },
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
dump.recordStreamEnd({
|
|
598
|
+
finishReason: streamFinishReason,
|
|
599
|
+
rawFinishReason: streamRawFinishReason,
|
|
600
|
+
finishObserved: _finishObserved,
|
|
601
|
+
aborted: _abortedByUser,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
453
605
|
const MAX_CONTINUATIONS = 6;
|
|
454
606
|
const continuationCount = opts.continuationCount ?? 0;
|
|
455
607
|
const continuationDecision = decideOauthCodexContinuation({
|
|
@@ -543,6 +695,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
543
695
|
}
|
|
544
696
|
} catch (err) {
|
|
545
697
|
unsubscribeFinish();
|
|
698
|
+
dump?.recordError(err);
|
|
546
699
|
const payload = toErrorPayload(err);
|
|
547
700
|
|
|
548
701
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -627,6 +780,16 @@ async function runAssistant(opts: RunOpts) {
|
|
|
627
780
|
}
|
|
628
781
|
throw err;
|
|
629
782
|
} finally {
|
|
783
|
+
if (dump) {
|
|
784
|
+
try {
|
|
785
|
+
const dumpPath = await dump.flush(cfg.projectRoot);
|
|
786
|
+
debugLog(`[RUNNER] Debug dump written to ${dumpPath}`);
|
|
787
|
+
} catch (dumpErr) {
|
|
788
|
+
debugLog(
|
|
789
|
+
`[RUNNER] Failed to write debug dump: ${dumpErr instanceof Error ? dumpErr.message : String(dumpErr)}`,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
630
793
|
debugLog(
|
|
631
794
|
`[RUNNER] Turn complete for session ${opts.sessionId}, message ${opts.assistantMessageId}`,
|
|
632
795
|
);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isProviderId,
|
|
20
20
|
providerEnvVar,
|
|
21
21
|
type ProviderId,
|
|
22
|
+
type ReasoningLevel,
|
|
22
23
|
} from '@ottocode/sdk';
|
|
23
24
|
import { sessions } from '@ottocode/database/schema';
|
|
24
25
|
import { time } from '../debug/index.ts';
|
|
@@ -48,6 +49,8 @@ export type InjectableConfig = {
|
|
|
48
49
|
model?: string;
|
|
49
50
|
apiKey?: string;
|
|
50
51
|
agent?: string;
|
|
52
|
+
reasoningText?: boolean;
|
|
53
|
+
reasoningLevel?: ReasoningLevel;
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
export type InjectableCredentials = Partial<
|
|
@@ -60,6 +63,8 @@ export type AskServerRequest = {
|
|
|
60
63
|
agent?: string;
|
|
61
64
|
provider?: string;
|
|
62
65
|
model?: string;
|
|
66
|
+
reasoningText?: boolean;
|
|
67
|
+
reasoningLevel?: ReasoningLevel;
|
|
63
68
|
sessionId?: string;
|
|
64
69
|
last?: boolean;
|
|
65
70
|
jsonMode?: boolean;
|
|
@@ -120,6 +125,10 @@ async function processAskRequest(
|
|
|
120
125
|
provider: injectedProvider,
|
|
121
126
|
model: injectedModel,
|
|
122
127
|
agent: injectedAgent,
|
|
128
|
+
reasoningText:
|
|
129
|
+
request.config?.reasoningText ?? request.reasoningText ?? true,
|
|
130
|
+
reasoningLevel:
|
|
131
|
+
request.config?.reasoningLevel ?? request.reasoningLevel ?? 'high',
|
|
123
132
|
},
|
|
124
133
|
providers: {
|
|
125
134
|
openai: { enabled: true },
|
|
@@ -299,6 +308,11 @@ async function processAskRequest(
|
|
|
299
308
|
await ensureProviderEnv(cfg, providerForMessage);
|
|
300
309
|
}
|
|
301
310
|
|
|
311
|
+
const reasoningText =
|
|
312
|
+
request.reasoningText ?? cfg.defaults.reasoningText ?? false;
|
|
313
|
+
const reasoningLevel =
|
|
314
|
+
request.reasoningLevel ?? cfg.defaults.reasoningLevel ?? 'high';
|
|
315
|
+
|
|
302
316
|
const assistantMessage = await dispatchAssistantMessage({
|
|
303
317
|
cfg,
|
|
304
318
|
db,
|
|
@@ -308,6 +322,8 @@ async function processAskRequest(
|
|
|
308
322
|
model: modelForMessage,
|
|
309
323
|
content: request.prompt,
|
|
310
324
|
oneShot: !request.sessionId && !request.last,
|
|
325
|
+
reasoningText,
|
|
326
|
+
reasoningLevel,
|
|
311
327
|
});
|
|
312
328
|
|
|
313
329
|
const headerAgent = session.agent ?? agentName;
|