feat: add Firmware provider support
- Add firmware-models.ts with model catalog (18 models) - Add implicit provider detection via FIRMWARE_API_KEY - Support Claude, GPT, Gemini, Grok, and DeepSeek models - Add provider documentation
This commit is contained in:
parent
4583f88626
commit
9eae669b91
76
docs/providers/firmware.md
Normal file
76
docs/providers/firmware.md
Normal 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)
|
||||
175
src/agents/firmware-models.ts
Normal file
175
src/agents/firmware-models.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@ -286,6 +286,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
||||
venice: "VENICE_API_KEY",
|
||||
mistral: "MISTRAL_API_KEY",
|
||||
opencode: "OPENCODE_API_KEY",
|
||||
firmware: "FIRMWARE_API_KEY",
|
||||
};
|
||||
const envVar = envMap[normalized];
|
||||
if (!envVar) return null;
|
||||
|
||||
23
src/agents/models-config.providers.firmware.test.ts
Normal file
23
src/agents/models-config.providers.firmware.test.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } 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", () => {
|
||||
it("should not include firmware when no API key is configured", async () => {
|
||||
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 () => {
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-"));
|
||||
const providers = await resolveImplicitProviders({ agentDir });
|
||||
|
||||
// This test would need to be run with FIRMWARE_API_KEY env var set
|
||||
// For now we're just checking the provider structure is correct
|
||||
expect(providers).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -13,6 +13,11 @@ import {
|
||||
SYNTHETIC_MODEL_CATALOG,
|
||||
} from "./synthetic-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<MoltbotConfig["models"]>;
|
||||
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 {
|
||||
return {
|
||||
baseUrl: SYNTHETIC_BASE_URL,
|
||||
@ -446,6 +459,13 @@ export async function resolveImplicitProviders(params: {
|
||||
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
|
||||
const ollamaKey =
|
||||
resolveEnvApiKeyVarName("ollama") ??
|
||||
|
||||
Loading…
Reference in New Issue
Block a user