fix(code): infer Fireworks provider from qualified model IDs (#4594)

Deep Agents Code now preserves the Fireworks provider when `/model`
receives a fully qualified Fireworks model or router ID, so the switch
confirmation and status bar display the provider correctly.

---

Switching models with a fully qualified Fireworks ID such as `/model
accounts/fireworks/models/kimi-k2p7-code` succeeded because LangChain
inferred the provider during model initialization. Deep Agents Code did
not make the same inference beforehand, though, so it lost the provider
name: the switch confirmation showed only the raw ID and the status bar
omitted the `fireworks:` prefix.

`detect_provider` now recognizes the broad `accounts/fireworks/` stem,
matching LangChain's inference for both model and router IDs. The
existing, narrower model and router prefixes remain responsible for
shortening those IDs in the status bar and classifying reasoning-effort
support.

Made by [Open
SWE](https://openswe.vercel.app/agents/1f74e649-60d9-d586-f671-41b471b5a5d8)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-13 01:50:31 -04:00
committed by GitHub
parent 4c49a24c4c
commit 4d2aa8a968
6 changed files with 86 additions and 4 deletions
+4 -1
View File
@@ -13,11 +13,14 @@ from typing import Final
DEFAULT_AGENT_NAME: Final[str] = "agent"
"""Default agent / assistant identifier when no `-a` flag is given."""
FIREWORKS_PROVIDER_ID_PREFIX: Final[str] = "accounts/fireworks/"
"""Prefix used to infer Fireworks from fully-qualified IDs."""
FIREWORKS_MODEL_ID_PREFIXES: Final[tuple[str, ...]] = (
"accounts/fireworks/models/",
"accounts/fireworks/routers/",
)
"""Fully-qualified prefixes for Fireworks model and router IDs."""
"""Model and router ID prefixes used for stripping and classification."""
SYSTEM_MESSAGE_PREFIX: Final[str] = "[SYSTEM]"
"""Prefix for synthetic human messages (e.g. interrupt cancellation notices).
+12 -2
View File
@@ -19,6 +19,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, Any, Protocol
from urllib.parse import unquote, urlparse
from deepagents_code._constants import FIREWORKS_PROVIDER_ID_PREFIX
from deepagents_code._env_vars import (
DISABLED_PROJECT_MCP_SERVERS,
ENABLED_PROJECT_MCP_SERVERS,
@@ -3796,8 +3797,8 @@ def detect_provider(model_name: str) -> str | None:
Returns:
Provider name (openai, anthropic, google_genai, google_vertexai,
nvidia) or `None` if the provider cannot be determined from the
name alone.
nvidia, fireworks) or `None` if the provider cannot be determined
from the name alone.
"""
model_lower = model_name.lower()
@@ -3819,6 +3820,15 @@ def detect_provider(model_name: str) -> str | None:
if model_lower.startswith(("nemotron", "nvidia/")):
return "nvidia"
# Fireworks uses fully-qualified IDs like `accounts/fireworks/models/<name>`.
# `init_chat_model` can infer the provider from this prefix, but the inferred
# name is not exposed on the returned model, so resolving it here keeps the
# provider visible to every downstream consumer of `detect_provider` (e.g.
# the `/model` confirmation, the status bar, and the early credential check)
# instead of leaving the raw ID unprefixed.
if model_lower.startswith(FIREWORKS_PROVIDER_ID_PREFIX):
return "fireworks"
return None
@@ -70,8 +70,10 @@ class ModelLabel(Widget):
name = self.model
if not name or not self.provider:
return name
# Match on normalized text but slice the original to preserve its casing.
name_lower = name.lower()
for prefix in PROVIDER_PREFIX_STRIPS.get(self.provider, ()):
if name.startswith(prefix):
if name_lower.startswith(prefix):
return name[len(prefix) :]
return name
@@ -4750,6 +4750,14 @@ class TestDetectProvider:
("gemini-3.1-pro-preview", "google_genai"),
("nemotron-3-nano-30b-a3b", "nvidia"),
("nvidia/nemotron-3-nano-30b-a3b", "nvidia"),
("accounts/fireworks/models/kimi-k2p7-code", "fireworks"),
("accounts/fireworks/routers/kimi-k2p7-code", "fireworks"),
("Accounts/Fireworks/Models/Kimi-K2P7-Code", "fireworks"),
("accounts/openai/models/gpt-5.5", None),
# A different account whose name merely starts with "fireworks"
# must not resolve to Fireworks; the trailing slash in the prefix
# is what anchors the match to the exact account namespace.
("accounts/fireworks-enterprise/models/kimi-k2p7-code", None),
("llama3", None),
("mistral-large", None),
("some-unknown-model", None),
@@ -1026,6 +1026,47 @@ class TestModelSwitchBareModelName:
assert settings.model_provider == "openai"
assert any("Switched to openai:gpt-5.5" in m for m in captured_messages)
async def test_fireworks_qualified_id_gets_provider_prefix(self) -> None:
"""A Fireworks `accounts/...` ID resolves to a `fireworks:` prefix.
Without provider inference the raw ID would surface unprefixed in the
confirmation message and the status bar (which reads
`settings.model_provider`). `detect_provider` recognizes the
fully-qualified Fireworks ID so both reflect the `fireworks` provider.
"""
app = DeepAgentsApp()
app._mount_message = AsyncMock() # ty: ignore
app._agent = _make_remote_agent()
settings.model_name = "claude-sonnet-4-5"
settings.model_provider = "anthropic"
captured_messages: list[str] = []
original_init = AppMessage.__init__
def capture_init(self: AppMessage, message: str, **kwargs: Any) -> None:
captured_messages.append(message)
original_init(self, message, **kwargs)
model_id = "accounts/fireworks/models/kimi-k2p7-code"
with (
patch(
"deepagents_code.model_config.get_provider_auth_status",
return_value=_CONFIGURED_AUTH_STATUS,
),
patch(
"deepagents_code.model_config.save_recent_model", return_value=True
) as mock_save,
patch.object(AppMessage, "__init__", capture_init),
):
await app._switch_model(model_id)
mock_save.assert_called_once_with(f"fireworks:{model_id}")
assert app._model_override == f"fireworks:{model_id}"
assert settings.model_name == model_id
assert settings.model_provider == "fireworks"
assert any(f"Switched to fireworks:{model_id}" in m for m in captured_messages)
async def test_bare_model_name_missing_credentials(self) -> None:
"""Bare model name shows credential error when provider creds are missing."""
app = DeepAgentsApp()
@@ -553,6 +553,24 @@ class TestModelLabelPrefixStripping:
assert "fireworks:glm-5p1-fast" in rendered
assert "accounts/fireworks/routers/" not in rendered
async def test_fireworks_prefix_stripped_case_insensitively(self) -> None:
"""A mixed-case fireworks ID is stripped, preserving the tail's casing.
`detect_provider` resolves mixed-case `accounts/fireworks/...` IDs to
the `fireworks` provider, so the display layer strips the prefix
case-insensitively too. The remaining model name keeps its original
casing rather than being lowercased.
"""
async with StatusBarApp().run_test() as pilot:
label = pilot.app.query_one("#model-display", ModelLabel)
label.provider = "fireworks"
label.model = "Accounts/Fireworks/Models/Kimi-K2P6"
await pilot.pause()
assert label._clean_model() == "Kimi-K2P6"
rendered = str(label.render())
assert "fireworks:Kimi-K2P6" in rendered
assert "Accounts/Fireworks/Models/" not in rendered
async def test_get_content_width_uses_stripped_name(self) -> None:
"""`get_content_width` sizes to the stripped name, not the raw model."""
async with StatusBarApp().run_test() as pilot: