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 = {