@ottocode/server 0.1.248 → 0.1.249
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/routes/auth.ts +15 -0
- package/src/routes/config/models.ts +22 -28
- package/src/routes/config/providers.ts +64 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.249",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/database": "0.1.
|
|
53
|
-
"@ottocode/sdk": "0.1.
|
|
52
|
+
"@ottocode/database": "0.1.249",
|
|
53
|
+
"@ottocode/sdk": "0.1.249",
|
|
54
54
|
"ai-sdk-ollama": "^3.8.3",
|
|
55
55
|
"drizzle-orm": "^0.44.5",
|
|
56
56
|
"hono": "^4.9.9",
|
package/src/routes/auth.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
26
26
|
import { logger } from '@ottocode/sdk';
|
|
27
27
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
28
|
+
import { getProviderDetails } from './config/utils.ts';
|
|
28
29
|
|
|
29
30
|
const oauthVerifiers = new Map<
|
|
30
31
|
string,
|
|
@@ -235,6 +236,7 @@ export function registerAuthRoutes(app: Hono) {
|
|
|
235
236
|
supportsOAuth: boolean;
|
|
236
237
|
supportsToken?: boolean;
|
|
237
238
|
supportsGhImport?: boolean;
|
|
239
|
+
custom?: boolean;
|
|
238
240
|
modelCount: number;
|
|
239
241
|
costRange?: { min: number; max: number };
|
|
240
242
|
}
|
|
@@ -267,6 +269,19 @@ export function registerAuthRoutes(app: Hono) {
|
|
|
267
269
|
};
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
const providerDetails = await getProviderDetails(undefined, cfg);
|
|
273
|
+
for (const detail of providerDetails) {
|
|
274
|
+
if (!detail.custom || providers[detail.id]) continue;
|
|
275
|
+
providers[detail.id] = {
|
|
276
|
+
configured: detail.authorized,
|
|
277
|
+
type: detail.authType,
|
|
278
|
+
label: detail.label,
|
|
279
|
+
supportsOAuth: false,
|
|
280
|
+
custom: true,
|
|
281
|
+
modelCount: detail.modelCount,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
270
285
|
return c.json({
|
|
271
286
|
onboardingComplete,
|
|
272
287
|
ottorouter: ottorouterWallet
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from './utils.ts';
|
|
28
28
|
|
|
29
29
|
const COPILOT_MODELS_URL = 'https://api.githubcopilot.com/models';
|
|
30
|
-
const REMOTE_CATALOG_REFRESH_TTL_MS =
|
|
30
|
+
const REMOTE_CATALOG_REFRESH_TTL_MS = 5 * 60 * 1000;
|
|
31
31
|
const PROVIDER_MODEL_REFRESH_TTL_MS = 60 * 1000;
|
|
32
32
|
|
|
33
33
|
type UiModel = {
|
|
@@ -38,6 +38,8 @@ type UiModel = {
|
|
|
38
38
|
vision?: boolean;
|
|
39
39
|
attachment?: boolean;
|
|
40
40
|
free?: boolean;
|
|
41
|
+
contextWindow?: number;
|
|
42
|
+
maxOutputTokens?: number;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
type UiProviderModels = {
|
|
@@ -62,6 +64,8 @@ function toUiModel(model: ModelInfo): UiModel {
|
|
|
62
64
|
vision: model.modalities?.input?.includes('image') ?? false,
|
|
63
65
|
attachment: model.attachment ?? false,
|
|
64
66
|
free: model.cost?.input === 0 && model.cost?.output === 0,
|
|
67
|
+
contextWindow: model.limit?.context,
|
|
68
|
+
maxOutputTokens: model.limit?.output,
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -93,6 +97,10 @@ async function refreshRemoteCatalogInBackground(): Promise<void> {
|
|
|
93
97
|
const providers = normalizeModelCatalogPayload(await response.json());
|
|
94
98
|
if (Object.keys(providers).length > 0) {
|
|
95
99
|
await mergeCachedModelCatalog(providers);
|
|
100
|
+
logger.debug('Refreshed remote model catalog', {
|
|
101
|
+
url,
|
|
102
|
+
providers: Object.keys(providers).length,
|
|
103
|
+
});
|
|
96
104
|
}
|
|
97
105
|
} catch (error) {
|
|
98
106
|
logger.debug('Failed to refresh remote model catalog', {
|
|
@@ -210,6 +218,7 @@ async function discoverProviderModels(args: {
|
|
|
210
218
|
}): Promise<ModelInfo[] | undefined> {
|
|
211
219
|
const { provider, providerDefinition, projectRoot } = args;
|
|
212
220
|
if (
|
|
221
|
+
providerDefinition.source !== 'custom' ||
|
|
213
222
|
providerDefinition.compatibility !== 'ollama' ||
|
|
214
223
|
!providerDefinition.baseURL
|
|
215
224
|
) {
|
|
@@ -225,7 +234,7 @@ async function discoverProviderModels(args: {
|
|
|
225
234
|
const discovered = await discoverOllamaModels({
|
|
226
235
|
baseURL: providerDefinition.baseURL,
|
|
227
236
|
apiKey,
|
|
228
|
-
includeDetails:
|
|
237
|
+
includeDetails: true,
|
|
229
238
|
});
|
|
230
239
|
return discovered.models;
|
|
231
240
|
} catch (error) {
|
|
@@ -241,8 +250,8 @@ function shouldLazyLoadProviderModels(
|
|
|
241
250
|
providerDefinition: NonNullable<ReturnType<typeof getProviderDefinition>>,
|
|
242
251
|
): boolean {
|
|
243
252
|
return (
|
|
244
|
-
providerDefinition.
|
|
245
|
-
providerDefinition.
|
|
253
|
+
providerDefinition.source === 'custom' &&
|
|
254
|
+
providerDefinition.compatibility === 'ollama'
|
|
246
255
|
);
|
|
247
256
|
}
|
|
248
257
|
|
|
@@ -313,20 +322,15 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
313
322
|
projectRoot,
|
|
314
323
|
});
|
|
315
324
|
}
|
|
316
|
-
const filteredModels =
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
provider,
|
|
326
|
-
providerCatalog.models,
|
|
327
|
-
authType,
|
|
328
|
-
)
|
|
329
|
-
: getConfiguredProviderModels(cfg, provider);
|
|
325
|
+
const filteredModels = shouldLazyLoadProviderModels(providerDefinition)
|
|
326
|
+
? getCachedOrConfiguredModels({
|
|
327
|
+
models: providerCatalog?.models,
|
|
328
|
+
cfg,
|
|
329
|
+
provider,
|
|
330
|
+
})
|
|
331
|
+
: providerCatalog
|
|
332
|
+
? filterModelsForAuthType(provider, providerCatalog.models, authType)
|
|
333
|
+
: getConfiguredProviderModels(cfg, provider);
|
|
330
334
|
const copilotAllowedModels =
|
|
331
335
|
provider === 'copilot'
|
|
332
336
|
? await getAuthorizedCopilotModels(projectRoot)
|
|
@@ -375,7 +379,6 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
375
379
|
void refreshRemoteCatalogInBackground();
|
|
376
380
|
|
|
377
381
|
const modelsMap: Record<string, UiProviderModels> = {};
|
|
378
|
-
const cacheUpdates: Parameters<typeof mergeCachedModelCatalog>[0] = {};
|
|
379
382
|
|
|
380
383
|
for (const provider of authorizedProviders) {
|
|
381
384
|
const providerCatalog =
|
|
@@ -417,18 +420,9 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
417
420
|
dynamicModels,
|
|
418
421
|
models: filteredModels.map(toUiModel),
|
|
419
422
|
};
|
|
420
|
-
cacheUpdates[provider] = {
|
|
421
|
-
id: provider,
|
|
422
|
-
label: providerDefinition.label,
|
|
423
|
-
models: filteredModels,
|
|
424
|
-
};
|
|
425
423
|
}
|
|
426
424
|
}
|
|
427
425
|
|
|
428
|
-
if (Object.keys(cacheUpdates).length > 0) {
|
|
429
|
-
void mergeCachedModelCatalog(cacheUpdates);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
426
|
return c.json(modelsMap);
|
|
433
427
|
} catch (error) {
|
|
434
428
|
logger.error('Failed to get all models', error);
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
loadConfig,
|
|
4
4
|
removeProviderSettings,
|
|
5
5
|
writeProviderSettings,
|
|
6
|
+
discoverOllamaModels,
|
|
6
7
|
isBuiltInProviderId,
|
|
8
|
+
type ModelInfo,
|
|
7
9
|
type ProviderCompatibility,
|
|
8
10
|
type ProviderPromptFamily,
|
|
9
11
|
type ProviderId,
|
|
@@ -32,6 +34,25 @@ type ProviderMutationBody = {
|
|
|
32
34
|
scope?: 'global' | 'local';
|
|
33
35
|
};
|
|
34
36
|
|
|
37
|
+
type ProviderDiscoveryBody = {
|
|
38
|
+
compatibility?: ProviderCompatibility;
|
|
39
|
+
baseURL?: string;
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function toDiscoveredModel(model: ModelInfo) {
|
|
44
|
+
return {
|
|
45
|
+
id: model.id,
|
|
46
|
+
label: model.label || model.id,
|
|
47
|
+
toolCall: model.toolCall,
|
|
48
|
+
reasoningText: model.reasoningText,
|
|
49
|
+
vision: model.modalities?.input?.includes('image') ?? false,
|
|
50
|
+
attachment: model.attachment ?? false,
|
|
51
|
+
contextWindow: model.limit?.context,
|
|
52
|
+
maxOutputTokens: model.limit?.output,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
export function registerProvidersRoute(app: Hono) {
|
|
36
57
|
app.get('/v1/config/providers', async (c) => {
|
|
37
58
|
try {
|
|
@@ -87,6 +108,49 @@ export function registerProvidersRoute(app: Hono) {
|
|
|
87
108
|
}
|
|
88
109
|
});
|
|
89
110
|
|
|
111
|
+
app.post('/v1/config/providers/discover-models', async (c) => {
|
|
112
|
+
try {
|
|
113
|
+
const embeddedConfig = (
|
|
114
|
+
c as unknown as {
|
|
115
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
116
|
+
}
|
|
117
|
+
).get('embeddedConfig');
|
|
118
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
119
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const body = await c.req.json<ProviderDiscoveryBody>();
|
|
123
|
+
const compatibility = body.compatibility || 'openai-compatible';
|
|
124
|
+
const baseURL = body.baseURL?.trim();
|
|
125
|
+
const apiKey = body.apiKey?.trim() || undefined;
|
|
126
|
+
if (!baseURL) return c.json({ error: 'Base URL is required' }, 400);
|
|
127
|
+
|
|
128
|
+
if (compatibility !== 'ollama') {
|
|
129
|
+
return c.json({
|
|
130
|
+
models: [],
|
|
131
|
+
unsupported: true,
|
|
132
|
+
message:
|
|
133
|
+
'Model discovery is currently available for Ollama providers.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const discovered = await discoverOllamaModels({
|
|
138
|
+
baseURL,
|
|
139
|
+
apiKey,
|
|
140
|
+
includeDetails: true,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return c.json({
|
|
144
|
+
baseURL: discovered.baseURL,
|
|
145
|
+
models: discovered.models.map(toDiscoveredModel),
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.error('Failed to discover provider models', error);
|
|
149
|
+
const errorResponse = serializeError(error);
|
|
150
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
90
154
|
app.put('/v1/config/providers/:provider', async (c) => {
|
|
91
155
|
try {
|
|
92
156
|
const embeddedConfig = (
|