@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
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
loadConfig,
|
|
4
|
+
removeProviderSettings,
|
|
5
|
+
writeProviderSettings,
|
|
6
|
+
isBuiltInProviderId,
|
|
7
|
+
type ProviderCompatibility,
|
|
8
|
+
type ProviderPromptFamily,
|
|
9
|
+
type ProviderId,
|
|
10
|
+
type ProviderSettingsEntry,
|
|
11
|
+
} from '@ottocode/sdk';
|
|
4
12
|
import type { EmbeddedAppConfig } from '../../index.ts';
|
|
5
13
|
import { logger } from '@ottocode/sdk';
|
|
6
14
|
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
7
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getAuthorizedProviders,
|
|
17
|
+
getDefault,
|
|
18
|
+
getProviderDetails,
|
|
19
|
+
} from './utils.ts';
|
|
20
|
+
|
|
21
|
+
type ProviderMutationBody = {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
custom?: boolean;
|
|
24
|
+
label?: string;
|
|
25
|
+
compatibility?: ProviderCompatibility;
|
|
26
|
+
family?: ProviderPromptFamily;
|
|
27
|
+
baseURL?: string | null;
|
|
28
|
+
apiKey?: string | null;
|
|
29
|
+
apiKeyEnv?: string | null;
|
|
30
|
+
models?: string[];
|
|
31
|
+
allowAnyModel?: boolean;
|
|
32
|
+
scope?: 'global' | 'local';
|
|
33
|
+
};
|
|
8
34
|
|
|
9
35
|
export function registerProvidersRoute(app: Hono) {
|
|
10
36
|
app.get('/v1/config/providers', async (c) => {
|
|
@@ -15,7 +41,7 @@ export function registerProvidersRoute(app: Hono) {
|
|
|
15
41
|
}
|
|
16
42
|
).get('embeddedConfig');
|
|
17
43
|
|
|
18
|
-
if (embeddedConfig) {
|
|
44
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
19
45
|
const providers = embeddedConfig.auth
|
|
20
46
|
? (Object.keys(embeddedConfig.auth) as ProviderId[])
|
|
21
47
|
: embeddedConfig.provider
|
|
@@ -24,6 +50,17 @@ export function registerProvidersRoute(app: Hono) {
|
|
|
24
50
|
|
|
25
51
|
return c.json({
|
|
26
52
|
providers,
|
|
53
|
+
details: providers.map((provider) => ({
|
|
54
|
+
id: provider,
|
|
55
|
+
label: provider,
|
|
56
|
+
source: 'built-in',
|
|
57
|
+
enabled: true,
|
|
58
|
+
authorized: true,
|
|
59
|
+
custom: false,
|
|
60
|
+
hasApiKey: false,
|
|
61
|
+
allowAnyModel: false,
|
|
62
|
+
modelCount: 0,
|
|
63
|
+
})),
|
|
27
64
|
default: getDefault(
|
|
28
65
|
embeddedConfig.provider,
|
|
29
66
|
embeddedConfig.defaults?.provider,
|
|
@@ -36,9 +73,11 @@ export function registerProvidersRoute(app: Hono) {
|
|
|
36
73
|
const cfg = await loadConfig(projectRoot);
|
|
37
74
|
|
|
38
75
|
const authorizedProviders = await getAuthorizedProviders(undefined, cfg);
|
|
76
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
39
77
|
|
|
40
78
|
return c.json({
|
|
41
79
|
providers: authorizedProviders,
|
|
80
|
+
details,
|
|
42
81
|
default: cfg.defaults.provider,
|
|
43
82
|
});
|
|
44
83
|
} catch (error) {
|
|
@@ -47,4 +86,98 @@ export function registerProvidersRoute(app: Hono) {
|
|
|
47
86
|
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
48
87
|
}
|
|
49
88
|
});
|
|
89
|
+
|
|
90
|
+
app.put('/v1/config/providers/:provider', async (c) => {
|
|
91
|
+
try {
|
|
92
|
+
const embeddedConfig = (
|
|
93
|
+
c as unknown as {
|
|
94
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
95
|
+
}
|
|
96
|
+
).get('embeddedConfig');
|
|
97
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
98
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
102
|
+
const provider = c.req.param('provider').trim();
|
|
103
|
+
const body = await c.req.json<ProviderMutationBody>();
|
|
104
|
+
const scope = body.scope || 'local';
|
|
105
|
+
if (!provider) return c.json({ error: 'Provider is required' }, 400);
|
|
106
|
+
|
|
107
|
+
const updates: ProviderSettingsEntry = {
|
|
108
|
+
enabled: body.enabled ?? true,
|
|
109
|
+
custom: isBuiltInProviderId(provider)
|
|
110
|
+
? body.custom
|
|
111
|
+
: (body.custom ?? true),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (body.label !== undefined)
|
|
115
|
+
updates.label = body.label.trim() || undefined;
|
|
116
|
+
if (body.compatibility !== undefined) {
|
|
117
|
+
updates.compatibility = body.compatibility;
|
|
118
|
+
}
|
|
119
|
+
if (body.family !== undefined) updates.family = body.family;
|
|
120
|
+
if (body.baseURL !== undefined) {
|
|
121
|
+
updates.baseURL = body.baseURL?.trim() || undefined;
|
|
122
|
+
}
|
|
123
|
+
if (body.apiKey !== undefined)
|
|
124
|
+
updates.apiKey = body.apiKey?.trim() || undefined;
|
|
125
|
+
if (body.apiKeyEnv !== undefined) {
|
|
126
|
+
updates.apiKeyEnv = body.apiKeyEnv?.trim() || undefined;
|
|
127
|
+
}
|
|
128
|
+
if (body.models !== undefined) {
|
|
129
|
+
updates.models = body.models
|
|
130
|
+
.map((model) => model.trim())
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
if (body.allowAnyModel !== undefined) {
|
|
134
|
+
updates.allowAnyModel = body.allowAnyModel;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isBuiltInProviderId(provider) && !updates.compatibility) {
|
|
138
|
+
return c.json({ error: 'Custom providers require compatibility' }, 400);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await writeProviderSettings(scope, provider, updates, projectRoot);
|
|
142
|
+
const cfg = await loadConfig(projectRoot);
|
|
143
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
144
|
+
return c.json({
|
|
145
|
+
success: true,
|
|
146
|
+
provider,
|
|
147
|
+
details,
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
logger.error('Failed to update provider settings', error);
|
|
151
|
+
const errorResponse = serializeError(error);
|
|
152
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
app.delete('/v1/config/providers/:provider', async (c) => {
|
|
157
|
+
try {
|
|
158
|
+
const embeddedConfig = (
|
|
159
|
+
c as unknown as {
|
|
160
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
161
|
+
}
|
|
162
|
+
).get('embeddedConfig');
|
|
163
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
164
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
168
|
+
const provider = c.req.param('provider').trim();
|
|
169
|
+
const scope =
|
|
170
|
+
(c.req.query('scope') as 'global' | 'local' | undefined) || 'local';
|
|
171
|
+
if (!provider) return c.json({ error: 'Provider is required' }, 400);
|
|
172
|
+
|
|
173
|
+
await removeProviderSettings(scope, provider, projectRoot);
|
|
174
|
+
const cfg = await loadConfig(projectRoot);
|
|
175
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
176
|
+
return c.json({ success: true, provider, details });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger.error('Failed to remove provider settings', error);
|
|
179
|
+
const errorResponse = serializeError(error);
|
|
180
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
50
183
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
getConfiguredProviderApiKey,
|
|
3
|
+
getConfiguredProviderIds,
|
|
4
|
+
getConfiguredProviderModels,
|
|
5
|
+
getProviderDefinition,
|
|
6
|
+
getProviderSettings,
|
|
3
7
|
type ProviderId,
|
|
4
8
|
isProviderAuthorized,
|
|
5
9
|
getGlobalAgentsDir,
|
|
@@ -11,6 +15,23 @@ import type { EmbeddedAppConfig } from '../../index.ts';
|
|
|
11
15
|
import type { OttoConfig } from '@ottocode/sdk';
|
|
12
16
|
import { loadAgentsConfig } from '../../runtime/agent/registry.ts';
|
|
13
17
|
|
|
18
|
+
export type ProviderDetail = {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
source: 'built-in' | 'custom';
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
authorized: boolean;
|
|
24
|
+
custom: boolean;
|
|
25
|
+
compatibility?: string;
|
|
26
|
+
family?: string;
|
|
27
|
+
baseURL?: string;
|
|
28
|
+
apiKeyEnv?: string;
|
|
29
|
+
hasApiKey: boolean;
|
|
30
|
+
allowAnyModel: boolean;
|
|
31
|
+
modelCount: number;
|
|
32
|
+
authType?: 'api' | 'oauth' | 'wallet';
|
|
33
|
+
};
|
|
34
|
+
|
|
14
35
|
export async function isProviderAuthorizedHybrid(
|
|
15
36
|
embeddedConfig: EmbeddedAppConfig | undefined,
|
|
16
37
|
fileConfig: OttoConfig,
|
|
@@ -31,7 +52,7 @@ export async function getAuthorizedProviders(
|
|
|
31
52
|
embeddedConfig: EmbeddedAppConfig | undefined,
|
|
32
53
|
fileConfig: OttoConfig,
|
|
33
54
|
): Promise<ProviderId[]> {
|
|
34
|
-
const allProviders =
|
|
55
|
+
const allProviders = getConfiguredProviderIds(fileConfig);
|
|
35
56
|
const authorizedProviders: ProviderId[] = [];
|
|
36
57
|
|
|
37
58
|
for (const provider of allProviders) {
|
|
@@ -48,6 +69,55 @@ export async function getAuthorizedProviders(
|
|
|
48
69
|
return authorizedProviders;
|
|
49
70
|
}
|
|
50
71
|
|
|
72
|
+
export async function getProviderDetails(
|
|
73
|
+
embeddedConfig: EmbeddedAppConfig | undefined,
|
|
74
|
+
fileConfig: OttoConfig,
|
|
75
|
+
): Promise<ProviderDetail[]> {
|
|
76
|
+
const providers = Array.from(
|
|
77
|
+
new Set<ProviderId>([
|
|
78
|
+
...getConfiguredProviderIds(fileConfig, { includeDisabled: true }),
|
|
79
|
+
...(embeddedConfig?.provider ? [embeddedConfig.provider] : []),
|
|
80
|
+
...((embeddedConfig?.auth
|
|
81
|
+
? (Object.keys(embeddedConfig.auth) as ProviderId[])
|
|
82
|
+
: []) as ProviderId[]),
|
|
83
|
+
]),
|
|
84
|
+
);
|
|
85
|
+
const details = await Promise.all(
|
|
86
|
+
providers.map(async (provider) => {
|
|
87
|
+
const definition = getProviderDefinition(fileConfig, provider);
|
|
88
|
+
if (!definition) return null;
|
|
89
|
+
const settings = getProviderSettings(fileConfig, provider);
|
|
90
|
+
const authorized = await isProviderAuthorizedHybrid(
|
|
91
|
+
embeddedConfig,
|
|
92
|
+
fileConfig,
|
|
93
|
+
provider,
|
|
94
|
+
);
|
|
95
|
+
const authType = await getAuthTypeForProvider(
|
|
96
|
+
embeddedConfig,
|
|
97
|
+
provider,
|
|
98
|
+
fileConfig.projectRoot,
|
|
99
|
+
);
|
|
100
|
+
return {
|
|
101
|
+
id: provider,
|
|
102
|
+
label: definition.label,
|
|
103
|
+
source: definition.source,
|
|
104
|
+
enabled: settings?.enabled !== false,
|
|
105
|
+
authorized,
|
|
106
|
+
custom: definition.source === 'custom',
|
|
107
|
+
compatibility: definition.compatibility,
|
|
108
|
+
family: definition.family,
|
|
109
|
+
baseURL: definition.baseURL,
|
|
110
|
+
apiKeyEnv: definition.apiKeyEnv,
|
|
111
|
+
hasApiKey: Boolean(getConfiguredProviderApiKey(fileConfig, provider)),
|
|
112
|
+
allowAnyModel: definition.allowAnyModel,
|
|
113
|
+
modelCount: getConfiguredProviderModels(fileConfig, provider).length,
|
|
114
|
+
authType,
|
|
115
|
+
} satisfies ProviderDetail;
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
return details.filter((detail): detail is ProviderDetail => Boolean(detail));
|
|
119
|
+
}
|
|
120
|
+
|
|
51
121
|
export function getDefault<T>(
|
|
52
122
|
embeddedValue: T | undefined,
|
|
53
123
|
embeddedDefaultValue: T | undefined,
|
package/src/routes/doctor.ts
CHANGED
|
@@ -2,7 +2,10 @@ import type { Hono } from 'hono';
|
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
3
|
import {
|
|
4
4
|
readConfig,
|
|
5
|
-
|
|
5
|
+
isProviderAuthorized,
|
|
6
|
+
getConfiguredProviderApiKey,
|
|
7
|
+
getConfiguredProviderEnvVar,
|
|
8
|
+
getConfiguredProviderIds,
|
|
6
9
|
buildFsTools,
|
|
7
10
|
buildGitTools,
|
|
8
11
|
getSecureAuthPath,
|
|
@@ -11,27 +14,8 @@ import {
|
|
|
11
14
|
getGlobalCommandsDir,
|
|
12
15
|
logger,
|
|
13
16
|
} from '@ottocode/sdk';
|
|
14
|
-
import type { ProviderId } from '@ottocode/sdk';
|
|
15
17
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
16
18
|
|
|
17
|
-
const PROVIDERS: ProviderId[] = [
|
|
18
|
-
'openai',
|
|
19
|
-
'anthropic',
|
|
20
|
-
'google',
|
|
21
|
-
'openrouter',
|
|
22
|
-
'opencode',
|
|
23
|
-
'ottorouter',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
function providerEnvVar(p: ProviderId): string | null {
|
|
27
|
-
if (p === 'openai') return 'OPENAI_API_KEY';
|
|
28
|
-
if (p === 'anthropic') return 'ANTHROPIC_API_KEY';
|
|
29
|
-
if (p === 'google') return 'GOOGLE_GENERATIVE_AI_API_KEY';
|
|
30
|
-
if (p === 'opencode') return 'OPENCODE_API_KEY';
|
|
31
|
-
if (p === 'ottorouter') return 'OTTOROUTER_PRIVATE_KEY';
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
19
|
async function fileExists(path: string | null): Promise<boolean> {
|
|
36
20
|
if (!path) return false;
|
|
37
21
|
try {
|
|
@@ -66,11 +50,14 @@ export function registerDoctorRoutes(app: Hono) {
|
|
|
66
50
|
try {
|
|
67
51
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
68
52
|
const { cfg, auth } = await readConfig(projectRoot);
|
|
53
|
+
const configuredProviders = getConfiguredProviderIds(cfg, {
|
|
54
|
+
includeDisabled: true,
|
|
55
|
+
});
|
|
69
56
|
|
|
70
57
|
const providers = await Promise.all(
|
|
71
|
-
|
|
72
|
-
const ok = await
|
|
73
|
-
const envVar =
|
|
58
|
+
configuredProviders.map(async (id) => {
|
|
59
|
+
const ok = await isProviderAuthorized(cfg, id);
|
|
60
|
+
const envVar = getConfiguredProviderEnvVar(cfg, id) ?? null;
|
|
74
61
|
const envConfigured = envVar ? !!process.env[envVar] : false;
|
|
75
62
|
|
|
76
63
|
const globalAuthPath = getSecureAuthPath();
|
|
@@ -104,7 +91,8 @@ export function registerDoctorRoutes(app: Hono) {
|
|
|
104
91
|
envConfigured ||
|
|
105
92
|
hasGlobalAuth ||
|
|
106
93
|
cfg.defaults.provider === id ||
|
|
107
|
-
hasStoredSecret
|
|
94
|
+
hasStoredSecret ||
|
|
95
|
+
Boolean(getConfiguredProviderApiKey(cfg, id));
|
|
108
96
|
|
|
109
97
|
return { id, ok, configured, sources };
|
|
110
98
|
}),
|
|
@@ -114,9 +102,9 @@ export function registerDoctorRoutes(app: Hono) {
|
|
|
114
102
|
agent: cfg.defaults.agent,
|
|
115
103
|
provider: cfg.defaults.provider,
|
|
116
104
|
model: cfg.defaults.model,
|
|
117
|
-
providerAuthorized: await
|
|
118
|
-
cfg
|
|
119
|
-
|
|
105
|
+
providerAuthorized: await isProviderAuthorized(
|
|
106
|
+
cfg,
|
|
107
|
+
cfg.defaults.provider,
|
|
120
108
|
),
|
|
121
109
|
};
|
|
122
110
|
|
package/src/routes/git/commit.ts
CHANGED
|
@@ -4,7 +4,12 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
import { generateText, streamText } from 'ai';
|
|
5
5
|
import { eq } from 'drizzle-orm';
|
|
6
6
|
import type { ProviderId } from '@ottocode/sdk';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
loadConfig,
|
|
9
|
+
getAuth,
|
|
10
|
+
getFastModelForAuth,
|
|
11
|
+
getProviderDefinition,
|
|
12
|
+
} from '@ottocode/sdk';
|
|
8
13
|
import { getDb } from '@ottocode/database';
|
|
9
14
|
import { sessions } from '@ottocode/database/schema';
|
|
10
15
|
import { gitCommitSchema, gitGenerateCommitMessageSchema } from './schemas.ts';
|
|
@@ -108,25 +113,31 @@ export function registerCommitRoutes(app: Hono) {
|
|
|
108
113
|
const config = await loadConfig();
|
|
109
114
|
|
|
110
115
|
let provider = (config.defaults?.provider || 'anthropic') as ProviderId;
|
|
116
|
+
let currentModel = config.defaults?.model ?? 'claude-3-5-sonnet-20241022';
|
|
111
117
|
|
|
112
118
|
if (sessionId) {
|
|
113
119
|
const db = await getDb();
|
|
114
120
|
const [session] = await db
|
|
115
|
-
.select({ provider: sessions.provider })
|
|
121
|
+
.select({ provider: sessions.provider, model: sessions.model })
|
|
116
122
|
.from(sessions)
|
|
117
123
|
.where(eq(sessions.id, sessionId));
|
|
118
124
|
if (session?.provider) {
|
|
119
125
|
provider = session.provider as ProviderId;
|
|
120
126
|
}
|
|
127
|
+
if (session?.model) {
|
|
128
|
+
currentModel = session.model;
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
const auth = await getAuth(provider, config.projectRoot);
|
|
124
133
|
const oauth = detectOAuth(provider, auth);
|
|
134
|
+
const providerDefinition = getProviderDefinition(config, provider);
|
|
125
135
|
|
|
126
136
|
const modelId =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
providerDefinition?.source === 'custom' ||
|
|
138
|
+
providerDefinition?.compatibility === 'ollama'
|
|
139
|
+
? currentModel
|
|
140
|
+
: (getFastModelForAuth(provider, auth?.type) ?? currentModel);
|
|
130
141
|
const model = await resolveModel(provider, modelId, config);
|
|
131
142
|
|
|
132
143
|
const userPrompt = `Generate a commit message for these git changes.
|
package/src/routes/research.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { getDb } from '@ottocode/database';
|
|
|
4
4
|
import { sessions, messages, messageParts } from '@ottocode/database/schema';
|
|
5
5
|
import { desc, eq, and, asc, count } from 'drizzle-orm';
|
|
6
6
|
import type { ProviderId } from '@ottocode/sdk';
|
|
7
|
-
import {
|
|
7
|
+
import { hasConfiguredProvider } from '@ottocode/sdk';
|
|
8
8
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
9
9
|
import { logger } from '@ottocode/sdk';
|
|
10
10
|
import { publish } from '../events/bus.ts';
|
|
@@ -89,7 +89,7 @@ export function registerResearchRoutes(app: Hono) {
|
|
|
89
89
|
const providerCandidate =
|
|
90
90
|
typeof body.provider === 'string' ? body.provider : undefined;
|
|
91
91
|
const provider: ProviderId = (() => {
|
|
92
|
-
if (providerCandidate &&
|
|
92
|
+
if (providerCandidate && hasConfiguredProvider(cfg, providerCandidate))
|
|
93
93
|
return providerCandidate;
|
|
94
94
|
return parent.provider as ProviderId;
|
|
95
95
|
})();
|
|
@@ -278,7 +278,7 @@ export function registerResearchRoutes(app: Hono) {
|
|
|
278
278
|
const providerCandidate =
|
|
279
279
|
typeof body.provider === 'string' ? body.provider : undefined;
|
|
280
280
|
const provider: ProviderId = (() => {
|
|
281
|
-
if (providerCandidate &&
|
|
281
|
+
if (providerCandidate && hasConfiguredProvider(cfg, providerCandidate))
|
|
282
282
|
return providerCandidate;
|
|
283
283
|
return cfg.defaults.provider;
|
|
284
284
|
})();
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
import { loadConfig, type ReasoningLevel } from '@ottocode/sdk';
|
|
3
|
-
import { getDb } from '@ottocode/database';
|
|
4
|
-
import { messages, messageParts, sessions } from '@ottocode/database/schema';
|
|
5
|
-
import { eq, inArray } from 'drizzle-orm';
|
|
6
2
|
import {
|
|
7
|
-
validateProviderModel,
|
|
8
|
-
isProviderAuthorized,
|
|
9
3
|
ensureProviderEnv,
|
|
4
|
+
getProviderDefinition,
|
|
5
|
+
isProviderAuthorized,
|
|
6
|
+
loadConfig,
|
|
7
|
+
type ReasoningLevel,
|
|
8
|
+
validateProviderModel,
|
|
10
9
|
} from '@ottocode/sdk';
|
|
10
|
+
import { getDb } from '@ottocode/database';
|
|
11
|
+
import { messages, messageParts, sessions } from '@ottocode/database/schema';
|
|
12
|
+
import { eq, inArray } from 'drizzle-orm';
|
|
11
13
|
import { dispatchAssistantMessage } from '../runtime/message/service.ts';
|
|
12
14
|
import { logger } from '@ottocode/sdk';
|
|
13
15
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
@@ -132,7 +134,7 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
132
134
|
// Validate model capabilities if tools are allowed for this agent
|
|
133
135
|
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
134
136
|
try {
|
|
135
|
-
validateProviderModel(provider, modelName, { wantsToolCalls });
|
|
137
|
+
validateProviderModel(provider, modelName, cfg, { wantsToolCalls });
|
|
136
138
|
} catch (err) {
|
|
137
139
|
logger.error('Model validation failed', err, { provider, modelName });
|
|
138
140
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -150,6 +152,7 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
150
152
|
);
|
|
151
153
|
}
|
|
152
154
|
await ensureProviderEnv(cfg, provider);
|
|
155
|
+
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
153
156
|
|
|
154
157
|
const { assistantMessageId } = await dispatchAssistantMessage({
|
|
155
158
|
cfg,
|
|
@@ -161,7 +164,10 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
161
164
|
content,
|
|
162
165
|
oneShot: Boolean(body?.oneShot),
|
|
163
166
|
userContext,
|
|
164
|
-
reasoningText:
|
|
167
|
+
reasoningText:
|
|
168
|
+
providerDefinition?.compatibility === 'ollama'
|
|
169
|
+
? (body?.reasoningText ?? false)
|
|
170
|
+
: reasoning,
|
|
165
171
|
reasoningLevel,
|
|
166
172
|
images,
|
|
167
173
|
files,
|
package/src/routes/sessions.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@ottocode/database/schema';
|
|
11
11
|
import { desc, eq, and, ne, inArray, or } from 'drizzle-orm';
|
|
12
12
|
import type { ProviderId } from '@ottocode/sdk';
|
|
13
|
-
import {
|
|
13
|
+
import { hasConfiguredProvider, validateProviderModel } from '@ottocode/sdk';
|
|
14
14
|
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';
|
|
@@ -79,9 +79,9 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
79
79
|
const providerCandidate =
|
|
80
80
|
typeof body.provider === 'string' ? body.provider : undefined;
|
|
81
81
|
const provider: ProviderId = (() => {
|
|
82
|
-
if (providerCandidate &&
|
|
82
|
+
if (providerCandidate && hasConfiguredProvider(cfg, providerCandidate))
|
|
83
83
|
return providerCandidate;
|
|
84
|
-
if (
|
|
84
|
+
if (hasConfiguredProvider(cfg, agentCfg.provider))
|
|
85
85
|
return agentCfg.provider;
|
|
86
86
|
return cfg.defaults.provider;
|
|
87
87
|
})();
|
|
@@ -208,7 +208,7 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
208
208
|
// Validate provider if provided
|
|
209
209
|
if (typeof body.provider === 'string') {
|
|
210
210
|
const providerName = body.provider.trim();
|
|
211
|
-
if (providerName &&
|
|
211
|
+
if (providerName && hasConfiguredProvider(cfg, providerName)) {
|
|
212
212
|
updates.provider = providerName;
|
|
213
213
|
} else if (providerName) {
|
|
214
214
|
return c.json({ error: `Invalid provider: ${providerName}` }, 400);
|
|
@@ -221,21 +221,15 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
221
221
|
if (modelName) {
|
|
222
222
|
const targetProvider = (updates.provider ||
|
|
223
223
|
existingSession.provider) as ProviderId;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
try {
|
|
225
|
+
validateProviderModel(targetProvider, modelName, cfg);
|
|
226
|
+
} catch {
|
|
227
|
+
return c.json(
|
|
228
|
+
{
|
|
229
|
+
error: `Model "${modelName}" not found for provider "${targetProvider}"`,
|
|
230
|
+
},
|
|
231
|
+
400,
|
|
230
232
|
);
|
|
231
|
-
if (!modelExists) {
|
|
232
|
-
return c.json(
|
|
233
|
-
{
|
|
234
|
-
error: `Model "${modelName}" not found for provider "${targetProvider}"`,
|
|
235
|
-
},
|
|
236
|
-
400,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
233
|
}
|
|
240
234
|
|
|
241
235
|
updates.model = modelName;
|