feat(plugins): externalize OpenCode Go provider (#117064)

This commit is contained in:
Vincent Koc
2026-08-01 06:31:19 +08:00
committed by GitHub
parent d92d68ac77
commit 61fb29e90a
19 changed files with 240 additions and 22 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -352,7 +352,7 @@ That stages grounded durable candidates into the short-term dreaming store while
</Accordion>
<Accordion title="2b. OpenCode provider overrides">
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.
</Accordion>
<Accordion title="2c. Browser migration and Chrome MCP readiness">
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).
+1 -1
View File
@@ -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.)
<Tip>
+4 -4
View File
@@ -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.
+1 -1
View File
@@ -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
+12 -3
View File
@@ -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
```
<Tabs>
<Tab title="Interactive">
<Steps>
@@ -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 |
| ------------------------------- | ----------------- | --------- | ---------- | ----------- |
+6
View File
@@ -55,6 +55,12 @@ one OpenCode setup.
**Best for:** the OpenCode-hosted Kimi, GLM, MiniMax, Qwen, and DeepSeek lineup.
<Steps>
<Step title="Install the Go catalog plugin">
```bash
openclaw plugins install @openclaw/opencode-go-provider
openclaw gateway restart
```
</Step>
<Step title="Run onboarding">
```bash
openclaw onboard --auth-choice opencode-go
+16
View File
@@ -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 <https://docs.openclaw.ai/providers/opencode-go> for setup and the current
model catalog.
+1 -1
View File
@@ -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",
+22 -2
View File
@@ -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
}
}
}
+1
View File
@@ -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/**",
@@ -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 <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",
+1
View File
@@ -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],
@@ -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("<root>");
expect(formatConfigPath(["channels", "slack", "accounts", 0, "token"])).toBe(
+9 -6
View File
@@ -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));
});
+17 -1
View File
@@ -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);
@@ -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: {
@@ -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");
@@ -349,6 +349,7 @@ describe("bundled plugin build entries", () => {
"mistral",
"novita",
"opencode",
"opencode-go",
"xiaomi",
]) {
expect(artifacts).not.toContain(`dist/extensions/${pluginId}/index.js`);