mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-18 23:52:42 +00:00
fix(provider): restore model suggestions (#27372)
This commit is contained in:
parent
22a5e6cc50
commit
20cec91550
2 changed files with 73 additions and 7 deletions
|
|
@ -979,6 +979,7 @@ export interface Interface {
|
|||
interface State {
|
||||
models: Map<string, LanguageModelV3>
|
||||
providers: Record<ProviderID, Info>
|
||||
catalog: Record<ProviderID, Info>
|
||||
sdk: Map<string, BundledSDK>
|
||||
modelLoaders: Record<string, CustomModelLoader>
|
||||
varsLoaders: Record<string, CustomVarsLoader>
|
||||
|
|
@ -1104,6 +1105,38 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
|||
}
|
||||
}
|
||||
|
||||
function suggestionModelIDs(provider: Info | undefined) {
|
||||
if (!provider) return []
|
||||
return Object.keys(provider.models).filter((id) => {
|
||||
const model = provider.models[id]
|
||||
if (model.status === "deprecated") return false
|
||||
if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function modelSuggestions(provider: Info | undefined, modelID: ModelID) {
|
||||
const available = suggestionModelIDs(provider)
|
||||
const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
|
||||
if (fuzzy.length) return fuzzy
|
||||
const query = modelID
|
||||
.toLowerCase()
|
||||
.split(/[^a-z0-9]+/)
|
||||
.filter((part) => part.length > 1)
|
||||
return sortBy(
|
||||
available
|
||||
.map((id) => ({
|
||||
id,
|
||||
score: query.filter((part) => id.toLowerCase().includes(part)).length,
|
||||
}))
|
||||
.filter((item) => item.score > 0),
|
||||
[(item) => item.score, "desc"],
|
||||
[(item) => item.id, "asc"],
|
||||
)
|
||||
.slice(0, 3)
|
||||
.map((item) => item.id)
|
||||
}
|
||||
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -1120,7 +1153,8 @@ const layer = Layer.effect(
|
|||
const bridge = yield* EffectBridge.make()
|
||||
const cfg = yield* config.get()
|
||||
const modelsDev = yield* modelsDevSvc.get()
|
||||
const database = mapValues(modelsDev, fromModelsDevProvider)
|
||||
const catalog = mapValues(modelsDev, fromModelsDevProvider)
|
||||
const database = mapValues(catalog, toPublicInfo)
|
||||
|
||||
const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
|
||||
const languages = new Map<string, LanguageModelV3>()
|
||||
|
|
@ -1437,6 +1471,7 @@ const layer = Layer.effect(
|
|||
return {
|
||||
models: languages,
|
||||
providers,
|
||||
catalog,
|
||||
sdk,
|
||||
modelLoaders,
|
||||
varsLoaders,
|
||||
|
|
@ -1597,16 +1632,20 @@ const layer = Layer.effect(
|
|||
const s = yield* InstanceState.get(state)
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) {
|
||||
const available = Object.keys(s.providers)
|
||||
const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
|
||||
const catalogProvider = s.catalog[providerID]
|
||||
const suggestions = catalogProvider
|
||||
? modelSuggestions(catalogProvider, modelID)
|
||||
: fuzzysort
|
||||
.go(providerID, Object.keys({ ...s.catalog, ...s.providers }), { limit: 3, threshold: -10000 })
|
||||
.map((m) => m.target)
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
|
||||
const info = provider.models[modelID]
|
||||
if (!info) {
|
||||
const available = Object.keys(provider.models)
|
||||
const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
|
||||
const current = modelSuggestions(provider, modelID)
|
||||
const suggestions = current.length ? current : modelSuggestions(s.catalog[providerID], modelID)
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
return info
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { testEffect } from "../lib/effect"
|
|||
|
||||
const env = makeRuntime(Env.Service, Env.defaultLayer)
|
||||
const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v))
|
||||
const remove = (k: string) => env.runSync((svc) => svc.remove(k))
|
||||
|
||||
async function run<A, E>(fn: (provider: Provider.Interface) => Effect.Effect<A, E, never>) {
|
||||
return AppRuntime.runPromise(
|
||||
|
|
@ -1604,6 +1605,32 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
|
|||
})
|
||||
})
|
||||
|
||||
test("ModelNotFoundError suggests catalog models for unloaded providers", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await WithInstance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
remove("OPENCODE_API_KEY")
|
||||
try {
|
||||
await getModel(ProviderID.opencode, ModelID.make("claude-haiku-fake-model"))
|
||||
throw new Error("expected model lookup to fail")
|
||||
} catch (e) {
|
||||
if (!Provider.ModelNotFoundError.isInstance(e)) throw e
|
||||
expect(e.data.suggestions).toContain("claude-haiku-4-5")
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("getProvider returns undefined for nonexistent provider", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue