diff --git a/docs/docs_map.md b/docs/docs_map.md index 352c9fddd966..488132c0b0dd 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -8585,7 +8585,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - Headings: - H2: Getting started - H2: Config example - - H2: Built-in catalog + - H2: Catalog - H2: Advanced configuration - H2: Related diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 367d7ae26c4f..420f4deb277c 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -352,7 +352,7 @@ That stages grounded durable candidates into the short-term dreaming store while - If you have added `models.providers.opencode`, `opencode-zen`, or `opencode-go` manually, it overrides the built-in OpenCode catalog from `openclaw/plugin-sdk/llm`. That can force models onto the wrong API or zero out costs. Doctor warns so you can remove the override and restore per-model API routing + costs. + If you have added `models.providers.opencode`, `opencode-zen`, or `opencode-go` manually while the matching official external plugin is installed and enabled, it overrides that plugin-provided catalog. That can force models onto the wrong API or zero out costs. Doctor warns so you can remove the override and restore per-model API routing + costs. Without the matching plugin, the entry remains a valid standalone custom provider. If your browser config still points at the removed Chrome extension path, doctor normalizes it to the current host-local Chrome MCP attach model (`browser.profiles.*.driver: "extension"` → `"existing-session"`; `browser.relayBindHost` removed). diff --git a/docs/help/testing-live.md b/docs/help/testing-live.md index 67e51602457f..3fe3e21cc356 100644 --- a/docs/help/testing-live.md +++ b/docs/help/testing-live.md @@ -612,7 +612,7 @@ If you have keys enabled, you can also test via: More providers you can include in the live matrix (if you have creds/config): -- Built-in: `anthropic`, `cerebras`, `github-copilot`, `google`, `google-antigravity`, `google-gemini-cli`, `google-vertex`, `groq`, `mistral`, `openai`, `openrouter`, `opencode`, `opencode-go`, `xai`, `zai` +- First-party provider plugins: `anthropic`, `cerebras`, `github-copilot`, `google`, `google-antigravity`, `google-gemini-cli`, `google-vertex`, `groq`, `mistral`, `openai`, `openrouter`, `opencode`, `opencode-go`, `xai`, `zai` - Via `models.providers` (custom endpoints): `minimax` (cloud/API), plus any OpenAI/Anthropic-compatible proxy (LM Studio, vLLM, LiteLLM, etc.) diff --git a/docs/plugins/plugin-inventory.md b/docs/plugins/plugin-inventory.md index df2ecc079383..53beb0a3f99f 100644 --- a/docs/plugins/plugin-inventory.md +++ b/docs/plugins/plugin-inventory.md @@ -51,7 +51,7 @@ Each entry lists the package, distribution route, and description. ## Core npm package -56 plugins +55 plugins - **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint. @@ -133,8 +133,6 @@ Each entry lists the package, distribution route, and description. - **[openai](/plugins/reference/openai)** (`@openclaw/openai-provider`) - included in OpenClaw. Adds OpenAI model provider support to OpenClaw. -- **[opencode-go](/plugins/reference/opencode-go)** (`@openclaw/opencode-go-provider`) - included in OpenClaw. Adds OpenCode Go model provider support to OpenClaw. - - **[openrouter](/plugins/reference/openrouter)** (`@openclaw/openrouter-provider`) - included in OpenClaw. Adds OpenRouter model provider support to OpenClaw. - **[policy](/plugins/reference/policy)** (`@openclaw/policy`) - included in OpenClaw. Adds policy-backed doctor checks for workspace conformance. @@ -167,7 +165,7 @@ Each entry lists the package, distribution route, and description. ## Official external packages -89 plugins +90 plugins - **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management. @@ -281,6 +279,8 @@ Each entry lists the package, distribution route, and description. - **[opencode](/plugins/reference/opencode)** (`@openclaw/opencode-provider`) - npm; ClawHub: `clawhub:@openclaw/opencode-provider`. Adds OpenCode model provider support to OpenClaw. +- **[opencode-go](/plugins/reference/opencode-go)** (`@openclaw/opencode-go-provider`) - npm; ClawHub: `clawhub:@openclaw/opencode-go-provider`. Adds OpenCode Go model provider support to OpenClaw. + - **[openshell](/plugins/reference/openshell)** (`@openclaw/openshell-sandbox`) - npm; ClawHub. OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution. - **[parallel](/tools/parallel-search)** (`@openclaw/parallel-plugin`) - npm; ClawHub: `clawhub:@openclaw/parallel-plugin`. Adds web search provider support. diff --git a/docs/plugins/reference/opencode-go.md b/docs/plugins/reference/opencode-go.md index 37a5c4453171..5e85c69c8237 100644 --- a/docs/plugins/reference/opencode-go.md +++ b/docs/plugins/reference/opencode-go.md @@ -12,7 +12,7 @@ Adds OpenCode Go model provider support to OpenClaw. ## Distribution - Package: `@openclaw/opencode-go-provider` -- Install route: included in OpenClaw +- Install route: npm; ClawHub: `clawhub:@openclaw/opencode-go-provider` ## Surface diff --git a/docs/providers/opencode-go.md b/docs/providers/opencode-go.md index 25c678b7bfdc..f81530153b37 100644 --- a/docs/providers/opencode-go.md +++ b/docs/providers/opencode-go.md @@ -9,16 +9,25 @@ title: "OpenCode Go" OpenCode Go is the Go catalog inside [OpenCode](/providers/opencode). It shares the `OPENCODE_API_KEY` credential with the Zen catalog, but keeps its own runtime provider id (`opencode-go`) so upstream per-model routing stays -correct. +correct. OpenClaw provides it as the official external +`@openclaw/opencode-go-provider` plugin. | Property | Value | | ---------------- | -------------------------------------------------- | | Runtime provider | `opencode-go` | +| Plugin | `@openclaw/opencode-go-provider` | | Auth | `OPENCODE_API_KEY` (alias: `OPENCODE_ZEN_API_KEY`) | | Parent setup | [OpenCode](/providers/opencode) | ## Getting started +Install the official plugin and restart the Gateway: + +```bash +openclaw plugins install @openclaw/opencode-go-provider +openclaw gateway restart +``` + @@ -65,10 +74,10 @@ correct. } ``` -## Built-in catalog +## Catalog Run `openclaw models list --provider opencode-go` for the current model list. -Bundled rows: +Current rows: | Model ref | Name | Context | Max output | Image input | | ------------------------------- | ----------------- | --------- | ---------- | ----------- | diff --git a/docs/providers/opencode.md b/docs/providers/opencode.md index 4c2fc132d78c..c7d0ae7836ae 100644 --- a/docs/providers/opencode.md +++ b/docs/providers/opencode.md @@ -55,6 +55,12 @@ one OpenCode setup. **Best for:** the OpenCode-hosted Kimi, GLM, MiniMax, Qwen, and DeepSeek lineup. + + ```bash + openclaw plugins install @openclaw/opencode-go-provider + openclaw gateway restart + ``` + ```bash openclaw onboard --auth-choice opencode-go diff --git a/extensions/opencode-go/README.md b/extensions/opencode-go/README.md new file mode 100644 index 000000000000..ac22575a8b6c --- /dev/null +++ b/extensions/opencode-go/README.md @@ -0,0 +1,16 @@ +# OpenClaw OpenCode Go Provider + +Official OpenClaw provider plugin for the OpenCode Go model catalog. + +Install from OpenClaw: + +```bash +openclaw plugins install @openclaw/opencode-go-provider +openclaw gateway restart +``` + +Configure `OPENCODE_API_KEY` or `OPENCODE_ZEN_API_KEY`, then select an +`opencode-go/...` model. + +See for setup and the current +model catalog. diff --git a/extensions/opencode-go/index.ts b/extensions/opencode-go/index.ts index bca0731cb50d..994a8c16952a 100644 --- a/extensions/opencode-go/index.ts +++ b/extensions/opencode-go/index.ts @@ -41,7 +41,7 @@ function resolveOpencodeGoCatalogAuth( export default defineSingleProviderPluginEntry({ id: PROVIDER_ID, name: "OpenCode Go Provider", - description: "Bundled OpenCode Go provider plugin", + description: "Official OpenCode Go provider plugin", manifest, provider: { label: "OpenCode Go", diff --git a/extensions/opencode-go/package.json b/extensions/opencode-go/package.json index b9bc0b455596..ce82d8a23c7c 100644 --- a/extensions/opencode-go/package.json +++ b/extensions/opencode-go/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/opencode-go-provider", "version": "2026.7.2", - "private": true, "description": "OpenClaw OpenCode Go provider plugin", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "devDependencies": { "@openclaw/plugin-sdk": "workspace:*" @@ -10,6 +13,23 @@ "openclaw": { "extensions": [ "./index.ts" - ] + ], + "install": { + "clawhubSpec": "clawhub:@openclaw/opencode-go-provider", + "npmSpec": "@openclaw/opencode-go-provider", + "defaultChoice": "npm", + "minHostVersion": ">=2026.7.2" + }, + "compat": { + "pluginApi": ">=2026.7.2" + }, + "build": { + "openclawVersion": "2026.7.2", + "bundledDist": false + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true + } } } diff --git a/package.json b/package.json index 15a31184f05c..ef91f544801c 100644 --- a/package.json +++ b/package.json @@ -295,6 +295,7 @@ "!dist/extensions/nostr/**", "!dist/extensions/novita/**", "!dist/extensions/opencode/**", + "!dist/extensions/opencode-go/**", "!dist/extensions/parallel/**", "!dist/extensions/perplexity/**", "!dist/extensions/qianfan/**", diff --git a/scripts/lib/official-external-provider-catalog.json b/scripts/lib/official-external-provider-catalog.json index 91f21ec83ff6..c3a3566bd5ad 100644 --- a/scripts/lib/official-external-provider-catalog.json +++ b/scripts/lib/official-external-provider-catalog.json @@ -1190,6 +1190,69 @@ } } }, + { + "name": "@openclaw/opencode-go-provider", + "description": "OpenClaw OpenCode Go provider plugin", + "source": "official", + "kind": "provider", + "openclaw": { + "plugin": { + "id": "opencode-go", + "label": "OpenCode Go" + }, + "providerEndpoints": [ + { + "endpointClass": "opencode-native", + "hostSuffixes": [ + "opencode.ai" + ] + } + ], + "providers": [ + { + "id": "opencode-go", + "name": "OpenCode Go", + "docs": "/providers/opencode-go", + "categories": [ + "cloud", + "llm" + ], + "envVars": [ + "OPENCODE_API_KEY", + "OPENCODE_ZEN_API_KEY" + ], + "authChoices": [ + { + "method": "api-key", + "choiceId": "opencode-go", + "choiceLabel": "OpenCode Go catalog", + "groupId": "opencode", + "groupLabel": "OpenCode", + "groupHint": "Shared API key for Zen + Go catalogs", + "optionKey": "opencodeGoApiKey", + "cliFlag": "--opencode-go-api-key", + "cliOption": "--opencode-go-api-key ", + "cliDescription": "OpenCode API key (Go catalog)", + "onboardingScopes": [ + "text-inference" + ] + } + ] + } + ], + "contracts": { + "mediaUnderstandingProviders": [ + "opencode-go" + ] + }, + "install": { + "clawhubSpec": "clawhub:@openclaw/opencode-go-provider", + "npmSpec": "@openclaw/opencode-go-provider", + "defaultChoice": "npm", + "minHostVersion": ">=2026.7.2" + } + } + }, { "name": "@openclaw/pixverse-provider", "description": "OpenClaw PixVerse video provider plugin", diff --git a/src/cli/plugins-location-bridges.test.ts b/src/cli/plugins-location-bridges.test.ts index f7ac26c7988d..0ff68efde751 100644 --- a/src/cli/plugins-location-bridges.test.ts +++ b/src/cli/plugins-location-bridges.test.ts @@ -170,6 +170,7 @@ describe("listPersistedBundledPluginLocationBridges", () => { ["mistral", "@openclaw/mistral-provider", true], ["novita", "@openclaw/novita-provider", true], ["opencode", "@openclaw/opencode-provider", true], + ["opencode-go", "@openclaw/opencode-go-provider", true], ["synthetic", "@openclaw/synthetic-provider", true], ["teams-meetings", "@openclaw/teams-meetings", true], ["volcengine", "@openclaw/volcengine-provider", true], diff --git a/src/commands/doctor-config-analysis.test.ts b/src/commands/doctor-config-analysis.test.ts index d329a47ee578..4bdfba8000a4 100644 --- a/src/commands/doctor-config-analysis.test.ts +++ b/src/commands/doctor-config-analysis.test.ts @@ -6,6 +6,7 @@ import { OpenClawSchema } from "../config/zod-schema.js"; import { formatConfigPath, noteImplicitFallbackClobberWarnings, + noteOpencodeProviderOverrides, noteSandboxOriginProxyWarning, resolveConfigPathTarget, stripUnknownConfigKeys, @@ -23,6 +24,66 @@ function collectImplicitFallbackClobberWarnings(cfg: OpenClawConfig): string[] { } describe("doctor config analysis helpers", () => { + it("describes OpenCode overrides against the plugin-provided catalog", () => { + noteMock.mockClear(); + + noteOpencodeProviderOverrides( + { + models: { + providers: { + opencode: { + baseUrl: "https://opencode.ai/zen/v1", + api: "openai-completions", + models: [], + }, + }, + }, + }, + { opencodePluginActive: true }, + ); + + expect(noteMock).toHaveBeenCalledWith( + expect.stringContaining("plugin-provided OpenCode Zen catalog"), + "OpenCode", + ); + expect(noteMock.mock.calls.at(-1)?.[0]).not.toContain("built-in"); + }); + + it("classifies external OpenCode overrides only while their plugins are active", () => { + noteMock.mockClear(); + + const cfg: OpenClawConfig = { + models: { + providers: { + opencode: { + baseUrl: "https://opencode.ai/zen/v1", + api: "openai-completions", + models: [], + }, + "opencode-go": { + baseUrl: "https://opencode.ai/zen/go/v1", + api: "openai-completions", + models: [], + }, + }, + }, + }; + + noteOpencodeProviderOverrides(cfg); + expect(noteMock).not.toHaveBeenCalled(); + + noteOpencodeProviderOverrides(cfg, { + opencodePluginActive: true, + opencodeGoPluginActive: true, + }); + expect(noteMock).toHaveBeenCalledWith( + expect.stringMatching( + /plugin-provided OpenCode Zen catalog[\s\S]*plugin-provided OpenCode Go catalog/u, + ), + "OpenCode", + ); + }); + it("formats config paths predictably", () => { expect(formatConfigPath([])).toBe(""); expect(formatConfigPath(["channels", "slack", "accounts", 0, "token"])).toBe( diff --git a/src/commands/doctor-config-analysis.ts b/src/commands/doctor-config-analysis.ts index 778946dff05c..be1933866e2d 100644 --- a/src/commands/doctor-config-analysis.ts +++ b/src/commands/doctor-config-analysis.ts @@ -129,21 +129,24 @@ export function stripUnknownConfigKeys(config: OpenClawConfig): { return { config: next, removed }; } -/** Warns when legacy OpenCode provider overrides shadow the built-in catalog. */ -export function noteOpencodeProviderOverrides(cfg: OpenClawConfig): void { +/** Warns when legacy OpenCode overrides shadow an active plugin-provided catalog. */ +export function noteOpencodeProviderOverrides( + cfg: OpenClawConfig, + options: { opencodePluginActive?: boolean; opencodeGoPluginActive?: boolean } = {}, +): void { const providers = cfg.models?.providers; if (!providers) { return; } const overrides: string[] = []; - if (providers.opencode) { + if (options.opencodePluginActive === true && providers.opencode) { overrides.push("opencode"); } - if (providers["opencode-zen"]) { + if (options.opencodePluginActive === true && providers["opencode-zen"]) { overrides.push("opencode-zen"); } - if (providers["opencode-go"]) { + if (options.opencodeGoPluginActive === true && providers["opencode-go"]) { overrides.push("opencode-go"); } if (overrides.length === 0) { @@ -158,7 +161,7 @@ export function noteOpencodeProviderOverrides(cfg: OpenClawConfig): void { ? providerEntry.api : undefined; return [ - `- models.providers.${id} is set; this overrides the built-in ${providerLabel} catalog.`, + `- models.providers.${id} is set; this overrides the plugin-provided ${providerLabel} catalog.`, api ? `- models.providers.${id}.api=${api}` : null, ].filter((line): line is string => Boolean(line)); }); diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index 3bcc5ee20b5f..27046a11e674 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -476,7 +476,23 @@ export async function loadAndMaybeMigrateDoctorConfig(params: { candidate: cfg, }); - noteOpencodeProviderOverrides(cfg); + const configuredOpencodePluginIds = [ + cfg.models?.providers?.opencode || cfg.models?.providers?.["opencode-zen"] + ? "opencode" + : undefined, + cfg.models?.providers?.["opencode-go"] ? "opencode-go" : undefined, + ].filter((pluginId): pluginId is string => pluginId !== undefined); + const activeOpencodePluginIds = + configuredOpencodePluginIds.length > 0 + ? (await import("../plugins/providers.js")).resolveEnabledProviderPluginIds({ + config: cfg, + onlyPluginIds: configuredOpencodePluginIds, + }) + : []; + noteOpencodeProviderOverrides(cfg, { + opencodePluginActive: activeOpencodePluginIds.includes("opencode"), + opencodeGoPluginActive: activeOpencodePluginIds.includes("opencode-go"), + }); noteImplicitFallbackClobberWarnings(cfg); noteSandboxOriginProxyWarning(cfg); diff --git a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts index 9a7eda7d378f..803648b70279 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts @@ -196,7 +196,7 @@ describe("doctor command", () => { } }); - it("warns about opencode provider overrides", async () => { + it("warns about active OpenCode provider overrides", async () => { mockDoctorConfigSnapshot({ config: { models: { diff --git a/src/plugins/official-external-plugin-catalog.test.ts b/src/plugins/official-external-plugin-catalog.test.ts index a82cead9a40f..10d3603033ba 100644 --- a/src/plugins/official-external-plugin-catalog.test.ts +++ b/src/plugins/official-external-plugin-catalog.test.ts @@ -1981,6 +1981,27 @@ describe("official external plugin catalog", () => { ]); }); + it("lists OpenCode Go with its provider and media-understanding contracts", () => { + const opencodeGo = expectCatalogEntry("opencode-go"); + const manifest = getOfficialExternalPluginCatalogManifest(opencodeGo); + + expect(resolveOfficialExternalPluginId(opencodeGo)).toBe("opencode-go"); + expect(resolveOfficialExternalPluginInstall(opencodeGo)).toEqual({ + clawhubSpec: "clawhub:@openclaw/opencode-go-provider", + npmSpec: "@openclaw/opencode-go-provider", + defaultChoice: "npm", + minHostVersion: ">=2026.7.2", + }); + expect(manifest?.providers?.map((provider) => provider.id)).toEqual(["opencode-go"]); + expect(manifest?.contracts?.mediaUnderstandingProviders).toEqual(["opencode-go"]); + expect(manifest?.providerEndpoints).toEqual([ + { + endpointClass: "opencode-native", + hostSuffixes: ["opencode.ai"], + }, + ]); + }); + it("lists Synthetic as an official external provider", () => { const synthetic = expectCatalogEntry("synthetic"); diff --git a/test/scripts/bundled-plugin-build-entries.test.ts b/test/scripts/bundled-plugin-build-entries.test.ts index 4aeb36016349..1c2e6872994b 100644 --- a/test/scripts/bundled-plugin-build-entries.test.ts +++ b/test/scripts/bundled-plugin-build-entries.test.ts @@ -349,6 +349,7 @@ describe("bundled plugin build entries", () => { "mistral", "novita", "opencode", + "opencode-go", "xiaomi", ]) { expect(artifacts).not.toContain(`dist/extensions/${pluginId}/index.js`);