@ottocode/server 0.1.259 → 0.1.261
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 -4
- package/src/openapi/register.ts +92 -0
- package/src/openapi/route.ts +22 -0
- package/src/routes/ask.ts +210 -99
- package/src/routes/auth.ts +1701 -626
- package/src/routes/branch.ts +281 -90
- package/src/routes/config/agents.ts +79 -32
- package/src/routes/config/cwd.ts +46 -14
- package/src/routes/config/debug.ts +159 -30
- package/src/routes/config/defaults.ts +182 -64
- package/src/routes/config/main.ts +109 -73
- package/src/routes/config/models.ts +304 -137
- package/src/routes/config/providers.ts +462 -166
- package/src/routes/config/utils.ts +2 -2
- package/src/routes/doctor.ts +395 -161
- package/src/routes/files.ts +650 -260
- package/src/routes/git/branch.ts +143 -52
- package/src/routes/git/commit.ts +347 -141
- package/src/routes/git/diff.ts +239 -116
- package/src/routes/git/init.ts +103 -23
- package/src/routes/git/pull.ts +167 -65
- package/src/routes/git/push.ts +222 -117
- package/src/routes/git/remote.ts +401 -100
- package/src/routes/git/staging.ts +502 -141
- package/src/routes/git/status.ts +171 -78
- package/src/routes/mcp.ts +1129 -404
- package/src/routes/openapi.ts +27 -4
- package/src/routes/ottorouter.ts +1221 -389
- package/src/routes/provider-usage.ts +153 -36
- package/src/routes/research.ts +817 -370
- package/src/routes/root.ts +50 -6
- package/src/routes/session-approval.ts +228 -54
- package/src/routes/session-files.ts +265 -134
- package/src/routes/session-messages.ts +330 -150
- package/src/routes/session-stream.ts +83 -2
- package/src/routes/sessions.ts +1830 -780
- package/src/routes/skills.ts +849 -161
- package/src/routes/terminals.ts +469 -103
- package/src/routes/tunnel.ts +394 -118
- package/src/runtime/agent/runner-reasoning.ts +38 -3
- package/src/runtime/agent/runner.ts +1 -0
- package/src/runtime/ask/service.ts +1 -0
- package/src/runtime/message/compaction-limits.ts +3 -3
- package/src/runtime/provider/reasoning.ts +18 -7
- package/src/runtime/session/db-operations.ts +4 -3
- package/src/runtime/utils/token.ts +7 -2
- package/src/tools/adapter.ts +21 -0
- package/src/openapi/paths/ask.ts +0 -81
- package/src/openapi/paths/auth.ts +0 -687
- package/src/openapi/paths/branch.ts +0 -102
- package/src/openapi/paths/config.ts +0 -485
- package/src/openapi/paths/doctor.ts +0 -165
- package/src/openapi/paths/files.ts +0 -236
- package/src/openapi/paths/git.ts +0 -690
- package/src/openapi/paths/mcp.ts +0 -339
- package/src/openapi/paths/messages.ts +0 -103
- package/src/openapi/paths/ottorouter.ts +0 -594
- package/src/openapi/paths/provider-usage.ts +0 -59
- package/src/openapi/paths/research.ts +0 -227
- package/src/openapi/paths/session-approval.ts +0 -93
- package/src/openapi/paths/session-extras.ts +0 -336
- package/src/openapi/paths/session-files.ts +0 -91
- package/src/openapi/paths/sessions.ts +0 -210
- package/src/openapi/paths/skills.ts +0 -377
- package/src/openapi/paths/stream.ts +0 -26
- package/src/openapi/paths/terminals.ts +0 -226
- package/src/openapi/paths/tunnel.ts +0 -163
- package/src/openapi/spec.ts +0 -73
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getDefault,
|
|
20
20
|
getProviderDetails,
|
|
21
21
|
} from './utils.ts';
|
|
22
|
+
import { openApiRoute } from '../../openapi/route.ts';
|
|
22
23
|
|
|
23
24
|
type ProviderMutationBody = {
|
|
24
25
|
enabled?: boolean;
|
|
@@ -53,191 +54,486 @@ function toDiscoveredModel(model: ModelInfo) {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export function registerProvidersRoute(app: Hono) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
openApiRoute(
|
|
58
|
+
app,
|
|
59
|
+
{
|
|
60
|
+
method: 'get',
|
|
61
|
+
path: '/v1/config/providers',
|
|
62
|
+
tags: ['config'],
|
|
63
|
+
operationId: 'getProviders',
|
|
64
|
+
summary: 'Get available providers',
|
|
65
|
+
description: 'Returns only providers that have been authorized',
|
|
66
|
+
parameters: [
|
|
67
|
+
{
|
|
68
|
+
in: 'query',
|
|
69
|
+
name: 'project',
|
|
70
|
+
required: false,
|
|
71
|
+
schema: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
},
|
|
74
|
+
description:
|
|
75
|
+
'Project root override (defaults to current working directory).',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
responses: {
|
|
79
|
+
'200': {
|
|
80
|
+
description: 'OK',
|
|
81
|
+
content: {
|
|
82
|
+
'application/json': {
|
|
83
|
+
schema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
providers: {
|
|
87
|
+
type: 'array',
|
|
88
|
+
items: {
|
|
89
|
+
$ref: '#/components/schemas/Provider',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
details: {
|
|
93
|
+
type: 'array',
|
|
94
|
+
items: {
|
|
95
|
+
$ref: '#/components/schemas/ProviderDetail',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
default: {
|
|
99
|
+
$ref: '#/components/schemas/Provider',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: ['providers', 'details', 'default'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
async (c) => {
|
|
110
|
+
try {
|
|
111
|
+
const embeddedConfig = (
|
|
112
|
+
c as unknown as {
|
|
113
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
114
|
+
}
|
|
115
|
+
).get('embeddedConfig');
|
|
116
|
+
|
|
117
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
118
|
+
const providers = embeddedConfig.auth
|
|
119
|
+
? (Object.keys(embeddedConfig.auth) as ProviderId[])
|
|
120
|
+
: embeddedConfig.provider
|
|
121
|
+
? [embeddedConfig.provider]
|
|
122
|
+
: [];
|
|
123
|
+
|
|
124
|
+
return c.json({
|
|
125
|
+
providers,
|
|
126
|
+
details: providers.map((provider) => ({
|
|
127
|
+
id: provider,
|
|
128
|
+
label: provider,
|
|
129
|
+
source: 'built-in',
|
|
130
|
+
enabled: true,
|
|
131
|
+
authorized: true,
|
|
132
|
+
custom: false,
|
|
133
|
+
hasApiKey: false,
|
|
134
|
+
allowAnyModel: false,
|
|
135
|
+
modelCount: 0,
|
|
136
|
+
})),
|
|
137
|
+
default: getDefault(
|
|
138
|
+
embeddedConfig.provider,
|
|
139
|
+
embeddedConfig.defaults?.provider,
|
|
140
|
+
undefined,
|
|
141
|
+
),
|
|
142
|
+
});
|
|
61
143
|
}
|
|
62
|
-
).get('embeddedConfig');
|
|
63
144
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
145
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
146
|
+
const cfg = await loadConfig(projectRoot);
|
|
147
|
+
|
|
148
|
+
const authorizedProviders = await getAuthorizedProviders(
|
|
149
|
+
undefined,
|
|
150
|
+
cfg,
|
|
151
|
+
);
|
|
152
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
70
153
|
|
|
71
154
|
return c.json({
|
|
72
|
-
providers,
|
|
73
|
-
details
|
|
74
|
-
|
|
75
|
-
label: provider,
|
|
76
|
-
source: 'built-in',
|
|
77
|
-
enabled: true,
|
|
78
|
-
authorized: true,
|
|
79
|
-
custom: false,
|
|
80
|
-
hasApiKey: false,
|
|
81
|
-
allowAnyModel: false,
|
|
82
|
-
modelCount: 0,
|
|
83
|
-
})),
|
|
84
|
-
default: getDefault(
|
|
85
|
-
embeddedConfig.provider,
|
|
86
|
-
embeddedConfig.defaults?.provider,
|
|
87
|
-
undefined,
|
|
88
|
-
),
|
|
155
|
+
providers: authorizedProviders,
|
|
156
|
+
details,
|
|
157
|
+
default: cfg.defaults.provider,
|
|
89
158
|
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
logger.error('Failed to get providers', error);
|
|
161
|
+
const errorResponse = serializeError(error);
|
|
162
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
90
163
|
}
|
|
164
|
+
},
|
|
165
|
+
);
|
|
91
166
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
167
|
+
openApiRoute(
|
|
168
|
+
app,
|
|
169
|
+
{
|
|
170
|
+
method: 'post',
|
|
171
|
+
path: '/v1/config/providers/discover-models',
|
|
172
|
+
tags: ['config'],
|
|
173
|
+
operationId: 'discoverProviderModels',
|
|
174
|
+
summary: 'Discover models for a provider',
|
|
175
|
+
description:
|
|
176
|
+
'Discovers available models from a provider base URL. Currently supports Ollama-compatible providers.',
|
|
177
|
+
requestBody: {
|
|
178
|
+
required: true,
|
|
179
|
+
content: {
|
|
180
|
+
'application/json': {
|
|
181
|
+
schema: {
|
|
182
|
+
type: 'object',
|
|
183
|
+
properties: {
|
|
184
|
+
compatibility: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description:
|
|
187
|
+
'Provider compatibility mode. Model discovery currently supports ollama.',
|
|
188
|
+
},
|
|
189
|
+
baseURL: {
|
|
190
|
+
type: 'string',
|
|
191
|
+
description: 'Provider base URL to inspect.',
|
|
192
|
+
},
|
|
193
|
+
apiKey: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: 'Optional API key for the provider.',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
required: ['baseURL'],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
responses: {
|
|
204
|
+
'200': {
|
|
205
|
+
description: 'Discovered provider models',
|
|
206
|
+
content: {
|
|
207
|
+
'application/json': {
|
|
208
|
+
schema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
baseURL: { type: 'string' },
|
|
212
|
+
models: {
|
|
213
|
+
type: 'array',
|
|
214
|
+
items: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
id: { type: 'string' },
|
|
218
|
+
label: { type: 'string' },
|
|
219
|
+
toolCall: { type: 'boolean' },
|
|
220
|
+
reasoningText: { type: 'boolean' },
|
|
221
|
+
vision: { type: 'boolean' },
|
|
222
|
+
attachment: { type: 'boolean' },
|
|
223
|
+
contextWindow: { type: 'number' },
|
|
224
|
+
maxOutputTokens: { type: 'number' },
|
|
225
|
+
},
|
|
226
|
+
required: ['id', 'label'],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
unsupported: { type: 'boolean' },
|
|
230
|
+
message: { type: 'string' },
|
|
231
|
+
},
|
|
232
|
+
required: ['models'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
'400': { description: 'Invalid discovery request' },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
async (c) => {
|
|
241
|
+
try {
|
|
242
|
+
const embeddedConfig = (
|
|
243
|
+
c as unknown as {
|
|
244
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
245
|
+
}
|
|
246
|
+
).get('embeddedConfig');
|
|
247
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
248
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const body = await c.req.json<ProviderDiscoveryBody>();
|
|
252
|
+
const compatibility = body.compatibility || 'openai-compatible';
|
|
253
|
+
const baseURL = body.baseURL?.trim();
|
|
254
|
+
const apiKey = body.apiKey?.trim() || undefined;
|
|
255
|
+
if (!baseURL) return c.json({ error: 'Base URL is required' }, 400);
|
|
256
|
+
|
|
257
|
+
if (compatibility !== 'ollama') {
|
|
258
|
+
return c.json({
|
|
259
|
+
models: [],
|
|
260
|
+
unsupported: true,
|
|
261
|
+
message:
|
|
262
|
+
'Model discovery is currently available for Ollama providers.',
|
|
263
|
+
});
|
|
115
264
|
}
|
|
116
|
-
).get('embeddedConfig');
|
|
117
|
-
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
118
|
-
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
119
|
-
}
|
|
120
265
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
266
|
+
const discovered = await discoverOllamaModels({
|
|
267
|
+
baseURL,
|
|
268
|
+
apiKey,
|
|
269
|
+
includeDetails: true,
|
|
270
|
+
});
|
|
126
271
|
|
|
127
|
-
if (compatibility !== 'ollama') {
|
|
128
272
|
return c.json({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
message:
|
|
132
|
-
'Model discovery is currently available for Ollama providers.',
|
|
273
|
+
baseURL: discovered.baseURL,
|
|
274
|
+
models: discovered.models.map(toDiscoveredModel),
|
|
133
275
|
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
logger.error('Failed to discover provider models', error);
|
|
278
|
+
const errorResponse = serializeError(error);
|
|
279
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
134
280
|
}
|
|
281
|
+
},
|
|
282
|
+
);
|
|
135
283
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
284
|
+
openApiRoute(
|
|
285
|
+
app,
|
|
286
|
+
{
|
|
287
|
+
method: 'put',
|
|
288
|
+
path: '/v1/config/providers/{provider}',
|
|
289
|
+
tags: ['config'],
|
|
290
|
+
operationId: 'updateProviderSettings',
|
|
291
|
+
summary: 'Create or update provider settings',
|
|
292
|
+
parameters: [
|
|
293
|
+
{
|
|
294
|
+
in: 'query',
|
|
295
|
+
name: 'project',
|
|
296
|
+
required: false,
|
|
297
|
+
schema: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
},
|
|
300
|
+
description:
|
|
301
|
+
'Project root override (defaults to current working directory).',
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
in: 'path',
|
|
305
|
+
name: 'provider',
|
|
306
|
+
required: true,
|
|
307
|
+
schema: {
|
|
308
|
+
$ref: '#/components/schemas/Provider',
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
requestBody: {
|
|
313
|
+
required: true,
|
|
314
|
+
content: {
|
|
315
|
+
'application/json': {
|
|
316
|
+
schema: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
enabled: {
|
|
320
|
+
type: 'boolean',
|
|
321
|
+
},
|
|
322
|
+
custom: {
|
|
323
|
+
type: 'boolean',
|
|
324
|
+
},
|
|
325
|
+
label: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
},
|
|
328
|
+
compatibility: {
|
|
329
|
+
type: 'string',
|
|
330
|
+
},
|
|
331
|
+
family: {
|
|
332
|
+
type: 'string',
|
|
333
|
+
},
|
|
334
|
+
baseURL: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
nullable: true,
|
|
337
|
+
},
|
|
338
|
+
apiKey: {
|
|
339
|
+
type: 'string',
|
|
340
|
+
nullable: true,
|
|
341
|
+
},
|
|
342
|
+
apiKeyEnv: {
|
|
343
|
+
type: 'string',
|
|
344
|
+
nullable: true,
|
|
345
|
+
},
|
|
346
|
+
models: {
|
|
347
|
+
type: 'array',
|
|
348
|
+
items: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
allowAnyModel: {
|
|
353
|
+
type: 'boolean',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
responses: {
|
|
361
|
+
'200': {
|
|
362
|
+
description: 'OK',
|
|
363
|
+
content: {
|
|
364
|
+
'application/json': {
|
|
365
|
+
schema: {
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties: {
|
|
368
|
+
success: {
|
|
369
|
+
type: 'boolean',
|
|
370
|
+
},
|
|
371
|
+
provider: {
|
|
372
|
+
$ref: '#/components/schemas/Provider',
|
|
373
|
+
},
|
|
374
|
+
details: {
|
|
375
|
+
type: 'array',
|
|
376
|
+
items: {
|
|
377
|
+
$ref: '#/components/schemas/ProviderDetail',
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
required: ['success', 'provider', 'details'],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
async (c) => {
|
|
389
|
+
try {
|
|
390
|
+
const embeddedConfig = (
|
|
391
|
+
c as unknown as {
|
|
392
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
393
|
+
}
|
|
394
|
+
).get('embeddedConfig');
|
|
395
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
396
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
158
397
|
}
|
|
159
|
-
).get('embeddedConfig');
|
|
160
|
-
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
161
|
-
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
162
|
-
}
|
|
163
398
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const updates: ProviderSettingsEntry = {
|
|
170
|
-
enabled: body.enabled ?? true,
|
|
171
|
-
custom: isBuiltInProviderId(provider)
|
|
172
|
-
? body.custom
|
|
173
|
-
: (body.custom ?? true),
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (body.label !== undefined)
|
|
177
|
-
updates.label = body.label.trim() || undefined;
|
|
178
|
-
if (body.compatibility !== undefined) {
|
|
179
|
-
updates.compatibility = body.compatibility;
|
|
180
|
-
}
|
|
181
|
-
if (body.family !== undefined) updates.family = body.family;
|
|
182
|
-
if (body.baseURL !== undefined) {
|
|
183
|
-
updates.baseURL = body.baseURL?.trim() || undefined;
|
|
184
|
-
}
|
|
185
|
-
if (body.apiKey !== undefined)
|
|
186
|
-
updates.apiKey = body.apiKey?.trim() || undefined;
|
|
187
|
-
if (body.apiKeyEnv !== undefined) {
|
|
188
|
-
updates.apiKeyEnv = body.apiKeyEnv?.trim() || undefined;
|
|
189
|
-
}
|
|
190
|
-
if (body.models !== undefined) {
|
|
191
|
-
updates.models = body.models
|
|
192
|
-
.map((model) => model.trim())
|
|
193
|
-
.filter(Boolean);
|
|
194
|
-
}
|
|
195
|
-
if (body.allowAnyModel !== undefined) {
|
|
196
|
-
updates.allowAnyModel = body.allowAnyModel;
|
|
197
|
-
}
|
|
399
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
400
|
+
const provider = c.req.param('provider').trim();
|
|
401
|
+
const body = await c.req.json<ProviderMutationBody>();
|
|
402
|
+
if (!provider) return c.json({ error: 'Provider is required' }, 400);
|
|
198
403
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
404
|
+
const updates: ProviderSettingsEntry = {
|
|
405
|
+
enabled: body.enabled ?? true,
|
|
406
|
+
custom: isBuiltInProviderId(provider)
|
|
407
|
+
? body.custom
|
|
408
|
+
: (body.custom ?? true),
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
if (body.label !== undefined)
|
|
412
|
+
updates.label = body.label.trim() || undefined;
|
|
413
|
+
if (body.compatibility !== undefined) {
|
|
414
|
+
updates.compatibility = body.compatibility;
|
|
415
|
+
}
|
|
416
|
+
if (body.family !== undefined) updates.family = body.family;
|
|
417
|
+
if (body.baseURL !== undefined) {
|
|
418
|
+
updates.baseURL = body.baseURL?.trim() || undefined;
|
|
419
|
+
}
|
|
420
|
+
if (body.apiKey !== undefined)
|
|
421
|
+
updates.apiKey = body.apiKey?.trim() || undefined;
|
|
422
|
+
if (body.apiKeyEnv !== undefined) {
|
|
423
|
+
updates.apiKeyEnv = body.apiKeyEnv?.trim() || undefined;
|
|
424
|
+
}
|
|
425
|
+
if (body.models !== undefined) {
|
|
426
|
+
updates.models = body.models
|
|
427
|
+
.map((model) => model.trim())
|
|
428
|
+
.filter(Boolean);
|
|
429
|
+
}
|
|
430
|
+
if (body.allowAnyModel !== undefined) {
|
|
431
|
+
updates.allowAnyModel = body.allowAnyModel;
|
|
432
|
+
}
|
|
202
433
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
provider,
|
|
209
|
-
details,
|
|
210
|
-
});
|
|
211
|
-
} catch (error) {
|
|
212
|
-
logger.error('Failed to update provider settings', error);
|
|
213
|
-
const errorResponse = serializeError(error);
|
|
214
|
-
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
app.delete('/v1/config/providers/:provider', async (c) => {
|
|
219
|
-
try {
|
|
220
|
-
const embeddedConfig = (
|
|
221
|
-
c as unknown as {
|
|
222
|
-
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
434
|
+
if (!isBuiltInProviderId(provider) && !updates.compatibility) {
|
|
435
|
+
return c.json(
|
|
436
|
+
{ error: 'Custom providers require compatibility' },
|
|
437
|
+
400,
|
|
438
|
+
);
|
|
223
439
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
440
|
+
|
|
441
|
+
await writeProviderSettings('global', provider, updates, projectRoot);
|
|
442
|
+
const cfg = await loadConfig(projectRoot);
|
|
443
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
444
|
+
return c.json({
|
|
445
|
+
success: true,
|
|
446
|
+
provider,
|
|
447
|
+
details,
|
|
448
|
+
});
|
|
449
|
+
} catch (error) {
|
|
450
|
+
logger.error('Failed to update provider settings', error);
|
|
451
|
+
const errorResponse = serializeError(error);
|
|
452
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
227
453
|
}
|
|
454
|
+
},
|
|
455
|
+
);
|
|
228
456
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
457
|
+
openApiRoute(
|
|
458
|
+
app,
|
|
459
|
+
{
|
|
460
|
+
method: 'delete',
|
|
461
|
+
path: '/v1/config/providers/{provider}',
|
|
462
|
+
tags: ['config'],
|
|
463
|
+
operationId: 'deleteProviderSettings',
|
|
464
|
+
summary: 'Delete provider override or custom provider entry',
|
|
465
|
+
parameters: [
|
|
466
|
+
{
|
|
467
|
+
in: 'query',
|
|
468
|
+
name: 'project',
|
|
469
|
+
required: false,
|
|
470
|
+
schema: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
},
|
|
473
|
+
description:
|
|
474
|
+
'Project root override (defaults to current working directory).',
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
in: 'path',
|
|
478
|
+
name: 'provider',
|
|
479
|
+
required: true,
|
|
480
|
+
schema: {
|
|
481
|
+
$ref: '#/components/schemas/Provider',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
responses: {
|
|
486
|
+
'200': {
|
|
487
|
+
description: 'OK',
|
|
488
|
+
content: {
|
|
489
|
+
'application/json': {
|
|
490
|
+
schema: {
|
|
491
|
+
type: 'object',
|
|
492
|
+
properties: {
|
|
493
|
+
success: {
|
|
494
|
+
type: 'boolean',
|
|
495
|
+
},
|
|
496
|
+
provider: {
|
|
497
|
+
$ref: '#/components/schemas/Provider',
|
|
498
|
+
},
|
|
499
|
+
details: {
|
|
500
|
+
type: 'array',
|
|
501
|
+
items: {
|
|
502
|
+
$ref: '#/components/schemas/ProviderDetail',
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
required: ['success', 'provider', 'details'],
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
async (c) => {
|
|
514
|
+
try {
|
|
515
|
+
const embeddedConfig = (
|
|
516
|
+
c as unknown as {
|
|
517
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
518
|
+
}
|
|
519
|
+
).get('embeddedConfig');
|
|
520
|
+
if (embeddedConfig && Object.keys(embeddedConfig).length > 0) {
|
|
521
|
+
return c.json({ error: 'Embedded config cannot be modified' }, 400);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
525
|
+
const provider = c.req.param('provider').trim();
|
|
526
|
+
if (!provider) return c.json({ error: 'Provider is required' }, 400);
|
|
527
|
+
|
|
528
|
+
await removeProviderSettings('global', provider, projectRoot);
|
|
529
|
+
const cfg = await loadConfig(projectRoot);
|
|
530
|
+
const details = await getProviderDetails(undefined, cfg);
|
|
531
|
+
return c.json({ success: true, provider, details });
|
|
532
|
+
} catch (error) {
|
|
533
|
+
logger.error('Failed to remove provider settings', error);
|
|
534
|
+
const errorResponse = serializeError(error);
|
|
535
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
);
|
|
243
539
|
}
|
|
@@ -80,7 +80,7 @@ export async function getProviderDetails(
|
|
|
80
80
|
]),
|
|
81
81
|
);
|
|
82
82
|
const details = await Promise.all(
|
|
83
|
-
providers.map(async (provider) => {
|
|
83
|
+
providers.map(async (provider): Promise<ProviderDetail | null> => {
|
|
84
84
|
const definition = getProviderDefinition(fileConfig, provider);
|
|
85
85
|
if (!definition) return null;
|
|
86
86
|
const settings = getProviderSettings(fileConfig, provider);
|
|
@@ -112,7 +112,7 @@ export async function getProviderDetails(
|
|
|
112
112
|
} satisfies ProviderDetail;
|
|
113
113
|
}),
|
|
114
114
|
);
|
|
115
|
-
return details.filter((detail)
|
|
115
|
+
return details.filter((detail) => detail !== null);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
export function getDefault<T>(
|