From c0a7b6a510758645c5267734b984f4885a605b67 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 23 Apr 2026 16:03:42 -0700 Subject: [PATCH] fix(plugins): align provider auth metadata --- CHANGELOG.md | 1 + extensions/anthropic/openclaw.plugin.json | 11 ++++++ extensions/comfy/index.test.ts | 41 +++++++++++++++++++++++ extensions/comfy/index.ts | 23 ++++++++++++- extensions/comfy/openclaw.plugin.json | 17 ++++++++++ extensions/moonshot/index.test.ts | 18 ++++++++++ extensions/moonshot/openclaw.plugin.json | 2 +- extensions/nvidia/index.test.ts | 41 +++++++++++++++++++++++ extensions/nvidia/index.ts | 19 ++++++++++- extensions/nvidia/openclaw.plugin.json | 15 +++++++++ 10 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 extensions/comfy/index.test.ts create mode 100644 extensions/nvidia/index.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c3655ab577b..e72bbe53426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai - Providers/Google: honor the private-network SSRF opt-in for Gemini image generation requests, so trusted proxy setups that resolve Google API hosts to private addresses can use `image_generate`. Fixes #67216. - Agents/transport: stop embedded runs from lowering the process-wide undici stream timeouts, so slow Gemini image generation and other long-running provider requests no longer inherit short run-attempt headers timeouts. Fixes #70423. Thanks @giangthb. - Providers/OpenRouter: send image-understanding prompts as user text before image parts, restoring non-empty vision responses for OpenRouter multimodal models. Fixes #70410. +- Plugins/providers: mirror runtime auth choices in bundled provider manifests and detect `KIMI_API_KEY` for Moonshot/Kimi web search before plugin runtime loads. Thanks @vincentkoc. - Memory/QMD: recreate stale managed QMD collections when startup repair finds the collection name already exists, so root memory narrows back to `MEMORY.md` instead of staying on broad workspace markdown indexing. - Agents/OpenAI: surface selected-model capacity failures from PI, Codex, and auto-reply harness paths with a model-switch hint instead of the generic empty-response error. Thanks @vincentkoc. - Providers/OpenAI: route `openai/gpt-image-2` through configured Codex OAuth directly when an `openai-codex` profile is active, instead of probing `OPENAI_API_KEY` first. diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index 4e3e959367a..27ba950e472 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -23,6 +23,17 @@ "groupLabel": "Anthropic", "groupHint": "Claude CLI + API key" }, + { + "provider": "anthropic", + "method": "setup-token", + "choiceId": "setup-token", + "choiceLabel": "Anthropic setup-token", + "choiceHint": "Manual token path", + "assistantPriority": 40, + "groupId": "anthropic", + "groupLabel": "Anthropic", + "groupHint": "Claude CLI + API key + token" + }, { "provider": "anthropic", "method": "api-key", diff --git a/extensions/comfy/index.test.ts b/extensions/comfy/index.test.ts new file mode 100644 index 00000000000..e0e11f18613 --- /dev/null +++ b/extensions/comfy/index.test.ts @@ -0,0 +1,41 @@ +import fs from "node:fs"; +import { describe, expect, it } from "vitest"; +import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js"; +import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import plugin from "./index.js"; + +type ComfyManifest = { + providerAuthChoices?: Array<{ choiceId?: string; method?: string; provider?: string }>; +}; + +function readManifest(): ComfyManifest { + return JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), + ) as ComfyManifest; +} + +describe("comfy provider plugin", () => { + it("registers cloud API-key auth metadata", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect(provider.id).toBe("comfy"); + expect(provider.envVars).toEqual(["COMFY_API_KEY", "COMFY_CLOUD_API_KEY"]); + expect(provider.auth?.map((method) => method.id)).toEqual(["cloud-api-key"]); + + const choice = resolveProviderPluginChoice({ + providers: [provider], + choice: "comfy-cloud-api-key", + }); + expect(choice?.provider.id).toBe("comfy"); + expect(choice?.method.id).toBe("cloud-api-key"); + expect(readManifest().providerAuthChoices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + provider: "comfy", + method: "cloud-api-key", + choiceId: "comfy-cloud-api-key", + }), + ]), + ); + }); +}); diff --git a/extensions/comfy/index.ts b/extensions/comfy/index.ts index 0157c4b3dcd..ee9e49c1d58 100644 --- a/extensions/comfy/index.ts +++ b/extensions/comfy/index.ts @@ -1,4 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { buildComfyImageGenerationProvider } from "./image-generation-provider.js"; import { buildComfyMusicGenerationProvider } from "./music-generation-provider.js"; import { buildComfyVideoGenerationProvider } from "./video-generation-provider.js"; @@ -15,7 +16,27 @@ export default definePluginEntry({ label: "ComfyUI", docsPath: "/providers/comfy", envVars: ["COMFY_API_KEY", "COMFY_CLOUD_API_KEY"], - auth: [], + auth: [ + createProviderApiKeyAuthMethod({ + providerId: PROVIDER_ID, + methodId: "cloud-api-key", + label: "Comfy Cloud API key", + hint: "API key for Comfy Cloud workflow runs", + optionKey: "comfyApiKey", + flagName: "--comfy-api-key", + envVar: "COMFY_API_KEY", + promptMessage: "Enter Comfy Cloud API key", + wizard: { + choiceId: "comfy-cloud-api-key", + choiceLabel: "Comfy Cloud API key", + choiceHint: "Required for cloud workflows", + groupId: "comfy", + groupLabel: "ComfyUI", + groupHint: "Local or cloud workflows", + onboardingScopes: ["image-generation"], + }, + }), + ], }); api.registerImageGenerationProvider(buildComfyImageGenerationProvider()); api.registerMusicGenerationProvider(buildComfyMusicGenerationProvider()); diff --git a/extensions/comfy/openclaw.plugin.json b/extensions/comfy/openclaw.plugin.json index aa0df4fe296..281d9912f9f 100644 --- a/extensions/comfy/openclaw.plugin.json +++ b/extensions/comfy/openclaw.plugin.json @@ -5,6 +5,23 @@ "providerAuthEnvVars": { "comfy": ["COMFY_API_KEY", "COMFY_CLOUD_API_KEY"] }, + "providerAuthChoices": [ + { + "provider": "comfy", + "method": "cloud-api-key", + "choiceId": "comfy-cloud-api-key", + "choiceLabel": "Comfy Cloud API key", + "choiceHint": "Required for cloud workflows", + "groupId": "comfy", + "groupLabel": "ComfyUI", + "groupHint": "Local or cloud workflows", + "optionKey": "comfyApiKey", + "cliFlag": "--comfy-api-key", + "cliOption": "--comfy-api-key ", + "cliDescription": "Comfy Cloud API key", + "onboardingScopes": ["image-generation"] + } + ], "contracts": { "imageGenerationProviders": ["comfy"], "musicGenerationProviders": ["comfy"], diff --git a/extensions/moonshot/index.test.ts b/extensions/moonshot/index.test.ts index 6fe4adcf4a7..a5527a47938 100644 --- a/extensions/moonshot/index.test.ts +++ b/extensions/moonshot/index.test.ts @@ -1,10 +1,28 @@ +import fs from "node:fs"; import type { Context, Model } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; import { createCapturedThinkingConfigStream } from "../../test/helpers/plugins/stream-hooks.js"; import plugin from "./index.js"; +import { createKimiWebSearchProvider } from "./src/kimi-web-search-provider.js"; + +type MoonshotManifest = { + providerAuthEnvVars?: Record; +}; + +function readManifest(): MoonshotManifest { + return JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), + ) as MoonshotManifest; +} describe("moonshot provider plugin", () => { + it("mirrors Kimi web-search env credentials in manifest metadata", () => { + const manifestEnvVars = readManifest().providerAuthEnvVars?.moonshot ?? []; + + expect(manifestEnvVars).toEqual(expect.arrayContaining(createKimiWebSearchProvider().envVars)); + }); + it("owns replay policy for OpenAI-compatible Moonshot transports without mangling native Kimi tool_call IDs", async () => { const provider = await registerSingleProviderPlugin(plugin); diff --git a/extensions/moonshot/openclaw.plugin.json b/extensions/moonshot/openclaw.plugin.json index 827af566e9d..db2af85de0e 100644 --- a/extensions/moonshot/openclaw.plugin.json +++ b/extensions/moonshot/openclaw.plugin.json @@ -3,7 +3,7 @@ "enabledByDefault": true, "providers": ["moonshot"], "providerAuthEnvVars": { - "moonshot": ["MOONSHOT_API_KEY"] + "moonshot": ["MOONSHOT_API_KEY", "KIMI_API_KEY"] }, "providerAuthChoices": [ { diff --git a/extensions/nvidia/index.test.ts b/extensions/nvidia/index.test.ts new file mode 100644 index 00000000000..fc2874a7357 --- /dev/null +++ b/extensions/nvidia/index.test.ts @@ -0,0 +1,41 @@ +import fs from "node:fs"; +import { describe, expect, it } from "vitest"; +import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js"; +import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import plugin from "./index.js"; + +type NvidiaManifest = { + providerAuthChoices?: Array<{ choiceId?: string; method?: string; provider?: string }>; +}; + +function readManifest(): NvidiaManifest { + return JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), + ) as NvidiaManifest; +} + +describe("nvidia provider plugin", () => { + it("registers API-key auth metadata", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect(provider.id).toBe("nvidia"); + expect(provider.envVars).toEqual(["NVIDIA_API_KEY"]); + expect(provider.auth?.map((method) => method.id)).toEqual(["api-key"]); + + const choice = resolveProviderPluginChoice({ + providers: [provider], + choice: "nvidia-api-key", + }); + expect(choice?.provider.id).toBe("nvidia"); + expect(choice?.method.id).toBe("api-key"); + expect(readManifest().providerAuthChoices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + provider: "nvidia", + method: "api-key", + choiceId: "nvidia-api-key", + }), + ]), + ); + }); +}); diff --git a/extensions/nvidia/index.ts b/extensions/nvidia/index.ts index 7f0cb36a872..a045f12e986 100644 --- a/extensions/nvidia/index.ts +++ b/extensions/nvidia/index.ts @@ -11,7 +11,24 @@ export default defineSingleProviderPluginEntry({ label: "NVIDIA", docsPath: "/providers/nvidia", envVars: ["NVIDIA_API_KEY"], - auth: [], + auth: [ + { + methodId: "api-key", + label: "NVIDIA API key", + hint: "API key", + optionKey: "nvidiaApiKey", + flagName: "--nvidia-api-key", + envVar: "NVIDIA_API_KEY", + promptMessage: "Enter NVIDIA API key", + wizard: { + choiceId: "nvidia-api-key", + choiceLabel: "NVIDIA API key", + groupId: "nvidia", + groupLabel: "NVIDIA", + groupHint: "API key", + }, + }, + ], catalog: { buildProvider: buildNvidiaProvider, }, diff --git a/extensions/nvidia/openclaw.plugin.json b/extensions/nvidia/openclaw.plugin.json index f08e51443b0..8f53945c976 100644 --- a/extensions/nvidia/openclaw.plugin.json +++ b/extensions/nvidia/openclaw.plugin.json @@ -5,6 +5,21 @@ "providerAuthEnvVars": { "nvidia": ["NVIDIA_API_KEY"] }, + "providerAuthChoices": [ + { + "provider": "nvidia", + "method": "api-key", + "choiceId": "nvidia-api-key", + "choiceLabel": "NVIDIA API key", + "groupId": "nvidia", + "groupLabel": "NVIDIA", + "groupHint": "API key", + "optionKey": "nvidiaApiKey", + "cliFlag": "--nvidia-api-key", + "cliOption": "--nvidia-api-key ", + "cliDescription": "NVIDIA API key" + } + ], "configSchema": { "type": "object", "additionalProperties": false,