This commit is contained in:
Colby Gilbert 2026-01-30 12:49:20 -03:00 committed by GitHub
commit a835f354c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 316 additions and 0 deletions

View File

@ -0,0 +1,76 @@
---
summary: "Use Firmware AI models via unified subscription API"
read_when:
- You want to use Firmware AI models in Moltbot
- You want access to Claude, GPT, Gemini, Grok, and DeepSeek via one API key on subscription pricing
- You need a unified AI subscription service
---
# Firmware
Firmware provides a unified AI subscription that gives you access to models from Anthropic, OpenAI, Google, xAI, and DeepSeek via a single API key and OpenAI-compatible endpoint. One subscription enables all supported models.
## Setup
**Best for:** unified access to multiple model providers with one subscription.
Get your API key from https://app.firmware.ai
### CLI setup
```bash
moltbot onboard --auth-choice firmware-api-key
# or non-interactive
moltbot onboard --firmware-api-key "$FIRMWARE_API_KEY"
```
### Config snippet
```json5
{
env: { FIRMWARE_API_KEY: "fw-..." },
agents: { defaults: { model: { primary: "firmware/gpt-5.2" } } }
}
```
## Available models
### Claude (Anthropic)
- `firmware/claude-opus-4-5` - Claude Opus 4.5 (reasoning, 200k context)
- `firmware/claude-sonnet-4-5` - Claude Sonnet 4.5 (reasoning, 200k context)
- `firmware/claude-haiku-4-5` - Claude Haiku 4.5 (reasoning, 200k context)
### GPT (OpenAI)
- `firmware/gpt-5.2` - GPT-5.2 (reasoning, 400k context)
- `firmware/gpt-5` - GPT-5 (reasoning, 128k context)
- `firmware/gpt-5-mini` - GPT-5 Mini (reasoning, 128k context)
- `firmware/gpt-5-nano` - GPT-5 Nano (reasoning, 128k context)
- `firmware/gpt-4o` - GPT-4o (128k context)
- `firmware/gpt-oss-120b` - GPT OSS 120B via Cerebras (reasoning, 128k context)
### Gemini (Google)
- `firmware/gemini-3-pro-preview` - Gemini 3 Pro Preview (reasoning, 1M context)
- `firmware/gemini-3-flash-preview` - Gemini 3 Flash Preview (reasoning, 1M context)
- `firmware/gemini-2.5-pro` - Gemini 2.5 Pro (reasoning, multimodal, 1M+ context)
- `firmware/gemini-2.5-flash` - Gemini 2.5 Flash (reasoning, 1M context)
### Grok (xAI)
- `firmware/grok-4-fast-reasoning` - Grok 4 Fast (Reasoning)
- `firmware/grok-4-fast-non-reasoning` - Grok 4 Fast (Non-Reasoning)
- `firmware/grok-code-fast-1` - Grok Code Fast 1 (reasoning)
### DeepSeek
- `firmware/deepseek-reasoner` - DeepSeek Reasoner (reasoning, 128k context)
- `firmware/deepseek-chat` - DeepSeek Chat (128k context)
## Notes
- Model refs always use `provider/model` (see [/concepts/models](/concepts/models))
- Auth details + reuse rules are in [/concepts/oauth](/concepts/oauth)
- All models are accessed via the unified Firmware API at `https://app.firmware.ai/api/v1`
- Pricing is available via your Firmware subscription (all models are zero-cost through Firmware's unified subscription)

View File

@ -0,0 +1,175 @@
import type { ModelDefinitionConfig } from "../config/types.js";
export const FIRMWARE_BASE_URL = "https://app.firmware.ai/api/v1";
export const FIRMWARE_DEFAULT_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
};
export const FIRMWARE_MODEL_CATALOG = [
// OpenAI models
{
id: "gpt-5.2",
name: "GPT-5.2",
reasoning: true,
input: ["text", "image"],
contextWindow: 400_000,
maxTokens: 128_000,
},
{
id: "gpt-5",
name: "GPT-5",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
{
id: "gpt-5-mini",
name: "GPT-5 Mini",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
{
id: "gpt-5-nano",
name: "GPT-5 Nano",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
{
id: "gpt-4o",
name: "GPT-4o",
reasoning: false,
input: ["text", "image"],
contextWindow: 128_000,
maxTokens: 16_384,
},
{
id: "gpt-oss-120b",
name: "GPT OSS 120B (Cerebras)",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
// Anthropic models
{
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
reasoning: true,
input: ["text", "image"],
contextWindow: 200_000,
maxTokens: 64_000,
},
{
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
reasoning: true,
input: ["text"],
contextWindow: 200_000,
maxTokens: 64_000,
},
{
id: "claude-haiku-4-5",
name: "Claude Haiku 4.5",
reasoning: true,
input: ["text"],
contextWindow: 200_000,
maxTokens: 64_000,
},
// Google models
{
id: "gemini-3-pro-preview",
name: "Gemini 3 Pro Preview",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 65_536,
},
{
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 65_536,
},
{
id: "gemini-2.5-pro",
name: "Gemini 2.5 Pro",
reasoning: true,
input: ["text", "image"],
contextWindow: 1_048_576,
maxTokens: 65_536,
},
{
id: "gemini-2.5-flash",
name: "Gemini 2.5 Flash",
reasoning: true,
input: ["text", "image"],
contextWindow: 1_000_000,
maxTokens: 65_536,
},
// xAI models
{
id: "grok-4-fast-reasoning",
name: "Grok 4 Fast (Reasoning)",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
{
id: "grok-4-fast-non-reasoning",
name: "Grok 4 Fast (Non-Reasoning)",
reasoning: false,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
{
id: "grok-code-fast-1",
name: "Grok Code Fast 1",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
// DeepSeek models
{
id: "deepseek-reasoner",
name: "DeepSeek Reasoner",
reasoning: true,
input: ["text"],
contextWindow: 128_000,
maxTokens: 65_536,
},
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 128_000,
maxTokens: 128_000,
},
] as const;
export type FirmwareCatalogEntry = (typeof FIRMWARE_MODEL_CATALOG)[number];
export function buildFirmwareModelDefinition(entry: FirmwareCatalogEntry): ModelDefinitionConfig {
return {
id: entry.id,
name: entry.name,
reasoning: entry.reasoning,
input: [...entry.input],
cost: FIRMWARE_DEFAULT_COST,
contextWindow: entry.contextWindow,
maxTokens: entry.maxTokens,
};
}

View File

@ -286,6 +286,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
venice: "VENICE_API_KEY", venice: "VENICE_API_KEY",
mistral: "MISTRAL_API_KEY", mistral: "MISTRAL_API_KEY",
opencode: "OPENCODE_API_KEY", opencode: "OPENCODE_API_KEY",
firmware: "FIRMWARE_API_KEY",
}; };
const envVar = envMap[normalized]; const envVar = envMap[normalized];
if (!envVar) return null; if (!envVar) return null;

View File

@ -0,0 +1,40 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import { resolveImplicitProviders } from "./models-config.providers.js";
import { mkdtempSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
describe("Firmware provider", () => {
let previousFirmwareKey: string | undefined;
beforeEach(() => {
previousFirmwareKey = process.env.FIRMWARE_API_KEY;
});
afterEach(() => {
if (previousFirmwareKey !== undefined) {
process.env.FIRMWARE_API_KEY = previousFirmwareKey;
} else {
delete process.env.FIRMWARE_API_KEY;
}
});
it("should not include firmware when no API key is configured", async () => {
delete process.env.FIRMWARE_API_KEY;
const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-"));
const providers = await resolveImplicitProviders({ agentDir });
expect(providers?.firmware).toBeUndefined();
});
it("should include firmware when FIRMWARE_API_KEY env var is set", async () => {
process.env.FIRMWARE_API_KEY = "test-firmware-key";
const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-"));
const providers = await resolveImplicitProviders({ agentDir });
expect(providers?.firmware).toBeDefined();
expect(providers?.firmware?.baseUrl).toBe("https://app.firmware.ai/api/v1");
expect(providers?.firmware?.api).toBe("openai-completions");
expect(providers?.firmware?.models?.length).toBeGreaterThan(0);
});
});

View File

@ -13,6 +13,11 @@ import {
SYNTHETIC_MODEL_CATALOG, SYNTHETIC_MODEL_CATALOG,
} from "./synthetic-models.js"; } from "./synthetic-models.js";
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
import {
buildFirmwareModelDefinition,
FIRMWARE_BASE_URL,
FIRMWARE_MODEL_CATALOG,
} from "./firmware-models.js";
type ModelsConfig = NonNullable<OpenClawConfig["models"]>; type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string]; export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
@ -344,6 +349,14 @@ function buildQwenPortalProvider(): ProviderConfig {
}; };
} }
function buildFirmwareProvider(): ProviderConfig {
return {
baseUrl: FIRMWARE_BASE_URL,
api: "openai-completions",
models: FIRMWARE_MODEL_CATALOG.map(buildFirmwareModelDefinition),
};
}
function buildSyntheticProvider(): ProviderConfig { function buildSyntheticProvider(): ProviderConfig {
return { return {
baseUrl: SYNTHETIC_BASE_URL, baseUrl: SYNTHETIC_BASE_URL,
@ -446,6 +459,13 @@ export async function resolveImplicitProviders(params: {
providers.xiaomi = { ...buildXiaomiProvider(), apiKey: xiaomiKey }; providers.xiaomi = { ...buildXiaomiProvider(), apiKey: xiaomiKey };
} }
const firmwareKey =
resolveEnvApiKeyVarName("firmware") ??
resolveApiKeyFromProfiles({ provider: "firmware", store: authStore });
if (firmwareKey) {
providers.firmware = { ...buildFirmwareProvider(), apiKey: firmwareKey };
}
// Ollama provider - only add if explicitly configured // Ollama provider - only add if explicitly configured
const ollamaKey = const ollamaKey =
resolveEnvApiKeyVarName("ollama") ?? resolveEnvApiKeyVarName("ollama") ??

View File

@ -54,6 +54,7 @@ describe("models-config", () => {
const previousSynthetic = process.env.SYNTHETIC_API_KEY; const previousSynthetic = process.env.SYNTHETIC_API_KEY;
const previousVenice = process.env.VENICE_API_KEY; const previousVenice = process.env.VENICE_API_KEY;
const previousXiaomi = process.env.XIAOMI_API_KEY; const previousXiaomi = process.env.XIAOMI_API_KEY;
const previousFirmware = process.env.FIRMWARE_API_KEY;
delete process.env.COPILOT_GITHUB_TOKEN; delete process.env.COPILOT_GITHUB_TOKEN;
delete process.env.GH_TOKEN; delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN; delete process.env.GITHUB_TOKEN;
@ -63,6 +64,7 @@ describe("models-config", () => {
delete process.env.SYNTHETIC_API_KEY; delete process.env.SYNTHETIC_API_KEY;
delete process.env.VENICE_API_KEY; delete process.env.VENICE_API_KEY;
delete process.env.XIAOMI_API_KEY; delete process.env.XIAOMI_API_KEY;
delete process.env.FIRMWARE_API_KEY;
try { try {
vi.resetModules(); vi.resetModules();
@ -97,6 +99,8 @@ describe("models-config", () => {
else process.env.VENICE_API_KEY = previousVenice; else process.env.VENICE_API_KEY = previousVenice;
if (previousXiaomi === undefined) delete process.env.XIAOMI_API_KEY; if (previousXiaomi === undefined) delete process.env.XIAOMI_API_KEY;
else process.env.XIAOMI_API_KEY = previousXiaomi; else process.env.XIAOMI_API_KEY = previousXiaomi;
if (previousFirmware === undefined) delete process.env.FIRMWARE_API_KEY;
else process.env.FIRMWARE_API_KEY = previousFirmware;
} }
}); });
}); });