Implement an integration for provider DeepInfra

This commit is contained in:
Georgi Atsev 2026-03-12 15:08:09 +02:00
parent 192f859325
commit f1ae6265e4
24 changed files with 1298 additions and 1 deletions

View File

@ -245,4 +245,4 @@
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.8-beta.1/OpenClaw-2026.3.8-beta.1.zip" length="23407015" type="application/octet-stream" sparkle:edSignature="KCqhSmu4b0tHf55RqcQOHorsc55CgBI5BUmK/NTizxNq04INn/7QvsamHYQou9DbB2IW6B2nawBC4nn4au5yDA=="/>
</item>
</channel>
</rss>
</rss>

View File

@ -252,6 +252,16 @@ OpenClaw ships with the piai 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 <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`)

View File

@ -1135,6 +1135,7 @@
"providers/cloudflare-ai-gateway",
"providers/claude-max-api-proxy",
"providers/deepgram",
"providers/deepinfra",
"providers/github-copilot",
"providers/google",
"providers/groq",

View File

@ -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 <key>
```
Or set the environment variable:
```bash
export DEEPINFRA_API_KEY="<your-deepinfra-api-key>" # pragma: allowlist secret
```
## Config snippet
```json5
{
env: { DEEPINFRA_API_KEY: "<your-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/<provider>/<model>` (e.g., `deepinfra/qwen/qwen3-max`).
- Default model: `deepinfra/openai/gpt-oss-120b`
- Base URL: `https://api.deepinfra.com/v1/openai/`

View File

@ -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)

View File

View File

@ -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<ProviderConfig> {
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,
})),
};
}

View File

@ -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<string, unknown> = {}) {
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<string, unknown> = {}) {
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<typeof vi.fn>,
runAssertions: () => Promise<void>,
) {
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);
});
});
});

View File

@ -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<ModelDefinitionConfig[]> {
// 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<string>();
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();
}
}

View File

@ -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",

View File

@ -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);
});
});

View File

@ -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";

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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",

View File

@ -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,

View File

@ -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();
}
});
});
});

View File

@ -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";

View File

@ -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} <key>`;
description: string;
};
// Shared source for provider API-key flags used by CLI registration + non-interactive inference.
export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray<OnboardProviderAuthFlag> = [
{
optionKey: "anthropicApiKey",
authChoice: "apiKey",
cliFlag: "--anthropic-api-key",
cliOption: "--anthropic-api-key <key>",
description: "Anthropic API key",
},
{
optionKey: "openaiApiKey",
authChoice: "openai-api-key",
cliFlag: "--openai-api-key",
cliOption: "--openai-api-key <key>",
description: "OpenAI API key",
},
{
optionKey: "mistralApiKey",
authChoice: "mistral-api-key",
cliFlag: "--mistral-api-key",
cliOption: "--mistral-api-key <key>",
description: "Mistral API key",
},
{
optionKey: "openrouterApiKey",
authChoice: "openrouter-api-key",
cliFlag: "--openrouter-api-key",
cliOption: "--openrouter-api-key <key>",
description: "OpenRouter API key",
},
{
optionKey: "kilocodeApiKey",
authChoice: "kilocode-api-key",
cliFlag: "--kilocode-api-key",
cliOption: "--kilocode-api-key <key>",
description: "Kilo Gateway API key",
},
{
optionKey: "deepinfraApiKey",
authChoice: "deepinfra-api-key",
cliFlag: "--deepinfra-api-key",
cliOption: "--deepinfra-api-key <key>",
description: "DeepInfra API key",
},
{
optionKey: "aiGatewayApiKey",
authChoice: "ai-gateway-api-key",
cliFlag: "--ai-gateway-api-key",
cliOption: "--ai-gateway-api-key <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 <key>",
description: "Cloudflare AI Gateway API key",
},
{
optionKey: "moonshotApiKey",
authChoice: "moonshot-api-key",
cliFlag: "--moonshot-api-key",
cliOption: "--moonshot-api-key <key>",
description: "Moonshot API key",
},
{
optionKey: "kimiCodeApiKey",
authChoice: "kimi-code-api-key",
cliFlag: "--kimi-code-api-key",
cliOption: "--kimi-code-api-key <key>",
description: "Kimi Coding API key",
},
{
optionKey: "geminiApiKey",
authChoice: "gemini-api-key",
cliFlag: "--gemini-api-key",
cliOption: "--gemini-api-key <key>",
description: "Gemini API key",
},
{
optionKey: "zaiApiKey",
authChoice: "zai-api-key",
cliFlag: "--zai-api-key",
cliOption: "--zai-api-key <key>",
description: "Z.AI API key",
},
{
optionKey: "xiaomiApiKey",
authChoice: "xiaomi-api-key",
cliFlag: "--xiaomi-api-key",
cliOption: "--xiaomi-api-key <key>",
description: "Xiaomi API key",
},
{
optionKey: "minimaxApiKey",
authChoice: "minimax-api",
cliFlag: "--minimax-api-key",
cliOption: "--minimax-api-key <key>",
description: "MiniMax API key",
},
{
optionKey: "syntheticApiKey",
authChoice: "synthetic-api-key",
cliFlag: "--synthetic-api-key",
cliOption: "--synthetic-api-key <key>",
description: "Synthetic API key",
},
{
optionKey: "veniceApiKey",
authChoice: "venice-api-key",
cliFlag: "--venice-api-key",
cliOption: "--venice-api-key <key>",
description: "Venice API key",
},
{
optionKey: "togetherApiKey",
authChoice: "together-api-key",
cliFlag: "--together-api-key",
cliOption: "--together-api-key <key>",
description: "Together AI API key",
},
{
optionKey: "huggingfaceApiKey",
authChoice: "huggingface-api-key",
cliFlag: "--huggingface-api-key",
cliOption: "--huggingface-api-key <key>",
description: "Hugging Face API key (HF token)",
},
{
optionKey: "opencodeZenApiKey",
authChoice: "opencode-zen",
cliFlag: "--opencode-zen-api-key",
cliOption: "--opencode-zen-api-key <key>",
description: "OpenCode API key (Zen catalog)",
},
{
optionKey: "opencodeGoApiKey",
authChoice: "opencode-go",
cliFlag: "--opencode-go-api-key",
cliOption: "--opencode-go-api-key <key>",
description: "OpenCode API key (Go catalog)",
},
{
optionKey: "xaiApiKey",
authChoice: "xai-api-key",
cliFlag: "--xai-api-key",
cliOption: "--xai-api-key <key>",
description: "xAI API key",
},
{
optionKey: "litellmApiKey",
authChoice: "litellm-api-key",
cliFlag: "--litellm-api-key",
cliOption: "--litellm-api-key <key>",
description: "LiteLLM API key",
},
{
optionKey: "qianfanApiKey",
authChoice: "qianfan-api-key",
cliFlag: "--qianfan-api-key",
cliOption: "--qianfan-api-key <key>",
description: "QIANFAN API key",
},
{
optionKey: "modelstudioApiKeyCn",
authChoice: "modelstudio-api-key-cn",
cliFlag: "--modelstudio-api-key-cn",
cliOption: "--modelstudio-api-key-cn <key>",
description: "Alibaba Cloud Model Studio Coding Plan API key (China)",
},
{
optionKey: "modelstudioApiKey",
authChoice: "modelstudio-api-key",
cliFlag: "--modelstudio-api-key",
cliOption: "--modelstudio-api-key <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 <key>",
description: "Volcano Engine API key",
},
{
optionKey: "byteplusApiKey",
authChoice: "byteplus-api-key",
cliFlag: "--byteplus-api-key",
cliOption: "--byteplus-api-key <key>",
description: "BytePlus API key",
},
];

View File

@ -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;

View File

@ -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",

View File

@ -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);
}

View File

@ -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;

View File

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