fix(doctor): include custom providers from opencode.json in provider check
The doctor's 'Model override uses unavailable provider' check only looked at providers from ~/.cache/opencode/models.json (built-in providers from models.dev). Custom OpenAI-compatible providers defined in the user's opencode.json (under the 'provider' key) were not included, causing false-positive warnings. Now loadAvailableModelsFromCache() also reads provider names from ~/.config/opencode/opencode.json and ~/.config/opencode/opencode.jsonc, merging them with the cache providers. This eliminates the false positive while preserving real warnings for truly unknown providers. 7 new tests cover: cache-only, custom-only, merged, deduplicated, JSONC variant, and malformed config resilience. Fixes #3199
This commit is contained in:
@@ -10,10 +10,51 @@ function getOpenCodeCacheDir(): string {
|
||||
return join(homedir(), ".cache", "opencode")
|
||||
}
|
||||
|
||||
function getOpenCodeConfigDir(): string {
|
||||
const xdgConfig = process.env.XDG_CONFIG_HOME
|
||||
if (xdgConfig) return join(xdgConfig, "opencode")
|
||||
return join(homedir(), ".config", "opencode")
|
||||
}
|
||||
|
||||
/**
|
||||
* Read custom provider names from opencode.json configs.
|
||||
* Custom providers defined in the user's opencode.json (under the "provider" key)
|
||||
* are valid at runtime but don't appear in the model cache (models.json), which
|
||||
* only contains built-in providers from models.dev. This causes false-positive
|
||||
* warnings in doctor.
|
||||
*/
|
||||
function loadCustomProviderNames(): string[] {
|
||||
const configDir = getOpenCodeConfigDir()
|
||||
const candidatePaths = [
|
||||
join(configDir, "opencode.json"),
|
||||
join(configDir, "opencode.jsonc"),
|
||||
]
|
||||
|
||||
for (const configPath of candidatePaths) {
|
||||
if (!existsSync(configPath)) continue
|
||||
try {
|
||||
const content = readFileSync(configPath, "utf-8")
|
||||
const data = parseJsonc<{ provider?: Record<string, unknown> }>(content)
|
||||
if (data?.provider && typeof data.provider === "object") {
|
||||
return Object.keys(data.provider)
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export function loadAvailableModelsFromCache(): AvailableModelsInfo {
|
||||
const cacheFile = join(getOpenCodeCacheDir(), "models.json")
|
||||
const customProviders = loadCustomProviderNames()
|
||||
|
||||
if (!existsSync(cacheFile)) {
|
||||
// Even without the cache, custom providers are valid
|
||||
if (customProviders.length > 0) {
|
||||
return { providers: customProviders, modelCount: 0, cacheExists: true }
|
||||
}
|
||||
return { providers: [], modelCount: 0, cacheExists: false }
|
||||
}
|
||||
|
||||
@@ -21,16 +62,19 @@ export function loadAvailableModelsFromCache(): AvailableModelsInfo {
|
||||
const content = readFileSync(cacheFile, "utf-8")
|
||||
const data = parseJsonc<Record<string, { models?: Record<string, unknown> }>>(content)
|
||||
|
||||
const providers = Object.keys(data)
|
||||
const cacheProviders = Object.keys(data)
|
||||
let modelCount = 0
|
||||
for (const providerId of providers) {
|
||||
for (const providerId of cacheProviders) {
|
||||
const models = data[providerId]?.models
|
||||
if (models && typeof models === "object") {
|
||||
modelCount += Object.keys(models).length
|
||||
}
|
||||
}
|
||||
|
||||
return { providers, modelCount, cacheExists: true }
|
||||
// Merge cache providers with custom providers from opencode.json
|
||||
const allProviders = [...new Set([...cacheProviders, ...customProviders])]
|
||||
|
||||
return { providers: allProviders, modelCount, cacheExists: true }
|
||||
} catch {
|
||||
return { providers: [], modelCount: 0, cacheExists: false }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user