Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
134ae6a
docs: add dsh (deepseek-harness) compatibility design spec
Abdelakil Aug 13, 2026
70d2e18
docs: add dsh compatibility implementation plan
Abdelakil Aug 13, 2026
4c4443b
docs: correct plan test runner, wrap-test location, and resolve_dsh_c…
Abdelakil Aug 13, 2026
08e5f3e
feat(dsh): add dsh provider runtime package
Abdelakil Aug 13, 2026
42992b1
feat(dsh): register dsh install env builder
Abdelakil Aug 13, 2026
b2bef4d
feat(proxy): add deepseek upstream target to provider registry
Abdelakil Aug 13, 2026
8cd38f5
feat(proxy): add deepseek_api_url to ProxyConfig
Abdelakil Aug 13, 2026
bf0ad4b
feat(proxy): expose DEEPSEEK_API_URL on the proxy server
Abdelakil Aug 13, 2026
4bc0a80
fix(proxy): align DeepSeek banner label column
Abdelakil Aug 13, 2026
1cc1037
docs: fix DeepSeek banner label alignment in plan
Abdelakil Aug 13, 2026
6b4d8bf
feat(proxy): route dsh DeepSeek traffic to the deepseek upstream
Abdelakil Aug 13, 2026
471331f
fix(proxy): route DeepSeek traffic to deepseek upstream at URL build
Abdelakil Aug 13, 2026
4e6be45
docs: correct Task 6 routing fix location in plan
Abdelakil Aug 13, 2026
a7af40c
fix(dsh): standardize missing-binary error to 'not found in PATH'
Abdelakil Aug 13, 2026
b21908e
feat(dsh): add wrap/unwrap dsh CLI commands
Abdelakil Aug 13, 2026
25aa99d
fix(proxy): add --deepseek-api-url option to proxy CLI
Abdelakil Aug 13, 2026
93bb9ca
docs(dsh): document dsh wrap in compatibility matrix and wiki
Abdelakil Aug 13, 2026
922e1d2
fix(dsh): capture pre-existing DEEPSEEK_BASE_URL as upstream override
Abdelakil Aug 13, 2026
b81dda5
fix(dsh): satisfy ruff and mypy lint gates
Zenjibad Aug 13, 2026
a7eb7bc
chore: drop superpowers planning artifacts from the feature branch
Zenjibad Aug 13, 2026
e9083f7
Merge branch 'headroomlabs-ai:main' into feat/dsh-compatibility
Zenjibad Aug 14, 2026
ebcadd3
Merge branch 'main' into feat/dsh-compatibility
JerrettDavis Aug 14, 2026
fe857e6
fix(proxy): resolve x-headroom-base-url before DeepSeek detection
Zenjibad Aug 14, 2026
4b6cf4c
docs(dsh): add dsh to wrap and unwrap lists and align pnpm wording
Zenjibad Aug 14, 2026
ac3bb22
style: format deepseek routing tests
Zenjibad Aug 14, 2026
38773bd
feat(mcp): register headroom MCP server with dsh
Zenjibad Aug 14, 2026
048fc80
feat(mcp): generalize dsh block model to multiple entries
Zenjibad Aug 14, 2026
736f234
feat(mcp): support multiple servers in DshRegistrar
Zenjibad Aug 14, 2026
c637cdc
feat(dsh): register Serena code memory in wrap dsh
Zenjibad Aug 14, 2026
0fdf996
feat(dsh): remove Serena and headroom MCP in unwrap dsh
Zenjibad Aug 14, 2026
f98ef49
fix(mcp): ensure newline before managed block marker
Zenjibad Aug 14, 2026
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Headroom compresses everything your AI agent reads — tool outputs, logs, RAG c

- **Library** — `compress(messages)` in Python or TypeScript, inline in any app
- **Proxy** — `headroom proxy --port 8787`, zero code changes, any language
- **Agent wrap** — `headroom wrap claude|codex|grok|copilot|cursor|aider|opencode|cline|continue|goose|openhands|openclaw|vibe|omp|zcode` in one command; undo with `headroom unwrap <tool>`
- **Agent wrap** — `headroom wrap claude|codex|dsh|grok|copilot|cursor|aider|opencode|cline|continue|goose|openhands|openclaw|vibe|omp|zcode` in one command; undo with `headroom unwrap <tool>`
- **MCP server** — `headroom_compress`, `headroom_retrieve`, `headroom_stats` for any MCP client
- **Cross-agent memory** — shared store across Claude, Codex, Gemini, Grok, auto-dedup
- **`headroom learn`** — mines failed sessions, writes corrections to `CLAUDE.local.md` (default, gitignored) or `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / `GROK.md`
Expand Down Expand Up @@ -245,9 +245,10 @@ shows an **Output Tokens Saved** card next to input compression, labelled
| Cortex Code | Library only | 60–65% savings (library mode; no `wrap`) |
| Kimi CLI | ✅ | OAuth bearer forwarded — log in once |
| ZCode | ✅ | starts proxy and prints base URLs for ZCode settings |
| DeepSeek Harness (dsh) | ✅ | `web` + `headless`; routes via `DEEPSEEK_BASE_URL` |

Any OpenAI-compatible client works via `headroom proxy`. MCP-native: `headroom mcp install`.
Undo durable wrapping with `headroom unwrap <tool>` (supports: `claude`, `copilot`, `codex`, `grok`, `kimi`, `omp`, `opencode`, `openclaw`, `zcode`).
Undo durable wrapping with `headroom unwrap <tool>` (supports: `claude`, `copilot`, `codex`, `dsh`, `grok`, `kimi`, `omp`, `opencode`, `openclaw`, `zcode`).
Registry authors can use the canonical [`server.json`](server.json) in the repo root instead of reconstructing the `headroom mcp serve` contract from prose.

### GitHub Copilot CLI subscription mode
Expand Down
8 changes: 8 additions & 0 deletions headroom/cli/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,11 @@ def dashboard(port: int, no_open: bool) -> None:
default=None,
help="Custom OpenAI API URL for passthrough endpoints (env: OPENAI_TARGET_API_URL)",
)
@click.option(
"--deepseek-api-url",
default=None,
help="Custom DeepSeek API URL for passthrough endpoints (env: DEEPSEEK_TARGET_API_URL)",
)
@click.option(
"--provider-name",
default=None,
Expand Down Expand Up @@ -1035,6 +1040,7 @@ def proxy(
anthropic_extra_headers: str | None,
openai_extra_headers: str | None,
openai_api_url: str | None,
deepseek_api_url: str | None,
provider_name: str | None,
gemini_api_url: str | None,
cloudcode_api_url: str | None,
Expand Down Expand Up @@ -1179,6 +1185,7 @@ def proxy(
provider_api_overrides = resolve_api_overrides(
anthropic_api_url=anthropic_api_url,
openai_api_url=openai_api_url,
deepseek_api_url=deepseek_api_url,
gemini_api_url=gemini_api_url,
cloudcode_api_url=cloudcode_api_url,
vertex_api_url=vertex_api_url,
Expand Down Expand Up @@ -1250,6 +1257,7 @@ def proxy(
anthropic_extra_headers=resolved_anthropic_extra_headers,
openai_extra_headers=resolved_openai_extra_headers,
openai_api_url=provider_api_overrides.openai,
deepseek_api_url=provider_api_overrides.deepseek,
provider_name=provider_name,
gemini_api_url=provider_api_overrides.gemini,
cloudcode_api_url=provider_api_overrides.cloudcode,
Expand Down
145 changes: 145 additions & 0 deletions headroom/cli/wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
validate_configuration as _validate_copilot_configuration,
)
from headroom.providers.cursor import render_setup_lines as _render_cursor_setup_lines
from headroom.providers.dsh.runtime import build_launch_env, resolve_dsh_command
from headroom.providers.grok import build_launch_env as _build_grok_launch_env
from headroom.providers.grok_build import render_setup_lines as _render_grok_build_setup_lines
from headroom.providers.grok_build.config import (
Expand Down Expand Up @@ -626,6 +627,7 @@ def _start_proxy(
anyllm_provider: str | None = None,
region: str | None = None,
openai_api_url: str | None = None,
deepseek_api_url: str | None = None,
anthropic_api_url: str | None = None,
vertex_api_url: str | None = None,
clear_vertex_api_url: bool = False,
Expand Down Expand Up @@ -677,6 +679,8 @@ def _start_proxy(

if openai_api_url:
cmd.extend(["--openai-api-url", openai_api_url])
if deepseek_api_url:
cmd.extend(["--deepseek-api-url", deepseek_api_url])

if anthropic_api_url:
cmd.extend(["--anthropic-api-url", anthropic_api_url])
Expand Down Expand Up @@ -715,6 +719,8 @@ def _start_proxy(
apply_agent_savings_env_defaults(proxy_env, savings_profile)
if openai_api_url:
proxy_env["OPENAI_TARGET_API_URL"] = openai_api_url
if deepseek_api_url:
proxy_env["DEEPSEEK_TARGET_API_URL"] = deepseek_api_url
if anthropic_api_url:
proxy_env["ANTHROPIC_TARGET_API_URL"] = anthropic_api_url
if clear_vertex_api_url:
Expand Down Expand Up @@ -3645,6 +3651,7 @@ def _ensure_proxy_unlocked(
anyllm_provider: str | None = None,
region: str | None = None,
openai_api_url: str | None = None,
deepseek_api_url: str | None = None,
anthropic_api_url: str | None = None,
vertex_api_url: str | None = None,
clear_vertex_api_url: bool = False,
Expand Down Expand Up @@ -3736,6 +3743,11 @@ def _ensure_proxy_unlocked(
requested_openai_url = _normalize_proxy_api_url(openai_api_url)
if running_openai_url != requested_openai_url:
missing.append("openai-api-url")
if deepseek_api_url:
if _normalize_proxy_api_url(
running_config.get("deepseek_api_url")
) != _normalize_proxy_api_url(deepseek_api_url):
missing.append("deepseek-api-url")
if not missing:
click.echo(f" Proxy already running on port {port}")
click.echo(f" Dashboard: http://127.0.0.1:{port}/dashboard")
Expand Down Expand Up @@ -3800,6 +3812,11 @@ def _ensure_proxy_unlocked(
requested_openai_url = _normalize_proxy_api_url(openai_api_url)
if running_openai_url != requested_openai_url:
missing.append("openai-api-url")
if deepseek_api_url:
if _normalize_proxy_api_url(
running_config.get("deepseek_api_url")
) != _normalize_proxy_api_url(deepseek_api_url):
missing.append("deepseek-api-url")

if missing:
flags_str = ", ".join(f"--{f}" for f in missing)
Expand Down Expand Up @@ -3895,6 +3912,11 @@ def _ensure_proxy_unlocked(
requested_openai_url = _normalize_proxy_api_url(openai_api_url)
if running_openai_url != requested_openai_url:
missing.append("openai-api-url")
if deepseek_api_url:
if _normalize_proxy_api_url(
running_config.get("deepseek_api_url")
) != _normalize_proxy_api_url(deepseek_api_url):
missing.append("deepseek-api-url")
if vertex_api_url or clear_vertex_api_url:
running_vertex_url = _normalize_proxy_api_url(
running_config.get("vertex_api_url")
Expand Down Expand Up @@ -3988,6 +4010,7 @@ def _ensure_proxy_unlocked(
anyllm_provider=anyllm_provider,
region=region,
openai_api_url=openai_api_url,
deepseek_api_url=deepseek_api_url,
anthropic_api_url=anthropic_api_url,
vertex_api_url=vertex_api_url,
clear_vertex_api_url=clear_vertex_api_url,
Expand Down Expand Up @@ -4293,6 +4316,7 @@ def _launch_tool(
anyllm_provider: str | None = None,
region: str | None = None,
openai_api_url: str | None = None,
deepseek_api_url: str | None = None,
copilot_api_token: str | None = None,
copilot_refresh_oauth_token: str | None = None,
copilot_api_token_expires_at: float | None = None,
Expand Down Expand Up @@ -4329,6 +4353,7 @@ def _launch_tool(
anyllm_provider=anyllm_provider,
region=region,
openai_api_url=openai_api_url,
deepseek_api_url=deepseek_api_url,
copilot_api_token=copilot_api_token,
copilot_refresh_oauth_token=copilot_refresh_oauth_token,
copilot_api_token_expires_at=copilot_api_token_expires_at,
Expand Down Expand Up @@ -7409,6 +7434,95 @@ def opencode(
_opencode_proxy.kill()


# =============================================================================
# DeepSeek Harness (dsh)
# =============================================================================


@wrap.command(context_settings={"ignore_unknown_options": True})
@_retired_context_tool_option
@click.option(
"--port", "-p", default=8787, type=click.IntRange(1, 65535), help="Proxy port (default: 8787)"
)
@click.option("--no-proxy", is_flag=True, help="Skip proxy startup (use existing proxy)")
@click.option("--learn", is_flag=True, help="Enable live traffic learning")
@click.option("--memory", is_flag=True, help="Enable persistent cross-session memory")
@click.option(
"--profile",
"profile",
default="web",
type=click.Choice(["web", "headless"]),
help="dsh launch profile (default: web)",
)
@click.option("--command", "command", default=None, help="Explicit dsh command/launcher override")
@click.option(
"--deepseek-api-url",
default=None,
help="DeepSeek upstream API URL (default: https://api.deepseek.com)",
)
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
@click.option("--prepare-only", is_flag=True, hidden=True)
@_serena_instructions_option
@_code_memory_option
@click.argument("dsh_args", nargs=-1, type=click.UNPROCESSED)
def dsh(
port: int,
no_proxy: bool,
learn: bool,
memory: bool,
profile: str,
command: str,
deepseek_api_url: str,
verbose: bool,
prepare_only: bool,
dsh_args: tuple,
) -> None:
"""Launch DeepSeek Harness (dsh) through Headroom proxy.

\b
Sets DEEPSEEK_BASE_URL to route dsh's OpenAI-compatible /chat/completions
traffic through Headroom. The DeepSeek bearer (DEEPSEEK_API_KEY) is
forwarded upstream, so no extra login is required.

\b
Examples:
headroom wrap dsh # Start proxy + dsh web
headroom wrap dsh --profile headless "task" # One-shot task
headroom wrap dsh --command "pnpm dsh" # Custom launcher
headroom wrap dsh --deepseek-api-url https://api.deepseek.com
"""
if prepare_only:
return
if deepseek_api_url is None:
deepseek_api_url = os.environ.get("DEEPSEEK_BASE_URL")

try:
argv = resolve_dsh_command(profile=profile, command=command, task_args=dsh_args)
except RuntimeError as exc:
click.echo(f"Error: {exc}")
raise SystemExit(1) from exc

env, env_vars_display = build_launch_env(port, os.environ)

from headroom.mcp_registry import DshRegistrar

_setup_coding_compressor(DshRegistrar(), serena_context="agent", verbose=verbose)

_launch_tool(
binary=argv[0],
args=tuple(argv[1:]),
env=env,
port=port,
no_proxy=no_proxy,
tool_label="DSH",
env_vars_display=env_vars_display,
learn=learn,
memory=memory,
agent_type="dsh",
deepseek_api_url=deepseek_api_url,
)


def _opencode_home_dir() -> Path:
"""Return the OpenCode home/config directory."""
env_path = os.environ.get("OPENCODE_HOME", "").strip()
Expand Down Expand Up @@ -7817,6 +7931,37 @@ def unwrap_omp(port: int, no_stop_proxy: bool) -> None:
click.echo()


@unwrap.command("dsh")
@click.option(
"--port", "-p", default=8787, type=click.IntRange(1, 65535), help="Proxy port (default: 8787)"
)
@click.option("--no-stop-proxy", is_flag=True, help="Do not stop the local Headroom proxy")
def unwrap_dsh(port: int, no_stop_proxy: bool) -> None:
"""Undo ``headroom wrap dsh`` (remove MCP entries and stop the proxy)."""
from headroom.mcp_registry import DshRegistrar

registrar = DshRegistrar()
removed_any = False
if registrar.detect():
serena_status = _remove_headroom_installed_serena_mcp(registrar)
if serena_status == "removed":
click.echo(" Removed Headroom-installed Serena MCP server from dsh.")
removed_any = True
elif serena_status == "failed":
click.echo(" Serena MCP server matched Headroom ledger but could not be removed.")

headroom_present = registrar.get_server("headroom") is not None
if registrar.unregister_server("headroom") and headroom_present:
click.echo(" Removed Headroom MCP server from dsh config.")
removed_any = True

if not removed_any:
click.echo(" Nothing to undo: no Headroom MCP markers found.")

if not no_stop_proxy:
_echo_unwrap_proxy_stop_status(_stop_local_proxy_for_unwrap(port), port)


# =============================================================================
# Grok CLI (unwrap)
# =============================================================================
Expand Down
2 changes: 2 additions & 0 deletions headroom/mcp_registry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .claude import ClaudeRegistrar
from .codex import CodexRegistrar
from .display import any_succeeded, format_result, format_results
from .dsh import DshRegistrar
from .grok import GrokRegistrar
from .install import (
DEFAULT_PROXY_URL,
Expand All @@ -32,6 +33,7 @@
"DEFAULT_PROXY_URL",
"ClaudeRegistrar",
"CodexRegistrar",
"DshRegistrar",
"GrokRegistrar",
"MCPRegistrar",
"OpencodeRegistrar",
Expand Down
Loading
Loading