Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion oddish/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ oddish-docstore-mcp = "oddish.mcp.docstore_server:main"

[tool.uv.sources]
# Keep the worker image on the exact Harbor revision used by Oddish's runtime gate.
harbor = { git = "https://github.com/abundant-ai/harbor", rev = "ca4fda6aa75180487c2c7c07fabaaf03d01b2e8d" }
harbor = { git = "https://github.com/abundant-ai/harbor", rev = "078136c5c9ca03f8498babe6ea372f39f9062025" }

[tool.uv]
override-dependencies = ["harbor==0.16.1"]
Expand Down
50 changes: 50 additions & 0 deletions oddish/src/oddish/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,49 @@ def to_moonshot_model_id(model: str | None) -> str | None:
return f"{MOONSHOT_PROVIDER}/{moonshot_bare_model_id(model)}"


# DeepSeek routing for the ``dsh`` harness. Trials use ``deepseek/<model>`` so
# they get their own provider/queue bucket distinct from OpenRouter or Fireworks.
DEEPSEEK_PROVIDER = "deepseek"
DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com"
_DEEPSEEK_PROVIDER_PREFIXES: frozenset[str] = frozenset({"deepseek", "ds"})
_DEEPSEEK_MODEL_ALIASES: dict[str, str] = {
"deepseek-v4-pro-0813": "deepseek-v4-pro",
}


def is_deepseek_model(model: str | None) -> bool:
"""Return True if *model* should route to DeepSeek's official API."""
if not model:
return False
raw = model.strip().lower()
if not raw:
return False
provider_prefix, bare = split_provider_model_name(raw)
if provider_prefix:
return provider_prefix.strip().lower() in _DEEPSEEK_PROVIDER_PREFIXES
bare_id = raw.split("/")[-1]
return bare_id.startswith("deepseek-")


def deepseek_bare_model_id(model: str) -> str:
"""Strip the ``deepseek/`` prefix and normalize GA aliases."""
raw = model.strip()
provider_prefix, bare = split_provider_model_name(raw)
if provider_prefix and provider_prefix.strip().lower() in _DEEPSEEK_PROVIDER_PREFIXES:
bare = bare.strip()
else:
bare = raw
return _DEEPSEEK_MODEL_ALIASES.get(bare, bare)


def to_deepseek_model_id(model: str | None) -> str | None:
"""Canonicalize a DeepSeek reference to ``deepseek/<bare-id>``."""
if not is_deepseek_model(model):
return model
assert model is not None
return f"{DEEPSEEK_PROVIDER}/{deepseek_bare_model_id(model)}"


# Fireworks routing. Fireworks serves GLM / MiniMax / Kimi (and many other open
# models) over a single Anthropic-compatible ``/messages`` endpoint, so they run
# on the claude-code harness against Fireworks instead of each model's own direct
Expand Down Expand Up @@ -938,6 +981,9 @@ def _build_agent_provider_map() -> dict[str, str]:
"meta": META_PROVIDER,
# Direct Anthropic API with the separate HDO key (ANTHROPIC_HDO_API_KEY).
"anthropic-hdo": ANTHROPIC_HDO_PROVIDER,
# DeepSeek official API for the dsh harness.
"deepseek": DEEPSEEK_PROVIDER,
"ds": DEEPSEEK_PROVIDER,
}


Expand Down Expand Up @@ -1020,6 +1066,8 @@ def _infer_provider_prefix(
return MOONSHOT_PROVIDER
if lowered.startswith("grok-"):
return XAI_PROVIDER
if lowered.startswith("deepseek-"):
return DEEPSEEK_PROVIDER

return None

Expand Down Expand Up @@ -1703,6 +1751,8 @@ def normalize_trial_model(
return to_minimax_model_id(cleaned)
if is_moonshot_model(cleaned):
return to_moonshot_model_id(cleaned)
if is_deepseek_model(cleaned):
return to_deepseek_model_id(cleaned)
# Explicit ``anthropic-hdo/`` keeps Claude on the direct Anthropic API
# with ANTHROPIC_HDO_API_KEY — must win over the Bedrock chokepoint.
if is_anthropic_hdo_model(cleaned):
Expand Down
34 changes: 31 additions & 3 deletions oddish/src/oddish/workers/harbor/model_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import Any

from oddish.config import (
DEEPSEEK_DEFAULT_BASE_URL,
FIREWORKS_DEFAULT_BASE_URL,
META_DEFAULT_BASE_URL,
MINIMAX_DEFAULT_BASE_URL,
Expand All @@ -23,6 +24,7 @@
ZAI_DEFAULT_BASE_URL,
infer_model_provider_prefix,
is_anthropic_hdo_model,
is_deepseek_model,
is_fireworks_model,
is_meta_model,
is_minimax_model,
Expand All @@ -49,6 +51,7 @@
ZAI_BASE_URL_KEYS = ("ZAI_BASE_URL",)
MINIMAX_BASE_URL_KEYS = ("MINIMAX_BASE_URL",)
MOONSHOT_BASE_URL_KEYS = ("MOONSHOT_BASE_URL",)
DEEPSEEK_BASE_URL_KEYS = ("DEEPSEEK_BASE_URL",)
GEMINI_BASE_URL_KEYS = (
"GOOGLE_GEMINI_BASE_URL",
"GEMINI_API_BASE_URL",
Expand Down Expand Up @@ -78,6 +81,7 @@
*ZAI_BASE_URL_KEYS,
*MINIMAX_BASE_URL_KEYS,
*MOONSHOT_BASE_URL_KEYS,
*DEEPSEEK_BASE_URL_KEYS,
*GEMINI_BASE_URL_KEYS,
*CURSOR_BASE_URL_KEYS,
)
Expand Down Expand Up @@ -113,6 +117,15 @@
# is keyed on a ``cursor/`` model prefix.
TBH_BASE_URL_KEYS = ("TBH_BASE_URL",)
_TBH_RUNTIME_HOSTS = ("api.meta.ai",)
_DSH_INSTALL_HOSTS: tuple[str, ...] = (
"raw.githubusercontent.com",
"github.com",
"objects.githubusercontent.com",
"codeload.github.com",
"nodejs.org",
"registry.npmjs.org",
)
_DSH_DEEPSEEK_RUNTIME_HOSTS = ("api.deepseek.com",)
# opencode has no pre-baked worker image: Harbor's ``OpenCode.install``
# bootstraps nvm, a Node runtime, and the ``opencode-ai`` npm package during
# agent SETUP -- which runs under the ENVIRONMENT baseline, before the
Expand All @@ -131,7 +144,10 @@
"nodejs.org", # Node runtime downloaded by nvm
"registry.npmjs.org", # npm metadata + package tarballs
)
_AGENT_RUNTIME_HOSTS: dict[str, tuple[str, ...]] = {"tbh": _TBH_RUNTIME_HOSTS}
_AGENT_RUNTIME_HOSTS: dict[str, tuple[str, ...]] = {
"tbh": _TBH_RUNTIME_HOSTS,
"dsh": _DSH_INSTALL_HOSTS + _DSH_DEEPSEEK_RUNTIME_HOSTS,
}

_DEFAULT_BEDROCK_REGION = "us-east-1"
_BEDROCK_STS_DOMAINS = ("sts.amazonaws.com",)
Expand Down Expand Up @@ -235,11 +251,17 @@ def agent_runtime_hosts(
extra_env = agent_kwargs.get("extra_env")
if not override and isinstance(extra_env, Mapping):
override = next(
(extra_env.get(k) for k in TBH_BASE_URL_KEYS if extra_env.get(k)), None
(
extra_env.get(k)
for k in (*TBH_BASE_URL_KEYS, *DEEPSEEK_BASE_URL_KEYS)
if extra_env.get(k)
),
None,
)
if not override and isinstance(agent_env, Mapping):
override = next(
(agent_env.get(k) for k in TBH_BASE_URL_KEYS if agent_env.get(k)), None
(agent_env.get(k) for k in (*TBH_BASE_URL_KEYS, *DEEPSEEK_BASE_URL_KEYS) if agent_env.get(k)),
None,
)
if isinstance(override, str):
host = _host_from_url(override)
Expand Down Expand Up @@ -320,6 +342,12 @@ def outbound_hosts_for_model(
)
if host:
hosts.append(host)
elif is_deepseek_model(model_name):
host = _default_host(
os.environ.get("DEEPSEEK_BASE_URL") or DEEPSEEK_DEFAULT_BASE_URL
)
if host:
hosts.append(host)
elif is_xai_model(model_name):
hosts.extend(_XAI_HOSTS)
elif is_meta_model(model_name):
Expand Down
1 change: 1 addition & 0 deletions oddish/src/oddish/workers/harbor/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ def _gemini_ai_sdk_alias_env(model: str | None) -> dict[str, str]:
"xai": ("XAI_API_KEY", "XAI_API_KEYS"),
"meta": ("META_API_KEY", "OPENAI_API_KEY"),
"fireworks": ("FIREWORKS_API_KEY",),
"deepseek": ("DEEPSEEK_API_KEY",),
"zai": ("ZAI_API_KEY",),
"minimax": ("MINIMAX_API_KEY",),
"moonshot": ("MOONSHOT_API_KEY",),
Expand Down
15 changes: 15 additions & 0 deletions oddish/tests/test_config_queue_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,21 @@ def test_moonshot_model_routes_to_moonshot_not_bedrock(monkeypatch):
), raw


def test_deepseek_model_routes_to_deepseek_provider(monkeypatch):
settings = _settings(monkeypatch, clear_openai_env=False)

cases = {
"deepseek-v4-pro": "deepseek/deepseek-v4-pro",
"deepseek/deepseek-v4-pro": "deepseek/deepseek-v4-pro",
"deepseek/deepseek-v4-pro-0813": "deepseek/deepseek-v4-pro",
"ds/deepseek-v4-flash": "deepseek/deepseek-v4-flash",
}
for raw, canonical in cases.items():
assert settings.normalize_trial_model("dsh", raw) == canonical, raw
assert settings.get_provider_for_trial("dsh", raw) == "deepseek", raw
assert settings.get_queue_key_for_trial("dsh", raw) == canonical, raw


def test_fireworks_models_route_to_fireworks_not_direct_providers(monkeypatch):
settings = _settings(monkeypatch, clear_openai_env=False)

Expand Down
10 changes: 5 additions & 5 deletions oddish/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading