@ottocode/server 0.1.227 → 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/oauth-codex-continuation.ts +6 -1
- package/src/runtime/agent/runner-setup.ts +43 -34
- package/src/runtime/agent/runner.ts +182 -11
- 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,
|
|
@@ -10,6 +10,7 @@ export type OauthCodexContinuationInput = {
|
|
|
10
10
|
firstToolSeen: boolean;
|
|
11
11
|
hasTrailingAssistantText: boolean;
|
|
12
12
|
endedWithToolActivity?: boolean;
|
|
13
|
+
lastToolName?: string;
|
|
13
14
|
droppedPseudoToolText: boolean;
|
|
14
15
|
lastAssistantText: string;
|
|
15
16
|
};
|
|
@@ -54,7 +55,7 @@ const MAX_UNCLEAN_EOF_RETRIES = 1;
|
|
|
54
55
|
|
|
55
56
|
function isUncleanEof(input: OauthCodexContinuationInput): boolean {
|
|
56
57
|
if (input.finishReason && input.finishReason !== 'unknown') return false;
|
|
57
|
-
if (input
|
|
58
|
+
if (isMissingAssistantSummary(input)) return true;
|
|
58
59
|
if (looksLikeIntermediateProgressText(input.lastAssistantText)) return true;
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
@@ -82,6 +83,10 @@ export function decideOauthCodexContinuation(
|
|
|
82
83
|
return { shouldContinue: true, reason: 'truncated' };
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
if (input.lastToolName === 'finish') {
|
|
87
|
+
return { shouldContinue: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
if (input.endedWithToolActivity) {
|
|
86
91
|
return { shouldContinue: true, reason: 'ended-on-tool-activity' };
|
|
87
92
|
}
|
|
@@ -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,27 +207,86 @@ 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;
|
|
238
|
+
let _endedWithToolActivity = false;
|
|
239
|
+
let _lastToolName: string | undefined;
|
|
185
240
|
let _abortedByUser = false;
|
|
186
241
|
let titleGenerationTriggered = false;
|
|
187
242
|
const unsubscribeFinish = subscribe(opts.sessionId, (evt) => {
|
|
188
243
|
if (evt.type === 'tool.call' || evt.type === 'tool.result') {
|
|
189
244
|
_toolActivityObserved = true;
|
|
190
245
|
_trailingAssistantTextAfterTool = false;
|
|
246
|
+
_endedWithToolActivity = true;
|
|
247
|
+
try {
|
|
248
|
+
_lastToolName = (evt.payload as { name?: string } | undefined)?.name;
|
|
249
|
+
} catch {
|
|
250
|
+
_lastToolName = undefined;
|
|
251
|
+
}
|
|
191
252
|
}
|
|
192
253
|
if (evt.type === 'tool.call') {
|
|
193
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
|
+
}
|
|
194
265
|
}
|
|
195
|
-
if (evt.type
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
}
|
|
203
290
|
}
|
|
204
291
|
});
|
|
205
292
|
|
|
@@ -209,6 +296,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
209
296
|
let currentPartId: string | null = null;
|
|
210
297
|
let accumulated = '';
|
|
211
298
|
let latestAssistantText = '';
|
|
299
|
+
let lastTextDeltaStepIndex: number | null = null;
|
|
212
300
|
let stepIndex = 0;
|
|
213
301
|
const oauthTextGuard = isOpenAIOAuth
|
|
214
302
|
? createOauthCodexTextGuardState()
|
|
@@ -305,10 +393,61 @@ async function runAssistant(opts: RunOpts) {
|
|
|
305
393
|
onFinish: onFinish as any,
|
|
306
394
|
// biome-ignore lint/suspicious/noExplicitAny: AI SDK streamText options type
|
|
307
395
|
} as any);
|
|
396
|
+
const tracedToolInputNamesById = new Map<string, string>();
|
|
308
397
|
|
|
309
398
|
for await (const part of result.fullStream) {
|
|
310
399
|
if (!part) continue;
|
|
311
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
|
+
|
|
312
451
|
if (part.type === 'text-delta') {
|
|
313
452
|
const rawDelta = part.text;
|
|
314
453
|
if (!rawDelta) continue;
|
|
@@ -322,11 +461,16 @@ async function runAssistant(opts: RunOpts) {
|
|
|
322
461
|
if (accumulated.trim()) {
|
|
323
462
|
latestAssistantText = accumulated;
|
|
324
463
|
}
|
|
464
|
+
if (accumulated.length > 0) {
|
|
465
|
+
lastTextDeltaStepIndex = stepIndex;
|
|
466
|
+
}
|
|
467
|
+
dump?.recordTextDelta(stepIndex, accumulated);
|
|
325
468
|
if (
|
|
326
469
|
(delta.trim().length > 0 && _toolActivityObserved) ||
|
|
327
470
|
(delta.trim().length > 0 && firstToolSeen())
|
|
328
471
|
) {
|
|
329
472
|
_trailingAssistantTextAfterTool = true;
|
|
473
|
+
_endedWithToolActivity = false;
|
|
330
474
|
}
|
|
331
475
|
|
|
332
476
|
if (!currentPartId && !accumulated.trim()) {
|
|
@@ -441,10 +585,25 @@ async function runAssistant(opts: RunOpts) {
|
|
|
441
585
|
`[RUNNER] Stream finished. finishSeen=${_finishObserved}, firstToolSeen=${fs}, trailingAssistantTextAfterTool=${_trailingAssistantTextAfterTool}, finishReason=${streamFinishReason}, rawFinishReason=${streamRawFinishReason}`,
|
|
442
586
|
);
|
|
443
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
|
+
|
|
444
605
|
const MAX_CONTINUATIONS = 6;
|
|
445
606
|
const continuationCount = opts.continuationCount ?? 0;
|
|
446
|
-
const endedWithToolActivity =
|
|
447
|
-
_toolActivityObserved && !_trailingAssistantTextAfterTool;
|
|
448
607
|
const continuationDecision = decideOauthCodexContinuation({
|
|
449
608
|
provider: opts.provider,
|
|
450
609
|
isOpenAIOAuth,
|
|
@@ -456,7 +615,8 @@ async function runAssistant(opts: RunOpts) {
|
|
|
456
615
|
rawFinishReason: streamRawFinishReason,
|
|
457
616
|
firstToolSeen: fs,
|
|
458
617
|
hasTrailingAssistantText: _trailingAssistantTextAfterTool,
|
|
459
|
-
endedWithToolActivity,
|
|
618
|
+
endedWithToolActivity: _endedWithToolActivity,
|
|
619
|
+
lastToolName: _lastToolName,
|
|
460
620
|
droppedPseudoToolText: oauthTextGuard?.dropped ?? false,
|
|
461
621
|
lastAssistantText: latestAssistantText,
|
|
462
622
|
});
|
|
@@ -535,6 +695,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
535
695
|
}
|
|
536
696
|
} catch (err) {
|
|
537
697
|
unsubscribeFinish();
|
|
698
|
+
dump?.recordError(err);
|
|
538
699
|
const payload = toErrorPayload(err);
|
|
539
700
|
|
|
540
701
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -619,6 +780,16 @@ async function runAssistant(opts: RunOpts) {
|
|
|
619
780
|
}
|
|
620
781
|
throw err;
|
|
621
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
|
+
}
|
|
622
793
|
debugLog(
|
|
623
794
|
`[RUNNER] Turn complete for session ${opts.sessionId}, message ${opts.assistantMessageId}`,
|
|
624
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;
|