@ottocode/server 0.1.245 → 0.1.246
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 +4 -3
- package/src/index.ts +5 -0
- package/src/openapi/paths/config.ts +118 -2
- package/src/openapi/paths/skills.ts +122 -0
- package/src/openapi/schemas.ts +35 -3
- package/src/routes/auth.ts +24 -30
- package/src/routes/branch.ts +3 -2
- package/src/routes/config/defaults.ts +10 -3
- package/src/routes/config/main.ts +3 -0
- package/src/routes/config/models.ts +84 -14
- package/src/routes/config/providers.ts +137 -4
- package/src/routes/config/utils.ts +72 -2
- package/src/routes/doctor.ts +15 -27
- package/src/routes/git/commit.ts +16 -5
- package/src/routes/research.ts +3 -3
- package/src/routes/session-messages.ts +14 -8
- package/src/routes/sessions.ts +12 -18
- package/src/routes/skills.ts +140 -59
- package/src/runtime/agent/registry.ts +5 -2
- package/src/runtime/agent/runner-setup.ts +123 -38
- package/src/runtime/agent/runner.ts +140 -4
- package/src/runtime/ask/service.ts +13 -10
- package/src/runtime/message/history-builder.ts +22 -6
- package/src/runtime/message/service.ts +7 -1
- package/src/runtime/prompt/builder.ts +12 -0
- package/src/runtime/prompt/capabilities.ts +200 -0
- package/src/runtime/provider/index.ts +98 -0
- package/src/runtime/provider/reasoning.ts +73 -17
- package/src/runtime/provider/selection.ts +16 -14
- package/src/runtime/session/manager.ts +1 -1
- package/src/runtime/session/queue.ts +7 -2
package/src/routes/skills.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import {
|
|
3
3
|
discoverSkills,
|
|
4
|
+
filterDiscoveredSkills,
|
|
4
5
|
loadSkill,
|
|
5
6
|
loadSkillFile,
|
|
6
7
|
discoverSkillFiles,
|
|
@@ -8,32 +9,56 @@ import {
|
|
|
8
9
|
validateSkillName,
|
|
9
10
|
parseSkillFile,
|
|
10
11
|
logger,
|
|
12
|
+
loadConfig,
|
|
13
|
+
writeSkillSettings,
|
|
11
14
|
} from '@ottocode/sdk';
|
|
12
15
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
13
16
|
|
|
17
|
+
function dedupeSkillsByName<T extends { name: string }>(skills: T[]): T[] {
|
|
18
|
+
const seen = new Set<string>();
|
|
19
|
+
return skills.filter((skill) => {
|
|
20
|
+
const key = skill.name.trim();
|
|
21
|
+
if (!key || seen.has(key)) return false;
|
|
22
|
+
seen.add(key);
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sortSkillsByName<T extends { name: string }>(skills: T[]): T[] {
|
|
28
|
+
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function mapSkillsWithEnabled(
|
|
32
|
+
discovered: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
scope: string;
|
|
36
|
+
path: string;
|
|
37
|
+
}>,
|
|
38
|
+
cfg: Awaited<ReturnType<typeof loadConfig>>,
|
|
39
|
+
) {
|
|
40
|
+
return discovered.map((skill) => ({
|
|
41
|
+
name: skill.name,
|
|
42
|
+
description: skill.description,
|
|
43
|
+
scope: skill.scope,
|
|
44
|
+
path: skill.path,
|
|
45
|
+
enabled: cfg.skills?.items?.[skill.name]?.enabled !== false,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
14
49
|
export function registerSkillsRoutes(app: Hono) {
|
|
15
50
|
app.get('/v1/skills', async (c) => {
|
|
16
51
|
try {
|
|
17
52
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
53
|
+
const cfg = await loadConfig(projectRoot);
|
|
18
54
|
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const unique = skills.filter((s) => {
|
|
25
|
-
const key = s.name.trim();
|
|
26
|
-
if (!key || seen.has(key)) return false;
|
|
27
|
-
seen.add(key);
|
|
28
|
-
return true;
|
|
29
|
-
});
|
|
55
|
+
const discovered = sortSkillsByName(
|
|
56
|
+
await discoverSkills(projectRoot, repoRoot),
|
|
57
|
+
);
|
|
58
|
+
const filtered = filterDiscoveredSkills(discovered, cfg.skills);
|
|
59
|
+
const unique = sortSkillsByName(dedupeSkillsByName(filtered));
|
|
30
60
|
return c.json({
|
|
31
|
-
skills: unique
|
|
32
|
-
name: s.name,
|
|
33
|
-
description: s.description,
|
|
34
|
-
scope: s.scope,
|
|
35
|
-
path: s.path,
|
|
36
|
-
})),
|
|
61
|
+
skills: mapSkillsWithEnabled(unique, cfg),
|
|
37
62
|
});
|
|
38
63
|
} catch (error) {
|
|
39
64
|
logger.error('Failed to list skills', error);
|
|
@@ -42,6 +67,68 @@ export function registerSkillsRoutes(app: Hono) {
|
|
|
42
67
|
}
|
|
43
68
|
});
|
|
44
69
|
|
|
70
|
+
app.get('/v1/config/skills', async (c) => {
|
|
71
|
+
try {
|
|
72
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
73
|
+
const cfg = await loadConfig(projectRoot);
|
|
74
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
75
|
+
const discovered = sortSkillsByName(
|
|
76
|
+
dedupeSkillsByName(await discoverSkills(projectRoot, repoRoot)),
|
|
77
|
+
);
|
|
78
|
+
const filtered = sortSkillsByName(
|
|
79
|
+
filterDiscoveredSkills(discovered, cfg.skills),
|
|
80
|
+
);
|
|
81
|
+
return c.json({
|
|
82
|
+
enabled: cfg.skills?.enabled !== false,
|
|
83
|
+
totalCount: discovered.length,
|
|
84
|
+
enabledCount: filtered.length,
|
|
85
|
+
items: mapSkillsWithEnabled(discovered, cfg),
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error('Failed to get skills config', error);
|
|
89
|
+
const errorResponse = serializeError(error);
|
|
90
|
+
return c.json(errorResponse, (errorResponse.error.status || 500) as 500);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
app.put('/v1/config/skills', async (c) => {
|
|
95
|
+
try {
|
|
96
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
97
|
+
const body = await c.req.json<{
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
items?: Record<string, { enabled?: boolean }>;
|
|
100
|
+
scope?: 'global' | 'local';
|
|
101
|
+
}>();
|
|
102
|
+
await writeSkillSettings(
|
|
103
|
+
body.scope || 'local',
|
|
104
|
+
{
|
|
105
|
+
...(body.enabled !== undefined ? { enabled: body.enabled } : {}),
|
|
106
|
+
...(body.items ? { items: body.items } : {}),
|
|
107
|
+
},
|
|
108
|
+
projectRoot,
|
|
109
|
+
);
|
|
110
|
+
const cfg = await loadConfig(projectRoot);
|
|
111
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
112
|
+
const discovered = sortSkillsByName(
|
|
113
|
+
dedupeSkillsByName(await discoverSkills(projectRoot, repoRoot)),
|
|
114
|
+
);
|
|
115
|
+
const filtered = sortSkillsByName(
|
|
116
|
+
filterDiscoveredSkills(discovered, cfg.skills),
|
|
117
|
+
);
|
|
118
|
+
return c.json({
|
|
119
|
+
success: true,
|
|
120
|
+
enabled: cfg.skills?.enabled !== false,
|
|
121
|
+
totalCount: discovered.length,
|
|
122
|
+
enabledCount: filtered.length,
|
|
123
|
+
items: mapSkillsWithEnabled(discovered, cfg),
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.error('Failed to update skills config', error);
|
|
127
|
+
const errorResponse = serializeError(error);
|
|
128
|
+
return c.json(errorResponse, (errorResponse.error.status || 500) as 500);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
45
132
|
app.get('/v1/skills/:name', async (c) => {
|
|
46
133
|
try {
|
|
47
134
|
const name = c.req.param('name');
|
|
@@ -72,52 +159,46 @@ export function registerSkillsRoutes(app: Hono) {
|
|
|
72
159
|
}
|
|
73
160
|
});
|
|
74
161
|
|
|
75
|
-
app.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await discoverSkills(projectRoot, repoRoot);
|
|
102
|
-
|
|
103
|
-
const result = await loadSkillFile(name, filePath);
|
|
104
|
-
if (!result) {
|
|
105
|
-
return c.json(
|
|
106
|
-
{ error: `File '${filePath}' not found in skill '${name}'` },
|
|
107
|
-
404,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return c.json({ content: result.content, path: result.resolvedPath });
|
|
111
|
-
} catch (error) {
|
|
112
|
-
logger.error('Failed to load skill file', error);
|
|
113
|
-
const errorResponse = serializeError(error);
|
|
162
|
+
app.get('/v1/skills/:name/files', async (c) => {
|
|
163
|
+
try {
|
|
164
|
+
const name = c.req.param('name');
|
|
165
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
166
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
167
|
+
await discoverSkills(projectRoot, repoRoot);
|
|
168
|
+
|
|
169
|
+
const files = await discoverSkillFiles(name);
|
|
170
|
+
return c.json({ files });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.error('Failed to list skill files', error);
|
|
173
|
+
const errorResponse = serializeError(error);
|
|
174
|
+
return c.json(errorResponse, (errorResponse.error.status || 500) as 500);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
app.get('/v1/skills/:name/files/*', async (c) => {
|
|
179
|
+
try {
|
|
180
|
+
const name = c.req.param('name');
|
|
181
|
+
const filePath = c.req.path.replace(`/v1/skills/${name}/files/`, '');
|
|
182
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
183
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
184
|
+
await discoverSkills(projectRoot, repoRoot);
|
|
185
|
+
|
|
186
|
+
const result = await loadSkillFile(name, filePath);
|
|
187
|
+
if (!result) {
|
|
114
188
|
return c.json(
|
|
115
|
-
|
|
116
|
-
|
|
189
|
+
{ error: `File '${filePath}' not found in skill '${name}'` },
|
|
190
|
+
404,
|
|
117
191
|
);
|
|
118
192
|
}
|
|
119
|
-
|
|
193
|
+
return c.json({ content: result.content, path: result.resolvedPath });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.error('Failed to load skill file', error);
|
|
196
|
+
const errorResponse = serializeError(error);
|
|
197
|
+
return c.json(errorResponse, (errorResponse.error.status || 500) as 500);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
120
200
|
|
|
201
|
+
app.post('/v1/skills/validate', async (c) => {
|
|
121
202
|
try {
|
|
122
203
|
const body = await c.req.json<{ content: string; path?: string }>();
|
|
123
204
|
if (!body.content) {
|
|
@@ -115,10 +115,11 @@ function mergeAgentEntries(
|
|
|
115
115
|
return merged;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const baseToolSet = ['progress_update', 'finish'
|
|
118
|
+
const baseToolSet = ['progress_update', 'finish'] as const;
|
|
119
119
|
|
|
120
120
|
const defaultToolExtras: Record<string, string[]> = {
|
|
121
121
|
build: [
|
|
122
|
+
'skill',
|
|
122
123
|
'read',
|
|
123
124
|
'edit',
|
|
124
125
|
'multiedit',
|
|
@@ -134,8 +135,9 @@ const defaultToolExtras: Record<string, string[]> = {
|
|
|
134
135
|
'apply_patch',
|
|
135
136
|
'websearch',
|
|
136
137
|
],
|
|
137
|
-
plan: ['read', 'ls', 'tree', 'ripgrep', 'update_todos', 'websearch'],
|
|
138
|
+
plan: ['skill', 'read', 'ls', 'tree', 'ripgrep', 'update_todos', 'websearch'],
|
|
138
139
|
general: [
|
|
140
|
+
'skill',
|
|
139
141
|
'read',
|
|
140
142
|
'edit',
|
|
141
143
|
'multiedit',
|
|
@@ -149,6 +151,7 @@ const defaultToolExtras: Record<string, string[]> = {
|
|
|
149
151
|
'update_todos',
|
|
150
152
|
],
|
|
151
153
|
init: [
|
|
154
|
+
'skill',
|
|
152
155
|
'read',
|
|
153
156
|
'edit',
|
|
154
157
|
'multiedit',
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadConfig,
|
|
3
3
|
logger,
|
|
4
|
+
getConfiguredProviderFamily,
|
|
4
5
|
getSessionSystemPromptPath,
|
|
5
6
|
getModelFamily,
|
|
7
|
+
type OttoConfig,
|
|
6
8
|
} from '@ottocode/sdk';
|
|
7
9
|
import { wrapLanguageModel } from 'ai';
|
|
8
10
|
import { devToolsMiddleware } from '@ai-sdk/devtools';
|
|
@@ -19,7 +21,7 @@ import type { Tool } from 'ai';
|
|
|
19
21
|
import { adaptTools } from '../../tools/adapter.ts';
|
|
20
22
|
import { buildDatabaseTools } from '../../tools/database/index.ts';
|
|
21
23
|
import { time } from '../debug/index.ts';
|
|
22
|
-
import { isDevtoolsEnabled } from '../debug/state.ts';
|
|
24
|
+
import { isDebugEnabled, isDevtoolsEnabled } from '../debug/state.ts';
|
|
23
25
|
import { buildHistoryMessages } from '../message/history-builder.ts';
|
|
24
26
|
import { getMaxOutputTokens } from '../utils/token.ts';
|
|
25
27
|
import { setupToolContext } from '../tools/setup.ts';
|
|
@@ -29,6 +31,39 @@ import { buildReasoningConfig } from '../provider/reasoning.ts';
|
|
|
29
31
|
import type { RunOpts } from '../session/queue.ts';
|
|
30
32
|
import type { ToolAdapterContext } from '../../tools/adapter.ts';
|
|
31
33
|
|
|
34
|
+
type RunnerSetupTimings = {
|
|
35
|
+
loadConfigAndDbMs: number;
|
|
36
|
+
resolveAgentConfigMs: number;
|
|
37
|
+
buildHistoryMs: number;
|
|
38
|
+
loadSessionMs: number;
|
|
39
|
+
composeSystemPromptMs: number;
|
|
40
|
+
discoverToolsMs: number;
|
|
41
|
+
resolveModelMs: number;
|
|
42
|
+
setupToolContextMs: number;
|
|
43
|
+
buildToolsetMs: number;
|
|
44
|
+
totalMs: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type TimedResult<T> = {
|
|
48
|
+
value: T;
|
|
49
|
+
durationMs: number;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function nowMs(): number {
|
|
53
|
+
const perf = globalThis.performance;
|
|
54
|
+
if (perf && typeof perf.now === 'function') return perf.now();
|
|
55
|
+
return Date.now();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function timePromise<T>(promise: Promise<T>): Promise<TimedResult<T>> {
|
|
59
|
+
const startedAt = nowMs();
|
|
60
|
+
const value = await promise;
|
|
61
|
+
return {
|
|
62
|
+
value,
|
|
63
|
+
durationMs: nowMs() - startedAt,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
export interface SetupResult {
|
|
33
68
|
cfg: Awaited<ReturnType<typeof loadConfig>>;
|
|
34
69
|
db: Awaited<ReturnType<typeof getDb>>;
|
|
@@ -50,6 +85,7 @@ export interface SetupResult {
|
|
|
50
85
|
needsSpoof: boolean;
|
|
51
86
|
isOpenAIOAuth: boolean;
|
|
52
87
|
mcpToolsRecord: Record<string, Tool>;
|
|
88
|
+
timings: RunnerSetupTimings;
|
|
53
89
|
}
|
|
54
90
|
|
|
55
91
|
export function mergeProviderOptions(
|
|
@@ -91,10 +127,13 @@ export function applyModelFamilyEditToolPolicy(
|
|
|
91
127
|
tools: string[],
|
|
92
128
|
provider: RunOpts['provider'],
|
|
93
129
|
model: string,
|
|
130
|
+
cfg?: OttoConfig,
|
|
94
131
|
): string[] {
|
|
95
132
|
if (!MODEL_FAMILY_EDIT_TOOL_POLICY_AGENTS.has(agent)) return tools;
|
|
96
133
|
|
|
97
|
-
const family =
|
|
134
|
+
const family = cfg
|
|
135
|
+
? getConfiguredProviderFamily(cfg, provider, model)
|
|
136
|
+
: getModelFamily(provider, model);
|
|
98
137
|
const next = tools.filter(
|
|
99
138
|
(toolName) => !EDITING_TOOL_NAMES.includes(toolName),
|
|
100
139
|
);
|
|
@@ -107,40 +146,67 @@ export function applyModelFamilyEditToolPolicy(
|
|
|
107
146
|
}
|
|
108
147
|
|
|
109
148
|
export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
149
|
+
const setupStartedAt = nowMs();
|
|
110
150
|
const cfgTimer = time('runner:loadConfig+db');
|
|
151
|
+
const loadConfigAndDbStartedAt = nowMs();
|
|
111
152
|
const cfg = await loadConfig(opts.projectRoot);
|
|
112
153
|
const db = await getDb(cfg.projectRoot);
|
|
154
|
+
const loadConfigAndDbMs = nowMs() - loadConfigAndDbStartedAt;
|
|
113
155
|
cfgTimer.end();
|
|
114
156
|
|
|
115
157
|
const agentTimer = time('runner:resolveAgentConfig');
|
|
116
|
-
const
|
|
158
|
+
const agentCfgPromise = timePromise(
|
|
159
|
+
resolveAgentConfig(cfg.projectRoot, opts.agent),
|
|
160
|
+
);
|
|
161
|
+
const historyPromise =
|
|
162
|
+
opts.omitHistory || (opts.isCompactCommand && opts.compactionContext)
|
|
163
|
+
? Promise.resolve({ value: [], durationMs: 0 })
|
|
164
|
+
: timePromise(
|
|
165
|
+
buildHistoryMessages(db, opts.sessionId, opts.assistantMessageId),
|
|
166
|
+
);
|
|
167
|
+
const sessionRowsPromise = timePromise(
|
|
168
|
+
db.select().from(sessions).where(eq(sessions.id, opts.sessionId)).limit(1),
|
|
169
|
+
);
|
|
170
|
+
const discoveredToolsPromise = timePromise(
|
|
171
|
+
discoverProjectTools(cfg.projectRoot, undefined, cfg.skills),
|
|
172
|
+
);
|
|
173
|
+
const { value: agentCfg, durationMs: resolveAgentConfigMs } =
|
|
174
|
+
await agentCfgPromise;
|
|
117
175
|
agentTimer.end({ agent: opts.agent });
|
|
118
176
|
|
|
119
177
|
const agentPrompt = agentCfg.prompt || '';
|
|
120
178
|
|
|
121
179
|
const historyTimer = time('runner:buildHistory');
|
|
122
|
-
|
|
123
|
-
if (opts.omitHistory || (opts.isCompactCommand && opts.compactionContext)) {
|
|
124
|
-
history = [];
|
|
125
|
-
} else {
|
|
126
|
-
history = await buildHistoryMessages(
|
|
127
|
-
db,
|
|
128
|
-
opts.sessionId,
|
|
129
|
-
opts.assistantMessageId,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
180
|
+
const { value: history, durationMs: buildHistoryMs } = await historyPromise;
|
|
132
181
|
historyTimer.end({ messages: history.length });
|
|
133
182
|
|
|
134
|
-
const sessionRows
|
|
135
|
-
|
|
136
|
-
.from(sessions)
|
|
137
|
-
.where(eq(sessions.id, opts.sessionId))
|
|
138
|
-
.limit(1);
|
|
183
|
+
const { value: sessionRows, durationMs: loadSessionMs } =
|
|
184
|
+
await sessionRowsPromise;
|
|
139
185
|
const contextSummary = sessionRows[0]?.contextSummary ?? undefined;
|
|
186
|
+
const toolsTimer = time('runner:discoverTools');
|
|
187
|
+
const { value: discovered, durationMs: discoverToolsMs } =
|
|
188
|
+
await discoveredToolsPromise;
|
|
189
|
+
const allTools = discovered.tools;
|
|
190
|
+
const { mcpToolsRecord } = discovered;
|
|
191
|
+
|
|
192
|
+
if (opts.agent === 'research') {
|
|
193
|
+
const currentSession = sessionRows[0];
|
|
194
|
+
const parentSessionId = currentSession?.parentSessionId ?? null;
|
|
195
|
+
|
|
196
|
+
const dbTools = buildDatabaseTools(cfg.projectRoot, parentSessionId);
|
|
197
|
+
for (const dt of dbTools) {
|
|
198
|
+
discovered.tools.push(dt);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
toolsTimer.end({
|
|
203
|
+
count: allTools.length + Object.keys(mcpToolsRecord).length,
|
|
204
|
+
});
|
|
140
205
|
|
|
141
206
|
const isFirstMessage = !history.some((m) => m.role === 'assistant');
|
|
142
207
|
|
|
143
208
|
const systemTimer = time('runner:composeSystemPrompt');
|
|
209
|
+
const composeSystemPromptStartedAt = nowMs();
|
|
144
210
|
const { getAuth } = await import('@ottocode/sdk');
|
|
145
211
|
const auth = await getAuth(opts.provider, cfg.projectRoot);
|
|
146
212
|
const oauth = detectOAuth(opts.provider, auth);
|
|
@@ -148,6 +214,8 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
148
214
|
const composed = await composeSystemPrompt({
|
|
149
215
|
provider: opts.provider,
|
|
150
216
|
model: opts.model,
|
|
217
|
+
promptFamily: getConfiguredProviderFamily(cfg, opts.provider, opts.model),
|
|
218
|
+
skillSettings: cfg.skills,
|
|
151
219
|
projectRoot: cfg.projectRoot,
|
|
152
220
|
agentPrompt,
|
|
153
221
|
oneShot: opts.oneShot,
|
|
@@ -180,6 +248,7 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
180
248
|
: oauth.needsSpoof
|
|
181
249
|
? 'spoof'
|
|
182
250
|
: 'standard';
|
|
251
|
+
const composeSystemPromptMs = nowMs() - composeSystemPromptStartedAt;
|
|
183
252
|
systemTimer.end();
|
|
184
253
|
logger.debug('[prompt] system prompt assembled', {
|
|
185
254
|
sessionId: opts.sessionId,
|
|
@@ -216,7 +285,7 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
216
285
|
(message) => message.role,
|
|
217
286
|
),
|
|
218
287
|
});
|
|
219
|
-
if (effectiveSystemPrompt) {
|
|
288
|
+
if (effectiveSystemPrompt && isDebugEnabled()) {
|
|
220
289
|
const systemPromptPath = getSessionSystemPromptPath(opts.sessionId);
|
|
221
290
|
try {
|
|
222
291
|
await mkdir(dirname(systemPromptPath), { recursive: true });
|
|
@@ -253,25 +322,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
253
322
|
if (opts.additionalPromptMessages?.length) {
|
|
254
323
|
additionalSystemMessages.push(...opts.additionalPromptMessages);
|
|
255
324
|
}
|
|
256
|
-
|
|
257
|
-
const toolsTimer = time('runner:discoverTools');
|
|
258
|
-
const discovered = await discoverProjectTools(cfg.projectRoot);
|
|
259
|
-
const allTools = discovered.tools;
|
|
260
|
-
const { mcpToolsRecord } = discovered;
|
|
261
|
-
|
|
262
|
-
if (opts.agent === 'research') {
|
|
263
|
-
const currentSession = sessionRows[0];
|
|
264
|
-
const parentSessionId = currentSession?.parentSessionId ?? null;
|
|
265
|
-
|
|
266
|
-
const dbTools = buildDatabaseTools(cfg.projectRoot, parentSessionId);
|
|
267
|
-
for (const dt of dbTools) {
|
|
268
|
-
discovered.tools.push(dt);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
toolsTimer.end({
|
|
273
|
-
count: allTools.length + Object.keys(mcpToolsRecord).length,
|
|
274
|
-
});
|
|
275
325
|
const allowedToolNames = applyModelFamilyEditToolPolicy(
|
|
276
326
|
agentCfg.name,
|
|
277
327
|
agentCfg.tools || [],
|
|
@@ -283,10 +333,13 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
283
333
|
(tool) => allowedNames.has(tool.name) || tool.name === 'load_mcp_tools',
|
|
284
334
|
);
|
|
285
335
|
|
|
336
|
+
const resolveModelStartedAt = nowMs();
|
|
286
337
|
const model = await resolveModel(opts.provider, opts.model, cfg, {
|
|
287
338
|
sessionId: opts.sessionId,
|
|
288
339
|
messageId: opts.assistantMessageId,
|
|
340
|
+
reasoningText: opts.reasoningText,
|
|
289
341
|
});
|
|
342
|
+
const resolveModelMs = nowMs() - resolveModelStartedAt;
|
|
290
343
|
const wrappedModel = isDevtoolsEnabled()
|
|
291
344
|
? wrapLanguageModel({
|
|
292
345
|
// biome-ignore lint/suspicious/noExplicitAny: OpenRouter provider uses v2 spec
|
|
@@ -297,14 +350,18 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
297
350
|
|
|
298
351
|
const maxOutputTokens = adapted.maxOutputTokens;
|
|
299
352
|
|
|
353
|
+
const setupToolContextStartedAt = nowMs();
|
|
300
354
|
const { sharedCtx, firstToolTimer, firstToolSeen } = await setupToolContext(
|
|
301
355
|
opts,
|
|
302
356
|
db,
|
|
303
357
|
);
|
|
358
|
+
const setupToolContextMs = nowMs() - setupToolContextStartedAt;
|
|
304
359
|
|
|
360
|
+
const buildToolsetStartedAt = nowMs();
|
|
305
361
|
const providerAuth = await getAuth(opts.provider, opts.projectRoot);
|
|
306
362
|
const authType = providerAuth?.type;
|
|
307
363
|
const toolset = adaptTools(gated, sharedCtx, opts.provider, authType);
|
|
364
|
+
const buildToolsetMs = nowMs() - buildToolsetStartedAt;
|
|
308
365
|
|
|
309
366
|
const providerOptions = { ...adapted.providerOptions };
|
|
310
367
|
let effectiveMaxOutputTokens = maxOutputTokens;
|
|
@@ -317,6 +374,7 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
317
374
|
}
|
|
318
375
|
|
|
319
376
|
const reasoningConfig = buildReasoningConfig({
|
|
377
|
+
cfg,
|
|
320
378
|
provider: opts.provider,
|
|
321
379
|
model: opts.model,
|
|
322
380
|
reasoningText: opts.reasoningText,
|
|
@@ -326,6 +384,32 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
326
384
|
mergeProviderOptions(providerOptions, reasoningConfig.providerOptions);
|
|
327
385
|
effectiveMaxOutputTokens = reasoningConfig.effectiveMaxOutputTokens;
|
|
328
386
|
|
|
387
|
+
const timings: RunnerSetupTimings = {
|
|
388
|
+
loadConfigAndDbMs,
|
|
389
|
+
resolveAgentConfigMs,
|
|
390
|
+
buildHistoryMs,
|
|
391
|
+
loadSessionMs,
|
|
392
|
+
composeSystemPromptMs,
|
|
393
|
+
discoverToolsMs,
|
|
394
|
+
resolveModelMs,
|
|
395
|
+
setupToolContextMs,
|
|
396
|
+
buildToolsetMs,
|
|
397
|
+
totalMs: nowMs() - setupStartedAt,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
logger.info('[latency] runner setup', {
|
|
401
|
+
sessionId: opts.sessionId,
|
|
402
|
+
messageId: opts.assistantMessageId,
|
|
403
|
+
agent: opts.agent,
|
|
404
|
+
provider: opts.provider,
|
|
405
|
+
model: opts.model,
|
|
406
|
+
historyMessages: history.length,
|
|
407
|
+
systemPromptChars: effectiveSystemPrompt.length,
|
|
408
|
+
additionalPromptMessages: additionalSystemMessages.length,
|
|
409
|
+
allowedToolCount: gated.length,
|
|
410
|
+
timings,
|
|
411
|
+
});
|
|
412
|
+
|
|
329
413
|
return {
|
|
330
414
|
cfg,
|
|
331
415
|
db,
|
|
@@ -345,5 +429,6 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
345
429
|
needsSpoof: oauth.needsSpoof,
|
|
346
430
|
isOpenAIOAuth: oauth.isOpenAIOAuth,
|
|
347
431
|
mcpToolsRecord,
|
|
432
|
+
timings,
|
|
348
433
|
};
|
|
349
434
|
}
|