From e018e16898adb241c4ca9f18f1b54034f36c2dea Mon Sep 17 00:00:00 2001 From: Ian Maurer Date: Fri, 21 Nov 2025 03:01:19 -0500 Subject: [PATCH] fix(cli): ensure clean exit on provider/model errors (#4223) Co-authored-by: GitHub Action Co-authored-by: Aiden Cline --- packages/opencode/src/cli/error.ts | 13 +++++++++++++ packages/opencode/src/provider/provider.ts | 18 ++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 464afc710..77f4cec6b 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -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}` : "") diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 7fbc1b88c..038ccc072 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -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,21 @@ export namespace Provider { }) const provider = s.providers[providerID] - if (!provider) throw new ModelNotFoundError({ providerID, modelID }) + if (!provider) { + const availableProviders = Object.keys(s.providers) + const matches = fuzzysort.go(providerID, availableProviders, { limit: 3, threshold: -10000 }) + const suggestions = matches.map((m) => m.target) + throw new ModelNotFoundError({ providerID, modelID, suggestions }) + } + const info = provider.info.models[modelID] - if (!info) throw new ModelNotFoundError({ providerID, modelID }) + if (!info) { + const availableModels = Object.keys(provider.info.models) + const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 }) + const suggestions = matches.map((m) => m.target) + throw new ModelNotFoundError({ providerID, modelID, suggestions }) + } + const sdk = await getSDK(provider.info, info) try { @@ -700,6 +713,7 @@ export namespace Provider { z.object({ providerID: z.string(), modelID: z.string(), + suggestions: z.array(z.string()).optional(), }), )