fix(providers): align Fireworks integration with project policy

This commit is contained in:
teknium1 2026-07-11 04:00:15 -07:00 committed by Teknium
parent c97d9a4c07
commit 31152ae108
24 changed files with 55 additions and 178 deletions

View file

@ -2,15 +2,12 @@
# Copy this file to .env and fill in your API keys
# =============================================================================
# LLM PROVIDER (Fireworks AI) — preferred BYOK provider
# LLM PROVIDER (Fireworks AI)
# =============================================================================
# Fireworks AI — fastest inference for production AI, 300+ models, one API key.
# Get your key at: https://app.fireworks.ai/settings/users/api-keys
# Address models directly by catalog ID, e.g.
# accounts/fireworks/models/kimi-k2p6, accounts/fireworks/models/glm-5p2
# FIREWORKS_API_KEY=
# FIREWORKS_BASE_URL=https://api.fireworks.ai/inference/v1 # Override default base URL
# =============================================================================
# LLM PROVIDER (OpenRouter)
# =============================================================================

View file

@ -1,5 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Fireworks AI">
<path d="M15.9851 19.1274C15.0969 19.1274 14.2999 18.6001 13.96 17.7838L9.86258 8H12.2608L15.9991 16.9499L19.7339 8H22.1321L18.0102 17.7873C17.6686 18.6001 16.8733 19.1274 15.9851 19.1274Z" fill="#FFFFFF"/>
<path d="M21.3316 23.8029C20.4469 23.8029 19.6533 23.2792 19.31 22.4698C18.9649 21.6535 19.1436 20.7215 19.7672 20.0891L27.2299 12.5302L28.1618 14.7287L21.3298 21.636L31.068 21.5817L32 23.7802L21.3333 23.8065L21.3298 23.8029H21.3316Z" fill="#FFFFFF"/>
<path d="M0 23.7766L0.931955 21.5781L10.6702 21.6324L3.83993 14.7234L4.77189 12.5249L12.2345 20.0838C12.8582 20.7145 13.0386 21.65 12.6918 22.4645C12.3484 23.2756 11.5513 23.7977 10.6702 23.7977L0.00350359 23.7731L0 23.7766Z" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 848 B

View file

@ -1,5 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Fireworks AI">
<path d="M15.9851 19.1274C15.0969 19.1274 14.2999 18.6001 13.96 17.7838L9.86258 8H12.2608L15.9991 16.9499L19.7339 8H22.1321L18.0102 17.7873C17.6686 18.6001 16.8733 19.1274 15.9851 19.1274Z" fill="#6720FF"/>
<path d="M21.3316 23.8029C20.4469 23.8029 19.6533 23.2792 19.31 22.4698C18.9649 21.6535 19.1436 20.7215 19.7672 20.0891L27.2299 12.5302L28.1618 14.7287L21.3298 21.636L31.068 21.5817L32 23.7802L21.3333 23.8065L21.3298 23.8029H21.3316Z" fill="#6720FF"/>
<path d="M0 23.7766L0.931955 21.5781L10.6702 21.6324L3.83993 14.7234L4.77189 12.5249L12.2345 20.0838C12.8582 20.7145 13.0386 21.65 12.6918 22.4645C12.3484 23.2756 11.5513 23.7977 10.6702 23.7977L0.00350359 23.7731L0 23.7766Z" fill="#6720FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 848 B

View file

@ -56,15 +56,12 @@ afterEach(() => {
})
describe('onboarding Picker', () => {
it('features Nous Portal and Fireworks, and hides other providers behind a disclosure', () => {
it('features Nous Portal and hides other providers behind a disclosure', () => {
setProviders([provider('anthropic', 'Anthropic Claude'), provider('nous', 'Nous Portal')])
render(<Picker ctx={ctx} />)
expect(screen.getByText('Nous Portal')).toBeTruthy()
// Fireworks is the preferred BYOK provider — a second hero card after Nous.
expect(screen.getByText('Fireworks AI')).toBeTruthy()
// Both hero cards carry a Recommended badge; the plain rows below do not.
expect(screen.getAllByText('Recommended')).toHaveLength(2)
expect(screen.getByText('Recommended')).toBeTruthy()
expect(screen.queryByText('Anthropic API Key')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Other providers' }))
@ -73,25 +70,14 @@ describe('onboarding Picker', () => {
expect(screen.getByRole('button', { name: 'Collapse' })).toBeTruthy()
})
it('still features Fireworks when Nous Portal is absent', () => {
it('shows every provider directly when Nous Portal is absent', () => {
setProviders([provider('anthropic', 'Anthropic Claude'), provider('openai-codex', 'OpenAI Codex / ChatGPT')])
render(<Picker ctx={ctx} />)
expect(screen.getByText('Anthropic API Key')).toBeTruthy()
expect(screen.getByText('OpenAI OAuth (ChatGPT)')).toBeTruthy()
expect(screen.queryByText('Other sign-in options')).toBeNull()
// Fireworks stays featured even without Nous — exactly one Recommended badge.
expect(screen.getByText('Fireworks AI')).toBeTruthy()
expect(screen.getAllByText('Recommended')).toHaveLength(1)
})
it('routes the Fireworks hero card into the API-key form', () => {
setProviders([provider('nous', 'Nous Portal')])
render(<Picker ctx={ctx} />)
fireEvent.click(screen.getByText('Fireworks AI'))
expect($desktopOnboarding.get().mode).toBe('apikey')
expect(screen.queryByText('Recommended')).toBeNull()
})
it('offers "choose later" on first run and persists the skip', () => {

View file

@ -28,10 +28,9 @@ import {
import type { ModelOptionProvider, OAuthProvider } from '@/types/hermes'
import { DocsLink, FlowPanel, Status } from './flow'
import { FeaturedFireworksRow, FeaturedProviderRow, KeyProviderRow, ProviderRow, sortProviders } from './providers'
import { FeaturedProviderRow, KeyProviderRow, ProviderRow, sortProviders } from './providers'
export {
FeaturedFireworksRow,
FeaturedProviderRow,
KeyProviderRow,
ProviderRow,
@ -56,18 +55,18 @@ export interface ApiKeyOption {
}
const API_KEY_OPTIONS: ApiKeyOption[] = [
{
id: 'fireworks',
name: 'Fireworks AI',
envKey: 'FIREWORKS_API_KEY',
docsUrl: 'https://app.fireworks.ai/settings/users/api-keys'
},
{
id: 'openrouter',
name: 'OpenRouter',
envKey: 'OPENROUTER_API_KEY',
docsUrl: 'https://openrouter.ai/keys'
},
{
id: 'fireworks',
name: 'Fireworks AI',
envKey: 'FIREWORKS_API_KEY',
docsUrl: 'https://app.fireworks.ai/settings/users/api-keys'
},
{
id: 'openai',
name: 'OpenAI',
@ -408,9 +407,8 @@ export function Picker({ ctx }: { ctx: OnboardingContext }) {
const { t } = useI18n()
const { localEndpoint, manual, mode, providers } = useStore($desktopOnboarding)
const [showAll, setShowAll] = useState(readShowAll)
// Which key-form option to preselect when we flip to 'apikey' mode. Set by the
// Fireworks hero card (its BYOK key) and the OpenRouter row; the generic
// "I have an API key" link leaves it undefined and lands on the first option.
// Which key-form option to preselect when we flip to 'apikey' mode. The
// OpenRouter row selects its key; the generic link lands on the first option.
const [apiKeyInitialEnv, setApiKeyInitialEnv] = useState<string | undefined>(undefined)
const openKeyForm = (envKey?: string) => {
setApiKeyInitialEnv(envKey)
@ -459,9 +457,6 @@ export function Picker({ ctx }: { ctx: OnboardingContext }) {
<div className="grid gap-2">
<div className="grid max-h-[60dvh] gap-2 overflow-y-auto p-1">
{featured ? <FeaturedProviderRow onSelect={select} provider={featured} /> : null}
{/* Fireworks preferred BYOK provider, second hero card after Nous.
Routes into the API-key form preselecting the Fireworks key. */}
<FeaturedFireworksRow onSelect={() => openKeyForm('FIREWORKS_API_KEY')} />
{showRest ? (
<>
{rest.map(p => (

View file

@ -62,34 +62,6 @@ export function FeaturedProviderRow({
)
}
// Fireworks is the preferred BYOK provider — a second hero card directly below
// Nous Portal. It isn't an OAuth provider (no sign-in flow), so it takes a bare
// onSelect that routes into the API-key form rather than an OAuthProvider.
export function FeaturedFireworksRow({ onSelect }: { onSelect: () => void }) {
const { t } = useI18n()
return (
<button
className="group relative flex w-full items-center justify-between gap-4 rounded-[8px] bg-primary/[0.06] px-3 py-2.5 text-left transition-colors hover:bg-primary/10"
onClick={onSelect}
type="button"
>
<div className="min-w-0">
<div className="flex items-center gap-2">
<img alt="" className="size-5 shrink-0" src={assetPath('fireworks.svg')} />
<span className="text-[length:var(--conversation-text-font-size)] font-semibold">Fireworks AI</span>
<span className="inline-flex items-center gap-1.5 bg-[#6720FF] px-2 py-0.5 text-[0.64rem] font-semibold uppercase tracking-[0.16em] text-white">
<img alt="" aria-hidden="true" className="size-2.5 shrink-0" src={assetPath('fireworks-white.svg')} />
{t.onboarding.recommended}
</span>
</div>
<p className="mt-1 text-xs leading-5 text-muted-foreground">{t.onboarding.fireworksPitch}</p>
</div>
<ChevronRight className="size-4 shrink-0 text-primary transition group-hover:translate-x-0.5" />
</button>
)
}
function ConnectedTag() {
const { t } = useI18n()

View file

@ -1928,12 +1928,11 @@ export const en: Translations = {
recommended: 'Recommended',
connected: 'Connected',
featuredPitch: 'One subscription, 300+ frontier models — the recommended way to run Hermes',
fireworksPitch: 'Fastest inference for production AI — 300+ models, one API, deploy in a click',
openRouterPitch: 'One key, hundreds of models — a solid default',
apiKeyOptions: {
fireworks: {
short: 'fastest inference',
description: 'Direct access to 300+ open & proprietary models on Fireworks AI — one API key, production speed.'
short: 'direct model API',
description: 'Direct access to models hosted by Fireworks AI.'
},
openrouter: {
short: 'one key, many models',

View file

@ -1873,12 +1873,11 @@ export const ja = defineLocale({
recommended: '推奨',
connected: '接続済み',
featuredPitch: '1 つのサブスクリプションで 300 以上の最先端モデル — Hermes を実行するための推奨方法',
fireworksPitch: 'プロダクション AI 向けの最速推論 — 300 以上のモデル、1 つの API、ワンクリックでデプロイ',
openRouterPitch: '1 つのキーで数百のモデル — 堅実なデフォルト',
apiKeyOptions: {
fireworks: {
short: '最速の推論',
description: 'Fireworks AI で 300 以上のオープン/プロプライエタリモデルに直接アクセス — 1 つの API キー、プロダクション速度。'
short: 'モデル API に直接接続',
description: 'Fireworks AI がホストするモデルに直接アクセスします。'
},
openrouter: {
short: '1 つのキーで多くのモデル',

View file

@ -1582,7 +1582,6 @@ export interface Translations {
recommended: string
connected: string
featuredPitch: string
fireworksPitch: string
openRouterPitch: string
apiKeyOptions: Record<string, { short: string; description: string }>
backToSignIn: string

View file

@ -1817,10 +1817,9 @@ export const zhHant = defineLocale({
recommended: '建議',
connected: '已連線',
featuredPitch: '一個訂閱300+ 前沿模型 — 執行 Hermes 的建議方式',
fireworksPitch: '面向生產 AI 的最快推理 — 300+ 模型,一個 API一鍵部署',
openRouterPitch: '一個金鑰,數百個模型 — 穩定的預設選擇',
apiKeyOptions: {
fireworks: { short: '最快推理', description: '透過 Fireworks AI 直接存取 300+ 開源與專有模型 — 一個 API 金鑰,生產級速度。' },
fireworks: { short: '直接模型 API', description: '直接存取 Fireworks AI 託管的模型。' },
openrouter: { short: '一個金鑰,多個模型', description: '用一個金鑰存取數百個模型。適合新安裝的預設選擇。' },
openai: { short: 'GPT 等級模型', description: '直接存取 OpenAI 模型。' },
gemini: { short: 'Gemini 模型', description: '直接存取 Google Gemini 模型。' },

View file

@ -2100,10 +2100,9 @@ export const zh: Translations = {
recommended: '推荐',
connected: '已连接',
featuredPitch: '一个订阅300+ 前沿模型 — 运行 Hermes 的推荐方式',
fireworksPitch: '面向生产 AI 的最快推理 — 300+ 模型,一个 API一键部署',
openRouterPitch: '一个密钥,数百个模型 — 稳妥的默认选择',
apiKeyOptions: {
fireworks: { short: '最快推理', description: '通过 Fireworks AI 直接访问 300+ 开源与专有模型 — 一个 API 密钥,生产级速度。' },
fireworks: { short: '直接模型 API', description: '直接访问 Fireworks AI 托管的模型。' },
openrouter: { short: '一个密钥,多个模型', description: '用一个密钥访问数百个模型。适合新安装的默认选择。' },
openai: { short: 'GPT 级模型', description: '直接访问 OpenAI 模型。' },
gemini: { short: 'Gemini 模型', description: '直接访问 Google Gemini 模型。' },

View file

@ -3543,14 +3543,6 @@ OPTIONAL_ENV_VARS = {
"category": "provider",
"advanced": True,
},
"FIREWORKS_BASE_URL": {
"description": "Fireworks AI base URL override",
"prompt": "Fireworks AI base URL (leave empty for default)",
"url": None,
"password": False,
"category": "provider",
"advanced": True,
},
"MINIMAX_API_KEY": {
"description": "MiniMax API key (international)",
"prompt": "MiniMax API key",

View file

@ -1051,9 +1051,6 @@ class ProviderEntry(NamedTuple):
CANONICAL_PROVIDERS: list[ProviderEntry] = [
ProviderEntry("nous", "Nous Portal", "Nous Portal (Everything your agent needs, 300+ models with bundled tool use)"),
# Fireworks AI — preferred BYOK inference partner. Sits directly below Nous
# Portal (Nous's own provider stays first); everything else follows.
ProviderEntry("fireworks", "Fireworks AI", "Fireworks AI (Fastest inference for production AI, 300+ models, one API key)"),
ProviderEntry("openrouter", "OpenRouter", "OpenRouter (Pay-per-use API aggregator)"),
ProviderEntry("moa", "Mixture of Agents", "Mixture of Agents (named presets; aggregator acts after reference models)"),
ProviderEntry("novita", "NovitaAI", "NovitaAI (Cloud: Model API, Agent Sandbox, GPU Cloud)"),
@ -1083,6 +1080,7 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [
ProviderEntry("ollama-cloud", "Ollama Cloud", "Ollama Cloud (Cloud-hosted open models, ollama.com)"),
ProviderEntry("arcee", "Arcee AI", "Arcee AI (Trinity models, direct API)"),
ProviderEntry("gmi", "GMI Cloud", "GMI Cloud (Multi-model direct API)"),
ProviderEntry("fireworks", "Fireworks AI", "Fireworks AI (OpenAI-compatible direct model API)"),
ProviderEntry("kilocode", "Kilo Code", "Kilo Code (Kilo Gateway API)"),
ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (Curated models, pay-as-you-go)"),
ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (Open models subscription)"),

View file

@ -200,7 +200,6 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = {
transport="openai_chat",
extra_env_vars=("FIREWORKS_API_KEY",),
base_url_override="https://api.fireworks.ai/inference/v1",
base_url_env_var="FIREWORKS_BASE_URL",
),
"ollama-cloud": HermesOverlay(
transport="openai_chat",

View file

@ -1,8 +1,7 @@
"""Fireworks AI provider profile.
Fireworks AI serves fast, production-grade inference for open and proprietary
models through an OpenAI-compatible chat-completions endpoint. It is a bundled,
preferred BYOK provider users paste a Fireworks API key and go.
models through an OpenAI-compatible chat-completions endpoint.
Address models directly by their catalog ID, e.g.
``accounts/fireworks/models/kimi-k2p6`` or ``accounts/fireworks/models/glm-5p2``.
@ -18,19 +17,11 @@ fireworks = ProviderProfile(
name="fireworks",
aliases=("fireworks-ai", "fw"),
display_name="Fireworks AI",
description="Fireworks AI — fastest inference for production AI, 300+ models, one API key",
description="Fireworks AI — OpenAI-compatible direct model API",
signup_url="https://app.fireworks.ai/settings/users/api-keys",
env_vars=("FIREWORKS_API_KEY", "FIREWORKS_BASE_URL"),
env_vars=("FIREWORKS_API_KEY",),
base_url="https://api.fireworks.ai/inference/v1",
auth_type="api_key",
# Attribution: lets Fireworks identify traffic originating from Hermes Agent
# (partner/revenue attribution). Matches the canonical Hermes attribution
# values in agent/auxiliary_client.py. Applied at client construction via the
# generic profile.default_headers path in run_agent.py.
default_headers={
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
"X-Title": "Hermes Agent",
},
# Auxiliary model for cheap tasks (compaction, title generation, vision).
# A standard pay-as-you-go catalog ``/models/`` ID.
default_aux_model="accounts/fireworks/models/glm-5p2",
@ -43,26 +34,3 @@ fireworks = ProviderProfile(
)
register_provider(fireworks)
# ---------------------------------------------------------------------------
# Future work (intentionally disabled for now — do not ship as customer-facing).
#
# A second Fireworks key tier authenticates against the same endpoint but can
# only address managed router IDs (accounts/fireworks/routers/...), not raw
# /models/ IDs, and cannot list the account catalog. Enabling it means detecting
# the key prefix and swapping the picker catalog + aux model to routers. Kept
# here (commented out) so the wiring is ready when we turn it on. Router IDs and
# behavior track fw-ai/fireconnect setup-cli.
#
# _MANAGED_KEY_PREFIX = "fpk_"
# _MANAGED_ROUTERS = (
# "accounts/fireworks/routers/glm-latest", # default
# "accounts/fireworks/routers/glm-fast-latest",
# "accounts/fireworks/routers/kimi-latest",
# "accounts/fireworks/routers/kimi-k2p6-turbo", # turbo tier — not a default
# )
#
# def _is_managed_key(api_key: str) -> bool:
# return bool(api_key) and api_key.startswith(_MANAGED_KEY_PREFIX)
# ---------------------------------------------------------------------------

View file

@ -2,4 +2,4 @@ name: fireworks-provider
kind: model-provider
version: 1.0.0
description: Fireworks AI — fast inference for open and proprietary models
author: Nous Research
author: Alex Jestin Taylor (@alex-fireworks) + Hermes Agent

View file

@ -77,6 +77,7 @@ AUTHOR_MAP = {
"lord-dubious@users.noreply.github.com": "lord-dubious", # PR #58453 salvage (preserve static custom provider models declared as dict rows)
"williamumu@users.noreply.github.com": "williamumu", # PR #31041 salvage (pairing: merge split legacy/new pairing store dirs at PairingStore init so approved users aren't re-prompted to pair)
"jonathan@mintrx.com": "JAlmanzarMint", # PR #52688 salvage (vision: rasterize SVG / re-encode unsupported raster formats to PNG before embedding), folded into #57890
"alex@fireworks.ai": "alex-fireworks", # PR #61182 salvage (Fireworks AI model-provider integration)
"al3060388206@gmail.com": "ooiuuii", # PR #58466/#58377 salvage (redact: fireworks fw-/fpk_ prefixes; telegram: redact bot tokens out of transport error strings). Also PR #58433 salvage (codex: accept recorded final_text when app-server omits turn/completed) and PR #58472 salvage (gateway: cap proxy SSE line buffer at 16MiB).
"Jigoooo@users.noreply.github.com": "Jigoooo", # PR #58474 salvage (auxiliary: fall back to token resolver when anthropic pool has no usable entry)
"root@vmi3351581.contaboserver.net": "ostravajih", # PR #58374 salvage (poolside: coerce integer finish_reason and tool_call id to strings)

View file

@ -1,9 +1,8 @@
"""Focused tests for Fireworks AI first-class provider wiring.
Fireworks is the bundled, preferred BYOK provider (sits right after Nous
Portal). These tests pin the wiring that makes it a real provider alias
resolution through BOTH CLI resolvers, ordering, config/doctor/overlay
registration, attribution headers, and credential/base-URL resolution without
These tests pin the wiring that makes Fireworks a real provider alias
resolution through both CLI resolvers, config/doctor/overlay registration,
and credential/base-URL resolution without
any live network calls.
"""
@ -48,21 +47,12 @@ class TestFireworksAliases:
class TestFireworksOrdering:
"""Product contract: Fireworks is the preferred provider and sits directly
below Nous Portal (Nous's own provider stays first)."""
"""Fireworks participates in the canonical provider catalog."""
def test_present_in_canonical_providers(self):
slugs = [p.slug for p in CANONICAL_PROVIDERS]
assert "fireworks" in slugs
def test_sits_immediately_after_nous(self):
slugs = [p.slug for p in CANONICAL_PROVIDERS]
assert slugs.index("fireworks") == slugs.index("nous") + 1, slugs[:4]
def test_ranks_ahead_of_the_other_byok_providers(self):
slugs = [p.slug for p in CANONICAL_PROVIDERS]
# Ahead of the generic aggregators / other direct providers.
assert slugs.index("fireworks") < slugs.index("openrouter")
def test_has_a_label(self):
assert _PROVIDER_LABELS.get("fireworks") == "Fireworks AI"
@ -76,8 +66,7 @@ class TestFireworksConfigRegistry:
assert OPTIONAL_ENV_VARS["FIREWORKS_API_KEY"]["category"] == "provider"
assert OPTIONAL_ENV_VARS["FIREWORKS_API_KEY"]["password"] is True
assert "FIREWORKS_BASE_URL" in OPTIONAL_ENV_VARS
assert OPTIONAL_ENV_VARS["FIREWORKS_BASE_URL"]["password"] is False
assert "FIREWORKS_BASE_URL" not in OPTIONAL_ENV_VARS
class TestFireworksOverlay:
@ -88,7 +77,7 @@ class TestFireworksOverlay:
overlay = HERMES_OVERLAYS["fireworks"]
assert overlay.transport == "openai_chat"
assert overlay.base_url_override == "https://api.fireworks.ai/inference/v1"
assert overlay.base_url_env_var == "FIREWORKS_BASE_URL"
assert not overlay.base_url_env_var
assert not overlay.is_aggregator
@ -153,16 +142,8 @@ class TestFireworksCredentials:
assert creds["api_key"] == "fw_test_key"
assert creds["base_url"] == "https://api.fireworks.ai/inference/v1"
def test_base_url_override_is_honored(self, monkeypatch):
monkeypatch.setenv("FIREWORKS_API_KEY", "fw_test_key")
monkeypatch.setenv("FIREWORKS_BASE_URL", "https://gateway.example.com/v1")
creds = resolve_api_key_provider_credentials("fireworks")
assert creds["base_url"] == "https://gateway.example.com/v1"
class TestFireworksAuxiliary:
"""resolve_provider_client wires the BYOK key, PAYG-safe aux model, and the
partner-attribution headers through to the OpenAI client."""
"""resolve_provider_client wires the BYOK key and PAYG-safe aux model."""
def _resolve(self, name):
from unittest.mock import patch
@ -174,13 +155,13 @@ class TestFireworksAuxiliary:
client, model = resolve_provider_client(name)
return client, model, mock_openai.call_args.kwargs
def test_client_carries_attribution_headers(self, monkeypatch):
def test_client_has_no_partner_attribution_headers(self, monkeypatch):
monkeypatch.setenv("FIREWORKS_API_KEY", "fw_test_key")
client, model, kwargs = self._resolve("fireworks")
assert client is not None
headers = kwargs.get("default_headers", {})
assert headers.get("HTTP-Referer") == "https://hermes-agent.nousresearch.com"
assert headers.get("X-Title") == "Hermes Agent"
assert "HTTP-Referer" not in headers
assert "X-Title" not in headers
assert kwargs["base_url"] == "https://api.fireworks.ai/inference/v1"
def test_aux_model_is_payg_safe(self, monkeypatch):

View file

@ -1,8 +1,7 @@
"""Unit tests for the Fireworks AI provider profile.
Pins the profile's contract without going live: identity, attribution headers
(so Fireworks can attribute Hermes traffic for the partnership), alias
registration, and the pay-as-you-go model defaults (direct catalog ``/models/``
Pins the profile's contract without going live: identity, alias registration,
and the pay-as-you-go model defaults (direct catalog ``/models/``
IDs, not the router-only tier).
"""
@ -30,7 +29,7 @@ class TestFireworksIdentity:
assert p.auth_type == "api_key"
assert p.base_url == "https://api.fireworks.ai/inference/v1"
assert "FIREWORKS_API_KEY" in p.env_vars
assert "FIREWORKS_BASE_URL" in p.env_vars
assert "FIREWORKS_BASE_URL" not in p.env_vars
def test_display_metadata_present(self, fireworks_profile):
# Prominence copy is surfaced in the picker; keep it non-empty rather
@ -40,13 +39,10 @@ class TestFireworksIdentity:
assert fireworks_profile.signup_url.startswith("https://")
class TestFireworksAttributionHeaders:
"""The profile carries the partner-attribution headers Fireworks captures."""
def test_referer_and_title_headers(self, fireworks_profile):
headers = fireworks_profile.default_headers
assert headers.get("HTTP-Referer") == "https://hermes-agent.nousresearch.com"
assert headers.get("X-Title") == "Hermes Agent"
class TestFireworksHeaders:
def test_no_partner_attribution_headers(self, fireworks_profile):
assert "HTTP-Referer" not in fireworks_profile.default_headers
assert "X-Title" not in fireworks_profile.default_headers
class TestFireworksAliases:

View file

@ -6,7 +6,7 @@ Opt-in only:
Unlike a bare OpenAI() client pointed at the endpoint, this drives Hermes'
own provider resolution ``resolve_provider_client('fireworks')`` so it
verifies the auth/config/base-URL/attribution-header/aux-model wiring that the
verifies the auth/config/base-URL/aux-model wiring that the
bundled provider actually ships, then makes a real call through that client.
"""
@ -51,8 +51,6 @@ def test_fireworks_basic_chat_through_runtime():
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Say exactly the word 'pong' and nothing else."}],
max_tokens=32,
temperature=0.0,
timeout=60,
)

View file

@ -113,6 +113,7 @@ Good defaults:
| **OpenAI Codex** | ChatGPT OAuth, uses Codex models | Device code auth via `hermes model` |
| **Anthropic** | Claude models directly — Max plan + extra usage credits (OAuth), or API key for pay-per-token | `hermes model` → OAuth login (requires Max + extra credits), or an Anthropic API key |
| **OpenRouter** | Multi-provider routing across many models | Enter your API key |
| **Fireworks AI** | Direct OpenAI-compatible model API | Set `FIREWORKS_API_KEY` |
| **Z.AI** | GLM / Zhipu-hosted models | Set `GLM_API_KEY` / `ZAI_API_KEY` (also accepts `Z_AI_API_KEY`) |
| **Kimi / Moonshot** | Moonshot-hosted coding and chat models | Set `KIMI_API_KEY` (or the Kimi-Coding-specific `KIMI_CODING_API_KEY`) |
| **Kimi / Moonshot China** | China-region Moonshot endpoint | Set `KIMI_CN_API_KEY` |

View file

@ -20,6 +20,7 @@ You need at least one way to connect to an LLM. Use `hermes model` to switch pro
| **GitHub Copilot ACP** | `hermes model` (spawns local `copilot --acp --stdio`) |
| **Anthropic** | `hermes model` (Claude Max + extra usage credits via OAuth; also supports Anthropic API key or manual setup-token — see note below) |
| **OpenRouter** | `OPENROUTER_API_KEY` in `~/.hermes/.env` |
| **Fireworks AI** | `FIREWORKS_API_KEY` in `~/.hermes/.env` (provider: `fireworks`; aliases: `fireworks-ai`, `fw`) |
| **NovitaAI** | `NOVITA_API_KEY` in `~/.hermes/.env` (provider: `novita`, 200+ models, Model API, Agent Sandbox, GPU Cloud) |
| **z.ai / GLM** | `GLM_API_KEY` in `~/.hermes/.env` (provider: `zai`) |
| **Kimi / Moonshot** | `KIMI_API_KEY` in `~/.hermes/.env` (provider: `kimi-coding`) |
@ -214,6 +215,10 @@ model:
These providers have built-in support with dedicated provider IDs. Set the API key and use `--provider` to select:
```bash
# Fireworks AI
hermes chat --provider fireworks --model accounts/fireworks/models/kimi-k2p6
# Requires: FIREWORKS_API_KEY in ~/.hermes/.env
# NovitaAI Model API
hermes chat --provider novita --model moonshotai/kimi-k2.5
# Requires: NOVITA_API_KEY in ~/.hermes/.env
@ -260,6 +265,8 @@ hermes chat --provider gmi --model zai-org/GLM-5.1-FP8
# Requires: GMI_API_KEY in ~/.hermes/.env
```
Fireworks uses its native slash-form catalog IDs, such as `accounts/fireworks/models/kimi-k2p6`. Run `hermes model`, choose **Fireworks AI**, and select from the live catalog or enter another Fireworks model ID. The default endpoint is `https://api.fireworks.ai/inference/v1`; configure a different endpoint through `model.base_url` in `config.yaml`, not `.env`.
Or set the provider permanently in `config.yaml`:
```yaml
model:

View file

@ -14,6 +14,7 @@ Hermes reads environment variables from the process environment and, for user-ma
|----------|-------------|
| `OPENROUTER_API_KEY` | OpenRouter API key (recommended for flexibility) |
| `OPENROUTER_BASE_URL` | Override the OpenRouter-compatible base URL |
| `FIREWORKS_API_KEY` | Fireworks AI API key ([app.fireworks.ai](https://app.fireworks.ai/settings/users/api-keys)). Configure endpoint overrides with `model.base_url` in `config.yaml`. |
| `HERMES_OPENROUTER_CACHE` | Enable OpenRouter response caching (`1`/`true`/`yes`/`on`). Overrides `openrouter.response_cache` in config.yaml. See [Response Caching](https://openrouter.ai/docs/guides/features/response-caching). |
| `HERMES_OPENROUTER_CACHE_TTL` | Cache TTL in seconds (1-86400). Overrides `openrouter.response_cache_ttl` in config.yaml. |
| `NOUS_BASE_URL` | Override Nous Portal base URL (rarely needed; development/testing only) |

View file

@ -1170,7 +1170,7 @@ These options apply to **auxiliary task configs** (`auxiliary:`, `compression:`)
| `"xai-oauth"` | Force xAI Grok OAuth (browser login for SuperGrok or X Premium+ subscribers, no API key). Same OAuth token covers chat, TTS, image, video, and transcription. | `hermes model` → xAI Grok OAuth (SuperGrok / Premium+) |
| `"main"` | Use your active custom/main endpoint. This can come from `OPENAI_BASE_URL` + `OPENAI_API_KEY` or from a custom endpoint saved via `hermes model` / `config.yaml`. Works with OpenAI, local models, or any OpenAI-compatible API. **Auxiliary tasks only — not valid for `model.provider`.** | Custom endpoint credentials + base URL |
Direct API-key providers from the main provider catalog also work here when you want side tasks to bypass your default router. `gmi` is valid once `GMI_API_KEY` is configured:
Direct API-key providers from the main provider catalog also work here when you want side tasks to bypass your default router. For example, `gmi` is valid once `GMI_API_KEY` is configured, and `fireworks` is valid once `FIREWORKS_API_KEY` is configured:
```yaml
auxiliary:
@ -1179,7 +1179,7 @@ auxiliary:
model: "anthropic/claude-opus-4.6"
```
For GMI auxiliary routing, use the exact model ID returned by GMI's `/v1/models` endpoint.
For GMI auxiliary routing, use the exact model ID returned by GMI's `/v1/models` endpoint. Fireworks model IDs use the provider's native slash form, for example `accounts/fireworks/models/glm-5p2`.
### Common Setups