Skip to content

Commit 86f1275

Browse files
Wire DeepSeek Harness (dsh) model routing in Oddish (#1256)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 005002b commit 86f1275

6 files changed

Lines changed: 103 additions & 9 deletions

File tree

oddish/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ oddish-docstore-mcp = "oddish.mcp.docstore_server:main"
117117

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

122122
[tool.uv]
123123
override-dependencies = ["harbor==0.16.1"]

oddish/src/oddish/config.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,49 @@ def to_moonshot_model_id(model: str | None) -> str | None:
413413
return f"{MOONSHOT_PROVIDER}/{moonshot_bare_model_id(model)}"
414414

415415

416+
# DeepSeek routing for the ``dsh`` harness. Trials use ``deepseek/<model>`` so
417+
# they get their own provider/queue bucket distinct from OpenRouter or Fireworks.
418+
DEEPSEEK_PROVIDER = "deepseek"
419+
DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com"
420+
_DEEPSEEK_PROVIDER_PREFIXES: frozenset[str] = frozenset({"deepseek", "ds"})
421+
_DEEPSEEK_MODEL_ALIASES: dict[str, str] = {
422+
"deepseek-v4-pro-0813": "deepseek-v4-pro",
423+
}
424+
425+
426+
def is_deepseek_model(model: str | None) -> bool:
427+
"""Return True if *model* should route to DeepSeek's official API."""
428+
if not model:
429+
return False
430+
raw = model.strip().lower()
431+
if not raw:
432+
return False
433+
provider_prefix, bare = split_provider_model_name(raw)
434+
if provider_prefix:
435+
return provider_prefix.strip().lower() in _DEEPSEEK_PROVIDER_PREFIXES
436+
bare_id = raw.split("/")[-1]
437+
return bare_id.startswith("deepseek-")
438+
439+
440+
def deepseek_bare_model_id(model: str) -> str:
441+
"""Strip the ``deepseek/`` prefix and normalize GA aliases."""
442+
raw = model.strip()
443+
provider_prefix, bare = split_provider_model_name(raw)
444+
if provider_prefix and provider_prefix.strip().lower() in _DEEPSEEK_PROVIDER_PREFIXES:
445+
bare = bare.strip()
446+
else:
447+
bare = raw
448+
return _DEEPSEEK_MODEL_ALIASES.get(bare, bare)
449+
450+
451+
def to_deepseek_model_id(model: str | None) -> str | None:
452+
"""Canonicalize a DeepSeek reference to ``deepseek/<bare-id>``."""
453+
if not is_deepseek_model(model):
454+
return model
455+
assert model is not None
456+
return f"{DEEPSEEK_PROVIDER}/{deepseek_bare_model_id(model)}"
457+
458+
416459
# Fireworks routing. Fireworks serves GLM / MiniMax / Kimi (and many other open
417460
# models) over a single Anthropic-compatible ``/messages`` endpoint, so they run
418461
# on the claude-code harness against Fireworks instead of each model's own direct
@@ -938,6 +981,9 @@ def _build_agent_provider_map() -> dict[str, str]:
938981
"meta": META_PROVIDER,
939982
# Direct Anthropic API with the separate HDO key (ANTHROPIC_HDO_API_KEY).
940983
"anthropic-hdo": ANTHROPIC_HDO_PROVIDER,
984+
# DeepSeek official API for the dsh harness.
985+
"deepseek": DEEPSEEK_PROVIDER,
986+
"ds": DEEPSEEK_PROVIDER,
941987
}
942988

943989

@@ -1020,6 +1066,8 @@ def _infer_provider_prefix(
10201066
return MOONSHOT_PROVIDER
10211067
if lowered.startswith("grok-"):
10221068
return XAI_PROVIDER
1069+
if lowered.startswith("deepseek-"):
1070+
return DEEPSEEK_PROVIDER
10231071

10241072
return None
10251073

@@ -1703,6 +1751,8 @@ def normalize_trial_model(
17031751
return to_minimax_model_id(cleaned)
17041752
if is_moonshot_model(cleaned):
17051753
return to_moonshot_model_id(cleaned)
1754+
if is_deepseek_model(cleaned):
1755+
return to_deepseek_model_id(cleaned)
17061756
# Explicit ``anthropic-hdo/`` keeps Claude on the direct Anthropic API
17071757
# with ANTHROPIC_HDO_API_KEY — must win over the Bedrock chokepoint.
17081758
if is_anthropic_hdo_model(cleaned):

oddish/src/oddish/workers/harbor/model_hosts.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import Any
1616

1717
from oddish.config import (
18+
DEEPSEEK_DEFAULT_BASE_URL,
1819
FIREWORKS_DEFAULT_BASE_URL,
1920
META_DEFAULT_BASE_URL,
2021
MINIMAX_DEFAULT_BASE_URL,
@@ -23,6 +24,7 @@
2324
ZAI_DEFAULT_BASE_URL,
2425
infer_model_provider_prefix,
2526
is_anthropic_hdo_model,
27+
is_deepseek_model,
2628
is_fireworks_model,
2729
is_meta_model,
2830
is_minimax_model,
@@ -49,6 +51,7 @@
4951
ZAI_BASE_URL_KEYS = ("ZAI_BASE_URL",)
5052
MINIMAX_BASE_URL_KEYS = ("MINIMAX_BASE_URL",)
5153
MOONSHOT_BASE_URL_KEYS = ("MOONSHOT_BASE_URL",)
54+
DEEPSEEK_BASE_URL_KEYS = ("DEEPSEEK_BASE_URL",)
5255
GEMINI_BASE_URL_KEYS = (
5356
"GOOGLE_GEMINI_BASE_URL",
5457
"GEMINI_API_BASE_URL",
@@ -78,6 +81,7 @@
7881
*ZAI_BASE_URL_KEYS,
7982
*MINIMAX_BASE_URL_KEYS,
8083
*MOONSHOT_BASE_URL_KEYS,
84+
*DEEPSEEK_BASE_URL_KEYS,
8185
*GEMINI_BASE_URL_KEYS,
8286
*CURSOR_BASE_URL_KEYS,
8387
)
@@ -113,6 +117,15 @@
113117
# is keyed on a ``cursor/`` model prefix.
114118
TBH_BASE_URL_KEYS = ("TBH_BASE_URL",)
115119
_TBH_RUNTIME_HOSTS = ("api.meta.ai",)
120+
_DSH_INSTALL_HOSTS: tuple[str, ...] = (
121+
"raw.githubusercontent.com",
122+
"github.com",
123+
"objects.githubusercontent.com",
124+
"codeload.github.com",
125+
"nodejs.org",
126+
"registry.npmjs.org",
127+
)
128+
_DSH_DEEPSEEK_RUNTIME_HOSTS = ("api.deepseek.com",)
116129
# opencode has no pre-baked worker image: Harbor's ``OpenCode.install``
117130
# bootstraps nvm, a Node runtime, and the ``opencode-ai`` npm package during
118131
# agent SETUP -- which runs under the ENVIRONMENT baseline, before the
@@ -131,7 +144,10 @@
131144
"nodejs.org", # Node runtime downloaded by nvm
132145
"registry.npmjs.org", # npm metadata + package tarballs
133146
)
134-
_AGENT_RUNTIME_HOSTS: dict[str, tuple[str, ...]] = {"tbh": _TBH_RUNTIME_HOSTS}
147+
_AGENT_RUNTIME_HOSTS: dict[str, tuple[str, ...]] = {
148+
"tbh": _TBH_RUNTIME_HOSTS,
149+
"dsh": _DSH_INSTALL_HOSTS + _DSH_DEEPSEEK_RUNTIME_HOSTS,
150+
}
135151

136152
_DEFAULT_BEDROCK_REGION = "us-east-1"
137153
_BEDROCK_STS_DOMAINS = ("sts.amazonaws.com",)
@@ -235,11 +251,17 @@ def agent_runtime_hosts(
235251
extra_env = agent_kwargs.get("extra_env")
236252
if not override and isinstance(extra_env, Mapping):
237253
override = next(
238-
(extra_env.get(k) for k in TBH_BASE_URL_KEYS if extra_env.get(k)), None
254+
(
255+
extra_env.get(k)
256+
for k in (*TBH_BASE_URL_KEYS, *DEEPSEEK_BASE_URL_KEYS)
257+
if extra_env.get(k)
258+
),
259+
None,
239260
)
240261
if not override and isinstance(agent_env, Mapping):
241262
override = next(
242-
(agent_env.get(k) for k in TBH_BASE_URL_KEYS if agent_env.get(k)), None
263+
(agent_env.get(k) for k in (*TBH_BASE_URL_KEYS, *DEEPSEEK_BASE_URL_KEYS) if agent_env.get(k)),
264+
None,
243265
)
244266
if isinstance(override, str):
245267
host = _host_from_url(override)
@@ -320,6 +342,12 @@ def outbound_hosts_for_model(
320342
)
321343
if host:
322344
hosts.append(host)
345+
elif is_deepseek_model(model_name):
346+
host = _default_host(
347+
os.environ.get("DEEPSEEK_BASE_URL") or DEEPSEEK_DEFAULT_BASE_URL
348+
)
349+
if host:
350+
hosts.append(host)
323351
elif is_xai_model(model_name):
324352
hosts.extend(_XAI_HOSTS)
325353
elif is_meta_model(model_name):

oddish/src/oddish/workers/harbor/runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def _gemini_ai_sdk_alias_env(model: str | None) -> dict[str, str]:
255255
"xai": ("XAI_API_KEY", "XAI_API_KEYS"),
256256
"meta": ("META_API_KEY", "OPENAI_API_KEY"),
257257
"fireworks": ("FIREWORKS_API_KEY",),
258+
"deepseek": ("DEEPSEEK_API_KEY",),
258259
"zai": ("ZAI_API_KEY",),
259260
"minimax": ("MINIMAX_API_KEY",),
260261
"moonshot": ("MOONSHOT_API_KEY",),

oddish/tests/test_config_queue_keys.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,21 @@ def test_moonshot_model_routes_to_moonshot_not_bedrock(monkeypatch):
276276
), raw
277277

278278

279+
def test_deepseek_model_routes_to_deepseek_provider(monkeypatch):
280+
settings = _settings(monkeypatch, clear_openai_env=False)
281+
282+
cases = {
283+
"deepseek-v4-pro": "deepseek/deepseek-v4-pro",
284+
"deepseek/deepseek-v4-pro": "deepseek/deepseek-v4-pro",
285+
"deepseek/deepseek-v4-pro-0813": "deepseek/deepseek-v4-pro",
286+
"ds/deepseek-v4-flash": "deepseek/deepseek-v4-flash",
287+
}
288+
for raw, canonical in cases.items():
289+
assert settings.normalize_trial_model("dsh", raw) == canonical, raw
290+
assert settings.get_provider_for_trial("dsh", raw) == "deepseek", raw
291+
assert settings.get_queue_key_for_trial("dsh", raw) == canonical, raw
292+
293+
279294
def test_fireworks_models_route_to_fireworks_not_direct_providers(monkeypatch):
280295
settings = _settings(monkeypatch, clear_openai_env=False)
281296

oddish/uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)