@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.
- package/package.json +4 -3
- package/src/events/types.ts +9 -9
- package/src/index.ts +9 -4
- package/src/openapi/paths/auth.ts +11 -11
- package/src/openapi/paths/config.ts +118 -2
- package/src/openapi/paths/{setu.ts → ottorouter.ts} +31 -31
- package/src/openapi/paths/skills.ts +122 -0
- package/src/openapi/schemas.ts +35 -3
- package/src/openapi/spec.ts +3 -3
- package/src/routes/auth.ts +40 -46
- 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/{setu.ts → ottorouter.ts} +52 -49
- 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 +14 -11
- 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 +106 -5
- package/src/runtime/provider/{setu.ts → ottorouter.ts} +22 -22
- package/src/runtime/provider/reasoning.ts +73 -17
- package/src/runtime/provider/selection.ts +17 -15
- package/src/runtime/session/db-operations.ts +1 -1
- package/src/runtime/session/manager.ts +1 -1
- package/src/runtime/session/queue.ts +7 -2
- package/src/runtime/stream/error-handler.ts +3 -3
|
@@ -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
|
-
'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
|
-
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import {
|
|
3
|
-
|
|
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
|
|
22
|
-
process.env.
|
|
21
|
+
const OTTOROUTER_BASE_URL =
|
|
22
|
+
process.env.OTTOROUTER_BASE_URL || 'https://api.ottorouter.org';
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
return
|
|
26
|
-
?
|
|
27
|
-
:
|
|
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
|
|
31
|
-
if (process.env.
|
|
32
|
-
return process.env.
|
|
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('
|
|
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
|
|
66
|
-
app.get('/v1/
|
|
65
|
+
export function registerOttoRouterRoutes(app: Hono) {
|
|
66
|
+
app.get('/v1/ottorouter/balance', async (c) => {
|
|
67
67
|
try {
|
|
68
|
-
const privateKey = await
|
|
68
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
69
69
|
if (!privateKey) {
|
|
70
|
-
return c.json({ error: '
|
|
70
|
+
return c.json({ error: 'OttoRouter wallet not configured' }, 401);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const balance = await
|
|
73
|
+
const balance = await fetchOttoRouterBalance({ privateKey });
|
|
74
74
|
if (!balance) {
|
|
75
|
-
return c.json(
|
|
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
|
|
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/
|
|
89
|
+
app.get('/v1/ottorouter/wallet', async (c) => {
|
|
87
90
|
try {
|
|
88
|
-
const privateKey = await
|
|
91
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
89
92
|
if (!privateKey) {
|
|
90
93
|
return c.json(
|
|
91
|
-
{ error: '
|
|
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
|
|
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/
|
|
115
|
+
app.get('/v1/ottorouter/usdc-balance', async (c) => {
|
|
113
116
|
try {
|
|
114
|
-
const privateKey = await
|
|
117
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
115
118
|
if (!privateKey) {
|
|
116
|
-
return c.json({ error: '
|
|
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 =
|
|
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/
|
|
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 =
|
|
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/
|
|
196
|
+
app.post('/v1/ottorouter/topup/polar', async (c) => {
|
|
194
197
|
try {
|
|
195
|
-
const privateKey = await
|
|
198
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
196
199
|
if (!privateKey) {
|
|
197
|
-
return c.json({ error: '
|
|
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 =
|
|
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/
|
|
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: '
|
|
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/
|
|
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: '
|
|
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/
|
|
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/
|
|
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 =
|
|
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/
|
|
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 =
|
|
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/
|
|
405
|
+
app.post('/v1/ottorouter/topup/razorpay', async (c) => {
|
|
403
406
|
try {
|
|
404
|
-
const privateKey = await
|
|
407
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
405
408
|
if (!privateKey) {
|
|
406
|
-
return c.json({ error: '
|
|
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 =
|
|
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/
|
|
444
|
+
app.post('/v1/ottorouter/topup/razorpay/verify', async (c) => {
|
|
442
445
|
try {
|
|
443
|
-
const privateKey = await
|
|
446
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
444
447
|
if (!privateKey) {
|
|
445
|
-
return c.json({ error: '
|
|
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 =
|
|
464
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
462
465
|
|
|
463
466
|
const response = await fetch(`${baseUrl}/v1/topup/razorpay/verify`, {
|
|
464
467
|
method: 'POST',
|
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;
|