From f1ae6265e4df4b14335fb1624af68d8b245b297a Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 12 Mar 2026 15:08:09 +0200 Subject: [PATCH 01/20] Implement an integration for provider DeepInfra --- appcast.xml | 2 +- docs/concepts/model-providers.md | 10 + docs/docs.json | 1 + docs/providers/deepinfra.md | 62 ++++ docs/providers/index.md | 1 + extensions/deepinfra/index.ts | 0 extensions/deepinfra/provider-catalog.ts | 34 +++ src/agents/deepinfra-models.test.ts | 277 ++++++++++++++++++ src/agents/deepinfra-models.ts | 154 ++++++++++ src/agents/models-config.e2e-harness.ts | 1 + .../models-config.providers.deepinfra.test.ts | 66 +++++ .../models-config.providers.discovery.ts | 1 + src/agents/models-config.providers.static.ts | 1 + .../pi-embedded-runner/deepinfra.test.ts | 21 ++ src/agents/provider-capabilities.test.ts | 6 + src/agents/transcript-policy.test.ts | 1 + ...onboard-auth.config-core.deepinfra.test.ts | 197 +++++++++++++ src/commands/onboard-auth.ts | 141 +++++++++ src/commands/onboard-provider-auth-flags.ts | 233 +++++++++++++++ src/commands/onboard-types.ts | 3 + src/config/io.ts | 1 + src/plugins/provider-auth-storage.ts | 24 ++ src/providers/deepinfra-shared.ts | 61 ++++ src/secrets/provider-env-vars.ts | 1 + 24 files changed, 1298 insertions(+), 1 deletion(-) create mode 100644 docs/providers/deepinfra.md create mode 100644 extensions/deepinfra/index.ts create mode 100644 extensions/deepinfra/provider-catalog.ts create mode 100644 src/agents/deepinfra-models.test.ts create mode 100644 src/agents/deepinfra-models.ts create mode 100644 src/agents/models-config.providers.deepinfra.test.ts create mode 100644 src/agents/pi-embedded-runner/deepinfra.test.ts create mode 100644 src/commands/onboard-auth.config-core.deepinfra.test.ts create mode 100644 src/commands/onboard-auth.ts create mode 100644 src/commands/onboard-provider-auth-flags.ts create mode 100644 src/providers/deepinfra-shared.ts diff --git a/appcast.xml b/appcast.xml index c1919972b22..bf80ac55964 100644 --- a/appcast.xml +++ b/appcast.xml @@ -245,4 +245,4 @@ - \ No newline at end of file + diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index ebcf7e49290..69ecef519f4 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -252,6 +252,16 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** See [/providers/kilocode](/providers/kilocode) for setup details. +### DeepInfra + +- Provider: `deepinfra` +- Auth: `DEEPINFRA_API_KEY` +- Example model: `deepinfra/openai/gpt-oss-120b` +- CLI: `openclaw onboard --deepinfra-api-key ` +- Base URL: `https://api.deepinfra.com/v1/openai/` + +See [/providers/deepinfra](/providers/deepinfra) for setup details. + ### Other bundled provider plugins - OpenRouter: `openrouter` (`OPENROUTER_API_KEY`) diff --git a/docs/docs.json b/docs/docs.json index a941bec2601..fdc0386c643 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1135,6 +1135,7 @@ "providers/cloudflare-ai-gateway", "providers/claude-max-api-proxy", "providers/deepgram", + "providers/deepinfra", "providers/github-copilot", "providers/google", "providers/groq", diff --git a/docs/providers/deepinfra.md b/docs/providers/deepinfra.md new file mode 100644 index 00000000000..71c63372459 --- /dev/null +++ b/docs/providers/deepinfra.md @@ -0,0 +1,62 @@ +--- +summary: "Use DeepInfra's unified API to access the most popular open source models in OpenClaw" +read_when: + - You want a single API key for the top open source LLMs + - You want to run models via DeepInfra's API in OpenClaw +--- + +# DeepInfra + +DeepInfra provides a **unified API** that routes requests to the most popular open source models behind a single +endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL. + +## Getting an API key + +1. Go to [https://deepinfra.com/](https://deepinfra.com/) +2. Sign in or create an account +3. Navigate to Dashboard / Keys and generate a new API key or use the auto created one + +## CLI setup + +```bash +openclaw onboard --deepinfra-api-key +``` + +Or set the environment variable: + +```bash +export DEEPINFRA_API_KEY="" # pragma: allowlist secret +``` + +## Config snippet + +```json5 +{ + env: { DEEPINFRA_API_KEY: "" }, // pragma: allowlist secret + agents: { + defaults: { + model: { primary: "deepinfra/openai/gpt-oss-120b" }, + }, + }, +} +``` + +## Available models + +OpenClaw dynamically discovers available DeepInfra models at startup. Use +`/models deepinfra` to see the full list of models available with your account. + +Any model available on [DeepInfra.com](https://deepinfra.com/) can be used with the `deepinfra/` prefix: + +``` +deepinfra/minimaxai/minimax-m2.5 +deepinfra/zai-org/glm-5 +deepinfra/moonshotai/kimi-k2.5 +...and many more +``` + +## Notes + +- Model refs are `deepinfra//` (e.g., `deepinfra/qwen/qwen3-max`). +- Default model: `deepinfra/openai/gpt-oss-120b` +- Base URL: `https://api.deepinfra.com/v1/openai/` diff --git a/docs/providers/index.md b/docs/providers/index.md index 93ccdf27635..b031e637ca4 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -29,6 +29,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi - [Amazon Bedrock](/providers/bedrock) - [Anthropic (API + Claude Code CLI)](/providers/anthropic) - [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway) +- [DeepInfra](/providers/deepinfra) - [GLM models](/providers/glm) - [Google (Gemini)](/providers/google) - [Groq (LPU inference)](/providers/groq) diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/extensions/deepinfra/provider-catalog.ts b/extensions/deepinfra/provider-catalog.ts new file mode 100644 index 00000000000..d2ac6db1885 --- /dev/null +++ b/extensions/deepinfra/provider-catalog.ts @@ -0,0 +1,34 @@ +import { + DEEPINFRA_BASE_URL, + DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + DEEPINFRA_DEFAULT_COST, + DEEPINFRA_DEFAULT_MAX_TOKENS, + DEEPINFRA_MODEL_CATALOG, +} from "../providers/deepinfra-shared.ts"; + +import { discoverDeepInfraModels } from "./deepinfra-models.js"; + +export async function buildDeepInfraProviderWithDiscovery(): Promise { + const models = await discoverDeepInfraModels(); + return { + baseUrl: DEEPINFRA_BASE_URL, + api: "openai-completions", + models, + }; +} + +export function buildDeepInfraStaticProvider(): ProviderConfig { + return { + baseUrl: DEEPINFRA_BASE_URL, + api: "openai-completions", + models: DEEPINFRA_MODEL_CATALOG.map((model) => ({ + id: model.id, + name: model.name, + reasoning: model.reasoning, + input: model.input, + cost: DEEPINFRA_DEFAULT_COST, + contextWindow: model.contextWindow ?? DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + maxTokens: model.maxTokens ?? DEEPINFRA_DEFAULT_MAX_TOKENS, + })), + }; +} diff --git a/src/agents/deepinfra-models.test.ts b/src/agents/deepinfra-models.test.ts new file mode 100644 index 00000000000..945d527efde --- /dev/null +++ b/src/agents/deepinfra-models.test.ts @@ -0,0 +1,277 @@ +import { describe, expect, it, vi } from "vitest"; +import { discoverDeepInfraModels, DEEPINFRA_MODELS_URL } from "./deepinfra-models.js"; + +// discoverDeepInfraModels checks for VITEST env and returns static catalog, +// so we need to temporarily unset it to test the fetch path. + +function makeModelEntry(overrides: Record = {}) { + return { + id: "openai/gpt-oss-120b", + object: "model", + owned_by: "deepinfra", + metadata: { + description: "A powerful model", + context_length: 131072, + max_tokens: 131072, + pricing: { + input_tokens: 3.0, + output_tokens: 15.0, + cache_read_tokens: 0.3, + }, + tags: ["vision", "reasoning_effort", "prompt_cache"], + }, + ...overrides, + }; +} + +function makeTextOnlyEntry(overrides: Record = {}) { + return makeModelEntry({ + id: "minimaxai/minimax-m2.5", + metadata: { + description: "Text only model", + context_length: 196608, + max_tokens: 196608, + pricing: { + input_tokens: 1.0, + output_tokens: 2.0, + }, + tags: [], + }, + ...overrides, + }); +} + +async function withFetchPathTest( + mockFetch: ReturnType, + runAssertions: () => Promise, +) { + const origNodeEnv = process.env.NODE_ENV; + const origVitest = process.env.VITEST; + delete process.env.NODE_ENV; + delete process.env.VITEST; + + vi.stubGlobal("fetch", mockFetch); + + try { + await runAssertions(); + } finally { + if (origNodeEnv === undefined) { + delete process.env.NODE_ENV; + } else { + process.env.NODE_ENV = origNodeEnv; + } + if (origVitest === undefined) { + delete process.env.VITEST; + } else { + process.env.VITEST = origVitest; + } + vi.unstubAllGlobals(); + } +} + +describe("discoverDeepInfraModels", () => { + it("returns static catalog in test environment", async () => { + const models = await discoverDeepInfraModels(); + expect(models.length).toBeGreaterThan(0); + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + + it("static catalog has correct defaults for default model", async () => { + const models = await discoverDeepInfraModels(); + const defaultModel = models.find((m) => m.id === "openai/gpt-oss-120b"); + expect(defaultModel).toBeDefined(); + expect(defaultModel?.name).toBe("gpt-oss-120b"); + expect(defaultModel?.reasoning).toBe(true); + expect(defaultModel?.input).toEqual(["text", "image"]); + expect(defaultModel?.contextWindow).toBe(131072); + expect(defaultModel?.maxTokens).toBe(131072); + expect(defaultModel?.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }); + }); +}); + +describe("discoverDeepInfraModels (fetch path)", () => { + it("fetches from the correct URL with Accept header", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeModelEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + await discoverDeepInfraModels(); + expect(mockFetch).toHaveBeenCalledWith( + DEEPINFRA_MODELS_URL, + expect.objectContaining({ + headers: { Accept: "application/json" }, + }), + ); + }); + }); + + it("parses model pricing correctly", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeModelEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const model = models.find((m) => m.id === "openai/gpt-oss-120b"); + expect(model).toBeDefined(); + expect(model?.cost.input).toBeCloseTo(3.0); + expect(model?.cost.output).toBeCloseTo(15.0); + expect(model?.cost.cacheRead).toBeCloseTo(0.3); + expect(model?.cost.cacheWrite).toBe(0); + }); + }); + + it("detects vision models with image modality", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeModelEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const model = models.find((m) => m.id === "openai/gpt-oss-120b"); + expect(model?.input).toEqual(["text", "image"]); + }); + }); + + it("detects text-only models without image modality", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeTextOnlyEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const model = models.find((m) => m.id === "minimaxai/minimax-m2.5"); + expect(model?.input).toEqual(["text"]); + }); + }); + + it("detects reasoning models via reasoning_effort tag", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeModelEntry(), makeTextOnlyEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.find((m) => m.id === "openai/gpt-oss-120b")?.reasoning).toBe(true); + expect(models.find((m) => m.id === "minimaxai/minimax-m2.5")?.reasoning).toBe(false); + }); + }); + + it("uses defaults when context_length and max_tokens are missing", async () => { + const entryNoLimits = makeModelEntry({ + id: "some/model", + metadata: { + pricing: { input_tokens: 1, output_tokens: 2 }, + tags: [], + }, + }); + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [entryNoLimits] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const model = models.find((m) => m.id === "some/model"); + expect(model?.contextWindow).toBe(128000); + expect(model?.maxTokens).toBe(8192); + }); + }); + + it("uses zero cost when pricing fields are missing", async () => { + const entryNoPricing = makeModelEntry({ + id: "some/free-model", + metadata: { + context_length: 32000, + max_tokens: 4096, + tags: [], + }, + }); + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [entryNoPricing] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const model = models.find((m) => m.id === "some/free-model"); + expect(model?.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }); + }); + }); + + it("skips models with null metadata (embeddings, image-gen, etc.)", async () => { + const embeddingEntry = { id: "BAAI/bge-m3", object: "model", metadata: null }; + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [embeddingEntry, makeModelEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.some((m) => m.id === "BAAI/bge-m3")).toBe(false); + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + }); + + it("deduplicates models with the same id", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [makeModelEntry(), makeModelEntry()] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + const matches = models.filter((m) => m.id === "openai/gpt-oss-120b"); + expect(matches.length).toBe(1); + }); + }); + + it("falls back to static catalog on network error", async () => { + const mockFetch = vi.fn().mockRejectedValue(new Error("network error")); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.length).toBeGreaterThan(0); + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + }); + + it("falls back to static catalog on HTTP error", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.length).toBeGreaterThan(0); + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + }); + + it("falls back to static catalog when response has empty data array", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [] }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.length).toBeGreaterThan(0); + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + }); + + it("falls back to static catalog when all entries have null metadata", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => + Promise.resolve({ + data: [ + { id: "BAAI/bge-m3", metadata: null }, + { id: "stabilityai/sdxl", metadata: null }, + ], + }), + }); + await withFetchPathTest(mockFetch, async () => { + const models = await discoverDeepInfraModels(); + expect(models.length).toBeGreaterThan(0); + // Falls back to static catalog + expect(models.some((m) => m.id === "openai/gpt-oss-120b")).toBe(true); + }); + }); +}); diff --git a/src/agents/deepinfra-models.ts b/src/agents/deepinfra-models.ts new file mode 100644 index 00000000000..b16312b9847 --- /dev/null +++ b/src/agents/deepinfra-models.ts @@ -0,0 +1,154 @@ +import type { ModelDefinitionConfig } from "../config/types.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { + DEEPINFRA_BASE_URL, + DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + DEEPINFRA_DEFAULT_COST, + DEEPINFRA_DEFAULT_MAX_TOKENS, + DEEPINFRA_MODEL_CATALOG, +} from "../providers/deepinfra-shared.js"; + +const log = createSubsystemLogger("deepinfra-models"); + +export const DEEPINFRA_MODELS_URL = `${DEEPINFRA_BASE_URL}models`; + +const DISCOVERY_TIMEOUT_MS = 5000; + +// --------------------------------------------------------------------------- +// API response types (DeepInfra OpenAI-compatible /models schema) +// --------------------------------------------------------------------------- + +interface DeepInfraModelPricing { + input_tokens?: number; + output_tokens?: number; + cache_read_tokens?: number; +} + +interface DeepInfraModelMetadata { + description?: string; + context_length?: number; + max_tokens?: number; + pricing?: DeepInfraModelPricing; + /** e.g. ["vision", "reasoning_effort", "prompt_cache"] */ + tags?: string[]; +} + +interface DeepInfraModelEntry { + id: string; + object?: string; + owned_by?: string; + metadata: DeepInfraModelMetadata | null; +} + +interface DeepInfraModelsResponse { + data: DeepInfraModelEntry[]; +} + +// --------------------------------------------------------------------------- +// Model parsing +// --------------------------------------------------------------------------- + +function parseModality(metadata: DeepInfraModelMetadata): Array<"text" | "image"> { + const hasVision = metadata.tags?.includes("vision") ?? false; + return hasVision ? ["text", "image"] : ["text"]; +} + +function parseReasoning(metadata: DeepInfraModelMetadata): boolean { + return metadata.tags?.includes("reasoning_effort") ?? false; +} + +function toModelDefinition(entry: DeepInfraModelEntry): ModelDefinitionConfig { + // metadata is guaranteed non-null at call site + const meta = entry.metadata!; + return { + id: entry.id, + name: entry.id, + reasoning: parseReasoning(meta), + input: parseModality(meta), + cost: { + input: meta.pricing?.input_tokens ?? 0, + output: meta.pricing?.output_tokens ?? 0, + cacheRead: meta.pricing?.cache_read_tokens ?? 0, + cacheWrite: 0, + }, + contextWindow: meta.context_length ?? DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + maxTokens: meta.max_tokens ?? DEEPINFRA_DEFAULT_MAX_TOKENS, + }; +} + +// --------------------------------------------------------------------------- +// Static fallback +// --------------------------------------------------------------------------- + +function buildStaticCatalog(): ModelDefinitionConfig[] { + return DEEPINFRA_MODEL_CATALOG.map((model) => ({ + id: model.id, + name: model.name, + reasoning: model.reasoning, + input: model.input, + cost: DEEPINFRA_DEFAULT_COST, + contextWindow: model.contextWindow ?? DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + maxTokens: model.maxTokens ?? DEEPINFRA_DEFAULT_MAX_TOKENS, + })); +} + +// --------------------------------------------------------------------------- +// Discovery +// --------------------------------------------------------------------------- + +/** + * Discover models from the DeepInfra API with fallback to static catalog. + * Skips models with null metadata (embeddings, image-gen, etc.). + */ +export async function discoverDeepInfraModels(): Promise { + // Skip API discovery in test environment + if (process.env.NODE_ENV === "test" || process.env.VITEST) { + return buildStaticCatalog(); + } + + try { + const response = await fetch(DEEPINFRA_MODELS_URL, { + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS), + }); + + if (!response.ok) { + log.warn(`Failed to discover models: HTTP ${response.status}, using static catalog`); + return buildStaticCatalog(); + } + + const data = (await response.json()) as DeepInfraModelsResponse; + if (!Array.isArray(data.data) || data.data.length === 0) { + log.warn("No models found from DeepInfra API, using static catalog"); + return buildStaticCatalog(); + } + + const models: ModelDefinitionConfig[] = []; + const discoveredIds = new Set(); + + for (const entry of data.data) { + if (!entry || typeof entry !== "object") { + continue; + } + const id = typeof entry.id === "string" ? entry.id.trim() : ""; + if (!id || discoveredIds.has(id)) { + continue; + } + // Skip non-completion models (embeddings, image-gen, etc.) + if (entry.metadata === null) { + continue; + } + try { + models.push(toModelDefinition(entry)); + discoveredIds.add(id); + } catch (e) { + log.warn(`Skipping malformed model entry "${id}": ${String(e)}`); + } + } + + return models.length > 0 ? models : buildStaticCatalog(); + } catch (error) { + log.warn(`Discovery failed: ${String(error)}, using static catalog`); + return buildStaticCatalog(); + } +} diff --git a/src/agents/models-config.e2e-harness.ts b/src/agents/models-config.e2e-harness.ts index 81518ec9aee..f39c80c4f49 100644 --- a/src/agents/models-config.e2e-harness.ts +++ b/src/agents/models-config.e2e-harness.ts @@ -109,6 +109,7 @@ export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [ "VOLCANO_ENGINE_API_KEY", "BYTEPLUS_API_KEY", "KILOCODE_API_KEY", + "DEEPINFRA_API_KEY", "KIMI_API_KEY", "KIMICODE_API_KEY", "GEMINI_API_KEY", diff --git a/src/agents/models-config.providers.deepinfra.test.ts b/src/agents/models-config.providers.deepinfra.test.ts new file mode 100644 index 00000000000..7f3cc846cf2 --- /dev/null +++ b/src/agents/models-config.providers.deepinfra.test.ts @@ -0,0 +1,66 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { captureEnv } from "../test-utils/env.ts"; +import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.ts"; +import { buildDeepInfraStaticProvider } from "./models-config.providers.ts"; + +const DEEPINFRA_MODEL_IDS = [ + "openai/gpt-oss-120b", + "minimaxai/minimax-m2.5", + "zai-org/glm-5", + "moonshotai/kimi-k2.5" +]; + +describe("DeepInfra implicit provider", () => { + it("should include deepinfra when DEEPINFRA_API_KEY is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["DEEPINFRA_API_KEY"]); + process.env.DEEPINFRA_API_KEY = "test-key"; // pragma: allowlist secret + + try { + const providers = await resolveImplicitProvidersForTest({ agentDir }); + expect(providers?.deepinfra).toBeDefined(); + expect(providers?.deepinfra?.models?.length).toBeGreaterThan(0); + } finally { + envSnapshot.restore(); + } + }); + + it("should not include deepinfra when no API key is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["DEEPINFRA_API_KEY"]); + delete process.env.DEEPINFRA_API_KEY; + + try { + const providers = await resolveImplicitProvidersForTest({ agentDir }); + expect(providers?.deepinfra).toBeUndefined(); + } finally { + envSnapshot.restore(); + } + }); + + it("should build deepinfra provider with correct configuration", () => { + const provider = buildDeepInfraStaticProvider(); + expect(provider.baseUrl).toBe("https://api.deepinfra.com/v1/openai/"); + expect(provider.api).toBe("openai-completions"); + expect(provider.models).toBeDefined(); + expect(provider.models.length).toBeGreaterThan(0); + }); + + it("should include the default deepinfra model", () => { + const provider = buildDeepInfraStaticProvider(); + const modelIds = provider.models.map((m) => m.id); + expect(modelIds).toContain("deepinfra/openai/gpt-oss-120b"); + }); + + it("should include the static fallback catalog", () => { + const provider = buildDeepInfraStaticProvider(); + const modelIds = provider.models.map((m) => m.id); + for (const modelId of DEEPINFRA_MODEL_IDS) { + expect(modelIds).toContain(modelId); + } + expect(provider.models).toHaveLength(DEEPINFRA_MODEL_IDS.length); + }); +}); diff --git a/src/agents/models-config.providers.discovery.ts b/src/agents/models-config.providers.discovery.ts index b138c4853d1..ee06a272ee9 100644 --- a/src/agents/models-config.providers.discovery.ts +++ b/src/agents/models-config.providers.discovery.ts @@ -19,6 +19,7 @@ import { SGLANG_DEFAULT_BASE_URL, SGLANG_PROVIDER_LABEL } from "./sglang-default import { VLLM_DEFAULT_BASE_URL, VLLM_PROVIDER_LABEL } from "./vllm-defaults.js"; export { buildHuggingfaceProvider } from "../../extensions/huggingface/provider-catalog.js"; export { buildKilocodeProviderWithDiscovery } from "../../extensions/kilocode/provider-catalog.js"; +export { buildDeepInfraProviderWithDiscovery } from "../../extensions/deepinfra/provider-catalog.js"; export { buildVeniceProvider } from "../../extensions/venice/provider-catalog.js"; export { buildVercelAiGatewayProvider } from "../../extensions/vercel-ai-gateway/provider-catalog.js"; diff --git a/src/agents/models-config.providers.static.ts b/src/agents/models-config.providers.static.ts index 71184e12286..d1cd2f51310 100644 --- a/src/agents/models-config.providers.static.ts +++ b/src/agents/models-config.providers.static.ts @@ -4,6 +4,7 @@ export { } from "../../extensions/byteplus/provider-catalog.js"; export { buildKimiCodingProvider } from "../../extensions/kimi-coding/provider-catalog.js"; export { buildKilocodeProvider } from "../../extensions/kilocode/provider-catalog.js"; +export { buildDeepInfraStaticProvider } from "../../extensions/deepinfra/provider-catalog.js"; export { buildMinimaxPortalProvider, buildMinimaxProvider, diff --git a/src/agents/pi-embedded-runner/deepinfra.test.ts b/src/agents/pi-embedded-runner/deepinfra.test.ts new file mode 100644 index 00000000000..710514637e5 --- /dev/null +++ b/src/agents/pi-embedded-runner/deepinfra.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { isCacheTtlEligibleProvider } from "./cache-ttl.ts"; + +describe("deepinfra cache-ttl eligibility", () => { + it("is eligible when model starts with zai", () => { + expect(isCacheTtlEligibleProvider("deepinfra", "zai-org/glm-5")).toBe(true); + }); + + it("is eligible when model starts with moonshot", () => { + expect(isCacheTtlEligibleProvider("deepinfra", "moonshotai/kimi-k2.5")).toBe(true); + }); + + it("is not eligible for other models on deepinfra", () => { + expect(isCacheTtlEligibleProvider("deepinfra", "openai/gpt-oss-120b")).toBe(false); + }); + + it("is case-insensitive for provider name", () => { + expect(isCacheTtlEligibleProvider("DeepInfra", "moonshotai/kimi-k2.5")).toBe(true); + expect(isCacheTtlEligibleProvider("DEEPINFRA", "Moonshotai/kimi-k2.5")).toBe(true); + }); +}); diff --git a/src/agents/provider-capabilities.test.ts b/src/agents/provider-capabilities.test.ts index 1712f6f810e..cd560471122 100644 --- a/src/agents/provider-capabilities.test.ts +++ b/src/agents/provider-capabilities.test.ts @@ -119,6 +119,12 @@ describe("resolveProviderCapabilities", () => { modelId: "gemini-2.0-flash", }), ).toBe(true); + expect( + shouldSanitizeGeminiThoughtSignaturesForModel({ + provider: "deepinfra", + modelId: "google/gemini-2.5-pro", + }), + ).toBe(true); expect( shouldSanitizeGeminiThoughtSignaturesForModel({ provider: "opencode-go", diff --git a/src/agents/transcript-policy.test.ts b/src/agents/transcript-policy.test.ts index 7409e7a4b12..3cd8aa82a05 100644 --- a/src/agents/transcript-policy.test.ts +++ b/src/agents/transcript-policy.test.ts @@ -158,6 +158,7 @@ describe("resolveTranscriptPolicy", () => { { provider: "openrouter", modelId: "google/gemini-2.5-pro-preview" }, { provider: "opencode", modelId: "google/gemini-2.5-flash" }, { provider: "kilocode", modelId: "gemini-2.0-flash" }, + { provider: "deepinfra", modelId: "google/gemini-2.5-pro" }, ])("sanitizes Gemini thought signatures for $provider routes", ({ provider, modelId }) => { const policy = resolveTranscriptPolicy({ provider, diff --git a/src/commands/onboard-auth.config-core.deepinfra.test.ts b/src/commands/onboard-auth.config-core.deepinfra.test.ts new file mode 100644 index 00000000000..bc3c19f6b9c --- /dev/null +++ b/src/commands/onboard-auth.config-core.deepinfra.test.ts @@ -0,0 +1,197 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { resolveApiKeyForProvider, resolveEnvApiKey } from "../agents/model-auth.js"; +import type { OpenClawConfig } from "../config/config.js"; +import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; +import { + DEEPINFRA_BASE_URL, + DEEPINFRA_DEFAULT_MODEL_ID, + DEEPINFRA_DEFAULT_MODEL_REF, + DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + DEEPINFRA_DEFAULT_MAX_TOKENS, + DEEPINFRA_DEFAULT_COST, + DEEPINFRA_MODEL_CATALOG, +} from "../providers/deepinfra-shared.js"; +import { captureEnv } from "../test-utils/env.js"; +import { applyDeepInfraProviderConfig, applyDeepInfraConfig } from "./onboard-auth.config-core.js"; + +const emptyCfg: OpenClawConfig = {}; +const DEEPINFRA_MODEL_IDS = DEEPINFRA_MODEL_CATALOG.map((m) => m.id); + +describe("DeepInfra provider config", () => { + describe("constants", () => { + it("DEEPINFRA_BASE_URL points to DeepInfra OpenAI-compatible endpoint", () => { + expect(DEEPINFRA_BASE_URL).toBe("https://api.deepinfra.com/v1/openai/"); + }); + + it("DEEPINFRA_DEFAULT_MODEL_REF includes provider prefix", () => { + expect(DEEPINFRA_DEFAULT_MODEL_REF).toBe(`deepinfra/${DEEPINFRA_DEFAULT_MODEL_ID}`); + }); + + it("DEEPINFRA_DEFAULT_MODEL_ID is openai/gpt-oss-120b", () => { + expect(DEEPINFRA_DEFAULT_MODEL_ID).toBe("openai/gpt-oss-120b"); + }); + + it("DEEPINFRA_DEFAULT_CONTEXT_WINDOW is 128000", () => { + expect(DEEPINFRA_DEFAULT_CONTEXT_WINDOW).toBe(128000); + }); + + it("DEEPINFRA_DEFAULT_MAX_TOKENS is 8192", () => { + expect(DEEPINFRA_DEFAULT_MAX_TOKENS).toBe(8192); + }); + + it("DEEPINFRA_DEFAULT_COST has zero values", () => { + expect(DEEPINFRA_DEFAULT_COST).toEqual({ + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }); + }); + }); + + describe("applyDeepInfraProviderConfig", () => { + it("registers deepinfra provider with correct baseUrl and api", () => { + const result = applyDeepInfraProviderConfig(emptyCfg); + const provider = result.models?.providers?.deepinfra; + expect(provider).toBeDefined(); + expect(provider?.baseUrl).toBe(DEEPINFRA_BASE_URL); + expect(provider?.api).toBe("openai-completions"); + }); + + it("includes the default model in the provider model list", () => { + const result = applyDeepInfraProviderConfig(emptyCfg); + const provider = result.models?.providers?.deepinfra; + const models = provider?.models; + expect(Array.isArray(models)).toBe(true); + const modelIds = models?.map((m) => m.id) ?? []; + expect(modelIds).toContain(DEEPINFRA_DEFAULT_MODEL_ID); + }); + + it("surfaces the full DeepInfra model catalog", () => { + const result = applyDeepInfraProviderConfig(emptyCfg); + const provider = result.models?.providers?.deepinfra; + const modelIds = provider?.models?.map((m) => m.id) ?? []; + for (const modelId of DEEPINFRA_MODEL_IDS) { + expect(modelIds).toContain(modelId); + } + }); + + it("appends missing catalog models to existing DeepInfra provider config", () => { + const result = applyDeepInfraProviderConfig({ + models: { + providers: { + deepinfra: { + baseUrl: DEEPINFRA_BASE_URL, + api: "openai-completions", + models: [{ ...DEEPINFRA_MODEL_CATALOG[0], cost: DEEPINFRA_DEFAULT_COST }], + }, + }, + }, + }); + const modelIds = result.models?.providers?.deepinfra?.models?.map((m) => m.id) ?? []; + for (const modelId of DEEPINFRA_MODEL_IDS) { + expect(modelIds).toContain(modelId); + } + }); + + it("sets DeepInfra alias in agent default models", () => { + const result = applyDeepInfraProviderConfig(emptyCfg); + const agentModel = result.agents?.defaults?.models?.[DEEPINFRA_DEFAULT_MODEL_REF]; + expect(agentModel).toBeDefined(); + expect(agentModel?.alias).toBe("DeepInfra"); + }); + + it("preserves existing alias if already set", () => { + const cfg: OpenClawConfig = { + agents: { + defaults: { + models: { + [DEEPINFRA_DEFAULT_MODEL_REF]: { alias: "My Custom Alias" }, + }, + }, + }, + }; + const result = applyDeepInfraProviderConfig(cfg); + const agentModel = result.agents?.defaults?.models?.[DEEPINFRA_DEFAULT_MODEL_REF]; + expect(agentModel?.alias).toBe("My Custom Alias"); + }); + + it("does not change the default model selection", () => { + const cfg: OpenClawConfig = { + agents: { + defaults: { + model: { primary: "openai/gpt-5" }, + }, + }, + }; + const result = applyDeepInfraProviderConfig(cfg); + expect(resolveAgentModelPrimaryValue(result.agents?.defaults?.model)).toBe("openai/gpt-5"); + }); + }); + + describe("applyDeepInfraConfig", () => { + it("sets deepinfra's default model as the config's default model", () => { + const result = applyDeepInfraConfig(emptyCfg); + expect(resolveAgentModelPrimaryValue(result.agents?.defaults?.model)).toBe( + DEEPINFRA_DEFAULT_MODEL_REF, + ); + }); + + it("also registers the provider", () => { + const result = applyDeepInfraConfig(emptyCfg); + const provider = result.models?.providers?.deepinfra; + expect(provider).toBeDefined(); + expect(provider?.baseUrl).toBe(DEEPINFRA_BASE_URL); + }); + }); + + describe("env var resolution", () => { + it("resolves DEEPINFRA_API_KEY from env", () => { + const envSnapshot = captureEnv(["DEEPINFRA_API_KEY"]); + process.env.DEEPINFRA_API_KEY = "test-deepinfra-key"; // pragma: allowlist secret + + try { + const result = resolveEnvApiKey("deepinfra"); + expect(result).not.toBeNull(); + expect(result?.apiKey).toBe("test-deepinfra-key"); + expect(result?.source).toContain("DEEPINFRA_API_KEY"); + } finally { + envSnapshot.restore(); + } + }); + + it("returns null when DEEPINFRA_API_KEY is not set", () => { + const envSnapshot = captureEnv(["DEEPINFRA_API_KEY"]); + delete process.env.DEEPINFRA_API_KEY; + + try { + const result = resolveEnvApiKey("deepinfra"); + expect(result).toBeNull(); + } finally { + envSnapshot.restore(); + } + }); + + it("resolves the deepinfra api key via resolveApiKeyForProvider", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["DEEPINFRA_API_KEY"]); + process.env.DEEPINFRA_API_KEY = "deepinfra-provider-test-key"; // pragma: allowlist secret + + try { + const auth = await resolveApiKeyForProvider({ + provider: "deepinfra", + agentDir, + }); + + expect(auth.apiKey).toBe("deepinfra-provider-test-key"); + expect(auth.mode).toBe("api-key"); + expect(auth.source).toContain("DEEPINFRA_API_KEY"); + } finally { + envSnapshot.restore(); + } + }); + }); +}); diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts new file mode 100644 index 00000000000..ab154bbc58f --- /dev/null +++ b/src/commands/onboard-auth.ts @@ -0,0 +1,141 @@ +export { + SYNTHETIC_DEFAULT_MODEL_ID, + SYNTHETIC_DEFAULT_MODEL_REF, +} from "../agents/synthetic-models.js"; +export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js"; +export { + applyAuthProfileConfig, + applyCloudflareAiGatewayConfig, + applyCloudflareAiGatewayProviderConfig, + applyHuggingfaceConfig, + applyHuggingfaceProviderConfig, + applyDeepInfraConfig, + applyDeepInfraProviderConfig, + applyKilocodeConfig, + applyKilocodeProviderConfig, + applyQianfanConfig, + applyQianfanProviderConfig, + applyKimiCodeConfig, + applyKimiCodeProviderConfig, + applyLitellmConfig, + applyLitellmProviderConfig, + applyMistralConfig, + applyMistralProviderConfig, + applyMoonshotConfig, + applyMoonshotConfigCn, + applyMoonshotProviderConfig, + applyMoonshotProviderConfigCn, + applyOpenrouterConfig, + applyOpenrouterProviderConfig, + applySyntheticConfig, + applySyntheticProviderConfig, + applyTogetherConfig, + applyTogetherProviderConfig, + applyVeniceConfig, + applyVeniceProviderConfig, + applyVercelAiGatewayConfig, + applyVercelAiGatewayProviderConfig, + applyXaiConfig, + applyXaiProviderConfig, + applyXiaomiConfig, + applyXiaomiProviderConfig, + applyZaiConfig, + applyZaiProviderConfig, + applyModelStudioConfig, + applyModelStudioConfigCn, + applyModelStudioProviderConfig, + applyModelStudioProviderConfigCn, + KILOCODE_BASE_URL, +} from "./onboard-auth.config-core.js"; +export { + applyMinimaxApiConfig, + applyMinimaxApiConfigCn, + applyMinimaxApiProviderConfig, + applyMinimaxApiProviderConfigCn, + applyMinimaxConfig, + applyMinimaxHostedConfig, + applyMinimaxHostedProviderConfig, + applyMinimaxProviderConfig, +} from "./onboard-auth.config-minimax.js"; + +export { + applyOpencodeZenConfig, + applyOpencodeZenProviderConfig, +} from "./onboard-auth.config-opencode.js"; +export { + applyOpencodeGoConfig, + applyOpencodeGoProviderConfig, +} from "./onboard-auth.config-opencode-go.js"; +export { + CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, + DEEPINFRA_DEFAULT_MODEL_REF, + KILOCODE_DEFAULT_MODEL_REF, + LITELLM_DEFAULT_MODEL_REF, + OPENROUTER_DEFAULT_MODEL_REF, + setOpenaiApiKey, + setAnthropicApiKey, + setCloudflareAiGatewayConfig, + setByteplusApiKey, + setQianfanApiKey, + setGeminiApiKey, + setDeepInfraApiKey, + setKilocodeApiKey, + setLitellmApiKey, + setKimiCodingApiKey, + setMinimaxApiKey, + setMistralApiKey, + setMoonshotApiKey, + setOpencodeGoApiKey, + setOpencodeZenApiKey, + setOpenrouterApiKey, + setSyntheticApiKey, + setTogetherApiKey, + setHuggingfaceApiKey, + setVeniceApiKey, + setVercelAiGatewayApiKey, + setXiaomiApiKey, + setVolcengineApiKey, + setZaiApiKey, + setXaiApiKey, + setModelStudioApiKey, + writeOAuthCredentials, + HUGGINGFACE_DEFAULT_MODEL_REF, + VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XIAOMI_DEFAULT_MODEL_REF, + ZAI_DEFAULT_MODEL_REF, + TOGETHER_DEFAULT_MODEL_REF, + MISTRAL_DEFAULT_MODEL_REF, + XAI_DEFAULT_MODEL_REF, + MODELSTUDIO_DEFAULT_MODEL_REF, +} from "./onboard-auth.credentials.js"; +export { + buildKilocodeModelDefinition, + buildMinimaxApiModelDefinition, + buildMinimaxModelDefinition, + buildMistralModelDefinition, + buildMoonshotModelDefinition, + buildZaiModelDefinition, + DEFAULT_MINIMAX_BASE_URL, + KILOCODE_DEFAULT_MODEL_ID, + MOONSHOT_CN_BASE_URL, + QIANFAN_BASE_URL, + QIANFAN_DEFAULT_MODEL_ID, + QIANFAN_DEFAULT_MODEL_REF, + KIMI_CODING_MODEL_ID, + KIMI_CODING_MODEL_REF, + MINIMAX_API_BASE_URL, + MINIMAX_CN_API_BASE_URL, + MINIMAX_HOSTED_MODEL_ID, + MINIMAX_HOSTED_MODEL_REF, + MOONSHOT_BASE_URL, + MOONSHOT_DEFAULT_MODEL_ID, + MOONSHOT_DEFAULT_MODEL_REF, + MISTRAL_BASE_URL, + MISTRAL_DEFAULT_MODEL_ID, + resolveZaiBaseUrl, + ZAI_CODING_CN_BASE_URL, + ZAI_DEFAULT_MODEL_ID, + ZAI_CODING_GLOBAL_BASE_URL, + ZAI_CN_BASE_URL, + ZAI_GLOBAL_BASE_URL, +} from "./onboard-auth.models.js"; diff --git a/src/commands/onboard-provider-auth-flags.ts b/src/commands/onboard-provider-auth-flags.ts new file mode 100644 index 00000000000..3de6dfc322e --- /dev/null +++ b/src/commands/onboard-provider-auth-flags.ts @@ -0,0 +1,233 @@ +import type { AuthChoice, OnboardOptions } from "./onboard-types.js"; + +type OnboardProviderAuthOptionKey = keyof Pick< + OnboardOptions, + | "anthropicApiKey" + | "openaiApiKey" + | "mistralApiKey" + | "openrouterApiKey" + | "kilocodeApiKey" + | "deepinfraApiKey" + | "aiGatewayApiKey" + | "cloudflareAiGatewayApiKey" + | "moonshotApiKey" + | "kimiCodeApiKey" + | "geminiApiKey" + | "zaiApiKey" + | "xiaomiApiKey" + | "minimaxApiKey" + | "syntheticApiKey" + | "veniceApiKey" + | "togetherApiKey" + | "huggingfaceApiKey" + | "opencodeZenApiKey" + | "opencodeGoApiKey" + | "xaiApiKey" + | "litellmApiKey" + | "qianfanApiKey" + | "modelstudioApiKeyCn" + | "modelstudioApiKey" + | "volcengineApiKey" + | "byteplusApiKey" +>; + +export type OnboardProviderAuthFlag = { + optionKey: OnboardProviderAuthOptionKey; + authChoice: AuthChoice; + cliFlag: `--${string}`; + cliOption: `--${string} `; + description: string; +}; + +// Shared source for provider API-key flags used by CLI registration + non-interactive inference. +export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray = [ + { + optionKey: "anthropicApiKey", + authChoice: "apiKey", + cliFlag: "--anthropic-api-key", + cliOption: "--anthropic-api-key ", + description: "Anthropic API key", + }, + { + optionKey: "openaiApiKey", + authChoice: "openai-api-key", + cliFlag: "--openai-api-key", + cliOption: "--openai-api-key ", + description: "OpenAI API key", + }, + { + optionKey: "mistralApiKey", + authChoice: "mistral-api-key", + cliFlag: "--mistral-api-key", + cliOption: "--mistral-api-key ", + description: "Mistral API key", + }, + { + optionKey: "openrouterApiKey", + authChoice: "openrouter-api-key", + cliFlag: "--openrouter-api-key", + cliOption: "--openrouter-api-key ", + description: "OpenRouter API key", + }, + { + optionKey: "kilocodeApiKey", + authChoice: "kilocode-api-key", + cliFlag: "--kilocode-api-key", + cliOption: "--kilocode-api-key ", + description: "Kilo Gateway API key", + }, + { + optionKey: "deepinfraApiKey", + authChoice: "deepinfra-api-key", + cliFlag: "--deepinfra-api-key", + cliOption: "--deepinfra-api-key ", + description: "DeepInfra API key", + }, + { + optionKey: "aiGatewayApiKey", + authChoice: "ai-gateway-api-key", + cliFlag: "--ai-gateway-api-key", + cliOption: "--ai-gateway-api-key ", + description: "Vercel AI Gateway API key", + }, + { + optionKey: "cloudflareAiGatewayApiKey", + authChoice: "cloudflare-ai-gateway-api-key", + cliFlag: "--cloudflare-ai-gateway-api-key", + cliOption: "--cloudflare-ai-gateway-api-key ", + description: "Cloudflare AI Gateway API key", + }, + { + optionKey: "moonshotApiKey", + authChoice: "moonshot-api-key", + cliFlag: "--moonshot-api-key", + cliOption: "--moonshot-api-key ", + description: "Moonshot API key", + }, + { + optionKey: "kimiCodeApiKey", + authChoice: "kimi-code-api-key", + cliFlag: "--kimi-code-api-key", + cliOption: "--kimi-code-api-key ", + description: "Kimi Coding API key", + }, + { + optionKey: "geminiApiKey", + authChoice: "gemini-api-key", + cliFlag: "--gemini-api-key", + cliOption: "--gemini-api-key ", + description: "Gemini API key", + }, + { + optionKey: "zaiApiKey", + authChoice: "zai-api-key", + cliFlag: "--zai-api-key", + cliOption: "--zai-api-key ", + description: "Z.AI API key", + }, + { + optionKey: "xiaomiApiKey", + authChoice: "xiaomi-api-key", + cliFlag: "--xiaomi-api-key", + cliOption: "--xiaomi-api-key ", + description: "Xiaomi API key", + }, + { + optionKey: "minimaxApiKey", + authChoice: "minimax-api", + cliFlag: "--minimax-api-key", + cliOption: "--minimax-api-key ", + description: "MiniMax API key", + }, + { + optionKey: "syntheticApiKey", + authChoice: "synthetic-api-key", + cliFlag: "--synthetic-api-key", + cliOption: "--synthetic-api-key ", + description: "Synthetic API key", + }, + { + optionKey: "veniceApiKey", + authChoice: "venice-api-key", + cliFlag: "--venice-api-key", + cliOption: "--venice-api-key ", + description: "Venice API key", + }, + { + optionKey: "togetherApiKey", + authChoice: "together-api-key", + cliFlag: "--together-api-key", + cliOption: "--together-api-key ", + description: "Together AI API key", + }, + { + optionKey: "huggingfaceApiKey", + authChoice: "huggingface-api-key", + cliFlag: "--huggingface-api-key", + cliOption: "--huggingface-api-key ", + description: "Hugging Face API key (HF token)", + }, + { + optionKey: "opencodeZenApiKey", + authChoice: "opencode-zen", + cliFlag: "--opencode-zen-api-key", + cliOption: "--opencode-zen-api-key ", + description: "OpenCode API key (Zen catalog)", + }, + { + optionKey: "opencodeGoApiKey", + authChoice: "opencode-go", + cliFlag: "--opencode-go-api-key", + cliOption: "--opencode-go-api-key ", + description: "OpenCode API key (Go catalog)", + }, + { + optionKey: "xaiApiKey", + authChoice: "xai-api-key", + cliFlag: "--xai-api-key", + cliOption: "--xai-api-key ", + description: "xAI API key", + }, + { + optionKey: "litellmApiKey", + authChoice: "litellm-api-key", + cliFlag: "--litellm-api-key", + cliOption: "--litellm-api-key ", + description: "LiteLLM API key", + }, + { + optionKey: "qianfanApiKey", + authChoice: "qianfan-api-key", + cliFlag: "--qianfan-api-key", + cliOption: "--qianfan-api-key ", + description: "QIANFAN API key", + }, + { + optionKey: "modelstudioApiKeyCn", + authChoice: "modelstudio-api-key-cn", + cliFlag: "--modelstudio-api-key-cn", + cliOption: "--modelstudio-api-key-cn ", + description: "Alibaba Cloud Model Studio Coding Plan API key (China)", + }, + { + optionKey: "modelstudioApiKey", + authChoice: "modelstudio-api-key", + cliFlag: "--modelstudio-api-key", + cliOption: "--modelstudio-api-key ", + description: "Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)", + }, + { + optionKey: "volcengineApiKey", + authChoice: "volcengine-api-key", + cliFlag: "--volcengine-api-key", + cliOption: "--volcengine-api-key ", + description: "Volcano Engine API key", + }, + { + optionKey: "byteplusApiKey", + authChoice: "byteplus-api-key", + cliFlag: "--byteplus-api-key", + cliOption: "--byteplus-api-key ", + description: "BytePlus API key", + }, +]; diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 832fae75448..6161fcafb00 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -14,6 +14,7 @@ export type BuiltInAuthChoice = | "openai-api-key" | "openrouter-api-key" | "kilocode-api-key" + | "deepinfra-api-key" | "litellm-api-key" | "ai-gateway-api-key" | "cloudflare-ai-gateway-api-key" @@ -62,6 +63,7 @@ export type BuiltInAuthChoiceGroupId = | "copilot" | "openrouter" | "kilocode" + | "deepinfra" | "litellm" | "ai-gateway" | "cloudflare-ai-gateway" @@ -119,6 +121,7 @@ export type OnboardOptions = { mistralApiKey?: string; openrouterApiKey?: string; kilocodeApiKey?: string; + deepinfraApiKey?: string; litellmApiKey?: string; aiGatewayApiKey?: string; cloudflareAiGatewayAccountId?: string; diff --git a/src/config/io.ts b/src/config/io.ts index fba17f253aa..214ac6210ec 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -71,6 +71,7 @@ const SHELL_ENV_EXPECTED_KEYS = [ "MODELSTUDIO_API_KEY", "SYNTHETIC_API_KEY", "KILOCODE_API_KEY", + "DEEPINFRA_API_KEY", "ELEVENLABS_API_KEY", "TELEGRAM_BOT_TOKEN", "DISCORD_BOT_TOKEN", diff --git a/src/plugins/provider-auth-storage.ts b/src/plugins/provider-auth-storage.ts index d8e15115902..97ac8ea466f 100644 --- a/src/plugins/provider-auth-storage.ts +++ b/src/plugins/provider-auth-storage.ts @@ -1,6 +1,7 @@ import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; import { upsertAuthProfile } from "../agents/auth-profiles.js"; import type { SecretInput } from "../config/types.secrets.js"; +import { DEEPINFRA_DEFAULT_MODEL_REF } from "../providers/deepinfra-shared.js"; import { KILOCODE_DEFAULT_MODEL_REF } from "../providers/kilocode-shared.js"; import { buildApiKeyCredential, @@ -12,6 +13,7 @@ import { const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); export { KILOCODE_DEFAULT_MODEL_REF }; +export { DEEPINFRA_DEFAULT_MODEL_REF }; export { buildApiKeyCredential, type ApiKeyStorageOptions, @@ -250,6 +252,20 @@ export async function setOpencodeGoApiKey( await setSharedOpencodeApiKey(key, agentDir, options); } +// TODO: use this to reduce the code duplication a bit. +function setApiKey( + providerId: string, + key: SecretInput, + agentDir?: string, + options?: ApiKeyStorageOptions, +) { + upsertAuthProfile({ + profileId: `${providerId}:default`, + credential: buildApiKeyCredential(providerId, key, undefined, options), + agentDir: resolveAuthAgentDir(agentDir), + }); +} + async function setSharedOpencodeApiKey( key: SecretInput, agentDir?: string, @@ -343,3 +359,11 @@ export async function setKilocodeApiKey( agentDir: resolveAuthAgentDir(agentDir), }); } + +export async function setDeepInfraApiKey( + key: SecretInput, + agentDir?: string, + options?: ApiKeyStorageOptions, +) { + setApiKey("deepinfra", key, agentDir, options); +} diff --git a/src/providers/deepinfra-shared.ts b/src/providers/deepinfra-shared.ts new file mode 100644 index 00000000000..2f788deefdc --- /dev/null +++ b/src/providers/deepinfra-shared.ts @@ -0,0 +1,61 @@ +export const DEEPINFRA_BASE_URL = "https://api.deepinfra.com/v1/openai/"; +export const DEEPINFRA_DEFAULT_MODEL_ID = "openai/gpt-oss-120b"; +export const DEEPINFRA_DEFAULT_MODEL_REF = `deepinfra/${DEEPINFRA_DEFAULT_MODEL_ID}`; +export const DEEPINFRA_DEFAULT_MODEL_NAME = "gpt-oss-120b"; +export type DeepInfraModelCatalogEntry = { + id: string; + name: string; + reasoning: boolean; + input: Array<"text" | "image">; + contextWindow: number; + maxTokens: number; +}; + +/** + * Static fallback catalog used by the sync onboarding path and as a + * fallback when dynamic model discovery from the gateway API fails. + * The full model list is fetched dynamically by {@link discoverDeepInfraModels} + * in `src/agents/deepinfra-models.ts`. + */ +export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ + { + id: DEEPINFRA_DEFAULT_MODEL_ID, + name: DEEPINFRA_DEFAULT_MODEL_NAME, + reasoning: true, + input: ["text", "image"], + contextWindow: 131072, + maxTokens: 131072, + }, + { + id: "minimaxai/minimax-m2.5", + name: "MiniMax M2.5", + reasoning: false, + input: ["text"], + contextWindow: 196608, + maxTokens: 196608, + }, + { + id: "zai-org/glm-5", + name: "GLM 5", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 202752, + }, + { + id: "moonshotai/kimi-k2.5", + name: "Kimi K2.5", + reasoning: false, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 262144, + }, +]; +export const DEEPINFRA_DEFAULT_CONTEXT_WINDOW = 128000; +export const DEEPINFRA_DEFAULT_MAX_TOKENS = 8192; +export const DEEPINFRA_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +} as const; diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index 269b37d836a..d0cd661a999 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -7,6 +7,7 @@ const CORE_PROVIDER_AUTH_ENV_VAR_CANDIDATES = { deepgram: ["DEEPGRAM_API_KEY"], cerebras: ["CEREBRAS_API_KEY"], litellm: ["LITELLM_API_KEY"], + deepinfra: ["DEEPINFRA_API_KEY"], } as const; const CORE_PROVIDER_SETUP_ENV_VAR_OVERRIDES = { From c4401fe867626070149178a9c649e4730b336bd4 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Mon, 16 Mar 2026 08:56:16 +0200 Subject: [PATCH 02/20] Changes according to review --- src/agents/models-config.providers.deepinfra.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/agents/models-config.providers.deepinfra.test.ts b/src/agents/models-config.providers.deepinfra.test.ts index 7f3cc846cf2..d89e4f6b65c 100644 --- a/src/agents/models-config.providers.deepinfra.test.ts +++ b/src/agents/models-config.providers.deepinfra.test.ts @@ -7,10 +7,10 @@ import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.ts" import { buildDeepInfraStaticProvider } from "./models-config.providers.ts"; const DEEPINFRA_MODEL_IDS = [ - "openai/gpt-oss-120b", - "minimaxai/minimax-m2.5", - "zai-org/glm-5", - "moonshotai/kimi-k2.5" + "openai/gpt-oss-120b", + "minimaxai/minimax-m2.5", + "zai-org/glm-5", + "moonshotai/kimi-k2.5", ]; describe("DeepInfra implicit provider", () => { @@ -52,7 +52,7 @@ describe("DeepInfra implicit provider", () => { it("should include the default deepinfra model", () => { const provider = buildDeepInfraStaticProvider(); const modelIds = provider.models.map((m) => m.id); - expect(modelIds).toContain("deepinfra/openai/gpt-oss-120b"); + expect(modelIds).toContain("openai/gpt-oss-120b"); }); it("should include the static fallback catalog", () => { From d16c10b07864953ce2c5a35f657e8c162f738882 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Mon, 16 Mar 2026 12:57:33 +0200 Subject: [PATCH 03/20] Test isCacheTtlEligibleProvider for DeepInfra --- src/agents/pi-embedded-runner/cache-ttl.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agents/pi-embedded-runner/cache-ttl.test.ts b/src/agents/pi-embedded-runner/cache-ttl.test.ts index f5ff8be2827..066cb555a30 100644 --- a/src/agents/pi-embedded-runner/cache-ttl.test.ts +++ b/src/agents/pi-embedded-runner/cache-ttl.test.ts @@ -29,6 +29,8 @@ describe("isCacheTtlEligibleProvider", () => { it("allows moonshot and zai providers", () => { expect(isCacheTtlEligibleProvider("moonshot", "kimi-k2.5")).toBe(true); expect(isCacheTtlEligibleProvider("zai", "glm-5")).toBe(true); + expect(isCacheTtlEligibleProvider("deepinfra", "zai-org/glm-5")).toBe(true); + expect(isCacheTtlEligibleProvider("deepinfra", "moonshotai/kimi-k2.5")).toBe(true); }); it("is case-insensitive for native providers", () => { @@ -46,5 +48,6 @@ describe("isCacheTtlEligibleProvider", () => { it("rejects unsupported providers and models", () => { expect(isCacheTtlEligibleProvider("openai", "gpt-4o")).toBe(false); expect(isCacheTtlEligibleProvider("openrouter", "openai/gpt-4o")).toBe(false); + expect(isCacheTtlEligibleProvider("deepinfra", "openai/gpt-4o")).toBe(false); }); }); From 774ebf0788f50df6f2f954191e3e46bf759dce58 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Mon, 16 Mar 2026 15:07:19 +0200 Subject: [PATCH 04/20] Addressed comment: Duplicated catalog-mapping logic with buildDeepInfraStaticProvider --- extensions/deepinfra/provider-catalog.ts | 25 +- src/agents/deepinfra-models.ts | 2 +- src/agents/models-config.providers.static.ts | 560 +++++++++++++++++++ 3 files changed, 569 insertions(+), 18 deletions(-) diff --git a/extensions/deepinfra/provider-catalog.ts b/extensions/deepinfra/provider-catalog.ts index d2ac6db1885..c047e32d986 100644 --- a/extensions/deepinfra/provider-catalog.ts +++ b/extensions/deepinfra/provider-catalog.ts @@ -1,12 +1,11 @@ import { DEEPINFRA_BASE_URL, - DEEPINFRA_DEFAULT_CONTEXT_WINDOW, - DEEPINFRA_DEFAULT_COST, - DEEPINFRA_DEFAULT_MAX_TOKENS, - DEEPINFRA_MODEL_CATALOG, } from "../providers/deepinfra-shared.ts"; -import { discoverDeepInfraModels } from "./deepinfra-models.js"; +import { + discoverDeepInfraModels, + buildStaticCatalog + } from "./deepinfra-models.js"; export async function buildDeepInfraProviderWithDiscovery(): Promise { const models = await discoverDeepInfraModels(); @@ -18,17 +17,9 @@ export async function buildDeepInfraProviderWithDiscovery(): Promise ({ - id: model.id, - name: model.name, - reasoning: model.reasoning, - input: model.input, - cost: DEEPINFRA_DEFAULT_COST, - contextWindow: model.contextWindow ?? DEEPINFRA_DEFAULT_CONTEXT_WINDOW, - maxTokens: model.maxTokens ?? DEEPINFRA_DEFAULT_MAX_TOKENS, - })), + return { + baseUrl: DEEPINFRA_BASE_URL, + api: "openai-completions", + models: buildStaticCatalog(), }; } diff --git a/src/agents/deepinfra-models.ts b/src/agents/deepinfra-models.ts index b16312b9847..a514a97cf43 100644 --- a/src/agents/deepinfra-models.ts +++ b/src/agents/deepinfra-models.ts @@ -80,7 +80,7 @@ function toModelDefinition(entry: DeepInfraModelEntry): ModelDefinitionConfig { // Static fallback // --------------------------------------------------------------------------- -function buildStaticCatalog(): ModelDefinitionConfig[] { +export function buildStaticCatalog(): ModelDefinitionConfig[] { return DEEPINFRA_MODEL_CATALOG.map((model) => ({ id: model.id, name: model.name, diff --git a/src/agents/models-config.providers.static.ts b/src/agents/models-config.providers.static.ts index d1cd2f51310..0bee474b6d7 100644 --- a/src/agents/models-config.providers.static.ts +++ b/src/agents/models-config.providers.static.ts @@ -1,3 +1,4 @@ +<<<<<<< HEAD export { buildBytePlusCodingProvider, buildBytePlusProvider, @@ -34,3 +35,562 @@ export { XIAOMI_DEFAULT_MODEL_ID, buildXiaomiProvider, } from "../../extensions/xiaomi/provider-catalog.js"; +======= +import type { OpenClawConfig } from "../config/config.js"; +import { DEEPINFRA_BASE_URL } from "../providers/deepinfra-shared.js"; +import { + KILOCODE_BASE_URL, + KILOCODE_DEFAULT_CONTEXT_WINDOW, + KILOCODE_DEFAULT_COST, + KILOCODE_DEFAULT_MAX_TOKENS, + KILOCODE_MODEL_CATALOG, +} from "../providers/kilocode-shared.js"; +import { + buildBytePlusModelDefinition, + BYTEPLUS_BASE_URL, + BYTEPLUS_MODEL_CATALOG, + BYTEPLUS_CODING_BASE_URL, + BYTEPLUS_CODING_MODEL_CATALOG, +} from "./byteplus-models.js"; +import { buildStaticCatalog } from "./deepinfra-models.js"; +import { + buildDoubaoModelDefinition, + DOUBAO_BASE_URL, + DOUBAO_MODEL_CATALOG, + DOUBAO_CODING_BASE_URL, + DOUBAO_CODING_MODEL_CATALOG, +} from "./doubao-models.js"; +import { + buildSyntheticModelDefinition, + SYNTHETIC_BASE_URL, + SYNTHETIC_MODEL_CATALOG, +} from "./synthetic-models.js"; +import { + TOGETHER_BASE_URL, + TOGETHER_MODEL_CATALOG, + buildTogetherModelDefinition, +} from "./together-models.js"; + +type ModelsConfig = NonNullable; +type ProviderConfig = NonNullable[string]; +type ProviderModelConfig = NonNullable[number]; + +const MINIMAX_PORTAL_BASE_URL = "https://api.minimax.io/anthropic"; +const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.5"; +const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01"; +const MINIMAX_DEFAULT_CONTEXT_WINDOW = 200000; +const MINIMAX_DEFAULT_MAX_TOKENS = 8192; +const MINIMAX_API_COST = { + input: 0.3, + output: 1.2, + cacheRead: 0.03, + cacheWrite: 0.12, +}; + +function buildMinimaxModel(params: { + id: string; + name: string; + reasoning: boolean; + input: ProviderModelConfig["input"]; +}): ProviderModelConfig { + return { + id: params.id, + name: params.name, + reasoning: params.reasoning, + input: params.input, + cost: MINIMAX_API_COST, + contextWindow: MINIMAX_DEFAULT_CONTEXT_WINDOW, + maxTokens: MINIMAX_DEFAULT_MAX_TOKENS, + }; +} + +function buildMinimaxTextModel(params: { + id: string; + name: string; + reasoning: boolean; +}): ProviderModelConfig { + return buildMinimaxModel({ ...params, input: ["text"] }); +} + +const XIAOMI_BASE_URL = "https://api.xiaomimimo.com/anthropic"; +export const XIAOMI_DEFAULT_MODEL_ID = "mimo-v2-flash"; +const XIAOMI_DEFAULT_CONTEXT_WINDOW = 262144; +const XIAOMI_DEFAULT_MAX_TOKENS = 8192; +const XIAOMI_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; +const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; +const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; +const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; +const MOONSHOT_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/"; +const KIMI_CODING_DEFAULT_MODEL_ID = "k2p5"; +const KIMI_CODING_DEFAULT_CONTEXT_WINDOW = 262144; +const KIMI_CODING_DEFAULT_MAX_TOKENS = 32768; +const KIMI_CODING_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1"; +const QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW = 128000; +const QWEN_PORTAL_DEFAULT_MAX_TOKENS = 8192; +const QWEN_PORTAL_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; +const OPENROUTER_DEFAULT_MODEL_ID = "auto"; +const OPENROUTER_DEFAULT_CONTEXT_WINDOW = 200000; +const OPENROUTER_DEFAULT_MAX_TOKENS = 8192; +const OPENROUTER_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +export const QIANFAN_BASE_URL = "https://qianfan.baidubce.com/v2"; +export const QIANFAN_DEFAULT_MODEL_ID = "deepseek-v3.2"; +const QIANFAN_DEFAULT_CONTEXT_WINDOW = 98304; +const QIANFAN_DEFAULT_MAX_TOKENS = 32768; +const QIANFAN_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +export const MODELSTUDIO_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1"; +export const MODELSTUDIO_DEFAULT_MODEL_ID = "qwen3.5-plus"; +const MODELSTUDIO_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const MODELSTUDIO_MODEL_CATALOG: ReadonlyArray = [ + { + id: "qwen3.5-plus", + name: "qwen3.5-plus", + reasoning: false, + input: ["text", "image"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 1_000_000, + maxTokens: 65_536, + }, + { + id: "qwen3-max-2026-01-23", + name: "qwen3-max-2026-01-23", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 262_144, + maxTokens: 65_536, + }, + { + id: "qwen3-coder-next", + name: "qwen3-coder-next", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 262_144, + maxTokens: 65_536, + }, + { + id: "qwen3-coder-plus", + name: "qwen3-coder-plus", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 1_000_000, + maxTokens: 65_536, + }, + { + id: "MiniMax-M2.5", + name: "MiniMax-M2.5", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 1_000_000, + maxTokens: 65_536, + }, + { + id: "glm-5", + name: "glm-5", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 202_752, + maxTokens: 16_384, + }, + { + id: "glm-4.7", + name: "glm-4.7", + reasoning: false, + input: ["text"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 202_752, + maxTokens: 16_384, + }, + { + id: "kimi-k2.5", + name: "kimi-k2.5", + reasoning: false, + input: ["text", "image"], + cost: MODELSTUDIO_DEFAULT_COST, + contextWindow: 262_144, + maxTokens: 32_768, + }, +]; + +const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"; +const NVIDIA_DEFAULT_MODEL_ID = "nvidia/llama-3.1-nemotron-70b-instruct"; +const NVIDIA_DEFAULT_CONTEXT_WINDOW = 131072; +const NVIDIA_DEFAULT_MAX_TOKENS = 4096; +const NVIDIA_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api"; + +export function buildMinimaxProvider(): ProviderConfig { + return { + baseUrl: MINIMAX_PORTAL_BASE_URL, + api: "anthropic-messages", + authHeader: true, + models: [ + buildMinimaxModel({ + id: MINIMAX_DEFAULT_VISION_MODEL_ID, + name: "MiniMax VL 01", + reasoning: false, + input: ["text", "image"], + }), + buildMinimaxTextModel({ + id: "MiniMax-M2.5", + name: "MiniMax M2.5", + reasoning: true, + }), + buildMinimaxTextModel({ + id: "MiniMax-M2.5-highspeed", + name: "MiniMax M2.5 Highspeed", + reasoning: true, + }), + ], + }; +} + +export function buildMinimaxPortalProvider(): ProviderConfig { + return { + baseUrl: MINIMAX_PORTAL_BASE_URL, + api: "anthropic-messages", + authHeader: true, + models: [ + buildMinimaxModel({ + id: MINIMAX_DEFAULT_VISION_MODEL_ID, + name: "MiniMax VL 01", + reasoning: false, + input: ["text", "image"], + }), + buildMinimaxTextModel({ + id: MINIMAX_DEFAULT_MODEL_ID, + name: "MiniMax M2.5", + reasoning: true, + }), + buildMinimaxTextModel({ + id: "MiniMax-M2.5-highspeed", + name: "MiniMax M2.5 Highspeed", + reasoning: true, + }), + ], + }; +} + +export function buildMoonshotProvider(): ProviderConfig { + return { + baseUrl: MOONSHOT_BASE_URL, + api: "openai-completions", + models: [ + { + id: MOONSHOT_DEFAULT_MODEL_ID, + name: "Kimi K2.5", + reasoning: false, + input: ["text", "image"], + cost: MOONSHOT_DEFAULT_COST, + contextWindow: MOONSHOT_DEFAULT_CONTEXT_WINDOW, + maxTokens: MOONSHOT_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + +export function buildKimiCodingProvider(): ProviderConfig { + return { + baseUrl: KIMI_CODING_BASE_URL, + api: "anthropic-messages", + models: [ + { + id: KIMI_CODING_DEFAULT_MODEL_ID, + name: "Kimi for Coding", + reasoning: true, + input: ["text", "image"], + cost: KIMI_CODING_DEFAULT_COST, + contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW, + maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + +export function buildQwenPortalProvider(): ProviderConfig { + return { + baseUrl: QWEN_PORTAL_BASE_URL, + api: "openai-completions", + models: [ + { + id: "coder-model", + name: "Qwen Coder", + reasoning: false, + input: ["text"], + cost: QWEN_PORTAL_DEFAULT_COST, + contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, + maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS, + }, + { + id: "vision-model", + name: "Qwen Vision", + reasoning: false, + input: ["text", "image"], + cost: QWEN_PORTAL_DEFAULT_COST, + contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, + maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + +export function buildSyntheticProvider(): ProviderConfig { + return { + baseUrl: SYNTHETIC_BASE_URL, + api: "anthropic-messages", + models: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition), + }; +} + +export function buildDoubaoProvider(): ProviderConfig { + return { + baseUrl: DOUBAO_BASE_URL, + api: "openai-completions", + models: DOUBAO_MODEL_CATALOG.map(buildDoubaoModelDefinition), + }; +} + +export function buildDoubaoCodingProvider(): ProviderConfig { + return { + baseUrl: DOUBAO_CODING_BASE_URL, + api: "openai-completions", + models: DOUBAO_CODING_MODEL_CATALOG.map(buildDoubaoModelDefinition), + }; +} + +export function buildBytePlusProvider(): ProviderConfig { + return { + baseUrl: BYTEPLUS_BASE_URL, + api: "openai-completions", + models: BYTEPLUS_MODEL_CATALOG.map(buildBytePlusModelDefinition), + }; +} + +export function buildBytePlusCodingProvider(): ProviderConfig { + return { + baseUrl: BYTEPLUS_CODING_BASE_URL, + api: "openai-completions", + models: BYTEPLUS_CODING_MODEL_CATALOG.map(buildBytePlusModelDefinition), + }; +} + +export function buildXiaomiProvider(): ProviderConfig { + return { + baseUrl: XIAOMI_BASE_URL, + api: "anthropic-messages", + models: [ + { + id: XIAOMI_DEFAULT_MODEL_ID, + name: "Xiaomi MiMo V2 Flash", + reasoning: false, + input: ["text"], + cost: XIAOMI_DEFAULT_COST, + contextWindow: XIAOMI_DEFAULT_CONTEXT_WINDOW, + maxTokens: XIAOMI_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + +export function buildTogetherProvider(): ProviderConfig { + return { + baseUrl: TOGETHER_BASE_URL, + api: "openai-completions", + models: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition), + }; +} + +export function buildOpenrouterProvider(): ProviderConfig { + return { + baseUrl: OPENROUTER_BASE_URL, + api: "openai-completions", + models: [ + { + id: OPENROUTER_DEFAULT_MODEL_ID, + name: "OpenRouter Auto", + reasoning: false, + input: ["text", "image"], + cost: OPENROUTER_DEFAULT_COST, + contextWindow: OPENROUTER_DEFAULT_CONTEXT_WINDOW, + maxTokens: OPENROUTER_DEFAULT_MAX_TOKENS, + }, + { + id: "openrouter/hunter-alpha", + name: "Hunter Alpha", + reasoning: true, + input: ["text"], + cost: OPENROUTER_DEFAULT_COST, + contextWindow: 1048576, + maxTokens: 65536, + }, + { + id: "openrouter/healer-alpha", + name: "Healer Alpha", + reasoning: true, + input: ["text", "image"], + cost: OPENROUTER_DEFAULT_COST, + contextWindow: 262144, + maxTokens: 65536, + }, + ], + }; +} + +export function buildOpenAICodexProvider(): ProviderConfig { + return { + baseUrl: OPENAI_CODEX_BASE_URL, + api: "openai-codex-responses", + models: [], + }; +} + +export function buildQianfanProvider(): ProviderConfig { + return { + baseUrl: QIANFAN_BASE_URL, + api: "openai-completions", + models: [ + { + id: QIANFAN_DEFAULT_MODEL_ID, + name: "DEEPSEEK V3.2", + reasoning: true, + input: ["text"], + cost: QIANFAN_DEFAULT_COST, + contextWindow: QIANFAN_DEFAULT_CONTEXT_WINDOW, + maxTokens: QIANFAN_DEFAULT_MAX_TOKENS, + }, + { + id: "ernie-5.0-thinking-preview", + name: "ERNIE-5.0-Thinking-Preview", + reasoning: true, + input: ["text", "image"], + cost: QIANFAN_DEFAULT_COST, + contextWindow: 119000, + maxTokens: 64000, + }, + ], + }; +} + +export function buildModelStudioProvider(): ProviderConfig { + return { + baseUrl: MODELSTUDIO_BASE_URL, + api: "openai-completions", + models: MODELSTUDIO_MODEL_CATALOG.map((model) => ({ ...model })), + }; +} + +export function buildNvidiaProvider(): ProviderConfig { + return { + baseUrl: NVIDIA_BASE_URL, + api: "openai-completions", + models: [ + { + id: NVIDIA_DEFAULT_MODEL_ID, + name: "NVIDIA Llama 3.1 Nemotron 70B Instruct", + reasoning: false, + input: ["text"], + cost: NVIDIA_DEFAULT_COST, + contextWindow: NVIDIA_DEFAULT_CONTEXT_WINDOW, + maxTokens: NVIDIA_DEFAULT_MAX_TOKENS, + }, + { + id: "meta/llama-3.3-70b-instruct", + name: "Meta Llama 3.3 70B Instruct", + reasoning: false, + input: ["text"], + cost: NVIDIA_DEFAULT_COST, + contextWindow: 131072, + maxTokens: 4096, + }, + { + id: "nvidia/mistral-nemo-minitron-8b-8k-instruct", + name: "NVIDIA Mistral NeMo Minitron 8B Instruct", + reasoning: false, + input: ["text"], + cost: NVIDIA_DEFAULT_COST, + contextWindow: 8192, + maxTokens: 2048, + }, + ], + }; +} + +export function buildKilocodeProvider(): ProviderConfig { + return { + baseUrl: KILOCODE_BASE_URL, + api: "openai-completions", + models: KILOCODE_MODEL_CATALOG.map((model) => ({ + id: model.id, + name: model.name, + reasoning: model.reasoning, + input: model.input, + cost: KILOCODE_DEFAULT_COST, + contextWindow: model.contextWindow ?? KILOCODE_DEFAULT_CONTEXT_WINDOW, + maxTokens: model.maxTokens ?? KILOCODE_DEFAULT_MAX_TOKENS, + })), + }; +} + +export function buildDeepInfraStaticProvider(): ProviderConfig { + return { + baseUrl: DEEPINFRA_BASE_URL, + api: "openai-completions", + models: buildStaticCatalog(), + }; +} +>>>>>>> c1f76e734 (Addressed comment: Duplicated catalog-mapping logic with buildDeepInfraStaticProvider) From 7b69a40d47ec3370722bbd064f295073e0b8a7c8 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Wed, 18 Mar 2026 07:15:57 +0200 Subject: [PATCH 05/20] :Fixes after rebasing with master --- extensions/deepinfra/index.ts | 70 +++ extensions/deepinfra/onboard.ts | 35 ++ extensions/deepinfra/openclaw.plugin.json | 28 + extensions/deepinfra/package.json | 12 + extensions/deepinfra/provider-catalog.ts | 18 +- pnpm-lock.yaml | 2 + src/agents/models-config.providers.static.ts | 560 ------------------ src/agents/models-config.providers.ts | 1 + .../pi-embedded-runner/cache-ttl.test.ts | 5 + .../pi-embedded-runner/deepinfra.test.ts | 2 +- ...onboard-auth.config-core.deepinfra.test.ts | 9 +- src/commands/onboard-auth.ts | 141 ----- src/plugin-sdk/provider-models.ts | 5 + ...undled-provider-auth-env-vars.generated.ts | 1 + src/plugins/contracts/registry.ts | 2 + src/secrets/provider-env-vars.ts | 1 - 16 files changed, 176 insertions(+), 716 deletions(-) create mode 100644 extensions/deepinfra/onboard.ts create mode 100644 extensions/deepinfra/openclaw.plugin.json create mode 100644 extensions/deepinfra/package.json delete mode 100644 src/commands/onboard-auth.ts diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts index e69de29bb2d..9ef33860680 100644 --- a/extensions/deepinfra/index.ts +++ b/extensions/deepinfra/index.ts @@ -0,0 +1,70 @@ +import { definePluginEntry } from "openclaw/plugin-sdk/core"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; +import { applyDeepInfraConfig, DEEPINFRA_DEFAULT_MODEL_REF } from "./onboard.js"; +import { buildDeepInfraProviderWithDiscovery } from "./provider-catalog.js"; + +const PROVIDER_ID = "deepinfra"; + +const DEEPINFRA_CACHE_TTL_MODEL_PREFIXES = [ + "anthropic/", + "moonshot/", + "moonshotai/", + "zai/", + "zai-org/", +] as const; + +function isDeepInfraCacheTtlModel(modelId: string): boolean { + return DEEPINFRA_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix)); +} + +export default definePluginEntry({ + id: PROVIDER_ID, + name: "DeepInfra Provider", + description: "Bundled DeepInfra provider plugin", + register(api) { + api.registerProvider({ + id: PROVIDER_ID, + label: "DeepInfra", + docsPath: "/providers/deepinfra", + envVars: ["DEEPINFRA_API_KEY"], + auth: [ + createProviderApiKeyAuthMethod({ + providerId: PROVIDER_ID, + methodId: "api-key", + label: "DeepInfra API key", + hint: "Unified API for open source models", + optionKey: "deepinfraApiKey", + flagName: "--deepinfra-api-key", + envVar: "DEEPINFRA_API_KEY", + promptMessage: "Enter DeepInfra API key", + defaultModel: DEEPINFRA_DEFAULT_MODEL_REF, + expectedProviders: ["deepinfra"], + applyConfig: (cfg) => applyDeepInfraConfig(cfg), + wizard: { + choiceId: "deepinfra-api-key", + choiceLabel: "DeepInfra API key", + groupId: "deepinfra", + groupLabel: "DeepInfra", + groupHint: "Unified API for open source models", + }, + }), + ], + catalog: { + order: "simple", + run: (ctx) => + buildSingleProviderApiKeyCatalog({ + ctx, + providerId: PROVIDER_ID, + buildProvider: buildDeepInfraProviderWithDiscovery, + }), + }, + capabilities: { + openAiCompatTurnValidation: false, + geminiThoughtSignatureSanitization: true, + geminiThoughtSignatureModelHints: ["gemini"], + }, + isCacheTtlEligible: (ctx) => isDeepInfraCacheTtlModel(ctx.modelId), + }); + }, +}); diff --git a/extensions/deepinfra/onboard.ts b/extensions/deepinfra/onboard.ts new file mode 100644 index 00000000000..248eac4ba4c --- /dev/null +++ b/extensions/deepinfra/onboard.ts @@ -0,0 +1,35 @@ +import { + DEEPINFRA_BASE_URL, + DEEPINFRA_DEFAULT_MODEL_REF, +} from "openclaw/plugin-sdk/provider-models"; +import { + applyAgentDefaultModelPrimary, + applyProviderConfigWithModelCatalog, + type OpenClawConfig, +} from "openclaw/plugin-sdk/provider-onboard"; +import { buildDeepInfraStaticProvider } from "./provider-catalog.js"; + +export { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF }; + +export function applyDeepInfraProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[DEEPINFRA_DEFAULT_MODEL_REF] = { + ...models[DEEPINFRA_DEFAULT_MODEL_REF], + alias: models[DEEPINFRA_DEFAULT_MODEL_REF]?.alias ?? "DeepInfra", + }; + + return applyProviderConfigWithModelCatalog(cfg, { + agentModels: models, + providerId: "deepinfra", + api: "openai-completions", + baseUrl: DEEPINFRA_BASE_URL, + catalogModels: buildDeepInfraStaticProvider().models ?? [], + }); +} + +export function applyDeepInfraConfig(cfg: OpenClawConfig): OpenClawConfig { + return applyAgentDefaultModelPrimary( + applyDeepInfraProviderConfig(cfg), + DEEPINFRA_DEFAULT_MODEL_REF, + ); +} diff --git a/extensions/deepinfra/openclaw.plugin.json b/extensions/deepinfra/openclaw.plugin.json new file mode 100644 index 00000000000..cac903f6973 --- /dev/null +++ b/extensions/deepinfra/openclaw.plugin.json @@ -0,0 +1,28 @@ +{ + "id": "deepinfra", + "providers": ["deepinfra"], + "providerAuthEnvVars": { + "deepinfra": ["DEEPINFRA_API_KEY"] + }, + "providerAuthChoices": [ + { + "provider": "deepinfra", + "method": "api-key", + "choiceId": "deepinfra-api-key", + "choiceLabel": "DeepInfra API key", + "choiceHint": "Unified API for open source models", + "groupId": "deepinfra", + "groupLabel": "DeepInfra", + "groupHint": "Unified API for open source models", + "optionKey": "deepinfraApiKey", + "cliFlag": "--deepinfra-api-key", + "cliOption": "--deepinfra-api-key ", + "cliDescription": "DeepInfra API key" + } + ], + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/extensions/deepinfra/package.json b/extensions/deepinfra/package.json new file mode 100644 index 00000000000..67915591d64 --- /dev/null +++ b/extensions/deepinfra/package.json @@ -0,0 +1,12 @@ +{ + "name": "@openclaw/deepinfra-provider", + "version": "2026.3.14", + "private": true, + "description": "OpenClaw DeepInfra provider plugin", + "type": "module", + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/extensions/deepinfra/provider-catalog.ts b/extensions/deepinfra/provider-catalog.ts index c047e32d986..bedfc5d7fdc 100644 --- a/extensions/deepinfra/provider-catalog.ts +++ b/extensions/deepinfra/provider-catalog.ts @@ -1,13 +1,11 @@ import { - DEEPINFRA_BASE_URL, -} from "../providers/deepinfra-shared.ts"; + type ModelProviderConfig, + discoverDeepInfraModels, + buildDeepInfraStaticCatalog, + DEEPINFRA_BASE_URL, +} from "openclaw/plugin-sdk/provider-models"; -import { - discoverDeepInfraModels, - buildStaticCatalog - } from "./deepinfra-models.js"; - -export async function buildDeepInfraProviderWithDiscovery(): Promise { +export async function buildDeepInfraProviderWithDiscovery(): Promise { const models = await discoverDeepInfraModels(); return { baseUrl: DEEPINFRA_BASE_URL, @@ -16,10 +14,10 @@ export async function buildDeepInfraProviderWithDiscovery(): Promise; -type ProviderConfig = NonNullable[string]; -type ProviderModelConfig = NonNullable[number]; - -const MINIMAX_PORTAL_BASE_URL = "https://api.minimax.io/anthropic"; -const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.5"; -const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01"; -const MINIMAX_DEFAULT_CONTEXT_WINDOW = 200000; -const MINIMAX_DEFAULT_MAX_TOKENS = 8192; -const MINIMAX_API_COST = { - input: 0.3, - output: 1.2, - cacheRead: 0.03, - cacheWrite: 0.12, -}; - -function buildMinimaxModel(params: { - id: string; - name: string; - reasoning: boolean; - input: ProviderModelConfig["input"]; -}): ProviderModelConfig { - return { - id: params.id, - name: params.name, - reasoning: params.reasoning, - input: params.input, - cost: MINIMAX_API_COST, - contextWindow: MINIMAX_DEFAULT_CONTEXT_WINDOW, - maxTokens: MINIMAX_DEFAULT_MAX_TOKENS, - }; -} - -function buildMinimaxTextModel(params: { - id: string; - name: string; - reasoning: boolean; -}): ProviderModelConfig { - return buildMinimaxModel({ ...params, input: ["text"] }); -} - -const XIAOMI_BASE_URL = "https://api.xiaomimimo.com/anthropic"; -export const XIAOMI_DEFAULT_MODEL_ID = "mimo-v2-flash"; -const XIAOMI_DEFAULT_CONTEXT_WINDOW = 262144; -const XIAOMI_DEFAULT_MAX_TOKENS = 8192; -const XIAOMI_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; -const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; -const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; -const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; -const MOONSHOT_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/"; -const KIMI_CODING_DEFAULT_MODEL_ID = "k2p5"; -const KIMI_CODING_DEFAULT_CONTEXT_WINDOW = 262144; -const KIMI_CODING_DEFAULT_MAX_TOKENS = 32768; -const KIMI_CODING_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1"; -const QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW = 128000; -const QWEN_PORTAL_DEFAULT_MAX_TOKENS = 8192; -const QWEN_PORTAL_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; -const OPENROUTER_DEFAULT_MODEL_ID = "auto"; -const OPENROUTER_DEFAULT_CONTEXT_WINDOW = 200000; -const OPENROUTER_DEFAULT_MAX_TOKENS = 8192; -const OPENROUTER_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -export const QIANFAN_BASE_URL = "https://qianfan.baidubce.com/v2"; -export const QIANFAN_DEFAULT_MODEL_ID = "deepseek-v3.2"; -const QIANFAN_DEFAULT_CONTEXT_WINDOW = 98304; -const QIANFAN_DEFAULT_MAX_TOKENS = 32768; -const QIANFAN_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -export const MODELSTUDIO_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1"; -export const MODELSTUDIO_DEFAULT_MODEL_ID = "qwen3.5-plus"; -const MODELSTUDIO_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const MODELSTUDIO_MODEL_CATALOG: ReadonlyArray = [ - { - id: "qwen3.5-plus", - name: "qwen3.5-plus", - reasoning: false, - input: ["text", "image"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 1_000_000, - maxTokens: 65_536, - }, - { - id: "qwen3-max-2026-01-23", - name: "qwen3-max-2026-01-23", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 262_144, - maxTokens: 65_536, - }, - { - id: "qwen3-coder-next", - name: "qwen3-coder-next", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 262_144, - maxTokens: 65_536, - }, - { - id: "qwen3-coder-plus", - name: "qwen3-coder-plus", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 1_000_000, - maxTokens: 65_536, - }, - { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 1_000_000, - maxTokens: 65_536, - }, - { - id: "glm-5", - name: "glm-5", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 202_752, - maxTokens: 16_384, - }, - { - id: "glm-4.7", - name: "glm-4.7", - reasoning: false, - input: ["text"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 202_752, - maxTokens: 16_384, - }, - { - id: "kimi-k2.5", - name: "kimi-k2.5", - reasoning: false, - input: ["text", "image"], - cost: MODELSTUDIO_DEFAULT_COST, - contextWindow: 262_144, - maxTokens: 32_768, - }, -]; - -const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"; -const NVIDIA_DEFAULT_MODEL_ID = "nvidia/llama-3.1-nemotron-70b-instruct"; -const NVIDIA_DEFAULT_CONTEXT_WINDOW = 131072; -const NVIDIA_DEFAULT_MAX_TOKENS = 4096; -const NVIDIA_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; - -const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api"; - -export function buildMinimaxProvider(): ProviderConfig { - return { - baseUrl: MINIMAX_PORTAL_BASE_URL, - api: "anthropic-messages", - authHeader: true, - models: [ - buildMinimaxModel({ - id: MINIMAX_DEFAULT_VISION_MODEL_ID, - name: "MiniMax VL 01", - reasoning: false, - input: ["text", "image"], - }), - buildMinimaxTextModel({ - id: "MiniMax-M2.5", - name: "MiniMax M2.5", - reasoning: true, - }), - buildMinimaxTextModel({ - id: "MiniMax-M2.5-highspeed", - name: "MiniMax M2.5 Highspeed", - reasoning: true, - }), - ], - }; -} - -export function buildMinimaxPortalProvider(): ProviderConfig { - return { - baseUrl: MINIMAX_PORTAL_BASE_URL, - api: "anthropic-messages", - authHeader: true, - models: [ - buildMinimaxModel({ - id: MINIMAX_DEFAULT_VISION_MODEL_ID, - name: "MiniMax VL 01", - reasoning: false, - input: ["text", "image"], - }), - buildMinimaxTextModel({ - id: MINIMAX_DEFAULT_MODEL_ID, - name: "MiniMax M2.5", - reasoning: true, - }), - buildMinimaxTextModel({ - id: "MiniMax-M2.5-highspeed", - name: "MiniMax M2.5 Highspeed", - reasoning: true, - }), - ], - }; -} - -export function buildMoonshotProvider(): ProviderConfig { - return { - baseUrl: MOONSHOT_BASE_URL, - api: "openai-completions", - models: [ - { - id: MOONSHOT_DEFAULT_MODEL_ID, - name: "Kimi K2.5", - reasoning: false, - input: ["text", "image"], - cost: MOONSHOT_DEFAULT_COST, - contextWindow: MOONSHOT_DEFAULT_CONTEXT_WINDOW, - maxTokens: MOONSHOT_DEFAULT_MAX_TOKENS, - }, - ], - }; -} - -export function buildKimiCodingProvider(): ProviderConfig { - return { - baseUrl: KIMI_CODING_BASE_URL, - api: "anthropic-messages", - models: [ - { - id: KIMI_CODING_DEFAULT_MODEL_ID, - name: "Kimi for Coding", - reasoning: true, - input: ["text", "image"], - cost: KIMI_CODING_DEFAULT_COST, - contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW, - maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS, - }, - ], - }; -} - -export function buildQwenPortalProvider(): ProviderConfig { - return { - baseUrl: QWEN_PORTAL_BASE_URL, - api: "openai-completions", - models: [ - { - id: "coder-model", - name: "Qwen Coder", - reasoning: false, - input: ["text"], - cost: QWEN_PORTAL_DEFAULT_COST, - contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, - maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS, - }, - { - id: "vision-model", - name: "Qwen Vision", - reasoning: false, - input: ["text", "image"], - cost: QWEN_PORTAL_DEFAULT_COST, - contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, - maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS, - }, - ], - }; -} - -export function buildSyntheticProvider(): ProviderConfig { - return { - baseUrl: SYNTHETIC_BASE_URL, - api: "anthropic-messages", - models: SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition), - }; -} - -export function buildDoubaoProvider(): ProviderConfig { - return { - baseUrl: DOUBAO_BASE_URL, - api: "openai-completions", - models: DOUBAO_MODEL_CATALOG.map(buildDoubaoModelDefinition), - }; -} - -export function buildDoubaoCodingProvider(): ProviderConfig { - return { - baseUrl: DOUBAO_CODING_BASE_URL, - api: "openai-completions", - models: DOUBAO_CODING_MODEL_CATALOG.map(buildDoubaoModelDefinition), - }; -} - -export function buildBytePlusProvider(): ProviderConfig { - return { - baseUrl: BYTEPLUS_BASE_URL, - api: "openai-completions", - models: BYTEPLUS_MODEL_CATALOG.map(buildBytePlusModelDefinition), - }; -} - -export function buildBytePlusCodingProvider(): ProviderConfig { - return { - baseUrl: BYTEPLUS_CODING_BASE_URL, - api: "openai-completions", - models: BYTEPLUS_CODING_MODEL_CATALOG.map(buildBytePlusModelDefinition), - }; -} - -export function buildXiaomiProvider(): ProviderConfig { - return { - baseUrl: XIAOMI_BASE_URL, - api: "anthropic-messages", - models: [ - { - id: XIAOMI_DEFAULT_MODEL_ID, - name: "Xiaomi MiMo V2 Flash", - reasoning: false, - input: ["text"], - cost: XIAOMI_DEFAULT_COST, - contextWindow: XIAOMI_DEFAULT_CONTEXT_WINDOW, - maxTokens: XIAOMI_DEFAULT_MAX_TOKENS, - }, - ], - }; -} - -export function buildTogetherProvider(): ProviderConfig { - return { - baseUrl: TOGETHER_BASE_URL, - api: "openai-completions", - models: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition), - }; -} - -export function buildOpenrouterProvider(): ProviderConfig { - return { - baseUrl: OPENROUTER_BASE_URL, - api: "openai-completions", - models: [ - { - id: OPENROUTER_DEFAULT_MODEL_ID, - name: "OpenRouter Auto", - reasoning: false, - input: ["text", "image"], - cost: OPENROUTER_DEFAULT_COST, - contextWindow: OPENROUTER_DEFAULT_CONTEXT_WINDOW, - maxTokens: OPENROUTER_DEFAULT_MAX_TOKENS, - }, - { - id: "openrouter/hunter-alpha", - name: "Hunter Alpha", - reasoning: true, - input: ["text"], - cost: OPENROUTER_DEFAULT_COST, - contextWindow: 1048576, - maxTokens: 65536, - }, - { - id: "openrouter/healer-alpha", - name: "Healer Alpha", - reasoning: true, - input: ["text", "image"], - cost: OPENROUTER_DEFAULT_COST, - contextWindow: 262144, - maxTokens: 65536, - }, - ], - }; -} - -export function buildOpenAICodexProvider(): ProviderConfig { - return { - baseUrl: OPENAI_CODEX_BASE_URL, - api: "openai-codex-responses", - models: [], - }; -} - -export function buildQianfanProvider(): ProviderConfig { - return { - baseUrl: QIANFAN_BASE_URL, - api: "openai-completions", - models: [ - { - id: QIANFAN_DEFAULT_MODEL_ID, - name: "DEEPSEEK V3.2", - reasoning: true, - input: ["text"], - cost: QIANFAN_DEFAULT_COST, - contextWindow: QIANFAN_DEFAULT_CONTEXT_WINDOW, - maxTokens: QIANFAN_DEFAULT_MAX_TOKENS, - }, - { - id: "ernie-5.0-thinking-preview", - name: "ERNIE-5.0-Thinking-Preview", - reasoning: true, - input: ["text", "image"], - cost: QIANFAN_DEFAULT_COST, - contextWindow: 119000, - maxTokens: 64000, - }, - ], - }; -} - -export function buildModelStudioProvider(): ProviderConfig { - return { - baseUrl: MODELSTUDIO_BASE_URL, - api: "openai-completions", - models: MODELSTUDIO_MODEL_CATALOG.map((model) => ({ ...model })), - }; -} - -export function buildNvidiaProvider(): ProviderConfig { - return { - baseUrl: NVIDIA_BASE_URL, - api: "openai-completions", - models: [ - { - id: NVIDIA_DEFAULT_MODEL_ID, - name: "NVIDIA Llama 3.1 Nemotron 70B Instruct", - reasoning: false, - input: ["text"], - cost: NVIDIA_DEFAULT_COST, - contextWindow: NVIDIA_DEFAULT_CONTEXT_WINDOW, - maxTokens: NVIDIA_DEFAULT_MAX_TOKENS, - }, - { - id: "meta/llama-3.3-70b-instruct", - name: "Meta Llama 3.3 70B Instruct", - reasoning: false, - input: ["text"], - cost: NVIDIA_DEFAULT_COST, - contextWindow: 131072, - maxTokens: 4096, - }, - { - id: "nvidia/mistral-nemo-minitron-8b-8k-instruct", - name: "NVIDIA Mistral NeMo Minitron 8B Instruct", - reasoning: false, - input: ["text"], - cost: NVIDIA_DEFAULT_COST, - contextWindow: 8192, - maxTokens: 2048, - }, - ], - }; -} - -export function buildKilocodeProvider(): ProviderConfig { - return { - baseUrl: KILOCODE_BASE_URL, - api: "openai-completions", - models: KILOCODE_MODEL_CATALOG.map((model) => ({ - id: model.id, - name: model.name, - reasoning: model.reasoning, - input: model.input, - cost: KILOCODE_DEFAULT_COST, - contextWindow: model.contextWindow ?? KILOCODE_DEFAULT_CONTEXT_WINDOW, - maxTokens: model.maxTokens ?? KILOCODE_DEFAULT_MAX_TOKENS, - })), - }; -} - -export function buildDeepInfraStaticProvider(): ProviderConfig { - return { - baseUrl: DEEPINFRA_BASE_URL, - api: "openai-completions", - models: buildStaticCatalog(), - }; -} ->>>>>>> c1f76e734 (Addressed comment: Duplicated catalog-mapping logic with buildDeepInfraStaticProvider) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index af9c3d6e34a..1fa7289ef54 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -11,6 +11,7 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles import { discoverBedrockModels } from "./bedrock-discovery.js"; import { normalizeGoogleModelId } from "./model-id-normalization.js"; import { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; +export { buildDeepInfraStaticProvider } from "../../extensions/deepinfra/provider-catalog.js"; export { buildKimiCodingProvider } from "../../extensions/kimi-coding/provider-catalog.js"; export { buildKilocodeProvider } from "../../extensions/kilocode/provider-catalog.js"; export { diff --git a/src/agents/pi-embedded-runner/cache-ttl.test.ts b/src/agents/pi-embedded-runner/cache-ttl.test.ts index 066cb555a30..a806309a240 100644 --- a/src/agents/pi-embedded-runner/cache-ttl.test.ts +++ b/src/agents/pi-embedded-runner/cache-ttl.test.ts @@ -15,6 +15,11 @@ vi.mock("../../plugins/provider-runtime.js", () => ({ params.context.modelId.startsWith(prefix), ); } + if (params.context.provider === "deepinfra") { + return ["anthropic/", "moonshot/", "moonshotai/", "zai/", "zai-org/"].some((prefix) => + params.context.modelId.startsWith(prefix), + ); + } return undefined; }, })); diff --git a/src/agents/pi-embedded-runner/deepinfra.test.ts b/src/agents/pi-embedded-runner/deepinfra.test.ts index 710514637e5..7c8a19a9a67 100644 --- a/src/agents/pi-embedded-runner/deepinfra.test.ts +++ b/src/agents/pi-embedded-runner/deepinfra.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { isCacheTtlEligibleProvider } from "./cache-ttl.ts"; +import { isCacheTtlEligibleProvider } from "./cache-ttl.js"; describe("deepinfra cache-ttl eligibility", () => { it("is eligible when model starts with zai", () => { diff --git a/src/commands/onboard-auth.config-core.deepinfra.test.ts b/src/commands/onboard-auth.config-core.deepinfra.test.ts index bc3c19f6b9c..b80b2a4544f 100644 --- a/src/commands/onboard-auth.config-core.deepinfra.test.ts +++ b/src/commands/onboard-auth.config-core.deepinfra.test.ts @@ -2,20 +2,23 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { + applyDeepInfraProviderConfig, + applyDeepInfraConfig, +} from "../../extensions/deepinfra/onboard.js"; import { resolveApiKeyForProvider, resolveEnvApiKey } from "../agents/model-auth.js"; import type { OpenClawConfig } from "../config/config.js"; import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; import { DEEPINFRA_BASE_URL, + DEEPINFRA_DEFAULT_CONTEXT_WINDOW, + DEEPINFRA_DEFAULT_COST, DEEPINFRA_DEFAULT_MODEL_ID, DEEPINFRA_DEFAULT_MODEL_REF, - DEEPINFRA_DEFAULT_CONTEXT_WINDOW, DEEPINFRA_DEFAULT_MAX_TOKENS, - DEEPINFRA_DEFAULT_COST, DEEPINFRA_MODEL_CATALOG, } from "../providers/deepinfra-shared.js"; import { captureEnv } from "../test-utils/env.js"; -import { applyDeepInfraProviderConfig, applyDeepInfraConfig } from "./onboard-auth.config-core.js"; const emptyCfg: OpenClawConfig = {}; const DEEPINFRA_MODEL_IDS = DEEPINFRA_MODEL_CATALOG.map((m) => m.id); diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts deleted file mode 100644 index ab154bbc58f..00000000000 --- a/src/commands/onboard-auth.ts +++ /dev/null @@ -1,141 +0,0 @@ -export { - SYNTHETIC_DEFAULT_MODEL_ID, - SYNTHETIC_DEFAULT_MODEL_REF, -} from "../agents/synthetic-models.js"; -export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js"; -export { - applyAuthProfileConfig, - applyCloudflareAiGatewayConfig, - applyCloudflareAiGatewayProviderConfig, - applyHuggingfaceConfig, - applyHuggingfaceProviderConfig, - applyDeepInfraConfig, - applyDeepInfraProviderConfig, - applyKilocodeConfig, - applyKilocodeProviderConfig, - applyQianfanConfig, - applyQianfanProviderConfig, - applyKimiCodeConfig, - applyKimiCodeProviderConfig, - applyLitellmConfig, - applyLitellmProviderConfig, - applyMistralConfig, - applyMistralProviderConfig, - applyMoonshotConfig, - applyMoonshotConfigCn, - applyMoonshotProviderConfig, - applyMoonshotProviderConfigCn, - applyOpenrouterConfig, - applyOpenrouterProviderConfig, - applySyntheticConfig, - applySyntheticProviderConfig, - applyTogetherConfig, - applyTogetherProviderConfig, - applyVeniceConfig, - applyVeniceProviderConfig, - applyVercelAiGatewayConfig, - applyVercelAiGatewayProviderConfig, - applyXaiConfig, - applyXaiProviderConfig, - applyXiaomiConfig, - applyXiaomiProviderConfig, - applyZaiConfig, - applyZaiProviderConfig, - applyModelStudioConfig, - applyModelStudioConfigCn, - applyModelStudioProviderConfig, - applyModelStudioProviderConfigCn, - KILOCODE_BASE_URL, -} from "./onboard-auth.config-core.js"; -export { - applyMinimaxApiConfig, - applyMinimaxApiConfigCn, - applyMinimaxApiProviderConfig, - applyMinimaxApiProviderConfigCn, - applyMinimaxConfig, - applyMinimaxHostedConfig, - applyMinimaxHostedProviderConfig, - applyMinimaxProviderConfig, -} from "./onboard-auth.config-minimax.js"; - -export { - applyOpencodeZenConfig, - applyOpencodeZenProviderConfig, -} from "./onboard-auth.config-opencode.js"; -export { - applyOpencodeGoConfig, - applyOpencodeGoProviderConfig, -} from "./onboard-auth.config-opencode-go.js"; -export { - CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, - DEEPINFRA_DEFAULT_MODEL_REF, - KILOCODE_DEFAULT_MODEL_REF, - LITELLM_DEFAULT_MODEL_REF, - OPENROUTER_DEFAULT_MODEL_REF, - setOpenaiApiKey, - setAnthropicApiKey, - setCloudflareAiGatewayConfig, - setByteplusApiKey, - setQianfanApiKey, - setGeminiApiKey, - setDeepInfraApiKey, - setKilocodeApiKey, - setLitellmApiKey, - setKimiCodingApiKey, - setMinimaxApiKey, - setMistralApiKey, - setMoonshotApiKey, - setOpencodeGoApiKey, - setOpencodeZenApiKey, - setOpenrouterApiKey, - setSyntheticApiKey, - setTogetherApiKey, - setHuggingfaceApiKey, - setVeniceApiKey, - setVercelAiGatewayApiKey, - setXiaomiApiKey, - setVolcengineApiKey, - setZaiApiKey, - setXaiApiKey, - setModelStudioApiKey, - writeOAuthCredentials, - HUGGINGFACE_DEFAULT_MODEL_REF, - VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, - XIAOMI_DEFAULT_MODEL_REF, - ZAI_DEFAULT_MODEL_REF, - TOGETHER_DEFAULT_MODEL_REF, - MISTRAL_DEFAULT_MODEL_REF, - XAI_DEFAULT_MODEL_REF, - MODELSTUDIO_DEFAULT_MODEL_REF, -} from "./onboard-auth.credentials.js"; -export { - buildKilocodeModelDefinition, - buildMinimaxApiModelDefinition, - buildMinimaxModelDefinition, - buildMistralModelDefinition, - buildMoonshotModelDefinition, - buildZaiModelDefinition, - DEFAULT_MINIMAX_BASE_URL, - KILOCODE_DEFAULT_MODEL_ID, - MOONSHOT_CN_BASE_URL, - QIANFAN_BASE_URL, - QIANFAN_DEFAULT_MODEL_ID, - QIANFAN_DEFAULT_MODEL_REF, - KIMI_CODING_MODEL_ID, - KIMI_CODING_MODEL_REF, - MINIMAX_API_BASE_URL, - MINIMAX_CN_API_BASE_URL, - MINIMAX_HOSTED_MODEL_ID, - MINIMAX_HOSTED_MODEL_REF, - MOONSHOT_BASE_URL, - MOONSHOT_DEFAULT_MODEL_ID, - MOONSHOT_DEFAULT_MODEL_REF, - MISTRAL_BASE_URL, - MISTRAL_DEFAULT_MODEL_ID, - resolveZaiBaseUrl, - ZAI_CODING_CN_BASE_URL, - ZAI_DEFAULT_MODEL_ID, - ZAI_CODING_GLOBAL_BASE_URL, - ZAI_CN_BASE_URL, - ZAI_GLOBAL_BASE_URL, -} from "./onboard-auth.models.js"; diff --git a/src/plugin-sdk/provider-models.ts b/src/plugin-sdk/provider-models.ts index 7103147e91d..b2d033ae138 100644 --- a/src/plugin-sdk/provider-models.ts +++ b/src/plugin-sdk/provider-models.ts @@ -105,6 +105,11 @@ export { discoverVercelAiGatewayModels, VERCEL_AI_GATEWAY_BASE_URL, } from "../agents/vercel-ai-gateway.js"; +export { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF } from "../providers/deepinfra-shared.js"; +export { + buildStaticCatalog as buildDeepInfraStaticCatalog, + discoverDeepInfraModels, +} from "../agents/deepinfra-models.js"; export function buildKilocodeModelDefinition(): ModelDefinitionConfig { return { diff --git a/src/plugins/bundled-provider-auth-env-vars.generated.ts b/src/plugins/bundled-provider-auth-env-vars.generated.ts index 80ebcedc2b9..116f83e16a7 100644 --- a/src/plugins/bundled-provider-auth-env-vars.generated.ts +++ b/src/plugins/bundled-provider-auth-env-vars.generated.ts @@ -6,6 +6,7 @@ export const BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES = { byteplus: ["BYTEPLUS_API_KEY"], chutes: ["CHUTES_API_KEY", "CHUTES_OAUTH_TOKEN"], "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"], + deepinfra: ["DEEPINFRA_API_KEY"], fal: ["FAL_KEY"], firecrawl: ["FIRECRAWL_API_KEY"], "github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"], diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index cde5b8e8e2d..130747ad683 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -5,6 +5,7 @@ import byteplusPlugin from "../../../extensions/byteplus/index.js"; import chutesPlugin from "../../../extensions/chutes/index.js"; import cloudflareAiGatewayPlugin from "../../../extensions/cloudflare-ai-gateway/index.js"; import copilotProxyPlugin from "../../../extensions/copilot-proxy/index.js"; +import deepinfraPlugin from "../../../extensions/deepinfra/index.js"; import elevenLabsPlugin from "../../../extensions/elevenlabs/index.js"; import falPlugin from "../../../extensions/fal/index.js"; import firecrawlPlugin from "../../../extensions/firecrawl/index.js"; @@ -360,6 +361,7 @@ const bundledProviderPlugins = dedupePlugins([ chutesPlugin, cloudflareAiGatewayPlugin, copilotProxyPlugin, + deepinfraPlugin, githubCopilotPlugin, falPlugin, googlePlugin, diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index d0cd661a999..269b37d836a 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -7,7 +7,6 @@ const CORE_PROVIDER_AUTH_ENV_VAR_CANDIDATES = { deepgram: ["DEEPGRAM_API_KEY"], cerebras: ["CEREBRAS_API_KEY"], litellm: ["LITELLM_API_KEY"], - deepinfra: ["DEEPINFRA_API_KEY"], } as const; const CORE_PROVIDER_SETUP_ENV_VAR_OVERRIDES = { From 6236093ccd6f07a3b67133a5198adff5698c1ad4 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 12:41:22 +0200 Subject: [PATCH 06/20] Fix model casing --- docs/providers/deepinfra.md | 6 +++--- src/agents/models-config.providers.deepinfra.test.ts | 6 +++--- src/providers/deepinfra-shared.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/providers/deepinfra.md b/docs/providers/deepinfra.md index 71c63372459..78ca59dc781 100644 --- a/docs/providers/deepinfra.md +++ b/docs/providers/deepinfra.md @@ -49,9 +49,9 @@ OpenClaw dynamically discovers available DeepInfra models at startup. Use Any model available on [DeepInfra.com](https://deepinfra.com/) can be used with the `deepinfra/` prefix: ``` -deepinfra/minimaxai/minimax-m2.5 -deepinfra/zai-org/glm-5 -deepinfra/moonshotai/kimi-k2.5 +deepinfra/MiniMaxAI/MiniMax-M2.5 +deepinfra/zai-org/GLM-5 +deepinfra/moonshotai/Kimi-K2.5 ...and many more ``` diff --git a/src/agents/models-config.providers.deepinfra.test.ts b/src/agents/models-config.providers.deepinfra.test.ts index d89e4f6b65c..b3f24d4ffe6 100644 --- a/src/agents/models-config.providers.deepinfra.test.ts +++ b/src/agents/models-config.providers.deepinfra.test.ts @@ -8,9 +8,9 @@ import { buildDeepInfraStaticProvider } from "./models-config.providers.ts"; const DEEPINFRA_MODEL_IDS = [ "openai/gpt-oss-120b", - "minimaxai/minimax-m2.5", - "zai-org/glm-5", - "moonshotai/kimi-k2.5", + "MiniMaxAI/MiniMax-M2.5", + "zai-org/GLM-5", + "moonshotai/Kimi-K2.5", ]; describe("DeepInfra implicit provider", () => { diff --git a/src/providers/deepinfra-shared.ts b/src/providers/deepinfra-shared.ts index 2f788deefdc..5cd6eafbae4 100644 --- a/src/providers/deepinfra-shared.ts +++ b/src/providers/deepinfra-shared.ts @@ -27,7 +27,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ maxTokens: 131072, }, { - id: "minimaxai/minimax-m2.5", + id: "MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5", reasoning: false, input: ["text"], @@ -35,7 +35,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ maxTokens: 196608, }, { - id: "zai-org/glm-5", + id: "zai-org/GLM-5", name: "GLM 5", reasoning: false, input: ["text"], @@ -43,7 +43,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ maxTokens: 202752, }, { - id: "moonshotai/kimi-k2.5", + id: "moonshotai/Kimi-K2.5", name: "Kimi K2.5", reasoning: false, input: ["text", "image"], From 395070e5f2c5690859b4ff80a31911be623aa10d Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 13:51:28 +0200 Subject: [PATCH 07/20] Remove the static catalog for DeepInfra and always rely on model discovery --- extensions/deepinfra/onboard.ts | 19 +++---- extensions/deepinfra/provider-catalog.ts | 9 ---- .../models-config.providers.deepinfra.test.ts | 20 ++++---- src/agents/models-config.providers.static.ts | 1 - src/agents/models-config.providers.ts | 1 - ...onboard-auth.config-core.deepinfra.test.ts | 51 ++----------------- src/plugin-sdk/provider-models.ts | 5 +- 7 files changed, 24 insertions(+), 82 deletions(-) diff --git a/extensions/deepinfra/onboard.ts b/extensions/deepinfra/onboard.ts index 248eac4ba4c..b19a7d6b1a2 100644 --- a/extensions/deepinfra/onboard.ts +++ b/extensions/deepinfra/onboard.ts @@ -4,10 +4,8 @@ import { } from "openclaw/plugin-sdk/provider-models"; import { applyAgentDefaultModelPrimary, - applyProviderConfigWithModelCatalog, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { buildDeepInfraStaticProvider } from "./provider-catalog.js"; export { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF }; @@ -18,13 +16,16 @@ export function applyDeepInfraProviderConfig(cfg: OpenClawConfig): OpenClawConfi alias: models[DEEPINFRA_DEFAULT_MODEL_REF]?.alias ?? "DeepInfra", }; - return applyProviderConfigWithModelCatalog(cfg, { - agentModels: models, - providerId: "deepinfra", - api: "openai-completions", - baseUrl: DEEPINFRA_BASE_URL, - catalogModels: buildDeepInfraStaticProvider().models ?? [], - }); + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + }; } export function applyDeepInfraConfig(cfg: OpenClawConfig): OpenClawConfig { diff --git a/extensions/deepinfra/provider-catalog.ts b/extensions/deepinfra/provider-catalog.ts index bedfc5d7fdc..6e3ba6c12b8 100644 --- a/extensions/deepinfra/provider-catalog.ts +++ b/extensions/deepinfra/provider-catalog.ts @@ -1,7 +1,6 @@ import { type ModelProviderConfig, discoverDeepInfraModels, - buildDeepInfraStaticCatalog, DEEPINFRA_BASE_URL, } from "openclaw/plugin-sdk/provider-models"; @@ -13,11 +12,3 @@ export async function buildDeepInfraProviderWithDiscovery(): Promise { }); it("should build deepinfra provider with correct configuration", () => { - const provider = buildDeepInfraStaticProvider(); - expect(provider.baseUrl).toBe("https://api.deepinfra.com/v1/openai/"); - expect(provider.api).toBe("openai-completions"); - expect(provider.models).toBeDefined(); - expect(provider.models.length).toBeGreaterThan(0); + const models = buildStaticCatalog(); + expect(models).toBeDefined(); + expect(models.length).toBeGreaterThan(0); }); it("should include the default deepinfra model", () => { - const provider = buildDeepInfraStaticProvider(); - const modelIds = provider.models.map((m) => m.id); + const models = buildStaticCatalog(); + const modelIds = models.map((m) => m.id); expect(modelIds).toContain("openai/gpt-oss-120b"); }); it("should include the static fallback catalog", () => { - const provider = buildDeepInfraStaticProvider(); - const modelIds = provider.models.map((m) => m.id); + const models = buildStaticCatalog(); + const modelIds = models.map((m) => m.id); for (const modelId of DEEPINFRA_MODEL_IDS) { expect(modelIds).toContain(modelId); } - expect(provider.models).toHaveLength(DEEPINFRA_MODEL_IDS.length); + expect(models).toHaveLength(DEEPINFRA_MODEL_IDS.length); }); }); diff --git a/src/agents/models-config.providers.static.ts b/src/agents/models-config.providers.static.ts index d1cd2f51310..71184e12286 100644 --- a/src/agents/models-config.providers.static.ts +++ b/src/agents/models-config.providers.static.ts @@ -4,7 +4,6 @@ export { } from "../../extensions/byteplus/provider-catalog.js"; export { buildKimiCodingProvider } from "../../extensions/kimi-coding/provider-catalog.js"; export { buildKilocodeProvider } from "../../extensions/kilocode/provider-catalog.js"; -export { buildDeepInfraStaticProvider } from "../../extensions/deepinfra/provider-catalog.js"; export { buildMinimaxPortalProvider, buildMinimaxProvider, diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 1fa7289ef54..af9c3d6e34a 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -11,7 +11,6 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles import { discoverBedrockModels } from "./bedrock-discovery.js"; import { normalizeGoogleModelId } from "./model-id-normalization.js"; import { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; -export { buildDeepInfraStaticProvider } from "../../extensions/deepinfra/provider-catalog.js"; export { buildKimiCodingProvider } from "../../extensions/kimi-coding/provider-catalog.js"; export { buildKilocodeProvider } from "../../extensions/kilocode/provider-catalog.js"; export { diff --git a/src/commands/onboard-auth.config-core.deepinfra.test.ts b/src/commands/onboard-auth.config-core.deepinfra.test.ts index b80b2a4544f..4cf04c3cc48 100644 --- a/src/commands/onboard-auth.config-core.deepinfra.test.ts +++ b/src/commands/onboard-auth.config-core.deepinfra.test.ts @@ -16,12 +16,10 @@ import { DEEPINFRA_DEFAULT_MODEL_ID, DEEPINFRA_DEFAULT_MODEL_REF, DEEPINFRA_DEFAULT_MAX_TOKENS, - DEEPINFRA_MODEL_CATALOG, } from "../providers/deepinfra-shared.js"; import { captureEnv } from "../test-utils/env.js"; const emptyCfg: OpenClawConfig = {}; -const DEEPINFRA_MODEL_IDS = DEEPINFRA_MODEL_CATALOG.map((m) => m.id); describe("DeepInfra provider config", () => { describe("constants", () => { @@ -56,48 +54,9 @@ describe("DeepInfra provider config", () => { }); describe("applyDeepInfraProviderConfig", () => { - it("registers deepinfra provider with correct baseUrl and api", () => { + it("does not persist a provider block (discovery populates models at runtime)", () => { const result = applyDeepInfraProviderConfig(emptyCfg); - const provider = result.models?.providers?.deepinfra; - expect(provider).toBeDefined(); - expect(provider?.baseUrl).toBe(DEEPINFRA_BASE_URL); - expect(provider?.api).toBe("openai-completions"); - }); - - it("includes the default model in the provider model list", () => { - const result = applyDeepInfraProviderConfig(emptyCfg); - const provider = result.models?.providers?.deepinfra; - const models = provider?.models; - expect(Array.isArray(models)).toBe(true); - const modelIds = models?.map((m) => m.id) ?? []; - expect(modelIds).toContain(DEEPINFRA_DEFAULT_MODEL_ID); - }); - - it("surfaces the full DeepInfra model catalog", () => { - const result = applyDeepInfraProviderConfig(emptyCfg); - const provider = result.models?.providers?.deepinfra; - const modelIds = provider?.models?.map((m) => m.id) ?? []; - for (const modelId of DEEPINFRA_MODEL_IDS) { - expect(modelIds).toContain(modelId); - } - }); - - it("appends missing catalog models to existing DeepInfra provider config", () => { - const result = applyDeepInfraProviderConfig({ - models: { - providers: { - deepinfra: { - baseUrl: DEEPINFRA_BASE_URL, - api: "openai-completions", - models: [{ ...DEEPINFRA_MODEL_CATALOG[0], cost: DEEPINFRA_DEFAULT_COST }], - }, - }, - }, - }); - const modelIds = result.models?.providers?.deepinfra?.models?.map((m) => m.id) ?? []; - for (const modelId of DEEPINFRA_MODEL_IDS) { - expect(modelIds).toContain(modelId); - } + expect(result.models?.providers?.deepinfra).toBeUndefined(); }); it("sets DeepInfra alias in agent default models", () => { @@ -143,11 +102,9 @@ describe("DeepInfra provider config", () => { ); }); - it("also registers the provider", () => { + it("does not persist a provider block (discovery populates models at runtime)", () => { const result = applyDeepInfraConfig(emptyCfg); - const provider = result.models?.providers?.deepinfra; - expect(provider).toBeDefined(); - expect(provider?.baseUrl).toBe(DEEPINFRA_BASE_URL); + expect(result.models?.providers?.deepinfra).toBeUndefined(); }); }); diff --git a/src/plugin-sdk/provider-models.ts b/src/plugin-sdk/provider-models.ts index b2d033ae138..4ee1c9d27f1 100644 --- a/src/plugin-sdk/provider-models.ts +++ b/src/plugin-sdk/provider-models.ts @@ -106,10 +106,7 @@ export { VERCEL_AI_GATEWAY_BASE_URL, } from "../agents/vercel-ai-gateway.js"; export { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF } from "../providers/deepinfra-shared.js"; -export { - buildStaticCatalog as buildDeepInfraStaticCatalog, - discoverDeepInfraModels, -} from "../agents/deepinfra-models.js"; +export { discoverDeepInfraModels } from "../agents/deepinfra-models.js"; export function buildKilocodeModelDefinition(): ModelDefinitionConfig { return { From 905e98604d4c81a43c486b7ec4f4c2de98583deb Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 13:56:38 +0200 Subject: [PATCH 08/20] Enable DeepInfra by default --- src/plugins/config-state.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index 26827e50aa3..d996c42380c 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -33,6 +33,7 @@ export const BUNDLED_ENABLED_BY_DEFAULT = new Set([ "anthropic", "byteplus", "cloudflare-ai-gateway", + "deepinfra", "device-pair", "github-copilot", "google", From beb45cba225329c5a4d664942e05e36b4007fe7f Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 15:53:51 +0200 Subject: [PATCH 09/20] Fix the way we decide if a DeepInfra model has reasoning capabilities --- src/agents/deepinfra-models.test.ts | 2 +- src/agents/deepinfra-models.ts | 6 ++++-- src/providers/deepinfra-shared.ts | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/agents/deepinfra-models.test.ts b/src/agents/deepinfra-models.test.ts index 945d527efde..6419286ad7e 100644 --- a/src/agents/deepinfra-models.test.ts +++ b/src/agents/deepinfra-models.test.ts @@ -18,7 +18,7 @@ function makeModelEntry(overrides: Record = {}) { output_tokens: 15.0, cache_read_tokens: 0.3, }, - tags: ["vision", "reasoning_effort", "prompt_cache"], + tags: ["vision", "reasoning_effort", "prompt_cache", "reasoning"], }, ...overrides, }; diff --git a/src/agents/deepinfra-models.ts b/src/agents/deepinfra-models.ts index a514a97cf43..571367c28bf 100644 --- a/src/agents/deepinfra-models.ts +++ b/src/agents/deepinfra-models.ts @@ -29,7 +29,7 @@ interface DeepInfraModelMetadata { context_length?: number; max_tokens?: number; pricing?: DeepInfraModelPricing; - /** e.g. ["vision", "reasoning_effort", "prompt_cache"] */ + /** e.g. ["vision", "reasoning_effort", "prompt_cache", "reasoning"] */ tags?: string[]; } @@ -54,7 +54,9 @@ function parseModality(metadata: DeepInfraModelMetadata): Array<"text" | "image" } function parseReasoning(metadata: DeepInfraModelMetadata): boolean { - return metadata.tags?.includes("reasoning_effort") ?? false; + return ( + (metadata.tags?.includes("reasoning_effort") || metadata.tags?.includes("reasoning")) ?? false + ); } function toModelDefinition(entry: DeepInfraModelEntry): ModelDefinitionConfig { diff --git a/src/providers/deepinfra-shared.ts b/src/providers/deepinfra-shared.ts index 5cd6eafbae4..1586aabee51 100644 --- a/src/providers/deepinfra-shared.ts +++ b/src/providers/deepinfra-shared.ts @@ -29,7 +29,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ { id: "MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5", - reasoning: false, + reasoning: true, input: ["text"], contextWindow: 196608, maxTokens: 196608, @@ -37,7 +37,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ { id: "zai-org/GLM-5", name: "GLM 5", - reasoning: false, + reasoning: true, input: ["text"], contextWindow: 202752, maxTokens: 202752, @@ -45,7 +45,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ { id: "moonshotai/Kimi-K2.5", name: "Kimi K2.5", - reasoning: false, + reasoning: true, input: ["text", "image"], contextWindow: 262144, maxTokens: 262144, From 60b197affedad0a3658744b27a008194de660c38 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 15:58:00 +0200 Subject: [PATCH 10/20] Disable image prompts for DeepInfra's gpt-oss-120b --- src/providers/deepinfra-shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/deepinfra-shared.ts b/src/providers/deepinfra-shared.ts index 1586aabee51..82e8cfb9793 100644 --- a/src/providers/deepinfra-shared.ts +++ b/src/providers/deepinfra-shared.ts @@ -22,7 +22,7 @@ export const DEEPINFRA_MODEL_CATALOG: DeepInfraModelCatalogEntry[] = [ id: DEEPINFRA_DEFAULT_MODEL_ID, name: DEEPINFRA_DEFAULT_MODEL_NAME, reasoning: true, - input: ["text", "image"], + input: ["text"], contextWindow: 131072, maxTokens: 131072, }, From 911dd19ac1520054b48a1ddcf04393ee45f5e427 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 16:02:29 +0200 Subject: [PATCH 11/20] Fix gemini caps for deepinfra mock in test --- src/agents/provider-capabilities.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/agents/provider-capabilities.test.ts b/src/agents/provider-capabilities.test.ts index cd560471122..fb5648490a7 100644 --- a/src/agents/provider-capabilities.test.ts +++ b/src/agents/provider-capabilities.test.ts @@ -17,6 +17,12 @@ const resolveProviderCapabilitiesWithPluginMock = vi.fn((params: { provider: str geminiThoughtSignatureSanitization: true, geminiThoughtSignatureModelHints: ["gemini"], }; + case "deepinfra": + return { + openAiCompatTurnValidation: false, + geminiThoughtSignatureSanitization: true, + geminiThoughtSignatureModelHints: ["gemini"], + }; case "openai-codex": return { providerFamily: "openai", From 18956e3a6d03ae7a7fea94f3d7573ed100e9e95b Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 16:08:01 +0200 Subject: [PATCH 12/20] Fix claude capabilities for DeepInfra. Add a test --- extensions/deepinfra/index.ts | 1 + src/agents/provider-capabilities.test.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts index 9ef33860680..08e0aa13b05 100644 --- a/extensions/deepinfra/index.ts +++ b/extensions/deepinfra/index.ts @@ -63,6 +63,7 @@ export default definePluginEntry({ openAiCompatTurnValidation: false, geminiThoughtSignatureSanitization: true, geminiThoughtSignatureModelHints: ["gemini"], + dropThinkingBlockModelHints: ["claude"], }, isCacheTtlEligible: (ctx) => isDeepInfraCacheTtlModel(ctx.modelId), }); diff --git a/src/agents/provider-capabilities.test.ts b/src/agents/provider-capabilities.test.ts index fb5648490a7..0b798301fb8 100644 --- a/src/agents/provider-capabilities.test.ts +++ b/src/agents/provider-capabilities.test.ts @@ -22,6 +22,7 @@ const resolveProviderCapabilitiesWithPluginMock = vi.fn((params: { provider: str openAiCompatTurnValidation: false, geminiThoughtSignatureSanitization: true, geminiThoughtSignatureModelHints: ["gemini"], + dropThinkingBlockModelHints: ["claude"], }; case "openai-codex": return { @@ -167,6 +168,12 @@ describe("resolveProviderCapabilities", () => { modelId: "claude-3.7-sonnet", }), ).toBe(true); + expect( + shouldDropThinkingBlocksForModel({ + provider: "deepinfra", + modelId: "anthropic/claude-3.5-sonnet", + }), + ).toBe(true); }); it("forwards config and workspace context to plugin capability lookup", () => { From 1e93e176a2cbed2bb9c8f281f82533c2fd3ce69d Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 17:03:18 +0200 Subject: [PATCH 13/20] Fix deepinfra.md to have case sensitive model names --- docs/providers/deepinfra.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/deepinfra.md b/docs/providers/deepinfra.md index 78ca59dc781..15996885ace 100644 --- a/docs/providers/deepinfra.md +++ b/docs/providers/deepinfra.md @@ -57,6 +57,6 @@ deepinfra/moonshotai/Kimi-K2.5 ## Notes -- Model refs are `deepinfra//` (e.g., `deepinfra/qwen/qwen3-max`). +- Model refs are `deepinfra//` (e.g., `deepinfra/Qwen/Qwen3-Max`). - Default model: `deepinfra/openai/gpt-oss-120b` - Base URL: `https://api.deepinfra.com/v1/openai/` From ae3cd03b39a9d35ed7ac62ba5f14b334595ad2dc Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 19:30:05 +0200 Subject: [PATCH 14/20] Adding label for deepinfra. Adding wrapper for claude integration with DeepInfra --- .github/labeler.yml | 4 + extensions/deepinfra/index.ts | 2 + ...tra-params.deepinfra-cache-control.test.ts | 92 +++++++++++++++++++ .../proxy-stream-wrappers.ts | 19 +++- src/plugin-sdk/provider-stream.ts | 2 + 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/agents/pi-embedded-runner/extra-params.deepinfra-cache-control.test.ts diff --git a/.github/labeler.yml b/.github/labeler.yml index 67a74985465..dccc4db9c5b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -325,3 +325,7 @@ - changed-files: - any-glob-to-any-file: - "extensions/fal/**" +"extensions: deepinfra": + - changed-files: + - any-glob-to-any-file: + - "extensions/deepinfra/**" diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts index 08e0aa13b05..e0f19efe415 100644 --- a/extensions/deepinfra/index.ts +++ b/extensions/deepinfra/index.ts @@ -1,6 +1,7 @@ import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; +import { createDeepInfraSystemCacheWrapper } from "openclaw/plugin-sdk/provider-stream"; import { applyDeepInfraConfig, DEEPINFRA_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildDeepInfraProviderWithDiscovery } from "./provider-catalog.js"; @@ -65,6 +66,7 @@ export default definePluginEntry({ geminiThoughtSignatureModelHints: ["gemini"], dropThinkingBlockModelHints: ["claude"], }, + wrapStreamFn: (ctx) => createDeepInfraSystemCacheWrapper(ctx.streamFn), isCacheTtlEligible: (ctx) => isDeepInfraCacheTtlModel(ctx.modelId), }); }, diff --git a/src/agents/pi-embedded-runner/extra-params.deepinfra-cache-control.test.ts b/src/agents/pi-embedded-runner/extra-params.deepinfra-cache-control.test.ts new file mode 100644 index 00000000000..789af3df4b4 --- /dev/null +++ b/src/agents/pi-embedded-runner/extra-params.deepinfra-cache-control.test.ts @@ -0,0 +1,92 @@ +import type { Model } from "@mariozechner/pi-ai"; +import { describe, expect, it } from "vitest"; +import { runExtraParamsCase } from "./extra-params.test-support.js"; + +type StreamPayload = { + messages: Array<{ + role: string; + content: unknown; + }>; +}; + +function runDeepInfraPayload(payload: StreamPayload, modelId: string) { + runExtraParamsCase({ + cfg: { + plugins: { + entries: { + deepinfra: { + enabled: true, + }, + }, + }, + }, + model: { + api: "openai-completions", + provider: "deepinfra", + id: modelId, + } as Model<"openai-completions">, + payload, + }); +} + +describe("extra-params: DeepInfra Anthropic cache_control", () => { + it("injects cache_control into system message for DeepInfra Anthropic models", () => { + const payload = { + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "Hello" }, + ], + }; + + runDeepInfraPayload(payload, "anthropic/claude-opus-4-6"); + + expect(payload.messages[0].content).toEqual([ + { type: "text", text: "You are a helpful assistant.", cache_control: { type: "ephemeral" } }, + ]); + expect(payload.messages[1].content).toBe("Hello"); + }); + + it("adds cache_control to last content block when system message is already array", () => { + const payload = { + messages: [ + { + role: "system", + content: [ + { type: "text", text: "Part 1" }, + { type: "text", text: "Part 2" }, + ], + }, + ], + }; + + runDeepInfraPayload(payload, "anthropic/claude-opus-4-6"); + + const content = payload.messages[0].content as Array>; + expect(content[0]).toEqual({ type: "text", text: "Part 1" }); + expect(content[1]).toEqual({ + type: "text", + text: "Part 2", + cache_control: { type: "ephemeral" }, + }); + }); + + it("does not inject cache_control for DeepInfra non-Anthropic models", () => { + const payload = { + messages: [{ role: "system", content: "You are a helpful assistant." }], + }; + + runDeepInfraPayload(payload, "google/gemini-2.5-pro"); + + expect(payload.messages[0].content).toBe("You are a helpful assistant."); + }); + + it("leaves payload unchanged when no system message exists", () => { + const payload = { + messages: [{ role: "user", content: "Hello" }], + }; + + runDeepInfraPayload(payload, "anthropic/claude-opus-4-6"); + + expect(payload.messages[0].content).toBe("Hello"); + }); +}); diff --git a/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts b/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts index cc5e7596050..50568ba6efd 100644 --- a/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts +++ b/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts @@ -15,6 +15,10 @@ function isOpenRouterAnthropicModel(provider: string, modelId: string): boolean return provider.toLowerCase() === "openrouter" && modelId.toLowerCase().startsWith("anthropic/"); } +function isDeepInfraAnthropicModel(provider: string, modelId: string): boolean { + return provider.toLowerCase() === "deepinfra" && modelId.toLowerCase().startsWith("anthropic/"); +} + function mapThinkingLevelToOpenRouterReasoningEffort( thinkingLevel: ThinkLevel, ): "none" | "minimal" | "low" | "medium" | "high" | "xhigh" { @@ -55,13 +59,16 @@ function normalizeProxyReasoningPayload(payload: unknown, thinkingLevel?: ThinkL } } -export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | undefined): StreamFn { +export function createSystemCacheWrapper( + baseStreamFn: StreamFn | undefined, + isEligible: (provider: string, modelId: string) => boolean, +): StreamFn { const underlying = baseStreamFn ?? streamSimple; return (model, context, options) => { if ( typeof model.provider !== "string" || typeof model.id !== "string" || - !isOpenRouterAnthropicModel(model.provider, model.id) + !isEligible(model.provider, model.id) ) { return underlying(model, context, options); } @@ -94,6 +101,14 @@ export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | unde }; } +export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | undefined): StreamFn { + return createSystemCacheWrapper(baseStreamFn, isOpenRouterAnthropicModel); +} + +export function createDeepInfraSystemCacheWrapper(baseStreamFn: StreamFn | undefined): StreamFn { + return createSystemCacheWrapper(baseStreamFn, isDeepInfraAnthropicModel); +} + export function createOpenRouterWrapper( baseStreamFn: StreamFn | undefined, thinkingLevel?: ThinkLevel, diff --git a/src/plugin-sdk/provider-stream.ts b/src/plugin-sdk/provider-stream.ts index ced7c2d9d4c..68df86e5116 100644 --- a/src/plugin-sdk/provider-stream.ts +++ b/src/plugin-sdk/provider-stream.ts @@ -9,9 +9,11 @@ export { sanitizeGoogleThinkingPayload, } from "../agents/pi-embedded-runner/google-stream-wrappers.js"; export { + createDeepInfraSystemCacheWrapper, createKilocodeWrapper, createOpenRouterSystemCacheWrapper, createOpenRouterWrapper, + createSystemCacheWrapper, isProxyReasoningUnsupported, } from "../agents/pi-embedded-runner/proxy-stream-wrappers.js"; export { From 0fd84e17335ac89cc7ccb41a0d1867a2563a9f6f Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Thu, 19 Mar 2026 20:33:12 +0200 Subject: [PATCH 15/20] Fix deepinfra-models.test.ts --- src/agents/deepinfra-models.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/deepinfra-models.test.ts b/src/agents/deepinfra-models.test.ts index 6419286ad7e..e400322896c 100644 --- a/src/agents/deepinfra-models.test.ts +++ b/src/agents/deepinfra-models.test.ts @@ -82,7 +82,7 @@ describe("discoverDeepInfraModels", () => { expect(defaultModel).toBeDefined(); expect(defaultModel?.name).toBe("gpt-oss-120b"); expect(defaultModel?.reasoning).toBe(true); - expect(defaultModel?.input).toEqual(["text", "image"]); + expect(defaultModel?.input).toEqual(["text"]); expect(defaultModel?.contextWindow).toBe(131072); expect(defaultModel?.maxTokens).toBe(131072); expect(defaultModel?.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }); From 774b5ee75e4cc422c4020bb9e8aaab6feefd2ac0 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Fri, 20 Mar 2026 07:31:29 +0200 Subject: [PATCH 16/20] Update the changelog with the DeepInfra integration feature --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b5bdcad66..0746c028c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai - Plugins/Matrix: add `allowBots` room policy so configured Matrix bot accounts can talk to each other, with optional mention-only gating. Thanks @gumadeiras. - Plugins/Matrix: add per-account `allowPrivateNetwork` opt-in for private/internal homeservers, while keeping public cleartext homeservers blocked. Thanks @gumadeiras. - Web tools/Tavily: add Tavily as a bundled web-search provider with dedicated `tavily_search` and `tavily_extract` tools, using canonical plugin-owned config under `plugins.entries.tavily.config.webSearch.*`. (#49200) thanks @lakshyaag-tavily. +- Plugins/DeepInfra: add DeepInfra as a bundled LLM provider with API-key auth, dynamic model discovery, and default-on extension wiring. (#48088) Thanks @ats3v. - Docs/plugins: add the community DingTalk plugin listing to the docs catalog. (#29913) Thanks @sliverp. - Docs/plugins: add the community QQbot plugin listing to the docs catalog. (#29898) Thanks @sliverp. From 762c281ffdac44e2640357c0438fe02b80dcc261 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Fri, 20 Mar 2026 09:46:15 +0200 Subject: [PATCH 17/20] DeepInfra: wire thinking level through proxy stream wrapper --- extensions/deepinfra/index.ts | 15 ++++- .../proxy-stream-wrappers.test.ts | 65 ++++++++++++++++++- .../proxy-stream-wrappers.ts | 17 +++++ src/plugin-sdk/provider-stream.ts | 1 + 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts index e0f19efe415..b4b7c5f4d86 100644 --- a/extensions/deepinfra/index.ts +++ b/extensions/deepinfra/index.ts @@ -1,7 +1,11 @@ import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; -import { createDeepInfraSystemCacheWrapper } from "openclaw/plugin-sdk/provider-stream"; +import { + createDeepInfraSystemCacheWrapper, + createDeepInfraWrapper, + isProxyReasoningUnsupported, +} from "openclaw/plugin-sdk/provider-stream"; import { applyDeepInfraConfig, DEEPINFRA_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildDeepInfraProviderWithDiscovery } from "./provider-catalog.js"; @@ -66,7 +70,14 @@ export default definePluginEntry({ geminiThoughtSignatureModelHints: ["gemini"], dropThinkingBlockModelHints: ["claude"], }, - wrapStreamFn: (ctx) => createDeepInfraSystemCacheWrapper(ctx.streamFn), + wrapStreamFn: (ctx) => { + const thinkingLevel = isProxyReasoningUnsupported(ctx.modelId) + ? undefined + : ctx.thinkingLevel; + let streamFn = createDeepInfraWrapper(ctx.streamFn, thinkingLevel); + streamFn = createDeepInfraSystemCacheWrapper(streamFn); + return streamFn; + }, isCacheTtlEligible: (ctx) => isDeepInfraCacheTtlModel(ctx.modelId), }); }, diff --git a/src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts b/src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts index 487d90582ef..54a39a53aee 100644 --- a/src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts +++ b/src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts @@ -2,7 +2,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { Context, Model } from "@mariozechner/pi-ai"; import { createAssistantMessageEventStream } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; -import { createOpenRouterWrapper } from "./proxy-stream-wrappers.js"; +import { createDeepInfraWrapper, createOpenRouterWrapper } from "./proxy-stream-wrappers.js"; describe("proxy stream wrappers", () => { it("adds OpenRouter attribution headers to stream options", () => { @@ -35,4 +35,67 @@ describe("proxy stream wrappers", () => { }, ]); }); + + describe("createDeepInfraWrapper", () => { + function capturePayloads() { + const payloads: unknown[] = []; + const baseStreamFn: StreamFn = (_model, _context, options) => { + const payload = { model: "test" }; + options?.onPayload?.(payload, _model); + payloads.push(structuredClone(payload)); + return createAssistantMessageEventStream(); + }; + return { baseStreamFn, payloads }; + } + + const model = { + api: "openai-completions", + provider: "deepinfra", + id: "moonshotai/Kimi-K2.5", + } as Model<"openai-completions">; + const context: Context = { messages: [] }; + + it("injects reasoning effort when thinkingLevel is set", () => { + const { baseStreamFn, payloads } = capturePayloads(); + const wrapped = createDeepInfraWrapper(baseStreamFn, "high"); + void wrapped(model, context, {}); + + expect(payloads[0]).toEqual({ + model: "test", + reasoning: { effort: "high" }, + }); + }); + + it("maps 'off' to no reasoning field", () => { + const { baseStreamFn, payloads } = capturePayloads(); + const wrapped = createDeepInfraWrapper(baseStreamFn, "off"); + void wrapped(model, context, {}); + + expect(payloads[0]).toEqual({ model: "test" }); + }); + + it("does not inject reasoning when thinkingLevel is undefined", () => { + const { baseStreamFn, payloads } = capturePayloads(); + const wrapped = createDeepInfraWrapper(baseStreamFn, undefined); + void wrapped(model, context, {}); + + expect(payloads[0]).toEqual({ model: "test" }); + }); + + it("preserves existing onPayload callback", () => { + const { baseStreamFn } = capturePayloads(); + const wrapped = createDeepInfraWrapper(baseStreamFn, "low"); + const seen: unknown[] = []; + void wrapped(model, context, { + onPayload: (payload) => { + seen.push(structuredClone(payload)); + }, + }); + + expect(seen[0]).toEqual({ + model: "test", + reasoning: { effort: "low" }, + }); + }); + }); }); diff --git a/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts b/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts index 50568ba6efd..70ea42bec41 100644 --- a/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts +++ b/src/agents/pi-embedded-runner/proxy-stream-wrappers.ts @@ -135,6 +135,23 @@ export function isProxyReasoningUnsupported(modelId: string): boolean { return modelId.toLowerCase().startsWith("x-ai/"); } +export function createDeepInfraWrapper( + baseStreamFn: StreamFn | undefined, + thinkingLevel?: ThinkLevel, +): StreamFn { + const underlying = baseStreamFn ?? streamSimple; + return (model, context, options) => { + const onPayload = options?.onPayload; + return underlying(model, context, { + ...options, + onPayload: (payload) => { + normalizeProxyReasoningPayload(payload, thinkingLevel); + return onPayload?.(payload, model); + }, + }); + }; +} + export function createKilocodeWrapper( baseStreamFn: StreamFn | undefined, thinkingLevel?: ThinkLevel, diff --git a/src/plugin-sdk/provider-stream.ts b/src/plugin-sdk/provider-stream.ts index 68df86e5116..74f9484e8e9 100644 --- a/src/plugin-sdk/provider-stream.ts +++ b/src/plugin-sdk/provider-stream.ts @@ -10,6 +10,7 @@ export { } from "../agents/pi-embedded-runner/google-stream-wrappers.js"; export { createDeepInfraSystemCacheWrapper, + createDeepInfraWrapper, createKilocodeWrapper, createOpenRouterSystemCacheWrapper, createOpenRouterWrapper, From d3b2515e0e9c5d3f08fedffee0b2f0569ea76782 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Fri, 20 Mar 2026 10:52:21 +0200 Subject: [PATCH 18/20] Remove onboard-provider-auth-flags.ts as it is not needed --- src/commands/onboard-provider-auth-flags.ts | 233 -------------------- 1 file changed, 233 deletions(-) delete mode 100644 src/commands/onboard-provider-auth-flags.ts diff --git a/src/commands/onboard-provider-auth-flags.ts b/src/commands/onboard-provider-auth-flags.ts deleted file mode 100644 index 3de6dfc322e..00000000000 --- a/src/commands/onboard-provider-auth-flags.ts +++ /dev/null @@ -1,233 +0,0 @@ -import type { AuthChoice, OnboardOptions } from "./onboard-types.js"; - -type OnboardProviderAuthOptionKey = keyof Pick< - OnboardOptions, - | "anthropicApiKey" - | "openaiApiKey" - | "mistralApiKey" - | "openrouterApiKey" - | "kilocodeApiKey" - | "deepinfraApiKey" - | "aiGatewayApiKey" - | "cloudflareAiGatewayApiKey" - | "moonshotApiKey" - | "kimiCodeApiKey" - | "geminiApiKey" - | "zaiApiKey" - | "xiaomiApiKey" - | "minimaxApiKey" - | "syntheticApiKey" - | "veniceApiKey" - | "togetherApiKey" - | "huggingfaceApiKey" - | "opencodeZenApiKey" - | "opencodeGoApiKey" - | "xaiApiKey" - | "litellmApiKey" - | "qianfanApiKey" - | "modelstudioApiKeyCn" - | "modelstudioApiKey" - | "volcengineApiKey" - | "byteplusApiKey" ->; - -export type OnboardProviderAuthFlag = { - optionKey: OnboardProviderAuthOptionKey; - authChoice: AuthChoice; - cliFlag: `--${string}`; - cliOption: `--${string} `; - description: string; -}; - -// Shared source for provider API-key flags used by CLI registration + non-interactive inference. -export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray = [ - { - optionKey: "anthropicApiKey", - authChoice: "apiKey", - cliFlag: "--anthropic-api-key", - cliOption: "--anthropic-api-key ", - description: "Anthropic API key", - }, - { - optionKey: "openaiApiKey", - authChoice: "openai-api-key", - cliFlag: "--openai-api-key", - cliOption: "--openai-api-key ", - description: "OpenAI API key", - }, - { - optionKey: "mistralApiKey", - authChoice: "mistral-api-key", - cliFlag: "--mistral-api-key", - cliOption: "--mistral-api-key ", - description: "Mistral API key", - }, - { - optionKey: "openrouterApiKey", - authChoice: "openrouter-api-key", - cliFlag: "--openrouter-api-key", - cliOption: "--openrouter-api-key ", - description: "OpenRouter API key", - }, - { - optionKey: "kilocodeApiKey", - authChoice: "kilocode-api-key", - cliFlag: "--kilocode-api-key", - cliOption: "--kilocode-api-key ", - description: "Kilo Gateway API key", - }, - { - optionKey: "deepinfraApiKey", - authChoice: "deepinfra-api-key", - cliFlag: "--deepinfra-api-key", - cliOption: "--deepinfra-api-key ", - description: "DeepInfra API key", - }, - { - optionKey: "aiGatewayApiKey", - authChoice: "ai-gateway-api-key", - cliFlag: "--ai-gateway-api-key", - cliOption: "--ai-gateway-api-key ", - description: "Vercel AI Gateway API key", - }, - { - optionKey: "cloudflareAiGatewayApiKey", - authChoice: "cloudflare-ai-gateway-api-key", - cliFlag: "--cloudflare-ai-gateway-api-key", - cliOption: "--cloudflare-ai-gateway-api-key ", - description: "Cloudflare AI Gateway API key", - }, - { - optionKey: "moonshotApiKey", - authChoice: "moonshot-api-key", - cliFlag: "--moonshot-api-key", - cliOption: "--moonshot-api-key ", - description: "Moonshot API key", - }, - { - optionKey: "kimiCodeApiKey", - authChoice: "kimi-code-api-key", - cliFlag: "--kimi-code-api-key", - cliOption: "--kimi-code-api-key ", - description: "Kimi Coding API key", - }, - { - optionKey: "geminiApiKey", - authChoice: "gemini-api-key", - cliFlag: "--gemini-api-key", - cliOption: "--gemini-api-key ", - description: "Gemini API key", - }, - { - optionKey: "zaiApiKey", - authChoice: "zai-api-key", - cliFlag: "--zai-api-key", - cliOption: "--zai-api-key ", - description: "Z.AI API key", - }, - { - optionKey: "xiaomiApiKey", - authChoice: "xiaomi-api-key", - cliFlag: "--xiaomi-api-key", - cliOption: "--xiaomi-api-key ", - description: "Xiaomi API key", - }, - { - optionKey: "minimaxApiKey", - authChoice: "minimax-api", - cliFlag: "--minimax-api-key", - cliOption: "--minimax-api-key ", - description: "MiniMax API key", - }, - { - optionKey: "syntheticApiKey", - authChoice: "synthetic-api-key", - cliFlag: "--synthetic-api-key", - cliOption: "--synthetic-api-key ", - description: "Synthetic API key", - }, - { - optionKey: "veniceApiKey", - authChoice: "venice-api-key", - cliFlag: "--venice-api-key", - cliOption: "--venice-api-key ", - description: "Venice API key", - }, - { - optionKey: "togetherApiKey", - authChoice: "together-api-key", - cliFlag: "--together-api-key", - cliOption: "--together-api-key ", - description: "Together AI API key", - }, - { - optionKey: "huggingfaceApiKey", - authChoice: "huggingface-api-key", - cliFlag: "--huggingface-api-key", - cliOption: "--huggingface-api-key ", - description: "Hugging Face API key (HF token)", - }, - { - optionKey: "opencodeZenApiKey", - authChoice: "opencode-zen", - cliFlag: "--opencode-zen-api-key", - cliOption: "--opencode-zen-api-key ", - description: "OpenCode API key (Zen catalog)", - }, - { - optionKey: "opencodeGoApiKey", - authChoice: "opencode-go", - cliFlag: "--opencode-go-api-key", - cliOption: "--opencode-go-api-key ", - description: "OpenCode API key (Go catalog)", - }, - { - optionKey: "xaiApiKey", - authChoice: "xai-api-key", - cliFlag: "--xai-api-key", - cliOption: "--xai-api-key ", - description: "xAI API key", - }, - { - optionKey: "litellmApiKey", - authChoice: "litellm-api-key", - cliFlag: "--litellm-api-key", - cliOption: "--litellm-api-key ", - description: "LiteLLM API key", - }, - { - optionKey: "qianfanApiKey", - authChoice: "qianfan-api-key", - cliFlag: "--qianfan-api-key", - cliOption: "--qianfan-api-key ", - description: "QIANFAN API key", - }, - { - optionKey: "modelstudioApiKeyCn", - authChoice: "modelstudio-api-key-cn", - cliFlag: "--modelstudio-api-key-cn", - cliOption: "--modelstudio-api-key-cn ", - description: "Alibaba Cloud Model Studio Coding Plan API key (China)", - }, - { - optionKey: "modelstudioApiKey", - authChoice: "modelstudio-api-key", - cliFlag: "--modelstudio-api-key", - cliOption: "--modelstudio-api-key ", - description: "Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)", - }, - { - optionKey: "volcengineApiKey", - authChoice: "volcengine-api-key", - cliFlag: "--volcengine-api-key", - cliOption: "--volcengine-api-key ", - description: "Volcano Engine API key", - }, - { - optionKey: "byteplusApiKey", - authChoice: "byteplus-api-key", - cliFlag: "--byteplus-api-key", - cliOption: "--byteplus-api-key ", - description: "BytePlus API key", - }, -]; From ef8adf3306448b94509835bbcefd0c230f3d78e1 Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Fri, 20 Mar 2026 11:03:47 +0200 Subject: [PATCH 19/20] Remove the export for DEEPINFRA_DEFAULT_MODEL_NAME --- src/providers/deepinfra-shared.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/providers/deepinfra-shared.ts b/src/providers/deepinfra-shared.ts index 82e8cfb9793..10c99998812 100644 --- a/src/providers/deepinfra-shared.ts +++ b/src/providers/deepinfra-shared.ts @@ -1,7 +1,6 @@ export const DEEPINFRA_BASE_URL = "https://api.deepinfra.com/v1/openai/"; export const DEEPINFRA_DEFAULT_MODEL_ID = "openai/gpt-oss-120b"; export const DEEPINFRA_DEFAULT_MODEL_REF = `deepinfra/${DEEPINFRA_DEFAULT_MODEL_ID}`; -export const DEEPINFRA_DEFAULT_MODEL_NAME = "gpt-oss-120b"; export type DeepInfraModelCatalogEntry = { id: string; name: string; @@ -11,6 +10,8 @@ export type DeepInfraModelCatalogEntry = { maxTokens: number; }; +const DEEPINFRA_DEFAULT_MODEL_NAME = "gpt-oss-120b"; + /** * Static fallback catalog used by the sync onboarding path and as a * fallback when dynamic model discovery from the gateway API fails. From 68d7048fa3fc2e062154960460e7cfdaabb538cf Mon Sep 17 00:00:00 2001 From: Georgi Atsev Date: Fri, 20 Mar 2026 18:09:09 +0200 Subject: [PATCH 20/20] Update the schema --- docs/.generated/config-baseline.json | 194 ++++++++++++++++++++------ docs/.generated/config-baseline.jsonl | 53 ++++--- 2 files changed, 183 insertions(+), 64 deletions(-) diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index f4715f11ea3..4ad27124118 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -8347,8 +8347,8 @@ "channels", "network" ], - "label": "BlueBubbles", - "help": "iMessage via the BlueBubbles mac app + REST API.", + "label": "@openclaw/bluebubbles", + "help": "BlueBubbles channel provider configuration used for Apple messaging bridge integrations. Keep DM policy aligned with your trusted sender model in shared deployments.", "hasChildren": true }, { @@ -9317,8 +9317,8 @@ "channels", "network" ], - "label": "Discord", - "help": "very well supported right now.", + "label": "@openclaw/discord", + "help": "Discord channel provider configuration for bot auth, retry policy, streaming, thread bindings, and optional voice capabilities. Keep privileged intents and advanced features disabled unless needed.", "hasChildren": true }, { @@ -15229,8 +15229,7 @@ "channels", "network" ], - "label": "Feishu", - "help": "飞书/Lark enterprise messaging with doc/wiki/drive tools.", + "label": "@openclaw/feishu", "hasChildren": true }, { @@ -17231,8 +17230,7 @@ "channels", "network" ], - "label": "Google Chat", - "help": "Google Workspace Chat app via HTTP webhooks.", + "label": "@openclaw/googlechat", "hasChildren": true }, { @@ -18618,8 +18616,8 @@ "channels", "network" ], - "label": "iMessage", - "help": "this is still a work in progress.", + "label": "@openclaw/imessage", + "help": "iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations.", "hasChildren": true }, { @@ -19976,8 +19974,8 @@ "channels", "network" ], - "label": "IRC", - "help": "classic IRC networks with DM/channel routing and pairing controls.", + "label": "@openclaw/irc", + "help": "IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw.", "hasChildren": true }, { @@ -21499,8 +21497,7 @@ "channels", "network" ], - "label": "LINE", - "help": "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", + "label": "@openclaw/line", "hasChildren": true }, { @@ -22068,8 +22065,7 @@ "channels", "network" ], - "label": "Matrix", - "help": "open protocol; install the plugin to enable.", + "label": "@openclaw/matrix", "hasChildren": true }, { @@ -23126,8 +23122,8 @@ "channels", "network" ], - "label": "Mattermost", - "help": "self-hosted Slack-style chat; install the plugin to enable.", + "label": "@openclaw/mattermost", + "help": "Mattermost channel provider configuration for bot credentials, base URL, and message trigger modes. Keep mention/trigger rules strict in high-volume team channels.", "hasChildren": true }, { @@ -24257,8 +24253,8 @@ "channels", "network" ], - "label": "Microsoft Teams", - "help": "Bot Framework; enterprise support.", + "label": "@openclaw/msteams", + "help": "Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers.", "hasChildren": true }, { @@ -25189,8 +25185,7 @@ "channels", "network" ], - "label": "Nextcloud Talk", - "help": "Self-hosted chat via Nextcloud Talk webhook bots.", + "label": "@openclaw/nextcloud-talk", "hasChildren": true }, { @@ -26410,8 +26405,7 @@ "channels", "network" ], - "label": "Nostr", - "help": "Decentralized protocol; encrypted DMs via NIP-04.", + "label": "@openclaw/nostr", "hasChildren": true }, { @@ -26639,8 +26633,8 @@ "channels", "network" ], - "label": "Signal", - "help": "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").", + "label": "@openclaw/signal", + "help": "Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups.", "hasChildren": true }, { @@ -28186,8 +28180,8 @@ "channels", "network" ], - "label": "Slack", - "help": "supported (Socket Mode).", + "label": "@openclaw/slack", + "help": "Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions.", "hasChildren": true }, { @@ -31018,8 +31012,7 @@ "channels", "network" ], - "label": "Synology Chat", - "help": "Connect your Synology NAS Chat to OpenClaw with full agent capabilities.", + "label": "@openclaw/synology-chat", "hasChildren": true }, { @@ -31042,8 +31035,8 @@ "channels", "network" ], - "label": "Telegram", - "help": "simplest way to get started — register a bot with @BotFather and get going.", + "label": "@openclaw/telegram", + "help": "Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics.", "hasChildren": true }, { @@ -35034,8 +35027,7 @@ "channels", "network" ], - "label": "Tlon", - "help": "decentralized messaging on Urbit; install the plugin to enable.", + "label": "@openclaw/tlon", "hasChildren": true }, { @@ -35473,8 +35465,7 @@ "channels", "network" ], - "label": "Twitch", - "help": "Twitch chat integration", + "label": "@openclaw/twitch", "hasChildren": true }, { @@ -35863,8 +35854,8 @@ "channels", "network" ], - "label": "WhatsApp", - "help": "works with your own number; recommend a separate phone + eSIM.", + "label": "@openclaw/whatsapp", + "help": "WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats.", "hasChildren": true }, { @@ -37231,8 +37222,7 @@ "channels", "network" ], - "label": "Zalo", - "help": "Vietnam-focused messaging platform with Bot API.", + "label": "@openclaw/zalo", "hasChildren": true }, { @@ -37812,8 +37802,7 @@ "channels", "network" ], - "label": "Zalo Personal", - "help": "Zalo personal account via QR code login.", + "label": "@openclaw/zalouser", "hasChildren": true }, { @@ -47031,6 +47020,127 @@ "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", "hasChildren": false }, + { + "path": "plugins.entries.deepinfra", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/deepinfra-provider", + "help": "OpenClaw DeepInfra provider plugin (plugin: deepinfra)", + "hasChildren": true + }, + { + "path": "plugins.entries.deepinfra.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/deepinfra-provider Config", + "help": "Plugin-defined config payload for deepinfra.", + "hasChildren": false + }, + { + "path": "plugins.entries.deepinfra.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/deepinfra-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.deepinfra.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.deepinfra.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.deepinfra.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.deepinfra.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.deepinfra.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.deepinfra.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, { "path": "plugins.entries.device-pair", "kind": "plugin", diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index 819422ac9aa..808a29d7b02 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -1,4 +1,4 @@ -{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5549} +{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5558} {"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -730,7 +730,7 @@ {"recordType":"path","path":"canvasHost.port","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Port","help":"TCP port used by the canvas host HTTP server when canvas hosting is enabled. Choose a non-conflicting port and align firewall/proxy policy accordingly.","hasChildren":false} {"recordType":"path","path":"canvasHost.root","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Root Directory","help":"Filesystem root directory served by canvas host for canvas content and static assets. Use a dedicated directory and avoid broad repo roots for least-privilege file exposure.","hasChildren":false} {"recordType":"path","path":"channels","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Channels","help":"Channel provider configurations plus shared defaults that control access policies, heartbeat visibility, and per-surface behavior. Keep defaults centralized and override per provider only where required.","hasChildren":true} -{"recordType":"path","path":"channels.bluebubbles","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"BlueBubbles","help":"iMessage via the BlueBubbles mac app + REST API.","hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/bluebubbles","help":"BlueBubbles channel provider configuration used for Apple messaging bridge integrations. Keep DM policy aligned with your trusted sender model in shared deployments.","hasChildren":true} {"recordType":"path","path":"channels.bluebubbles.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.bluebubbles.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.bluebubbles.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -818,7 +818,7 @@ {"recordType":"path","path":"channels.bluebubbles.serverUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.bluebubbles.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.bluebubbles.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.discord","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord","help":"very well supported right now.","hasChildren":true} +{"recordType":"path","path":"channels.discord","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/discord","help":"Discord channel provider configuration for bot auth, retry policy, streaming, thread bindings, and optional voice capabilities. Keep privileged intents and advanced features disabled unless needed.","hasChildren":true} {"recordType":"path","path":"channels.discord.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -1352,7 +1352,7 @@ {"recordType":"path","path":"channels.discord.voice.tts.provider","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.voice.tts.summaryModel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.voice.tts.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","hasChildren":true} +{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/feishu","hasChildren":true} {"recordType":"path","path":"channels.feishu.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.feishu.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.feishu.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1532,7 +1532,7 @@ {"recordType":"path","path":"channels.feishu.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.feishu.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/feishu/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.feishu.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app via HTTP webhooks.","hasChildren":true} +{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/googlechat","hasChildren":true} {"recordType":"path","path":"channels.googlechat.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.googlechat.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.googlechat.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1660,7 +1660,7 @@ {"recordType":"path","path":"channels.googlechat.typingIndicator","kind":"channel","type":"string","required":false,"enumValues":["none","message","reaction"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.googlechat.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.googlechat.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.imessage","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"iMessage","help":"this is still a work in progress.","hasChildren":true} +{"recordType":"path","path":"channels.imessage","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/imessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations.","hasChildren":true} {"recordType":"path","path":"channels.imessage.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.imessage.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.imessage.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1788,7 +1788,7 @@ {"recordType":"path","path":"channels.imessage.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.imessage.service","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.imessage.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.irc","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC","help":"classic IRC networks with DM/channel routing and pairing controls.","hasChildren":true} +{"recordType":"path","path":"channels.irc","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/irc","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw.","hasChildren":true} {"recordType":"path","path":"channels.irc.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.irc.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.irc.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1928,7 +1928,7 @@ {"recordType":"path","path":"channels.irc.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.irc.tls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.irc.username","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.line","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"LINE","help":"LINE Messaging API bot for Japan/Taiwan/Thailand markets.","hasChildren":true} +{"recordType":"path","path":"channels.line","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/line","hasChildren":true} {"recordType":"path","path":"channels.line.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.line.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.line.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1980,7 +1980,7 @@ {"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/matrix","hasChildren":true} {"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -2077,7 +2077,7 @@ {"recordType":"path","path":"channels.matrix.threadBindings.spawnSubagentSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.matrix.threadReplies","kind":"channel","type":"string","required":false,"enumValues":["off","inbound","always"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.matrix.userId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.mattermost","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost","help":"self-hosted Slack-style chat; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.mattermost","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/mattermost","help":"Mattermost channel provider configuration for bot credentials, base URL, and message trigger modes. Keep mention/trigger rules strict in high-volume team channels.","hasChildren":true} {"recordType":"path","path":"channels.mattermost.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.mattermost.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.mattermost.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -2177,7 +2177,7 @@ {"recordType":"path","path":"channels.mattermost.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Require Mention","help":"Require @mention in channels before responding (default: true).","hasChildren":false} {"recordType":"path","path":"channels.mattermost.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.mattermost.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Microsoft Teams","help":"Bot Framework; enterprise support.","hasChildren":true} +{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/msteams","help":"Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers.","hasChildren":true} {"recordType":"path","path":"channels.msteams.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.msteams.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.msteams.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -2265,7 +2265,7 @@ {"recordType":"path","path":"channels.msteams.webhook","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.msteams.webhook.path","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.msteams.webhook.port","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.nextcloud-talk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nextcloud Talk","help":"Self-hosted chat via Nextcloud Talk webhook bots.","hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/nextcloud-talk","hasChildren":true} {"recordType":"path","path":"channels.nextcloud-talk.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.nextcloud-talk.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -2381,7 +2381,7 @@ {"recordType":"path","path":"channels.nextcloud-talk.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.nextcloud-talk.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.nextcloud-talk.webhookPublicUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized protocol; encrypted DMs via NIP-04.","hasChildren":true} +{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/nostr","hasChildren":true} {"recordType":"path","path":"channels.nostr.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.nostr.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.nostr.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -2402,7 +2402,7 @@ {"recordType":"path","path":"channels.nostr.profile.website","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.nostr.relays","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.nostr.relays.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.signal","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal","help":"signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").","hasChildren":true} +{"recordType":"path","path":"channels.signal","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/signal","help":"Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups.","hasChildren":true} {"recordType":"path","path":"channels.signal.account","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.","hasChildren":false} {"recordType":"path","path":"channels.signal.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.signal.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -2546,7 +2546,7 @@ {"recordType":"path","path":"channels.signal.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.signal.startupTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.signal.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.slack","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack","help":"supported (Socket Mode).","hasChildren":true} +{"recordType":"path","path":"channels.slack","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/slack","help":"Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions.","hasChildren":true} {"recordType":"path","path":"channels.slack.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.slack.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.slack.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -2798,9 +2798,9 @@ {"recordType":"path","path":"channels.slack.userToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.slack.userTokenReadOnly","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["auth","channels","network","security"],"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes.","hasChildren":false} {"recordType":"path","path":"channels.slack.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/slack/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","hasChildren":true} +{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/synology-chat","hasChildren":true} {"recordType":"path","path":"channels.synology-chat.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.telegram","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram","help":"simplest way to get started — register a bot with @BotFather and get going.","hasChildren":true} +{"recordType":"path","path":"channels.telegram","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/telegram","help":"Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics.","hasChildren":true} {"recordType":"path","path":"channels.telegram.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.telegram.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.telegram.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -3158,7 +3158,7 @@ {"recordType":"path","path":"channels.telegram.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.telegram.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.telegram.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"decentralized messaging on Urbit; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/tlon","hasChildren":true} {"recordType":"path","path":"channels.tlon.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.tlon.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.tlon.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -3201,7 +3201,7 @@ {"recordType":"path","path":"channels.tlon.ship","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.tlon.showModelSignature","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.tlon.url","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.twitch","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Twitch","help":"Twitch chat integration","hasChildren":true} +{"recordType":"path","path":"channels.twitch","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/twitch","hasChildren":true} {"recordType":"path","path":"channels.twitch.accessToken","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.twitch.accounts","kind":"channel","type":"object","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.twitch.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -3237,7 +3237,7 @@ {"recordType":"path","path":"channels.twitch.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.twitch.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.twitch.username","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.whatsapp","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp","help":"works with your own number; recommend a separate phone + eSIM.","hasChildren":true} +{"recordType":"path","path":"channels.whatsapp","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/whatsapp","help":"WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats.","hasChildren":true} {"recordType":"path","path":"channels.whatsapp.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.whatsapp.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.whatsapp.accounts.*.ackReaction","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -3365,7 +3365,7 @@ {"recordType":"path","path":"channels.whatsapp.selfChatMode","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number).","hasChildren":false} {"recordType":"path","path":"channels.whatsapp.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.whatsapp.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.zalo","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Zalo","help":"Vietnam-focused messaging platform with Bot API.","hasChildren":true} +{"recordType":"path","path":"channels.zalo","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/zalo","hasChildren":true} {"recordType":"path","path":"channels.zalo.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.zalo.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.zalo.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -3417,7 +3417,7 @@ {"recordType":"path","path":"channels.zalo.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.zalo.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.zalo.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.zalouser","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Zalo Personal","help":"Zalo personal account via QR code login.","hasChildren":true} +{"recordType":"path","path":"channels.zalouser","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"@openclaw/zalouser","hasChildren":true} {"recordType":"path","path":"channels.zalouser.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.zalouser.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.zalouser.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -4155,6 +4155,15 @@ {"recordType":"path","path":"plugins.entries.copilot-proxy.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true} {"recordType":"path","path":"plugins.entries.copilot-proxy.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"plugins.entries.copilot-proxy.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.deepinfra","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/deepinfra-provider","help":"OpenClaw DeepInfra provider plugin (plugin: deepinfra)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.deepinfra.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/deepinfra-provider Config","help":"Plugin-defined config payload for deepinfra.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.deepinfra.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/deepinfra-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.deepinfra.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.deepinfra.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.deepinfra.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.deepinfra.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.deepinfra.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.deepinfra.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false} {"recordType":"path","path":"plugins.entries.device-pair","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Device Pairing","help":"Generate setup codes and approve device pairing requests. (plugin: device-pair)","hasChildren":true} {"recordType":"path","path":"plugins.entries.device-pair.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Device Pairing Config","help":"Plugin-defined config payload for device-pair.","hasChildren":true} {"recordType":"path","path":"plugins.entries.device-pair.config.publicUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway URL","help":"Public WebSocket URL used for /pair setup codes (ws/wss or http/https).","hasChildren":false}