mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-09 10:24:11 +00:00
Compare commits
10 Commits
v1.1.45
...
fix/cli-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6a3be31de | ||
|
|
715265de4b | ||
|
|
2048f32491 | ||
|
|
2d7ba43a21 | ||
|
|
bd198d8550 | ||
|
|
cbffbcdd3d | ||
|
|
2be8b2269f | ||
|
|
c1fa257a92 | ||
|
|
7fd81dd93e | ||
|
|
d554e7aaef |
@@ -1,11 +1,24 @@
|
||||
import { ConfigMarkdown } from "@/config/markdown"
|
||||
import { Config } from "../config/config"
|
||||
import { MCP } from "../mcp"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { UI } from "./ui"
|
||||
|
||||
export function FormatError(input: unknown) {
|
||||
if (MCP.Failed.isInstance(input))
|
||||
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
||||
if (Provider.ModelNotFoundError.isInstance(input)) {
|
||||
const { providerID, modelID, suggestions } = input.data
|
||||
return [
|
||||
`Model not found: ${providerID}/${modelID}`,
|
||||
...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
|
||||
`Try: \`opencode models\` to list available models`,
|
||||
`Or check your config (opencode.json) provider/model names`,
|
||||
].join("\n")
|
||||
}
|
||||
if (Provider.InitError.isInstance(input)) {
|
||||
return `Failed to initialize provider "${input.data.providerID}". Check credentials and configuration.`
|
||||
}
|
||||
if (Config.JsonError.isInstance(input)) {
|
||||
return (
|
||||
`Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import z from "zod"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import { Config } from "../config/config"
|
||||
import { mergeDeep, sortBy } from "remeda"
|
||||
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
|
||||
@@ -597,9 +598,78 @@ export namespace Provider {
|
||||
})
|
||||
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) throw new ModelNotFoundError({ providerID, modelID })
|
||||
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<number>(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)
|
||||
const entries: { combo: string; norm: string }[] = []
|
||||
for (const [pid, prov] of Object.entries(s.providers)) {
|
||||
for (const mid of Object.keys(prov.info.models)) {
|
||||
entries.push({ combo: pid + "/" + mid, norm: normalize(mid) })
|
||||
}
|
||||
}
|
||||
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)
|
||||
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 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 })
|
||||
}
|
||||
const info = provider.info.models[modelID]
|
||||
if (!info) throw new ModelNotFoundError({ providerID, modelID })
|
||||
if (!info) {
|
||||
const candidates = Object.keys(provider.info.models)
|
||||
// Normalize punctuation differences like '-' vs '.' by stripping non-alphanumerics
|
||||
const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "")
|
||||
const corpus = candidates.map((raw) => ({ raw, norm: normalize(raw) }))
|
||||
const query = normalize(modelID)
|
||||
const results = fuzzysort.go(query, corpus as any, { limit: 5, key: "norm" })
|
||||
const ranked = results.map((r) => ("obj" in r ? (r as any).obj.raw : (r as any).target)) as string[]
|
||||
const fallback = fuzzysort.go(modelID, candidates, { limit: 5 }).map((r) => r.target)
|
||||
const merged = Array.from(new Set([...ranked, ...fallback]))
|
||||
const suggestions = merged.slice(0, 3).map((m) => providerID + "/" + m)
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
const sdk = await getSDK(provider.info, info)
|
||||
|
||||
try {
|
||||
@@ -700,6 +770,7 @@ export namespace Provider {
|
||||
z.object({
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
suggestions: z.array(z.string()).optional(),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user