@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.246",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,9 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/
|
|
53
|
-
"@ottocode/
|
|
52
|
+
"@ottocode/database": "0.1.246",
|
|
53
|
+
"@ottocode/sdk": "0.1.246",
|
|
54
|
+
"ai-sdk-ollama": "^3.8.3",
|
|
54
55
|
"drizzle-orm": "^0.44.5",
|
|
55
56
|
"hono": "^4.9.9",
|
|
56
57
|
"zod": "^4.3.6"
|
package/src/index.ts
CHANGED
|
@@ -284,6 +284,11 @@ export {
|
|
|
284
284
|
composeSystemPrompt,
|
|
285
285
|
type ComposedSystemPrompt,
|
|
286
286
|
} from './runtime/prompt/builder.ts';
|
|
287
|
+
export {
|
|
288
|
+
buildCapabilitySummary,
|
|
289
|
+
type CapabilitySummaryResult,
|
|
290
|
+
type CapabilitySummaryMCPTool,
|
|
291
|
+
} from './runtime/prompt/capabilities.ts';
|
|
287
292
|
export {
|
|
288
293
|
AskServiceError,
|
|
289
294
|
handleAskRequest,
|
|
@@ -91,9 +91,123 @@ export const configPaths = {
|
|
|
91
91
|
type: 'array',
|
|
92
92
|
items: { $ref: '#/components/schemas/Provider' },
|
|
93
93
|
},
|
|
94
|
+
details: {
|
|
95
|
+
type: 'array',
|
|
96
|
+
items: { $ref: '#/components/schemas/ProviderDetail' },
|
|
97
|
+
},
|
|
94
98
|
default: { $ref: '#/components/schemas/Provider' },
|
|
95
99
|
},
|
|
96
|
-
required: ['providers', 'default'],
|
|
100
|
+
required: ['providers', 'details', 'default'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
'/v1/config/providers/{provider}': {
|
|
109
|
+
put: {
|
|
110
|
+
tags: ['config'],
|
|
111
|
+
operationId: 'updateProviderSettings',
|
|
112
|
+
summary: 'Create or update provider settings',
|
|
113
|
+
parameters: [
|
|
114
|
+
projectQueryParam(),
|
|
115
|
+
{
|
|
116
|
+
in: 'path',
|
|
117
|
+
name: 'provider',
|
|
118
|
+
required: true,
|
|
119
|
+
schema: { $ref: '#/components/schemas/Provider' },
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
requestBody: {
|
|
123
|
+
required: true,
|
|
124
|
+
content: {
|
|
125
|
+
'application/json': {
|
|
126
|
+
schema: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {
|
|
129
|
+
enabled: { type: 'boolean' },
|
|
130
|
+
custom: { type: 'boolean' },
|
|
131
|
+
label: { type: 'string' },
|
|
132
|
+
compatibility: { type: 'string' },
|
|
133
|
+
family: { type: 'string' },
|
|
134
|
+
baseURL: { type: 'string', nullable: true },
|
|
135
|
+
apiKey: { type: 'string', nullable: true },
|
|
136
|
+
apiKeyEnv: { type: 'string', nullable: true },
|
|
137
|
+
models: {
|
|
138
|
+
type: 'array',
|
|
139
|
+
items: { type: 'string' },
|
|
140
|
+
},
|
|
141
|
+
allowAnyModel: { type: 'boolean' },
|
|
142
|
+
scope: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
enum: ['global', 'local'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
responses: {
|
|
152
|
+
200: {
|
|
153
|
+
description: 'OK',
|
|
154
|
+
content: {
|
|
155
|
+
'application/json': {
|
|
156
|
+
schema: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
properties: {
|
|
159
|
+
success: { type: 'boolean' },
|
|
160
|
+
provider: { $ref: '#/components/schemas/Provider' },
|
|
161
|
+
details: {
|
|
162
|
+
type: 'array',
|
|
163
|
+
items: { $ref: '#/components/schemas/ProviderDetail' },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
required: ['success', 'provider', 'details'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
delete: {
|
|
174
|
+
tags: ['config'],
|
|
175
|
+
operationId: 'deleteProviderSettings',
|
|
176
|
+
summary: 'Delete provider override or custom provider entry',
|
|
177
|
+
parameters: [
|
|
178
|
+
projectQueryParam(),
|
|
179
|
+
{
|
|
180
|
+
in: 'query',
|
|
181
|
+
name: 'scope',
|
|
182
|
+
required: false,
|
|
183
|
+
schema: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
enum: ['global', 'local'],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
in: 'path',
|
|
190
|
+
name: 'provider',
|
|
191
|
+
required: true,
|
|
192
|
+
schema: { $ref: '#/components/schemas/Provider' },
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
responses: {
|
|
196
|
+
200: {
|
|
197
|
+
description: 'OK',
|
|
198
|
+
content: {
|
|
199
|
+
'application/json': {
|
|
200
|
+
schema: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
success: { type: 'boolean' },
|
|
204
|
+
provider: { $ref: '#/components/schemas/Provider' },
|
|
205
|
+
details: {
|
|
206
|
+
type: 'array',
|
|
207
|
+
items: { $ref: '#/components/schemas/ProviderDetail' },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ['success', 'provider', 'details'],
|
|
97
211
|
},
|
|
98
212
|
},
|
|
99
213
|
},
|
|
@@ -128,8 +242,10 @@ export const configPaths = {
|
|
|
128
242
|
items: { $ref: '#/components/schemas/Model' },
|
|
129
243
|
},
|
|
130
244
|
default: { type: 'string', nullable: true },
|
|
245
|
+
allowAnyModel: { type: 'boolean' },
|
|
246
|
+
label: { type: 'string' },
|
|
131
247
|
},
|
|
132
|
-
required: ['models'],
|
|
248
|
+
required: ['models', 'allowAnyModel', 'label'],
|
|
133
249
|
},
|
|
134
250
|
},
|
|
135
251
|
},
|
|
@@ -1,6 +1,128 @@
|
|
|
1
1
|
import { errorResponse, projectQueryParam } from '../helpers';
|
|
2
2
|
|
|
3
3
|
export const skillsPaths = {
|
|
4
|
+
'/v1/config/skills': {
|
|
5
|
+
get: {
|
|
6
|
+
tags: ['config'],
|
|
7
|
+
operationId: 'getSkillsConfig',
|
|
8
|
+
summary: 'Get skills enable/disable config and counts',
|
|
9
|
+
parameters: [projectQueryParam()],
|
|
10
|
+
responses: {
|
|
11
|
+
200: {
|
|
12
|
+
description: 'OK',
|
|
13
|
+
content: {
|
|
14
|
+
'application/json': {
|
|
15
|
+
schema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
enabled: { type: 'boolean' },
|
|
19
|
+
totalCount: { type: 'number' },
|
|
20
|
+
enabledCount: { type: 'number' },
|
|
21
|
+
items: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
name: { type: 'string' },
|
|
27
|
+
description: { type: 'string' },
|
|
28
|
+
scope: { type: 'string' },
|
|
29
|
+
path: { type: 'string' },
|
|
30
|
+
enabled: { type: 'boolean' },
|
|
31
|
+
},
|
|
32
|
+
required: [
|
|
33
|
+
'name',
|
|
34
|
+
'description',
|
|
35
|
+
'scope',
|
|
36
|
+
'path',
|
|
37
|
+
'enabled',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['enabled', 'totalCount', 'enabledCount', 'items'],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
500: errorResponse(),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
put: {
|
|
51
|
+
tags: ['config'],
|
|
52
|
+
operationId: 'updateSkillsConfig',
|
|
53
|
+
summary: 'Update skills enable/disable config',
|
|
54
|
+
parameters: [projectQueryParam()],
|
|
55
|
+
requestBody: {
|
|
56
|
+
required: true,
|
|
57
|
+
content: {
|
|
58
|
+
'application/json': {
|
|
59
|
+
schema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
enabled: { type: 'boolean' },
|
|
63
|
+
scope: { type: 'string', enum: ['global', 'local'] },
|
|
64
|
+
items: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
additionalProperties: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
enabled: { type: 'boolean' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
responses: {
|
|
79
|
+
200: {
|
|
80
|
+
description: 'OK',
|
|
81
|
+
content: {
|
|
82
|
+
'application/json': {
|
|
83
|
+
schema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
success: { type: 'boolean' },
|
|
87
|
+
enabled: { type: 'boolean' },
|
|
88
|
+
totalCount: { type: 'number' },
|
|
89
|
+
enabledCount: { type: 'number' },
|
|
90
|
+
items: {
|
|
91
|
+
type: 'array',
|
|
92
|
+
items: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
name: { type: 'string' },
|
|
96
|
+
description: { type: 'string' },
|
|
97
|
+
scope: { type: 'string' },
|
|
98
|
+
path: { type: 'string' },
|
|
99
|
+
enabled: { type: 'boolean' },
|
|
100
|
+
},
|
|
101
|
+
required: [
|
|
102
|
+
'name',
|
|
103
|
+
'description',
|
|
104
|
+
'scope',
|
|
105
|
+
'path',
|
|
106
|
+
'enabled',
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: [
|
|
112
|
+
'success',
|
|
113
|
+
'enabled',
|
|
114
|
+
'totalCount',
|
|
115
|
+
'enabledCount',
|
|
116
|
+
'items',
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
500: errorResponse(),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
4
126
|
'/v1/skills': {
|
|
5
127
|
get: {
|
|
6
128
|
tags: ['config'],
|
package/src/openapi/schemas.ts
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
-
import { providerIds } from '@ottocode/sdk';
|
|
2
|
-
|
|
3
1
|
export const schemas = {
|
|
4
2
|
Provider: {
|
|
5
3
|
type: 'string',
|
|
6
|
-
|
|
4
|
+
description: 'Built-in or custom provider identifier',
|
|
5
|
+
},
|
|
6
|
+
ProviderDetail: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
id: { $ref: '#/components/schemas/Provider' },
|
|
10
|
+
label: { type: 'string' },
|
|
11
|
+
source: { type: 'string', enum: ['built-in', 'custom'] },
|
|
12
|
+
enabled: { type: 'boolean' },
|
|
13
|
+
authorized: { type: 'boolean' },
|
|
14
|
+
custom: { type: 'boolean' },
|
|
15
|
+
compatibility: { type: 'string', nullable: true },
|
|
16
|
+
family: { type: 'string', nullable: true },
|
|
17
|
+
baseURL: { type: 'string', nullable: true },
|
|
18
|
+
apiKeyEnv: { type: 'string', nullable: true },
|
|
19
|
+
hasApiKey: { type: 'boolean' },
|
|
20
|
+
allowAnyModel: { type: 'boolean' },
|
|
21
|
+
modelCount: { type: 'integer' },
|
|
22
|
+
authType: { type: 'string', nullable: true },
|
|
23
|
+
},
|
|
24
|
+
required: [
|
|
25
|
+
'id',
|
|
26
|
+
'label',
|
|
27
|
+
'source',
|
|
28
|
+
'enabled',
|
|
29
|
+
'authorized',
|
|
30
|
+
'custom',
|
|
31
|
+
'hasApiKey',
|
|
32
|
+
'allowAnyModel',
|
|
33
|
+
'modelCount',
|
|
34
|
+
],
|
|
7
35
|
},
|
|
8
36
|
AskResponse: {
|
|
9
37
|
type: 'object',
|
|
@@ -190,6 +218,10 @@ export const schemas = {
|
|
|
190
218
|
type: 'array',
|
|
191
219
|
items: { $ref: '#/components/schemas/Provider' },
|
|
192
220
|
},
|
|
221
|
+
providerDetails: {
|
|
222
|
+
type: 'array',
|
|
223
|
+
items: { $ref: '#/components/schemas/ProviderDetail' },
|
|
224
|
+
},
|
|
193
225
|
defaults: {
|
|
194
226
|
type: 'object',
|
|
195
227
|
properties: {
|
package/src/routes/auth.ts
CHANGED
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
exchange,
|
|
17
17
|
authorizeWeb,
|
|
18
18
|
exchangeWeb,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
authorizeOpenAIWeb,
|
|
20
|
+
exchangeOpenAIWeb,
|
|
21
21
|
authorizeCopilot,
|
|
22
22
|
pollForCopilotTokenOnce,
|
|
23
23
|
type ProviderId,
|
|
@@ -488,45 +488,23 @@ export function registerAuthRoutes(app: Hono) {
|
|
|
488
488
|
try {
|
|
489
489
|
const provider = c.req.param('provider');
|
|
490
490
|
const mode = c.req.query('mode') || 'max';
|
|
491
|
+
const host = c.req.header('host') || 'localhost:3000';
|
|
492
|
+
const protocol = c.req.header('x-forwarded-proto') || 'http';
|
|
491
493
|
|
|
492
494
|
let url: string;
|
|
493
495
|
let verifier: string;
|
|
494
496
|
let callbackUrl = '';
|
|
495
497
|
|
|
496
498
|
if (provider === 'anthropic') {
|
|
497
|
-
const host = c.req.header('host') || 'localhost:3000';
|
|
498
|
-
const protocol = c.req.header('x-forwarded-proto') || 'http';
|
|
499
499
|
callbackUrl = `${protocol}://${host}/v1/auth/${provider}/oauth/callback`;
|
|
500
500
|
const result = authorizeWeb(mode as 'max' | 'console', callbackUrl);
|
|
501
501
|
url = result.url;
|
|
502
502
|
verifier = result.verifier;
|
|
503
503
|
} else if (provider === 'openai') {
|
|
504
|
-
|
|
504
|
+
callbackUrl = `${protocol}://${host}/v1/auth/${provider}/oauth/callback`;
|
|
505
|
+
const result = authorizeOpenAIWeb(callbackUrl);
|
|
505
506
|
url = result.url;
|
|
506
507
|
verifier = result.verifier;
|
|
507
|
-
callbackUrl = 'localhost';
|
|
508
|
-
result
|
|
509
|
-
.waitForCallback()
|
|
510
|
-
.then(async (code) => {
|
|
511
|
-
const tokens = await exchangeOpenAI(code, verifier);
|
|
512
|
-
await setAuth(
|
|
513
|
-
'openai',
|
|
514
|
-
{
|
|
515
|
-
type: 'oauth',
|
|
516
|
-
refresh: tokens.refresh,
|
|
517
|
-
access: tokens.access,
|
|
518
|
-
expires: tokens.expires,
|
|
519
|
-
accountId: tokens.accountId,
|
|
520
|
-
idToken: tokens.idToken,
|
|
521
|
-
},
|
|
522
|
-
undefined,
|
|
523
|
-
'global',
|
|
524
|
-
);
|
|
525
|
-
result.close();
|
|
526
|
-
})
|
|
527
|
-
.catch(() => {
|
|
528
|
-
result.close();
|
|
529
|
-
});
|
|
530
508
|
} else {
|
|
531
509
|
return c.json({ error: 'OAuth not supported for this provider' }, 400);
|
|
532
510
|
}
|
|
@@ -594,8 +572,24 @@ export function registerAuthRoutes(app: Hono) {
|
|
|
594
572
|
'global',
|
|
595
573
|
);
|
|
596
574
|
} else if (provider === 'openai') {
|
|
597
|
-
|
|
598
|
-
|
|
575
|
+
const tokens = await exchangeOpenAIWeb(
|
|
576
|
+
code ?? '',
|
|
577
|
+
verifier,
|
|
578
|
+
callbackUrl,
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
await setAuth(
|
|
582
|
+
'openai',
|
|
583
|
+
{
|
|
584
|
+
type: 'oauth',
|
|
585
|
+
refresh: tokens.refresh,
|
|
586
|
+
access: tokens.access,
|
|
587
|
+
expires: tokens.expires,
|
|
588
|
+
accountId: tokens.accountId,
|
|
589
|
+
idToken: tokens.idToken,
|
|
590
|
+
},
|
|
591
|
+
undefined,
|
|
592
|
+
'global',
|
|
599
593
|
);
|
|
600
594
|
}
|
|
601
595
|
|
package/src/routes/branch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import { loadConfig } from '@ottocode/sdk';
|
|
3
3
|
import { getDb } from '@ottocode/database';
|
|
4
|
-
import {
|
|
4
|
+
import { hasConfiguredProvider, logger } from '@ottocode/sdk';
|
|
5
5
|
import {
|
|
6
6
|
createBranch,
|
|
7
7
|
listBranches,
|
|
@@ -28,7 +28,8 @@ export function registerBranchRoutes(app: Hono) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const provider =
|
|
31
|
-
typeof body.provider === 'string' &&
|
|
31
|
+
typeof body.provider === 'string' &&
|
|
32
|
+
hasConfiguredProvider(cfg, body.provider)
|
|
32
33
|
? body.provider
|
|
33
34
|
: undefined;
|
|
34
35
|
|
|
@@ -2,6 +2,7 @@ import type { Hono } from 'hono';
|
|
|
2
2
|
import {
|
|
3
3
|
setConfig,
|
|
4
4
|
loadConfig,
|
|
5
|
+
hasConfiguredProvider,
|
|
5
6
|
type ProviderId,
|
|
6
7
|
type ReasoningLevel,
|
|
7
8
|
} from '@ottocode/sdk';
|
|
@@ -12,6 +13,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
12
13
|
app.patch('/v1/config/defaults', async (c) => {
|
|
13
14
|
try {
|
|
14
15
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
16
|
+
const cfg = await loadConfig(projectRoot);
|
|
15
17
|
const body = await c.req.json<{
|
|
16
18
|
agent?: string;
|
|
17
19
|
provider?: string;
|
|
@@ -41,7 +43,12 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
41
43
|
}> = {};
|
|
42
44
|
|
|
43
45
|
if (body.agent) updates.agent = body.agent;
|
|
44
|
-
if (body.provider)
|
|
46
|
+
if (body.provider) {
|
|
47
|
+
if (!hasConfiguredProvider(cfg, body.provider)) {
|
|
48
|
+
return c.json({ error: `Invalid provider: ${body.provider}` }, 400);
|
|
49
|
+
}
|
|
50
|
+
updates.provider = body.provider as ProviderId;
|
|
51
|
+
}
|
|
45
52
|
if (body.model) updates.model = body.model;
|
|
46
53
|
if (body.toolApproval) updates.toolApproval = body.toolApproval;
|
|
47
54
|
if (body.guidedMode !== undefined) updates.guidedMode = body.guidedMode;
|
|
@@ -62,11 +69,11 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
62
69
|
|
|
63
70
|
await setConfig(scope, updates, projectRoot);
|
|
64
71
|
|
|
65
|
-
const
|
|
72
|
+
const nextCfg = await loadConfig(projectRoot);
|
|
66
73
|
|
|
67
74
|
return c.json({
|
|
68
75
|
success: true,
|
|
69
|
-
defaults:
|
|
76
|
+
defaults: nextCfg.defaults,
|
|
70
77
|
});
|
|
71
78
|
} catch (error) {
|
|
72
79
|
logger.error('Failed to update defaults', error);
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
discoverAllAgents,
|
|
8
8
|
getAuthorizedProviders,
|
|
9
9
|
getDefault,
|
|
10
|
+
getProviderDetails,
|
|
10
11
|
} from './utils.ts';
|
|
11
12
|
|
|
12
13
|
export function registerMainConfigRoute(app: Hono) {
|
|
@@ -37,6 +38,7 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
37
38
|
embeddedConfig,
|
|
38
39
|
cfg,
|
|
39
40
|
);
|
|
41
|
+
const providerDetails = await getProviderDetails(embeddedConfig, cfg);
|
|
40
42
|
|
|
41
43
|
const defaults = {
|
|
42
44
|
agent: getDefault(
|
|
@@ -80,6 +82,7 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
80
82
|
return c.json({
|
|
81
83
|
agents: allAgents,
|
|
82
84
|
providers: authorizedProviders,
|
|
85
|
+
providerDetails,
|
|
83
86
|
defaults,
|
|
84
87
|
});
|
|
85
88
|
} catch (error) {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import {
|
|
3
|
+
discoverOllamaModels,
|
|
3
4
|
loadConfig,
|
|
4
5
|
catalog,
|
|
6
|
+
getConfiguredProviderModels,
|
|
7
|
+
getProviderDefinition,
|
|
8
|
+
providerAllowsAnyModel,
|
|
5
9
|
getAuth,
|
|
6
10
|
logger,
|
|
7
11
|
readEnvKey,
|
|
12
|
+
type ModelInfo,
|
|
8
13
|
type ProviderId,
|
|
9
14
|
filterModelsForAuthType,
|
|
10
15
|
} from '@ottocode/sdk';
|
|
@@ -79,6 +84,49 @@ async function getAuthorizedCopilotModels(
|
|
|
79
84
|
return successful ? merged : null;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
async function discoverProviderModels(args: {
|
|
88
|
+
provider: ProviderId;
|
|
89
|
+
providerDefinition: NonNullable<ReturnType<typeof getProviderDefinition>>;
|
|
90
|
+
projectRoot: string;
|
|
91
|
+
}): Promise<ModelInfo[] | undefined> {
|
|
92
|
+
const { provider, providerDefinition, projectRoot } = args;
|
|
93
|
+
if (
|
|
94
|
+
providerDefinition.compatibility !== 'ollama' ||
|
|
95
|
+
!providerDefinition.baseURL
|
|
96
|
+
) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const auth = await getAuth(provider, projectRoot);
|
|
102
|
+
const apiKey =
|
|
103
|
+
auth?.type === 'api'
|
|
104
|
+
? auth.key
|
|
105
|
+
: (readEnvKey(provider) ?? providerDefinition.apiKey);
|
|
106
|
+
const discovered = await discoverOllamaModels({
|
|
107
|
+
baseURL: providerDefinition.baseURL,
|
|
108
|
+
apiKey,
|
|
109
|
+
includeDetails: false,
|
|
110
|
+
});
|
|
111
|
+
return discovered.models;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.warn('Failed to discover Ollama models', {
|
|
114
|
+
provider,
|
|
115
|
+
error: error instanceof Error ? error.message : String(error),
|
|
116
|
+
});
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function shouldLazyLoadProviderModels(
|
|
122
|
+
providerDefinition: NonNullable<ReturnType<typeof getProviderDefinition>>,
|
|
123
|
+
): boolean {
|
|
124
|
+
return (
|
|
125
|
+
providerDefinition.compatibility === 'ollama' ||
|
|
126
|
+
providerDefinition.source === 'custom'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
export function registerModelsRoutes(app: Hono) {
|
|
83
131
|
app.get('/v1/config/providers/:provider/models', async (c) => {
|
|
84
132
|
try {
|
|
@@ -103,8 +151,9 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
103
151
|
return c.json({ error: 'Provider not authorized' }, 403);
|
|
104
152
|
}
|
|
105
153
|
|
|
106
|
-
const providerCatalog = catalog[provider];
|
|
107
|
-
|
|
154
|
+
const providerCatalog = catalog[provider as keyof typeof catalog];
|
|
155
|
+
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
156
|
+
if (!providerDefinition) {
|
|
108
157
|
logger.warn('Provider not found in catalog', { provider });
|
|
109
158
|
return c.json({ error: 'Provider not found' }, 404);
|
|
110
159
|
}
|
|
@@ -114,11 +163,21 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
114
163
|
provider,
|
|
115
164
|
projectRoot,
|
|
116
165
|
);
|
|
117
|
-
const
|
|
166
|
+
const discoveredModels = await discoverProviderModels({
|
|
118
167
|
provider,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
);
|
|
168
|
+
providerDefinition,
|
|
169
|
+
projectRoot,
|
|
170
|
+
});
|
|
171
|
+
const filteredModels =
|
|
172
|
+
providerDefinition.compatibility === 'ollama'
|
|
173
|
+
? (discoveredModels ?? [])
|
|
174
|
+
: providerCatalog
|
|
175
|
+
? filterModelsForAuthType(
|
|
176
|
+
provider,
|
|
177
|
+
providerCatalog.models,
|
|
178
|
+
authType,
|
|
179
|
+
)
|
|
180
|
+
: getConfiguredProviderModels(cfg, provider);
|
|
122
181
|
const copilotAllowedModels =
|
|
123
182
|
provider === 'copilot'
|
|
124
183
|
? await getAuthorizedCopilotModels(projectRoot)
|
|
@@ -145,6 +204,8 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
145
204
|
embeddedConfig?.defaults?.model,
|
|
146
205
|
cfg.defaults.model,
|
|
147
206
|
),
|
|
207
|
+
allowAnyModel: providerAllowsAnyModel(cfg, provider),
|
|
208
|
+
label: providerDefinition.label,
|
|
148
209
|
});
|
|
149
210
|
} catch (error) {
|
|
150
211
|
logger.error('Failed to get provider models', error);
|
|
@@ -184,21 +245,30 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
184
245
|
> = {};
|
|
185
246
|
|
|
186
247
|
for (const provider of authorizedProviders) {
|
|
187
|
-
const providerCatalog = catalog[provider];
|
|
188
|
-
|
|
248
|
+
const providerCatalog = catalog[provider as keyof typeof catalog];
|
|
249
|
+
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
250
|
+
if (providerDefinition) {
|
|
251
|
+
const dynamicModels =
|
|
252
|
+
shouldLazyLoadProviderModels(providerDefinition);
|
|
189
253
|
const authType = await getAuthTypeForProvider(
|
|
190
254
|
embeddedConfig,
|
|
191
255
|
provider,
|
|
192
256
|
projectRoot,
|
|
193
257
|
);
|
|
194
|
-
const filteredModels =
|
|
195
|
-
provider
|
|
196
|
-
providerCatalog
|
|
197
|
-
|
|
198
|
-
|
|
258
|
+
const filteredModels = dynamicModels
|
|
259
|
+
? getConfiguredProviderModels(cfg, provider)
|
|
260
|
+
: providerCatalog
|
|
261
|
+
? filterModelsForAuthType(
|
|
262
|
+
provider,
|
|
263
|
+
providerCatalog.models,
|
|
264
|
+
authType,
|
|
265
|
+
)
|
|
266
|
+
: getConfiguredProviderModels(cfg, provider);
|
|
199
267
|
modelsMap[provider] = {
|
|
200
|
-
label:
|
|
268
|
+
label: providerDefinition.label,
|
|
201
269
|
authType,
|
|
270
|
+
allowAnyModel: providerDefinition.allowAnyModel,
|
|
271
|
+
dynamicModels,
|
|
202
272
|
models: filteredModels.map((m) => ({
|
|
203
273
|
id: m.id,
|
|
204
274
|
label: m.label || m.id,
|