@ottocode/server 0.1.244 → 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.
Files changed (39) hide show
  1. package/package.json +4 -3
  2. package/src/events/types.ts +9 -9
  3. package/src/index.ts +9 -4
  4. package/src/openapi/paths/auth.ts +11 -11
  5. package/src/openapi/paths/config.ts +118 -2
  6. package/src/openapi/paths/{setu.ts → ottorouter.ts} +31 -31
  7. package/src/openapi/paths/skills.ts +122 -0
  8. package/src/openapi/schemas.ts +35 -3
  9. package/src/openapi/spec.ts +3 -3
  10. package/src/routes/auth.ts +40 -46
  11. package/src/routes/branch.ts +3 -2
  12. package/src/routes/config/defaults.ts +10 -3
  13. package/src/routes/config/main.ts +3 -0
  14. package/src/routes/config/models.ts +84 -14
  15. package/src/routes/config/providers.ts +137 -4
  16. package/src/routes/config/utils.ts +72 -2
  17. package/src/routes/doctor.ts +15 -27
  18. package/src/routes/git/commit.ts +16 -5
  19. package/src/routes/{setu.ts → ottorouter.ts} +52 -49
  20. package/src/routes/research.ts +3 -3
  21. package/src/routes/session-messages.ts +14 -8
  22. package/src/routes/sessions.ts +12 -18
  23. package/src/routes/skills.ts +140 -59
  24. package/src/runtime/agent/registry.ts +5 -2
  25. package/src/runtime/agent/runner-setup.ts +123 -38
  26. package/src/runtime/agent/runner.ts +140 -4
  27. package/src/runtime/ask/service.ts +14 -11
  28. package/src/runtime/message/history-builder.ts +22 -6
  29. package/src/runtime/message/service.ts +7 -1
  30. package/src/runtime/prompt/builder.ts +12 -0
  31. package/src/runtime/prompt/capabilities.ts +200 -0
  32. package/src/runtime/provider/index.ts +106 -5
  33. package/src/runtime/provider/{setu.ts → ottorouter.ts} +22 -22
  34. package/src/runtime/provider/reasoning.ts +73 -17
  35. package/src/runtime/provider/selection.ts +17 -15
  36. package/src/runtime/session/db-operations.ts +1 -1
  37. package/src/runtime/session/manager.ts +1 -1
  38. package/src/runtime/session/queue.ts +7 -2
  39. package/src/runtime/stream/error-handler.ts +3 -3
@@ -1,5 +1,9 @@
1
1
  import {
2
- catalog,
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 = Object.keys(catalog) as ProviderId[];
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,
@@ -2,7 +2,10 @@ import type { Hono } from 'hono';
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import {
4
4
  readConfig,
5
- isAuthorized,
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
- 'setu',
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 === 'setu') return 'SETU_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
- PROVIDERS.map(async (id) => {
72
- const ok = await isAuthorized(id, projectRoot);
73
- const envVar = providerEnvVar(id);
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 isAuthorized(
118
- cfg.defaults.provider as ProviderId,
119
- projectRoot,
105
+ providerAuthorized: await isProviderAuthorized(
106
+ cfg,
107
+ cfg.defaults.provider,
120
108
  ),
121
109
  };
122
110
 
@@ -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 { loadConfig, getAuth, getFastModelForAuth } from '@ottocode/sdk';
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
- getFastModelForAuth(provider, auth?.type) ??
128
- config.defaults?.model ??
129
- 'claude-3-5-sonnet-20241022';
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.
@@ -1,6 +1,6 @@
1
1
  import type { Hono } from 'hono';
2
2
  import {
3
- fetchSetuBalance,
3
+ fetchOttoRouterBalance,
4
4
  getPublicKeyFromPrivate,
5
5
  getAuth,
6
6
  loadConfig,
@@ -18,23 +18,23 @@ import {
18
18
  type TopupMethod,
19
19
  } from '../runtime/topup/manager.ts';
20
20
 
21
- const SETU_BASE_URL =
22
- process.env.SETU_BASE_URL || 'https://api.setu.ottocode.io';
21
+ const OTTOROUTER_BASE_URL =
22
+ process.env.OTTOROUTER_BASE_URL || 'https://api.ottorouter.org';
23
23
 
24
- function getSetuBaseUrl(): string {
25
- return SETU_BASE_URL.endsWith('/')
26
- ? SETU_BASE_URL.slice(0, -1)
27
- : SETU_BASE_URL;
24
+ function getOttoRouterBaseUrl(): string {
25
+ return OTTOROUTER_BASE_URL.endsWith('/')
26
+ ? OTTOROUTER_BASE_URL.slice(0, -1)
27
+ : OTTOROUTER_BASE_URL;
28
28
  }
29
29
 
30
- async function getSetuPrivateKey(): Promise<string | null> {
31
- if (process.env.SETU_PRIVATE_KEY) {
32
- return process.env.SETU_PRIVATE_KEY;
30
+ async function getOttoRouterPrivateKey(): Promise<string | null> {
31
+ if (process.env.OTTOROUTER_PRIVATE_KEY) {
32
+ return process.env.OTTOROUTER_PRIVATE_KEY;
33
33
  }
34
34
 
35
35
  try {
36
36
  const cfg = await loadConfig(process.cwd());
37
- const auth = await getAuth('setu', cfg.projectRoot);
37
+ const auth = await getAuth('ottorouter', cfg.projectRoot);
38
38
  if (auth?.type === 'wallet' && auth.secret) {
39
39
  return auth.secret;
40
40
  }
@@ -62,33 +62,36 @@ function buildWalletHeaders(privateKey: string): Record<string, string> {
62
62
  };
63
63
  }
64
64
 
65
- export function registerSetuRoutes(app: Hono) {
66
- app.get('/v1/setu/balance', async (c) => {
65
+ export function registerOttoRouterRoutes(app: Hono) {
66
+ app.get('/v1/ottorouter/balance', async (c) => {
67
67
  try {
68
- const privateKey = await getSetuPrivateKey();
68
+ const privateKey = await getOttoRouterPrivateKey();
69
69
  if (!privateKey) {
70
- return c.json({ error: 'Setu wallet not configured' }, 401);
70
+ return c.json({ error: 'OttoRouter wallet not configured' }, 401);
71
71
  }
72
72
 
73
- const balance = await fetchSetuBalance({ privateKey });
73
+ const balance = await fetchOttoRouterBalance({ privateKey });
74
74
  if (!balance) {
75
- return c.json({ error: 'Failed to fetch balance from Setu' }, 502);
75
+ return c.json(
76
+ { error: 'Failed to fetch balance from OttoRouter' },
77
+ 502,
78
+ );
76
79
  }
77
80
 
78
81
  return c.json(balance);
79
82
  } catch (error) {
80
- logger.error('Failed to fetch Setu balance', error);
83
+ logger.error('Failed to fetch OttoRouter balance', error);
81
84
  const errorResponse = serializeError(error);
82
85
  return c.json(errorResponse, errorResponse.error.status || 500);
83
86
  }
84
87
  });
85
88
 
86
- app.get('/v1/setu/wallet', async (c) => {
89
+ app.get('/v1/ottorouter/wallet', async (c) => {
87
90
  try {
88
- const privateKey = await getSetuPrivateKey();
91
+ const privateKey = await getOttoRouterPrivateKey();
89
92
  if (!privateKey) {
90
93
  return c.json(
91
- { error: 'Setu wallet not configured', configured: false },
94
+ { error: 'OttoRouter wallet not configured', configured: false },
92
95
  200,
93
96
  );
94
97
  }
@@ -103,17 +106,17 @@ export function registerSetuRoutes(app: Hono) {
103
106
  publicKey,
104
107
  });
105
108
  } catch (error) {
106
- logger.error('Failed to get Setu wallet info', error);
109
+ logger.error('Failed to get OttoRouter wallet info', error);
107
110
  const errorResponse = serializeError(error);
108
111
  return c.json(errorResponse, errorResponse.error.status || 500);
109
112
  }
110
113
  });
111
114
 
112
- app.get('/v1/setu/usdc-balance', async (c) => {
115
+ app.get('/v1/ottorouter/usdc-balance', async (c) => {
113
116
  try {
114
- const privateKey = await getSetuPrivateKey();
117
+ const privateKey = await getOttoRouterPrivateKey();
115
118
  if (!privateKey) {
116
- return c.json({ error: 'Setu wallet not configured' }, 401);
119
+ return c.json({ error: 'OttoRouter wallet not configured' }, 401);
117
120
  }
118
121
 
119
122
  const publicKey = getPublicKeyFromPrivate(privateKey);
@@ -121,7 +124,7 @@ export function registerSetuRoutes(app: Hono) {
121
124
  return c.json({ error: 'Invalid private key' }, 400);
122
125
  }
123
126
 
124
- const baseUrl = getSetuBaseUrl();
127
+ const baseUrl = getOttoRouterBaseUrl();
125
128
  const response = await fetch(
126
129
  `${baseUrl}/v1/wallet/${publicKey}/balances?limit=100&showNative=false&showNfts=false&showZeroBalance=false`,
127
130
  {
@@ -161,14 +164,14 @@ export function registerSetuRoutes(app: Hono) {
161
164
  }
162
165
  });
163
166
 
164
- app.get('/v1/setu/topup/polar/estimate', async (c) => {
167
+ app.get('/v1/ottorouter/topup/polar/estimate', async (c) => {
165
168
  try {
166
169
  const amount = c.req.query('amount');
167
170
  if (!amount) {
168
171
  return c.json({ error: 'Missing amount parameter' }, 400);
169
172
  }
170
173
 
171
- const baseUrl = getSetuBaseUrl();
174
+ const baseUrl = getOttoRouterBaseUrl();
172
175
  const response = await fetch(
173
176
  `${baseUrl}/v1/topup/polar/estimate?amount=${amount}`,
174
177
  {
@@ -190,11 +193,11 @@ export function registerSetuRoutes(app: Hono) {
190
193
  }
191
194
  });
192
195
 
193
- app.post('/v1/setu/topup/polar', async (c) => {
196
+ app.post('/v1/ottorouter/topup/polar', async (c) => {
194
197
  try {
195
- const privateKey = await getSetuPrivateKey();
198
+ const privateKey = await getOttoRouterPrivateKey();
196
199
  if (!privateKey) {
197
- return c.json({ error: 'Setu wallet not configured' }, 401);
200
+ return c.json({ error: 'OttoRouter wallet not configured' }, 401);
198
201
  }
199
202
 
200
203
  const body = await c.req.json();
@@ -212,7 +215,7 @@ export function registerSetuRoutes(app: Hono) {
212
215
  }
213
216
 
214
217
  const walletHeaders = buildWalletHeaders(privateKey);
215
- const baseUrl = getSetuBaseUrl();
218
+ const baseUrl = getOttoRouterBaseUrl();
216
219
 
217
220
  const response = await fetch(`${baseUrl}/v1/topup/polar`, {
218
221
  method: 'POST',
@@ -236,7 +239,7 @@ export function registerSetuRoutes(app: Hono) {
236
239
  }
237
240
  });
238
241
 
239
- app.post('/v1/setu/topup/select', async (c) => {
242
+ app.post('/v1/ottorouter/topup/select', async (c) => {
240
243
  try {
241
244
  const body = await c.req.json();
242
245
  const { sessionId, method } = body as {
@@ -264,7 +267,7 @@ export function registerSetuRoutes(app: Hono) {
264
267
  }
265
268
 
266
269
  publish({
267
- type: 'setu.topup.method_selected',
270
+ type: 'ottorouter.topup.method_selected',
268
271
  sessionId,
269
272
  payload: { method },
270
273
  });
@@ -277,7 +280,7 @@ export function registerSetuRoutes(app: Hono) {
277
280
  }
278
281
  });
279
282
 
280
- app.post('/v1/setu/topup/cancel', async (c) => {
283
+ app.post('/v1/ottorouter/topup/cancel', async (c) => {
281
284
  try {
282
285
  const body = await c.req.json();
283
286
  const { sessionId, reason } = body as {
@@ -301,7 +304,7 @@ export function registerSetuRoutes(app: Hono) {
301
304
  }
302
305
 
303
306
  publish({
304
- type: 'setu.topup.cancelled',
307
+ type: 'ottorouter.topup.cancelled',
305
308
  sessionId,
306
309
  payload: { reason: reason ?? 'User cancelled' },
307
310
  });
@@ -314,7 +317,7 @@ export function registerSetuRoutes(app: Hono) {
314
317
  }
315
318
  });
316
319
 
317
- app.get('/v1/setu/topup/pending', async (c) => {
320
+ app.get('/v1/ottorouter/topup/pending', async (c) => {
318
321
  try {
319
322
  const sessionId = c.req.query('sessionId');
320
323
  if (!sessionId) {
@@ -341,14 +344,14 @@ export function registerSetuRoutes(app: Hono) {
341
344
  }
342
345
  });
343
346
 
344
- app.get('/v1/setu/topup/polar/status', async (c) => {
347
+ app.get('/v1/ottorouter/topup/polar/status', async (c) => {
345
348
  try {
346
349
  const checkoutId = c.req.query('checkoutId');
347
350
  if (!checkoutId) {
348
351
  return c.json({ error: 'Missing checkoutId parameter' }, 400);
349
352
  }
350
353
 
351
- const baseUrl = getSetuBaseUrl();
354
+ const baseUrl = getOttoRouterBaseUrl();
352
355
  const response = await fetch(
353
356
  `${baseUrl}/v1/topup/polar/status?checkoutId=${checkoutId}`,
354
357
  {
@@ -370,14 +373,14 @@ export function registerSetuRoutes(app: Hono) {
370
373
  }
371
374
  });
372
375
 
373
- app.get('/v1/setu/topup/razorpay/estimate', async (c) => {
376
+ app.get('/v1/ottorouter/topup/razorpay/estimate', async (c) => {
374
377
  try {
375
378
  const amount = c.req.query('amount');
376
379
  if (!amount) {
377
380
  return c.json({ error: 'Missing amount parameter' }, 400);
378
381
  }
379
382
 
380
- const baseUrl = getSetuBaseUrl();
383
+ const baseUrl = getOttoRouterBaseUrl();
381
384
  const response = await fetch(
382
385
  `${baseUrl}/v1/topup/razorpay/estimate?amount=${amount}`,
383
386
  {
@@ -399,11 +402,11 @@ export function registerSetuRoutes(app: Hono) {
399
402
  }
400
403
  });
401
404
 
402
- app.post('/v1/setu/topup/razorpay', async (c) => {
405
+ app.post('/v1/ottorouter/topup/razorpay', async (c) => {
403
406
  try {
404
- const privateKey = await getSetuPrivateKey();
407
+ const privateKey = await getOttoRouterPrivateKey();
405
408
  if (!privateKey) {
406
- return c.json({ error: 'Setu wallet not configured' }, 401);
409
+ return c.json({ error: 'OttoRouter wallet not configured' }, 401);
407
410
  }
408
411
 
409
412
  const body = await c.req.json();
@@ -414,7 +417,7 @@ export function registerSetuRoutes(app: Hono) {
414
417
  }
415
418
 
416
419
  const walletHeaders = buildWalletHeaders(privateKey);
417
- const baseUrl = getSetuBaseUrl();
420
+ const baseUrl = getOttoRouterBaseUrl();
418
421
 
419
422
  const response = await fetch(`${baseUrl}/v1/topup/razorpay`, {
420
423
  method: 'POST',
@@ -438,11 +441,11 @@ export function registerSetuRoutes(app: Hono) {
438
441
  }
439
442
  });
440
443
 
441
- app.post('/v1/setu/topup/razorpay/verify', async (c) => {
444
+ app.post('/v1/ottorouter/topup/razorpay/verify', async (c) => {
442
445
  try {
443
- const privateKey = await getSetuPrivateKey();
446
+ const privateKey = await getOttoRouterPrivateKey();
444
447
  if (!privateKey) {
445
- return c.json({ error: 'Setu wallet not configured' }, 401);
448
+ return c.json({ error: 'OttoRouter wallet not configured' }, 401);
446
449
  }
447
450
 
448
451
  const body = await c.req.json();
@@ -458,7 +461,7 @@ export function registerSetuRoutes(app: Hono) {
458
461
  }
459
462
 
460
463
  const walletHeaders = buildWalletHeaders(privateKey);
461
- const baseUrl = getSetuBaseUrl();
464
+ const baseUrl = getOttoRouterBaseUrl();
462
465
 
463
466
  const response = await fetch(`${baseUrl}/v1/topup/razorpay/verify`, {
464
467
  method: 'POST',
@@ -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 { isProviderId } from '@ottocode/sdk';
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 && isProviderId(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 && isProviderId(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: reasoning,
167
+ reasoningText:
168
+ providerDefinition?.compatibility === 'ollama'
169
+ ? (body?.reasoningText ?? false)
170
+ : reasoning,
165
171
  reasoningLevel,
166
172
  images,
167
173
  files,
@@ -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 { isProviderId, catalog } from '@ottocode/sdk';
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 && isProviderId(providerCandidate))
82
+ if (providerCandidate && hasConfiguredProvider(cfg, providerCandidate))
83
83
  return providerCandidate;
84
- if (agentCfg.provider && isProviderId(agentCfg.provider))
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 && isProviderId(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
- // Check if model exists for the provider
226
- const providerCatalog = catalog[targetProvider];
227
- if (providerCatalog) {
228
- const modelExists = providerCatalog.models.some(
229
- (m) => m.id === modelName,
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;