opencode-models-discovery 0.7.0 → 0.8.0
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/README.md
CHANGED
|
@@ -8,19 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
> A universal OpenCode plugin for dynamic model discovery across **any OpenAI-compatible provider**.
|
|
10
10
|
|
|
11
|
-
Originally inspired by [opencode-lmstudio](https://github.com/nicktasios/opencode-lmstudio), this project has been fully refactored into a general-purpose model discovery plugin with richer configuration controls for providers, models, naming,
|
|
11
|
+
Originally inspired by [opencode-lmstudio](https://github.com/nicktasios/opencode-lmstudio), this project has been fully refactored into a general-purpose model discovery plugin with richer configuration controls for providers, models, naming, and discovery behavior.
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
15
|
- **Universal Provider Support**: Works with any OpenAI-compatible provider (LM Studio, Ollama, LocalAI, gateways, and more)
|
|
16
|
-
- **Dynamic Model Discovery**: Queries each provider's
|
|
16
|
+
- **Dynamic Model Discovery**: Queries each provider's configured models endpoint to discover available models
|
|
17
17
|
- **Auto-Injection**: Automatically adds unconfigured models into OpenCode provider config
|
|
18
18
|
- **Provider Filtering**: Include or exclude specific providers from discovery
|
|
19
19
|
- **Model Filtering**: Use regex rules to precisely control which discovered models are injected
|
|
20
|
-
- **Configurable Discovery**: Control discovery behavior with enable/disable switches
|
|
20
|
+
- **Configurable Discovery**: Control discovery behavior with global and provider-level enable/disable switches
|
|
21
21
|
- **Smart Model Formatting**: Optional human-friendly display names for discovered models
|
|
22
22
|
- **Organization Owner Extraction**: Extracts and sets `organizationOwner` from model IDs when available
|
|
23
|
-
- **Health Check Monitoring**: Verifies providers are accessible before attempting discovery
|
|
24
23
|
- **Model Merging**: Intelligently merges discovered models with existing configuration
|
|
25
24
|
- **Error Handling**: Smart error categorization with actionable suggestions
|
|
26
25
|
|
|
@@ -43,11 +42,16 @@ Add the plugin to your `opencode.json`:
|
|
|
43
42
|
"opencode-models-discovery@latest"
|
|
44
43
|
],
|
|
45
44
|
"provider": {
|
|
46
|
-
"
|
|
45
|
+
"deepseek": {
|
|
47
46
|
"npm": "@ai-sdk/openai-compatible",
|
|
48
|
-
"name": "
|
|
47
|
+
"name": "DeepSeek",
|
|
49
48
|
"options": {
|
|
50
|
-
"baseURL": "
|
|
49
|
+
"baseURL": "https://api.deepseek.com",
|
|
50
|
+
"apiKey": "YOUR_DEEPSEEK_API_KEY",
|
|
51
|
+
"modelsDiscovery": {
|
|
52
|
+
"enabled": true,
|
|
53
|
+
"endpoint": "/models"
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
},
|
|
53
57
|
"lmstudio": {
|
|
@@ -63,6 +67,8 @@ Add the plugin to your `opencode.json`:
|
|
|
63
67
|
|
|
64
68
|
### Configuration
|
|
65
69
|
|
|
70
|
+
The plugin still supports global configuration in the `plugin` array, but for new setups it is recommended to prefer `provider.<name>.options.modelsDiscovery` for provider-specific behavior. This keeps discovery rules close to the provider they affect and avoids older global rules unintentionally changing newer providers.
|
|
71
|
+
|
|
66
72
|
The plugin configuration is placed in the `plugin` array using tuple format `["plugin-name", { config }]`:
|
|
67
73
|
|
|
68
74
|
```json
|
|
@@ -95,10 +101,19 @@ Each provider can override discovery behavior through `provider.<name>.options.m
|
|
|
95
101
|
| Option | Type | Description |
|
|
96
102
|
|--------|------|-------------|
|
|
97
103
|
| `provider.<name>.options.modelsDiscovery.enabled` | `boolean` | Override global discovery and provider filters for a single provider |
|
|
104
|
+
| `provider.<name>.options.modelsDiscovery.endpoint` | `string` | Provider-specific models endpoint path. Defaults to `/v1/models` |
|
|
98
105
|
| `provider.<name>.options.modelsDiscovery.models.includeRegex` | `string[]` | Provider-specific model include filter |
|
|
99
106
|
| `provider.<name>.options.modelsDiscovery.models.excludeRegex` | `string[]` | Provider-specific model exclude filter |
|
|
100
107
|
| `provider.<name>.options.modelsDiscovery.smartModelName` | `boolean` | Override global `smartModelName` for a single provider |
|
|
101
108
|
|
|
109
|
+
Recommended approach for new configurations:
|
|
110
|
+
|
|
111
|
+
1. Keep global plugin config minimal, or use it only as a broad default
|
|
112
|
+
2. Put endpoint, enablement, and model filtering rules on each provider
|
|
113
|
+
3. Use provider-level overrides whenever a provider does not follow the usual `/v1/models` convention
|
|
114
|
+
|
|
115
|
+
If `provider.<name>.options.modelsDiscovery.endpoint` is omitted, the plugin uses `/v1/models`.
|
|
116
|
+
|
|
102
117
|
Priority rules:
|
|
103
118
|
|
|
104
119
|
1. `provider.<name>.options.modelsDiscovery.enabled` overrides global `discovery.enabled` and `providers.include/exclude`
|
|
@@ -110,16 +125,9 @@ Priority rules:
|
|
|
110
125
|
{
|
|
111
126
|
"plugin": [
|
|
112
127
|
["opencode-models-discovery", {
|
|
113
|
-
"providers": {
|
|
114
|
-
"include": ["ollama"]
|
|
115
|
-
},
|
|
116
|
-
"models": {
|
|
117
|
-
"includeRegex": ["^qwen/"]
|
|
118
|
-
},
|
|
119
128
|
"discovery": {
|
|
120
129
|
"enabled": false
|
|
121
|
-
}
|
|
122
|
-
"smartModelName": false
|
|
130
|
+
}
|
|
123
131
|
}]
|
|
124
132
|
],
|
|
125
133
|
"provider": {
|
|
@@ -130,6 +138,7 @@ Priority rules:
|
|
|
130
138
|
"baseURL": "http://127.0.0.1:1234/v1",
|
|
131
139
|
"modelsDiscovery": {
|
|
132
140
|
"enabled": true,
|
|
141
|
+
"endpoint": "/v1/models",
|
|
133
142
|
"models": {
|
|
134
143
|
"includeRegex": ["^gpt-"]
|
|
135
144
|
},
|
|
@@ -137,6 +146,70 @@ Priority rules:
|
|
|
137
146
|
}
|
|
138
147
|
},
|
|
139
148
|
"models": {}
|
|
149
|
+
},
|
|
150
|
+
"deepseek": {
|
|
151
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
152
|
+
"name": "DeepSeek",
|
|
153
|
+
"options": {
|
|
154
|
+
"baseURL": "https://api.deepseek.com",
|
|
155
|
+
"apiKey": "sk-example-deepseek-key",
|
|
156
|
+
"modelsDiscovery": {
|
|
157
|
+
"enabled": true,
|
|
158
|
+
"endpoint": "/models",
|
|
159
|
+
"smartModelName": true
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"models": {}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
In this example:
|
|
169
|
+
|
|
170
|
+
1. `lmstudio` explicitly enables discovery and uses the default `/v1/models` endpoint
|
|
171
|
+
2. `lmstudio` limits discovery to models matching `^gpt-`
|
|
172
|
+
3. `deepseek` explicitly enables discovery but uses `"/models"` instead of `/v1/models`
|
|
173
|
+
4. The API key uses an example placeholder and should be replaced in real configs
|
|
174
|
+
|
|
175
|
+
#### Provider-First Example
|
|
176
|
+
|
|
177
|
+
This is the recommended style for newer configs, especially when different providers need different discovery paths:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"$schema": "https://opencode.ai/config.json",
|
|
182
|
+
"plugin": [
|
|
183
|
+
["opencode-models-discovery", {
|
|
184
|
+
"smartModelName": false
|
|
185
|
+
}]
|
|
186
|
+
],
|
|
187
|
+
"provider": {
|
|
188
|
+
"ollama": {
|
|
189
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
190
|
+
"name": "Ollama",
|
|
191
|
+
"options": {
|
|
192
|
+
"baseURL": "http://127.0.0.1:11434/v1",
|
|
193
|
+
"modelsDiscovery": {
|
|
194
|
+
"enabled": true,
|
|
195
|
+
"models": {
|
|
196
|
+
"includeRegex": ["^qwen/"]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"deepseek": {
|
|
202
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
203
|
+
"name": "DeepSeek",
|
|
204
|
+
"options": {
|
|
205
|
+
"baseURL": "https://api.deepseek.com",
|
|
206
|
+
"apiKey": "YOUR_DEEPSEEK_API_KEY",
|
|
207
|
+
"modelsDiscovery": {
|
|
208
|
+
"enabled": true,
|
|
209
|
+
"endpoint": "/models",
|
|
210
|
+
"smartModelName": true
|
|
211
|
+
}
|
|
212
|
+
}
|
|
140
213
|
}
|
|
141
214
|
}
|
|
142
215
|
}
|
|
@@ -144,10 +217,10 @@ Priority rules:
|
|
|
144
217
|
|
|
145
218
|
In this example:
|
|
146
219
|
|
|
147
|
-
1.
|
|
148
|
-
2. `
|
|
149
|
-
3. `
|
|
150
|
-
4.
|
|
220
|
+
1. The global plugin config only keeps a shared default
|
|
221
|
+
2. `ollama` uses the default discovery path derived from its `/v1` baseURL
|
|
222
|
+
3. `deepseek` does not rely on `/v1/models` and explicitly uses `"/models"`
|
|
223
|
+
4. Each provider can evolve independently without changing global include or endpoint rules
|
|
151
224
|
|
|
152
225
|
#### Provider Filtering
|
|
153
226
|
|
|
@@ -199,8 +272,8 @@ Regex filtering only applies to auto-discovered models. Models already explicitl
|
|
|
199
272
|
|
|
200
273
|
1. On OpenCode startup, the plugin's `config` hook is called
|
|
201
274
|
2. The plugin iterates through all configured providers
|
|
202
|
-
3. For each provider, it checks
|
|
203
|
-
4. For each accessible provider, it queries the `/v1/models`
|
|
275
|
+
3. For each provider, it checks whether it is OpenAI-compatible by npm, by a `/v1` baseURL, by an explicit discovery endpoint override, or by a forced provider-level discovery override
|
|
276
|
+
4. For each accessible provider, it queries the configured models endpoint, defaulting to `/v1/models`
|
|
204
277
|
5. Discovered models are automatically merged into the provider's configuration
|
|
205
278
|
6. The enhanced configuration is used for the current session
|
|
206
279
|
|
|
@@ -217,6 +290,7 @@ The plugin supports any OpenAI-compatible provider. Here are the most common one
|
|
|
217
290
|
| **Text Generation WebUI** | 5000 | OpenAI-compatible extension | `@ai-sdk/openai-compatible` |
|
|
218
291
|
| **FastChat (Vicuna)** | 8001 | Multi-model serving | `@ai-sdk/openai-compatible` |
|
|
219
292
|
| **vLLM** | 8000 | High-performance inference | `@ai-sdk/openai-compatible` |
|
|
293
|
+
| **DeepSeek** | Cloud | OpenAI-compatible API with `/models` discovery endpoint | `@ai-sdk/openai-compatible` |
|
|
220
294
|
| **CLIProxyAPI** | 8317 | A LLM proxy server | `@ai-sdk/anthropic` (with `/v1` backend) & `@ai-sdk/openai-compatible` |
|
|
221
295
|
|
|
222
296
|
#### Anthropic API with Custom Backend
|
|
@@ -248,17 +322,21 @@ Cloud services with OpenAI-compatible APIs are also supported:
|
|
|
248
322
|
|
|
249
323
|
### Provider Detection
|
|
250
324
|
|
|
251
|
-
The plugin identifies OpenAI-compatible providers using
|
|
325
|
+
The plugin identifies OpenAI-compatible providers using these detection signals:
|
|
252
326
|
|
|
253
327
|
1. **Strict Detection**: `npm === "@ai-sdk/openai-compatible"`
|
|
254
328
|
2. **URL-based Detection**: `baseURL` contains `/v1/` pattern
|
|
329
|
+
3. **Endpoint Override Detection**: `options.modelsDiscovery.endpoint` is configured
|
|
330
|
+
|
|
331
|
+
In addition, `options.modelsDiscovery.enabled === true` can force discovery even when the provider does not match the detection rules above.
|
|
255
332
|
|
|
256
|
-
A provider is considered discoverable if
|
|
333
|
+
A provider is considered discoverable if it matches any detection signal above, or if discovery is explicitly forced on.
|
|
257
334
|
|
|
258
335
|
#### Examples of Supported Configurations
|
|
259
336
|
|
|
260
337
|
```json
|
|
261
338
|
{
|
|
339
|
+
"plugin": ["opencode-models-discovery"],
|
|
262
340
|
"provider": {
|
|
263
341
|
"ollama": {
|
|
264
342
|
"npm": "@ai-sdk/openai-compatible",
|
|
@@ -271,6 +349,7 @@ A provider is considered discoverable if **either** condition matches.
|
|
|
271
349
|
|
|
272
350
|
```json
|
|
273
351
|
{
|
|
352
|
+
"plugin": ["opencode-models-discovery"],
|
|
274
353
|
"provider": {
|
|
275
354
|
"ollama-anthropic": {
|
|
276
355
|
"npm": "@ai-sdk/anthropic",
|
|
@@ -283,23 +362,51 @@ A provider is considered discoverable if **either** condition matches.
|
|
|
283
362
|
|
|
284
363
|
```json
|
|
285
364
|
{
|
|
365
|
+
"plugin": [
|
|
366
|
+
["opencode-models-discovery", {
|
|
367
|
+
"smartModelName": false
|
|
368
|
+
}]
|
|
369
|
+
],
|
|
286
370
|
"provider": {
|
|
287
371
|
"lmstudio": {
|
|
288
372
|
"npm": "@ai-sdk/openai-compatible",
|
|
289
373
|
"name": "LM Studio",
|
|
290
|
-
"options": {
|
|
374
|
+
"options": {
|
|
375
|
+
"baseURL": "http://127.0.0.1:1234/v1",
|
|
376
|
+
"modelsDiscovery": {
|
|
377
|
+
"enabled": true
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"plugin": ["opencode-models-discovery"],
|
|
388
|
+
"provider": {
|
|
389
|
+
"deepseek": {
|
|
390
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
391
|
+
"name": "DeepSeek",
|
|
392
|
+
"options": {
|
|
393
|
+
"baseURL": "https://api.deepseek.com",
|
|
394
|
+
"modelsDiscovery": {
|
|
395
|
+
"endpoint": "/models"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
291
398
|
}
|
|
292
399
|
}
|
|
293
400
|
}
|
|
294
401
|
```
|
|
295
402
|
|
|
296
|
-
This means providers using `@ai-sdk/anthropic` with OpenAI-compatible backends
|
|
403
|
+
This means providers using `@ai-sdk/anthropic` with OpenAI-compatible backends are also supported when the `baseURL` contains `/v1/`, when a provider-specific discovery endpoint is configured, or when provider-level discovery is explicitly forced on. It also means providers like DeepSeek can be discovered from a non-`/v1` baseURL as long as the models endpoint is configured explicitly.
|
|
297
404
|
|
|
298
405
|
## Requirements
|
|
299
406
|
|
|
300
407
|
- OpenCode with plugin support
|
|
301
408
|
- At least one OpenAI-compatible provider running locally or remotely
|
|
302
|
-
- Provider server API accessible
|
|
409
|
+
- Provider server API accessible, using either a `/v1`-style base URL or an explicitly configured models endpoint such as `/models`
|
|
303
410
|
|
|
304
411
|
## Logging
|
|
305
412
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "opencode-models-discovery",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "OpenCode plugin for auto-discovery of OpenAI-compatible models with dynamic provider configuration",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToastNotifier } from '../ui/toast-notifier'
|
|
2
2
|
import { categorizeModel, formatModelName, extractModelOwner } from '../utils'
|
|
3
|
-
import { normalizeBaseURL,
|
|
3
|
+
import { normalizeBaseURL, discoverModelsFromProvider, autoDetectOpenAICompatibleProvider, canDiscoverModels } from '../utils/openai-compatible-api'
|
|
4
4
|
import { getProviderFilter, getDiscoveryConfig, getModelRegexFilter, getProviderModelRegexFilter, shouldDiscoverModel, shouldDiscoverProviderWithOverride } from '../types/plugin-config'
|
|
5
5
|
import type { PluginLogger } from './logger'
|
|
6
6
|
import type { PluginInput } from '@opencode-ai/plugin'
|
|
@@ -31,8 +31,10 @@ export async function enhanceConfig(
|
|
|
31
31
|
for (const [providerName, providerConfig] of Object.entries(providers)) {
|
|
32
32
|
const p = providerConfig as any
|
|
33
33
|
const providerDiscoveryConfig = p.options?.modelsDiscovery ?? {}
|
|
34
|
+
const modelsEndpoint = providerDiscoveryConfig.endpoint ?? '/v1/models'
|
|
35
|
+
const forceDiscoveryEnabled = providerDiscoveryConfig.enabled === true
|
|
34
36
|
|
|
35
|
-
if (!canDiscoverModels(p)) {
|
|
37
|
+
if (!forceDiscoveryEnabled && !canDiscoverModels(p)) {
|
|
36
38
|
continue
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -52,24 +54,19 @@ export async function enhanceConfig(
|
|
|
52
54
|
|
|
53
55
|
const apiKey = p.options?.apiKey
|
|
54
56
|
|
|
55
|
-
const isHealthy = await checkProviderHealth(baseURL, apiKey)
|
|
56
|
-
if (!isHealthy) {
|
|
57
|
-
// Provider offline - silent, this is normal for health checks
|
|
58
|
-
continue
|
|
59
|
-
}
|
|
60
|
-
|
|
61
57
|
let models: OpenAIModel[]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} catch (error) {
|
|
58
|
+
const discovery = await discoverModelsFromProvider(baseURL, apiKey, modelsEndpoint)
|
|
59
|
+
if (!discovery.ok) {
|
|
65
60
|
logger.warn('Provider model discovery failed', {
|
|
66
61
|
provider: providerName,
|
|
67
62
|
baseURL,
|
|
68
|
-
|
|
63
|
+
endpoint: modelsEndpoint,
|
|
69
64
|
})
|
|
70
65
|
continue
|
|
71
66
|
}
|
|
72
67
|
|
|
68
|
+
models = discovery.models
|
|
69
|
+
|
|
73
70
|
if (models.length === 0) {
|
|
74
71
|
continue
|
|
75
72
|
}
|
|
@@ -2,6 +2,11 @@ import type { OpenAIModel, OpenAIModelsResponse } from '../types'
|
|
|
2
2
|
|
|
3
3
|
const OPENAI_COMPATIBLE_MODELS_ENDPOINT = "/v1/models"
|
|
4
4
|
|
|
5
|
+
export interface ModelsDiscoveryResult {
|
|
6
|
+
ok: boolean
|
|
7
|
+
models: OpenAIModel[]
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export function normalizeBaseURL(baseURL: string): string {
|
|
6
11
|
let normalized = baseURL.replace(/\/+$/, '')
|
|
7
12
|
if (normalized.endsWith('/v1')) {
|
|
@@ -15,27 +20,13 @@ export function buildAPIURL(baseURL: string, endpoint: string = OPENAI_COMPATIBL
|
|
|
15
20
|
return `${normalized}${endpoint}`
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
export async function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
headers["Authorization"] = `Bearer ${apiKey}`
|
|
24
|
-
}
|
|
25
|
-
const response = await fetch(url, {
|
|
26
|
-
method: "GET",
|
|
27
|
-
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
28
|
-
signal: AbortSignal.timeout(3000),
|
|
29
|
-
})
|
|
30
|
-
return response.ok
|
|
31
|
-
} catch {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function discoverModelsFromProvider(baseURL: string, apiKey?: string): Promise<OpenAIModel[]> {
|
|
23
|
+
export async function discoverModelsFromProvider(
|
|
24
|
+
baseURL: string,
|
|
25
|
+
apiKey?: string,
|
|
26
|
+
endpoint: string = OPENAI_COMPATIBLE_MODELS_ENDPOINT
|
|
27
|
+
): Promise<ModelsDiscoveryResult> {
|
|
37
28
|
try {
|
|
38
|
-
const url = buildAPIURL(baseURL)
|
|
29
|
+
const url = buildAPIURL(baseURL, endpoint)
|
|
39
30
|
const headers: Record<string, string> = {
|
|
40
31
|
"Content-Type": "application/json",
|
|
41
32
|
}
|
|
@@ -49,19 +40,22 @@ export async function discoverModelsFromProvider(baseURL: string, apiKey?: strin
|
|
|
49
40
|
})
|
|
50
41
|
|
|
51
42
|
if (!response.ok) {
|
|
52
|
-
return []
|
|
43
|
+
return { ok: false, models: [] }
|
|
53
44
|
}
|
|
54
45
|
|
|
55
46
|
const data = (await response.json()) as OpenAIModelsResponse
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
models: data.data ?? [],
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
return { ok: false, models: [] }
|
|
59
53
|
}
|
|
60
54
|
}
|
|
61
55
|
|
|
62
|
-
export async function fetchModelsDirect(baseURL: string): Promise<string[]> {
|
|
56
|
+
export async function fetchModelsDirect(baseURL: string, endpoint: string = OPENAI_COMPATIBLE_MODELS_ENDPOINT): Promise<string[]> {
|
|
63
57
|
try {
|
|
64
|
-
const url = buildAPIURL(baseURL)
|
|
58
|
+
const url = buildAPIURL(baseURL, endpoint)
|
|
65
59
|
const response = await fetch(url, {
|
|
66
60
|
method: "GET",
|
|
67
61
|
signal: AbortSignal.timeout(3000),
|
|
@@ -87,8 +81,8 @@ export async function autoDetectOpenAICompatibleProvider(): Promise<{ name: stri
|
|
|
87
81
|
for (const candidate of candidates) {
|
|
88
82
|
for (const port of candidate.ports) {
|
|
89
83
|
const baseURL = `http://127.0.0.1:${port}`
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
84
|
+
const discovery = await discoverModelsFromProvider(baseURL)
|
|
85
|
+
if (discovery.ok) {
|
|
92
86
|
return { name: candidate.name, baseURL }
|
|
93
87
|
}
|
|
94
88
|
}
|
|
@@ -108,8 +102,14 @@ export function hasOpenAICompatibleURL(provider: any): boolean {
|
|
|
108
102
|
return /\/v1(\/|$)/.test(baseURL)
|
|
109
103
|
}
|
|
110
104
|
|
|
105
|
+
export function hasModelsDiscoveryEndpoint(provider: any): boolean {
|
|
106
|
+
if (!provider || typeof provider !== 'object') return false
|
|
107
|
+
const endpoint = provider.options?.modelsDiscovery?.endpoint
|
|
108
|
+
return typeof endpoint === 'string' && endpoint.length > 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
111
|
export function canDiscoverModels(provider: any): boolean {
|
|
112
|
-
return isOpenAICompatibleProvider(provider) || hasOpenAICompatibleURL(provider)
|
|
112
|
+
return isOpenAICompatibleProvider(provider) || hasOpenAICompatibleURL(provider) || hasModelsDiscoveryEndpoint(provider)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function isValidModel(model: any): model is { id: string; [key: string]: any } {
|
|
@@ -117,4 +117,4 @@ export function isValidModel(model: any): model is { id: string; [key: string]:
|
|
|
117
117
|
typeof model === 'object' &&
|
|
118
118
|
typeof model.id === 'string' &&
|
|
119
119
|
model.id.length > 0
|
|
120
|
-
}
|
|
120
|
+
}
|
|
@@ -13,8 +13,9 @@ export function validateConfig(config: any): ValidationResult {
|
|
|
13
13
|
if (config.provider && typeof config.provider === 'object') {
|
|
14
14
|
for (const [providerName, providerConfig] of Object.entries(config.provider)) {
|
|
15
15
|
const p = providerConfig as any
|
|
16
|
+
const forceDiscoveryEnabled = p.options?.modelsDiscovery?.enabled === true
|
|
16
17
|
|
|
17
|
-
if (canDiscoverModels(p)) {
|
|
18
|
+
if (forceDiscoveryEnabled || canDiscoverModels(p)) {
|
|
18
19
|
if (!p.options?.baseURL) {
|
|
19
20
|
warnings.push(`Provider '${providerName}' missing baseURL`)
|
|
20
21
|
}
|
|
@@ -30,4 +31,4 @@ export function validateConfig(config: any): ValidationResult {
|
|
|
30
31
|
errors,
|
|
31
32
|
warnings
|
|
32
33
|
}
|
|
33
|
-
}
|
|
34
|
+
}
|