From fe98abf675ff3eac7fbda958ffaaa34edfbbac33 Mon Sep 17 00:00:00 2001 From: Alishahryar1 Date: Wed, 20 May 2026 20:57:11 -0700 Subject: [PATCH] feat(admin): add Fireworks API key and proxy to admin manifest Registers FIREWORKS_API_KEY / FIREWORKS_PROXY in api/admin_config FIELDS so the UI can set credentials and provider status stays accurate. Adds admin apply test and contract guards linking PROVIDER_CATALOG to FIELD_BY_KEY; updates .env.example. --- .env.example | 8 ++- api/admin_config.py | 18 +++++ tests/api/test_admin.py | 26 ++++++++ .../contracts/test_admin_provider_manifest.py | 66 +++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/contracts/test_admin_provider_manifest.py diff --git a/.env.example b/.env.example index 41e7424..3e7c85d 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,10 @@ OPENCODE_API_KEY="" ZAI_API_KEY="" +# Fireworks AI Config (OpenAI-compatible Chat Completions at api.fireworks.ai/inference/v1) +FIREWORKS_API_KEY="" + + # LM Studio Config (local provider, no API key required) LM_STUDIO_BASE_URL="http://localhost:1234/v1" @@ -40,7 +44,7 @@ OLLAMA_BASE_URL="http://localhost:11434" # All Claude model requests are mapped to these models, plain model is fallback # Format: provider_type/model/name -# Valid providers: "nvidia_nim" | "open_router" | "deepseek" | "lmstudio" | "llamacpp" | "ollama" | "kimi" | "wafer" | "opencode" | "zai" +# Valid providers: "nvidia_nim" | "open_router" | "deepseek" | "lmstudio" | "llamacpp" | "ollama" | "kimi" | "wafer" | "opencode" | "zai" | "fireworks" MODEL_OPUS= MODEL_SONNET= MODEL_HAIKU= @@ -59,6 +63,7 @@ FCC_SMOKE_MODEL_KIMI= FCC_SMOKE_MODEL_WAFER= FCC_SMOKE_MODEL_OPENCODE= FCC_SMOKE_MODEL_ZAI= +FCC_SMOKE_MODEL_FIREWORKS= FCC_SMOKE_NIM_MODELS= FCC_SMOKE_NIM_EXTRA_MODELS= FCC_SMOKE_OPENROUTER_FREE_MODELS= @@ -84,6 +89,7 @@ KIMI_PROXY="" WAFER_PROXY="" OPENCODE_PROXY="" ZAI_PROXY="" +FIREWORKS_PROXY="" PROVIDER_RATE_LIMIT=1 PROVIDER_RATE_WINDOW=3 diff --git a/api/admin_config.py b/api/admin_config.py index f5c4d7e..1acc047 100644 --- a/api/admin_config.py +++ b/api/admin_config.py @@ -176,6 +176,15 @@ FIELDS: tuple[ConfigFieldSpec, ...] = ( secret=True, description="Z.ai Coding Plan API key.", ), + ConfigFieldSpec( + "FIREWORKS_API_KEY", + "Fireworks API Key", + "providers", + "secret", + settings_attr="fireworks_api_key", + secret=True, + description="Fireworks AI inference API key.", + ), ConfigFieldSpec( "LM_STUDIO_BASE_URL", "LM Studio Base URL", @@ -269,6 +278,15 @@ FIELDS: tuple[ConfigFieldSpec, ...] = ( secret=True, advanced=True, ), + ConfigFieldSpec( + "FIREWORKS_PROXY", + "Fireworks Proxy", + "providers", + "secret", + settings_attr="fireworks_proxy", + secret=True, + advanced=True, + ), ConfigFieldSpec( "MODEL", "Default Model", diff --git a/tests/api/test_admin.py b/tests/api/test_admin.py index 71a4fec..21c41e0 100644 --- a/tests/api/test_admin.py +++ b/tests/api/test_admin.py @@ -102,6 +102,7 @@ def test_admin_config_masks_secrets_and_exposes_manifest(monkeypatch, tmp_path): keys = {field["key"] for field in body["fields"]} assert "ANTHROPIC_AUTH_TOKEN" in keys assert "OPENROUTER_API_KEY" in keys + assert "FIREWORKS_API_KEY" in keys assert "ZAI_BASE_URL" not in keys assert "CLAUDE_WORKSPACE" not in keys assert "CLAUDE_CLI_BIN" not in keys @@ -181,6 +182,31 @@ def test_admin_apply_writes_complete_managed_env_and_masks_preview( } +def test_admin_apply_writes_fireworks_key_and_masks_preview(monkeypatch, tmp_path): + _set_home(monkeypatch, tmp_path) + _clear_process_config(monkeypatch) + app = create_app(lifespan_enabled=False) + + response = _local_client(app).post( + "/admin/api/config/apply", + json={ + "values": { + "MODEL": "fireworks/test-model", + "FIREWORKS_API_KEY": "fw-secret", + } + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["applied"] is True + assert "FIREWORKS_API_KEY=********" in body["env_preview"] + env_file = tmp_path / ".fcc" / ".env" + text = env_file.read_text(encoding="utf-8") + assert "MODEL=fireworks/test-model" in text + assert "FIREWORKS_API_KEY=fw-secret" in text + + def test_admin_apply_preserves_hidden_diagnostics_and_smoke_values( monkeypatch, tmp_path ): diff --git a/tests/contracts/test_admin_provider_manifest.py b/tests/contracts/test_admin_provider_manifest.py new file mode 100644 index 0000000..e4ef741 --- /dev/null +++ b/tests/contracts/test_admin_provider_manifest.py @@ -0,0 +1,66 @@ +"""Ensure admin UI manifest exposes every catalog credential/proxy binding.""" + +from __future__ import annotations + +from api.admin_config import FIELD_BY_KEY +from config.provider_catalog import PROVIDER_CATALOG +from config.settings import Settings + + +def test_provider_catalog_remote_credentials_in_admin_manifest() -> None: + missing: list[str] = [] + wrong_attr: list[str] = [] + + for provider_id, desc in PROVIDER_CATALOG.items(): + if desc.credential_env is None: + continue + if desc.credential_attr is None: + missing.append( + f"{provider_id}: credential_env set but credential_attr missing" + ) + continue + entry = FIELD_BY_KEY.get(desc.credential_env) + if entry is None: + missing.append( + f"{provider_id}: {desc.credential_env} not in admin FIELD_BY_KEY" + ) + continue + if entry.settings_attr != desc.credential_attr: + wrong_attr.append( + f"{provider_id}: {desc.credential_env} maps settings_attr=" + f"{entry.settings_attr!r}, catalog expects " + f"{desc.credential_attr!r}" + ) + + assert not missing and not wrong_attr, "\n".join(missing + wrong_attr) + + +def test_provider_catalog_proxy_attrs_in_admin_manifest() -> None: + missing_key: list[str] = [] + wrong_attr: list[str] = [] + + for provider_id, desc in PROVIDER_CATALOG.items(): + if desc.proxy_attr is None: + continue + mf = Settings.model_fields[desc.proxy_attr] + alias = mf.validation_alias + if alias is None: + missing_key.append( + f"{provider_id}: {desc.proxy_attr} has no validation_alias " + "(admin manifest expects env-backed proxy)" + ) + continue + env_key = str(alias) + entry = FIELD_BY_KEY.get(env_key) + if entry is None: + missing_key.append( + f"{provider_id}: proxy env {env_key} not in FIELD_BY_KEY" + ) + continue + if entry.settings_attr != desc.proxy_attr: + wrong_attr.append( + f"{provider_id}: {env_key} maps settings_attr=" + f"{entry.settings_attr!r}, catalog expects {desc.proxy_attr!r}" + ) + + assert not missing_key and not wrong_attr, "\n".join(missing_key + wrong_attr)