From bd198d8550ac0ace785613ed694b3903990748bb Mon Sep 17 00:00:00 2001 From: Ian Maurer Date: Wed, 12 Nov 2025 11:13:24 -0500 Subject: [PATCH] fix(cli): robust suggestions for unknown provider and model-only input; ignore bun build artifacts --- packages/opencode/.gitignore | 1 + packages/opencode/src/provider/provider.ts | 41 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/opencode/.gitignore b/packages/opencode/.gitignore index e057ca61f..a389d213b 100644 --- a/packages/opencode/.gitignore +++ b/packages/opencode/.gitignore @@ -2,3 +2,4 @@ research dist gen app.log +*.bun-build diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 5e3da288c..c177514a3 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -526,6 +526,19 @@ export namespace Provider { if (!provider) { let suggestions: string[] = [] const normalize = (str: string) => str.toLowerCase().replace(/[^a-z0-9]/g, "") + const levenshtein = (a: string, b: string) => { + const m = a.length, n = b.length + const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0)) + for (let i = 0; i <= m; i++) dp[i][0] = i + for (let j = 0; j <= n; j++) dp[0][j] = j + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1 + dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost) + } + } + return dp[m][n] + } if (!modelID || modelID.trim() === "") { // Treat single-token input as an unqualified model; search across all providers' models. const q = normalize(providerID) @@ -538,11 +551,31 @@ export namespace Provider { const byNorm = fuzzysort.go(q, entries as any, { limit: 5, key: "norm" }).map((r: any) => r.obj.combo) const combos = entries.map((e) => e.combo) const byRaw = fuzzysort.go(providerID, combos, { limit: 5 }).map((r) => r.target) - suggestions = Array.from(new Set([...byNorm, ...byRaw])).slice(0, 3) + let merged = Array.from(new Set([...byNorm, ...byRaw])) + if (merged.length === 0) { + // fallback to edit distance on normalized mid + const scored = entries + .map((e) => ({ combo: e.combo, d: levenshtein(q, e.norm) })) + .sort((a, b) => a.d - b.d) + .slice(0, 3) + .map((x) => x.combo) + merged = scored + } + suggestions = merged.slice(0, 3) } else { - const providerSuggestions = fuzzysort - .go(providerID, Object.keys(s.providers), { limit: 3 }) - .map((r) => r.target + "/" + modelID) + const pcands = Object.keys(s.providers) + const corpus = pcands.map((raw) => ({ raw, norm: normalize(raw) })) + const q = normalize(providerID) + const hits = fuzzysort.go(q, corpus as any, { limit: 5, key: "norm" }) + let ranked = hits.map((r: any) => r.obj.raw) + if (ranked.length === 0) { + ranked = pcands + .map((p) => ({ p, d: levenshtein(q, normalize(p)) })) + .sort((a, b) => a.d - b.d) + .slice(0, 3) + .map((x) => x.p) + } + const providerSuggestions = ranked.map((r) => r + "/" + modelID) suggestions = providerSuggestions } throw new ModelNotFoundError({ providerID, modelID, suggestions })