From 1b19c790bfdd68feb8bf83c9b2597c371880cb8a Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Thu, 18 Jun 2026 01:47:38 -0700 Subject: [PATCH] feat: externalize GMI provider plugin (#94350) * feat: externalize gmi provider plugin * fix(plugins): repair gmi alias installs --------- Co-authored-by: Vincent Koc --- docs/concepts/model-providers.md | 1 - docs/plugins/plugin-inventory.md | 8 ++-- docs/plugins/reference/gmi.md | 6 +-- docs/providers/gmi.md | 17 +++++--- extensions/gmi/README.md | 14 ++++++ extensions/gmi/index.ts | 2 +- extensions/gmi/npm-shrinkwrap.json | 12 ++++++ extensions/gmi/openclaw.plugin.json | 2 + extensions/gmi/package.json | 26 +++++++++-- package.json | 1 + .../official-external-provider-catalog.json | 43 +++++++++++++++++++ ...release-configured-plugin-installs.test.ts | 42 ++++++++++++++++++ .../release-configured-plugin-installs.ts | 6 ++- .../official-external-plugin-catalog.test.ts | 12 ++++++ .../official-external-plugin-catalog.ts | 1 + src/plugins/provider-install-catalog.test.ts | 37 ++++++++++++++++ src/plugins/provider-install-catalog.ts | 9 ++++ test/plugin-clawhub-release.test.ts | 29 ++++++++++--- 18 files changed, 244 insertions(+), 24 deletions(-) create mode 100644 extensions/gmi/README.md create mode 100644 extensions/gmi/npm-shrinkwrap.json diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 98ad2e29d609..1bdfb5f915e0 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -301,7 +301,6 @@ See [/providers/kilocode](/providers/kilocode) for setup details. | DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` | | DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` | | GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - | -| GMI Cloud | `gmi` | `GMI_API_KEY` | `gmi/google/gemini-3.1-flash-lite` | | Groq | `groq` | `GROQ_API_KEY` | - | | Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` | | Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` | diff --git a/docs/plugins/plugin-inventory.md b/docs/plugins/plugin-inventory.md index 75b65248691e..763ceddb62b9 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 -91 plugins +90 plugins - **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint. @@ -111,8 +111,6 @@ Each entry lists the package, distribution route, and description. - **[github-copilot](/plugins/reference/github-copilot)** (`@openclaw/github-copilot-provider`) - included in OpenClaw. Adds GitHub Copilot model provider support to OpenClaw. -- **[gmi](/plugins/reference/gmi)** (`@openclaw/gmi-provider`) - included in OpenClaw. Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw. - - **[google](/plugins/reference/google)** (`@openclaw/google-plugin`) - included in OpenClaw. Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw. - **[gradium](/plugins/reference/gradium)** (`@openclaw/gradium-speech`) - included in OpenClaw. Adds text-to-speech provider support. @@ -237,7 +235,7 @@ Each entry lists the package, distribution route, and description. ## Official external packages -35 plugins +36 plugins - **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management. @@ -265,6 +263,8 @@ Each entry lists the package, distribution route, and description. - **[feishu](/plugins/reference/feishu)** (`@openclaw/feishu`) - npm; ClawHub. OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng). +- **[gmi](/plugins/reference/gmi)** (`@openclaw/gmi-provider`) - npm; ClawHub: `clawhub:@openclaw/gmi-provider`. OpenClaw GMI Cloud provider plugin. + - **[google-meet](/plugins/reference/google-meet)** (`@openclaw/google-meet`) - npm; ClawHub. OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports. - **[googlechat](/plugins/reference/googlechat)** (`@openclaw/googlechat`) - npm; ClawHub. OpenClaw Google Chat channel plugin for spaces and direct messages. diff --git a/docs/plugins/reference/gmi.md b/docs/plugins/reference/gmi.md index c5fece02dcf1..dffdfe4a2aca 100644 --- a/docs/plugins/reference/gmi.md +++ b/docs/plugins/reference/gmi.md @@ -1,5 +1,5 @@ --- -summary: "Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw." +summary: "OpenClaw GMI Cloud provider plugin." read_when: - You are installing, configuring, or auditing the gmi plugin title: "Gmi plugin" @@ -7,12 +7,12 @@ title: "Gmi plugin" # Gmi plugin -Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw. +OpenClaw GMI Cloud provider plugin. ## Distribution - Package: `@openclaw/gmi-provider` -- Install route: included in OpenClaw +- Install route: npm; ClawHub: `clawhub:@openclaw/gmi-provider` ## Surface diff --git a/docs/providers/gmi.md b/docs/providers/gmi.md index f62fc9f34c19..dd7f25bbda81 100644 --- a/docs/providers/gmi.md +++ b/docs/providers/gmi.md @@ -7,9 +7,9 @@ title: "GMI Cloud" --- GMI Cloud is a hosted inference platform for frontier and open-weight models -behind an OpenAI-compatible API. In OpenClaw it is a bundled model provider, -which means you can select it with the provider id `gmi`, store credentials -through normal model auth, and use model refs like +behind an OpenAI-compatible API. In OpenClaw it is an official external provider +plugin, which means you install it once, select it with the provider id `gmi`, +store credentials through normal model auth, and use model refs like `gmi/google/gemini-3.1-flash-lite`. Use GMI when you want one API key for several hosted model families, including @@ -24,7 +24,14 @@ model availability, billing, rate limits, and any provider-side routing policy. ## Setup -Create an API key in GMI Cloud, then run: +Install the plugin, restart the gateway, then create an API key in GMI Cloud: + +```bash +openclaw plugins install @openclaw/gmi-provider +openclaw gateway restart +``` + +Then run: ```bash openclaw onboard --auth-choice gmi-api-key @@ -60,7 +67,7 @@ GPU control matters more than hosted convenience. ## Models -The bundled catalog seeds commonly available GMI Cloud route ids, including: +The plugin catalog seeds commonly available GMI Cloud route ids, including: - `gmi/zai-org/GLM-5.1-FP8` - `gmi/deepseek-ai/DeepSeek-V3.2` diff --git a/extensions/gmi/README.md b/extensions/gmi/README.md new file mode 100644 index 000000000000..8509f30ad35d --- /dev/null +++ b/extensions/gmi/README.md @@ -0,0 +1,14 @@ +# OpenClaw GMI Cloud Provider + +Official OpenClaw provider plugin for hosted GMI Cloud models through an +OpenAI-compatible API. + +Install from OpenClaw: + +```bash +openclaw plugins install @openclaw/gmi-provider +openclaw gateway restart +``` + +Configure a GMI Cloud API key, then select models with refs such as +`gmi/google/gemini-3.1-flash-lite`. diff --git a/extensions/gmi/index.ts b/extensions/gmi/index.ts index c84f38062cfe..30dbb807ea42 100644 --- a/extensions/gmi/index.ts +++ b/extensions/gmi/index.ts @@ -11,7 +11,7 @@ const PROVIDER_ID = "gmi"; export default defineSingleProviderPluginEntry({ id: PROVIDER_ID, name: "GMI Cloud Provider", - description: "Bundled GMI Cloud provider plugin", + description: "GMI Cloud provider plugin", provider: { label: "GMI Cloud", docsPath: "/providers/gmi", diff --git a/extensions/gmi/npm-shrinkwrap.json b/extensions/gmi/npm-shrinkwrap.json new file mode 100644 index 000000000000..b492fc505344 --- /dev/null +++ b/extensions/gmi/npm-shrinkwrap.json @@ -0,0 +1,12 @@ +{ + "name": "@openclaw/gmi-provider", + "version": "2026.6.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@openclaw/gmi-provider", + "version": "2026.6.8" + } + } +} diff --git a/extensions/gmi/openclaw.plugin.json b/extensions/gmi/openclaw.plugin.json index dca4a21493db..416725b257ef 100644 --- a/extensions/gmi/openclaw.plugin.json +++ b/extensions/gmi/openclaw.plugin.json @@ -1,5 +1,7 @@ { "id": "gmi", + "name": "GMI Cloud", + "description": "OpenClaw GMI Cloud provider plugin.", "activation": { "onStartup": false }, diff --git a/extensions/gmi/package.json b/extensions/gmi/package.json index 043db6b5613c..b9e1a0e18240 100644 --- a/extensions/gmi/package.json +++ b/extensions/gmi/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/gmi-provider", "version": "2026.6.8", - "private": true, - "description": "OpenClaw GMI Cloud provider plugin", + "description": "OpenClaw GMI Cloud 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/gmi-provider", + "npmSpec": "@openclaw/gmi-provider", + "defaultChoice": "npm", + "minHostVersion": ">=2026.6.8" + }, + "compat": { + "pluginApi": ">=2026.6.8" + }, + "build": { + "openclawVersion": "2026.6.8", + "bundledDist": false + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true + } } } diff --git a/package.json b/package.json index e9b33db3b904..a8ab41d12325 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "!dist/extensions/feishu/**", "!dist/extensions/google-meet/**", "!dist/extensions/googlechat/**", + "!dist/extensions/gmi/**", "!dist/extensions/line/**", "!dist/extensions/llama-cpp/**", "!dist/extensions/lobster/**", diff --git a/scripts/lib/official-external-provider-catalog.json b/scripts/lib/official-external-provider-catalog.json index 45a2ddbf8fc6..f9b1740cb981 100644 --- a/scripts/lib/official-external-provider-catalog.json +++ b/scripts/lib/official-external-provider-catalog.json @@ -113,6 +113,49 @@ } } }, + { + "name": "@openclaw/gmi-provider", + "description": "OpenClaw GMI Cloud provider plugin", + "source": "official", + "kind": "provider", + "openclaw": { + "plugin": { + "id": "gmi", + "label": "GMI Cloud" + }, + "providers": [ + { + "id": "gmi", + "aliases": ["gmi-cloud", "gmicloud"], + "name": "GMI Cloud", + "docs": "/providers/gmi", + "categories": ["cloud", "llm"], + "authChoices": [ + { + "method": "api-key", + "choiceId": "gmi-api-key", + "choiceLabel": "GMI Cloud API key", + "choiceHint": "OpenAI-compatible GMI Cloud endpoint.", + "groupId": "gmi", + "groupLabel": "GMI Cloud", + "groupHint": "OpenAI-compatible GMI Cloud endpoint", + "optionKey": "gmiApiKey", + "cliFlag": "--gmi-api-key", + "cliOption": "--gmi-api-key ", + "cliDescription": "GMI Cloud API key", + "onboardingScopes": ["text-inference"] + } + ] + } + ], + "install": { + "clawhubSpec": "clawhub:@openclaw/gmi-provider", + "npmSpec": "@openclaw/gmi-provider", + "defaultChoice": "npm", + "minHostVersion": ">=2026.6.8" + } + } + }, { "name": "@openclaw/pixverse-provider", "description": "OpenClaw PixVerse video provider plugin", diff --git a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts index aa5735e3a915..c3a20ec0d0dc 100644 --- a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts +++ b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts @@ -257,6 +257,48 @@ describe("configured plugin install release step", () => { expect(result.channelIds).toStrictEqual([]); }); + it("collects provider plugins from documented external provider aliases", async () => { + mocks.resolveProviderInstallCatalogEntries.mockReturnValue([ + { + pluginId: "gmi", + providerId: "gmi", + providerAliases: ["gmi-cloud", "gmicloud"], + }, + ]); + + const { collectReleaseConfiguredPluginIds } = + await import("./release-configured-plugin-installs.js"); + const result = collectReleaseConfiguredPluginIds({ + cfg: { + agents: { + defaults: { + model: "gmi-cloud/google/gemini-3.1-flash-lite", + }, + }, + auth: { + profiles: { + gmi: { + provider: "gmi-cloud", + mode: "api_key", + }, + }, + }, + models: { + providers: { + gmicloud: { + baseUrl: "https://api.gmi-serving.com/v1", + models: [], + }, + }, + }, + }, + env: {}, + }); + + expect(result.pluginIds).toEqual(["gmi"]); + expect(result.channelIds).toStrictEqual([]); + }); + it("collects Codex from selectable OpenAI agent models even without integration discovery", async () => { const { collectReleaseConfiguredPluginIds } = await import("./release-configured-plugin-installs.js"); diff --git a/src/commands/doctor/shared/release-configured-plugin-installs.ts b/src/commands/doctor/shared/release-configured-plugin-installs.ts index 76f36a4d370c..0d8489fdaccf 100644 --- a/src/commands/doctor/shared/release-configured-plugin-installs.ts +++ b/src/commands/doctor/shared/release-configured-plugin-installs.ts @@ -192,7 +192,11 @@ function collectProviderPluginIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): env, includeUntrustedWorkspacePlugins: false, })) { - if (configuredProviders.has(entry.providerId.toLowerCase())) { + if ( + [entry.providerId, ...(entry.providerAliases ?? [])].some((providerId) => + configuredProviders.has(providerId.toLowerCase()), + ) + ) { ids.add(entry.pluginId); } } diff --git a/src/plugins/official-external-plugin-catalog.test.ts b/src/plugins/official-external-plugin-catalog.test.ts index 5bbc66f349e1..ebfd5261cc51 100644 --- a/src/plugins/official-external-plugin-catalog.test.ts +++ b/src/plugins/official-external-plugin-catalog.test.ts @@ -55,6 +55,18 @@ describe("official external plugin catalog", () => { ); }); + it("lists GMI Cloud as an official external provider", () => { + const gmi = expectCatalogEntry("gmi"); + + expect(resolveOfficialExternalPluginId(gmi)).toBe("gmi"); + expect(resolveOfficialExternalPluginInstall(gmi)).toEqual({ + clawhubSpec: "clawhub:@openclaw/gmi-provider", + npmSpec: "@openclaw/gmi-provider", + defaultChoice: "npm", + minHostVersion: ">=2026.6.8", + }); + }); + it("allows invalid-config recovery for externalized stock plugins", () => { expect(resolveOfficialExternalPluginInstall(expectCatalogEntry("brave"))).toMatchObject({ npmSpec: "@openclaw/brave-plugin", diff --git a/src/plugins/official-external-plugin-catalog.ts b/src/plugins/official-external-plugin-catalog.ts index a0bd496a719a..a834ef7522a3 100644 --- a/src/plugins/official-external-plugin-catalog.ts +++ b/src/plugins/official-external-plugin-catalog.ts @@ -33,6 +33,7 @@ export type OfficialExternalProviderAuthChoice = { export type OfficialExternalProviderCatalogProvider = { id?: string; + aliases?: readonly string[]; name?: string; docs?: string; categories?: readonly string[]; diff --git a/src/plugins/provider-install-catalog.test.ts b/src/plugins/provider-install-catalog.test.ts index 3d6836cdf9bb..88fd69b2be42 100644 --- a/src/plugins/provider-install-catalog.test.ts +++ b/src/plugins/provider-install-catalog.test.ts @@ -620,6 +620,43 @@ describe("provider install catalog", () => { }); }); + it("preserves official external provider aliases for configured-plugin repair", () => { + listOfficialExternalProviderCatalogEntries.mockReturnValue([ + { + name: "@openclaw/gmi-provider", + source: "official", + kind: "provider", + openclaw: { + plugin: { id: "gmi", label: "GMI Cloud" }, + providers: [ + { + id: "gmi", + aliases: ["gmi-cloud", "gmicloud"], + name: "GMI Cloud", + authChoices: [ + { + method: "api-key", + choiceId: "gmi-api-key", + choiceLabel: "GMI Cloud API key", + }, + ], + }, + ], + install: { + npmSpec: "@openclaw/gmi-provider", + defaultChoice: "npm", + }, + }, + }, + ]); + + expect(resolveProviderInstallCatalogEntry("gmi-api-key")).toMatchObject({ + pluginId: "gmi", + providerId: "gmi", + providerAliases: ["gmi-cloud", "gmicloud"], + }); + }); + it("surfaces provider-index ClawHub install metadata as the preferred source", () => { loadOpenClawProviderIndex.mockReturnValue({ version: 1, diff --git a/src/plugins/provider-install-catalog.ts b/src/plugins/provider-install-catalog.ts index 21fc651e9057..fd297dc6d3e6 100644 --- a/src/plugins/provider-install-catalog.ts +++ b/src/plugins/provider-install-catalog.ts @@ -27,6 +27,7 @@ import { /** Provider setup choice paired with install metadata for the owning plugin. */ export type ProviderInstallCatalogEntry = ProviderAuthChoiceMetadata & { + providerAliases?: string[]; label: string; origin: PluginOrigin; install: PluginPackageInstall; @@ -332,6 +333,13 @@ function resolveOfficialExternalProviderInstallCatalogEntries(params: { if (!providerId || !label) { continue; } + const providerAliases = [ + ...new Set( + (provider.aliases ?? []) + .map((alias) => alias.trim()) + .filter((alias) => alias && alias !== providerId), + ), + ]; for (const choice of provider.authChoices ?? []) { const methodId = choice.method?.trim(); const choiceId = choice.choiceId?.trim(); @@ -342,6 +350,7 @@ function resolveOfficialExternalProviderInstallCatalogEntries(params: { entries.push({ pluginId, providerId, + ...(providerAliases.length > 0 ? { providerAliases } : {}), methodId, choiceId, choiceLabel, diff --git a/test/plugin-clawhub-release.test.ts b/test/plugin-clawhub-release.test.ts index 8260ef28964a..e3fdf917d18f 100644 --- a/test/plugin-clawhub-release.test.ts +++ b/test/plugin-clawhub-release.test.ts @@ -122,10 +122,32 @@ describe("OpenClaw dual-published plugin metadata", () => { { extensionId: "diagnostics-otel", packageName: "@openclaw/diagnostics-otel", + install: { + clawhubSpec: "clawhub:@openclaw/diagnostics-otel", + defaultChoice: "npm", + minHostVersion: ">=2026.4.25", + npmSpec: "@openclaw/diagnostics-otel", + }, }, { extensionId: "diagnostics-prometheus", packageName: "@openclaw/diagnostics-prometheus", + install: { + clawhubSpec: "clawhub:@openclaw/diagnostics-prometheus", + defaultChoice: "npm", + minHostVersion: ">=2026.4.25", + npmSpec: "@openclaw/diagnostics-prometheus", + }, + }, + { + extensionId: "gmi", + packageName: "@openclaw/gmi-provider", + install: { + clawhubSpec: "clawhub:@openclaw/gmi-provider", + defaultChoice: "npm", + minHostVersion: ">=2026.6.8", + npmSpec: "@openclaw/gmi-provider", + }, }, ] as const; @@ -158,12 +180,7 @@ describe("OpenClaw dual-published plugin metadata", () => { }; }; - expect(packageJson.openclaw?.install).toEqual({ - clawhubSpec: `clawhub:${plugin.packageName}`, - defaultChoice: "npm", - minHostVersion: ">=2026.4.25", - npmSpec: plugin.packageName, - }); + expect(packageJson.openclaw?.install).toEqual(plugin.install); expect(packageJson.openclaw?.release).toEqual({ publishToClawHub: true, publishToNpm: true,