From 095f022aa00367062720180072ae8eb9cbef7ad8 Mon Sep 17 00:00:00 2001 From: kina0630 Date: Fri, 1 May 2026 22:09:06 +0800 Subject: [PATCH 1/4] feat: add deepseek config support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- coding-agent/src/coding_agent/core/config.py | 6 ++++++ coding-agent/tests/core/test_config.py | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/coding-agent/src/coding_agent/core/config.py b/coding-agent/src/coding_agent/core/config.py index a7ca30e46..ca18ea3a7 100644 --- a/coding-agent/src/coding_agent/core/config.py +++ b/coding-agent/src/coding_agent/core/config.py @@ -20,6 +20,7 @@ class Config(BaseModel): "kimi", "kimi-code", "kimi-code-anthropic", + "deepseek", ] = "openai" model: str = "gpt-4o" api_key: SecretStr | None = None @@ -100,6 +101,11 @@ def load_config(cli_args: dict[str, object] | None = None) -> Config: if kimi_code_token: values["api_key"] = kimi_code_token + if values.get("provider") == "deepseek" and "api_key" not in values: + deepseek_token = os.environ.get("DEEPSEEK_API_KEY") + if deepseek_token: + values["api_key"] = deepseek_token + return Config.model_validate(values) diff --git a/coding-agent/tests/core/test_config.py b/coding-agent/tests/core/test_config.py index d80515cc7..238398cc3 100644 --- a/coding-agent/tests/core/test_config.py +++ b/coding-agent/tests/core/test_config.py @@ -44,7 +44,7 @@ def test_custom_values(self): @pytest.mark.parametrize( "provider", - ["kimi", "kimi-code", "kimi-code-anthropic"], + ["kimi", "kimi-code", "kimi-code-anthropic", "deepseek"], ) def test_accepts_kimi_family_providers(self, provider): c = Config(provider=provider) @@ -115,3 +115,15 @@ def test_kimi_code_uses_kimi_code_api_key_when_agent_api_key_missing( assert c.provider == provider assert c.api_key is not None assert c.api_key.get_secret_value() == "sk-kimi-code-token" + + def test_deepseek_uses_deepseek_api_key_when_agent_api_key_missing( + self, monkeypatch + ): + monkeypatch.delenv("AGENT_API_KEY", raising=False) + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-deepseek-token") + + c = load_config(cli_args={"provider": "deepseek"}) + + assert c.provider == "deepseek" + assert c.api_key is not None + assert c.api_key.get_secret_value() == "sk-deepseek-token" From ca52a2484908abb8fad6e6b460efe510877947fe Mon Sep 17 00:00:00 2001 From: kina0630 Date: Fri, 1 May 2026 22:11:14 +0800 Subject: [PATCH 2/4] feat: route deepseek through anthropic Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../src/coding_agent/plugins/llm_provider.py | 11 ++++ .../coding_agent/plugins/test_llm_provider.py | 52 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/coding-agent/src/coding_agent/plugins/llm_provider.py b/coding-agent/src/coding_agent/plugins/llm_provider.py index c600676ba..e13a75709 100644 --- a/coding-agent/src/coding_agent/plugins/llm_provider.py +++ b/coding-agent/src/coding_agent/plugins/llm_provider.py @@ -95,6 +95,17 @@ def provide_llm(self, **kwargs: Any) -> LLMProvider: base_url="https://api.kimi.com/coding/", default_headers={"User-Agent": "claude-code/1.0.17"}, ) + elif self._provider_name == "deepseek": + import os + + from coding_agent.providers.anthropic import AnthropicProvider + + api_key = self._api_key or os.environ.get("DEEPSEEK_API_KEY", "") + self._instance = AnthropicProvider( + model=self._model, + api_key=api_key, + base_url="https://api.deepseek.com/anthropic", + ) else: raise ValueError(f"unsupported provider: {self._provider_name}") diff --git a/coding-agent/tests/coding_agent/plugins/test_llm_provider.py b/coding-agent/tests/coding_agent/plugins/test_llm_provider.py index e75d557bb..d5ebc382f 100644 --- a/coding-agent/tests/coding_agent/plugins/test_llm_provider.py +++ b/coding-agent/tests/coding_agent/plugins/test_llm_provider.py @@ -220,3 +220,55 @@ def test_kimi_code_anthropic_sets_claude_code_user_agent(self): assert isinstance(result, AnthropicProvider) headers = result._client.default_headers assert headers.get("User-Agent") == "claude-code/1.0.17" + + +class TestDeepSeekProvider: + def test_deepseek_creates_anthropic_instance(self): + plugin = LLMProviderPlugin( + provider="deepseek", + model="deepseek-v4-pro", + api_key="sk-deepseek-test", + ) + + result = plugin.provide_llm() + + assert isinstance(result, AnthropicProvider) + assert result.model_name == "deepseek-v4-pro" + + def test_deepseek_uses_deepseek_anthropic_base_url(self): + plugin = LLMProviderPlugin( + provider="deepseek", + model="deepseek-v4-flash", + api_key="sk-deepseek-test", + ) + + result = plugin.provide_llm() + + assert isinstance(result, AnthropicProvider) + assert result._base_url == "https://api.deepseek.com/anthropic" + + def test_deepseek_api_key_env_fallback(self, monkeypatch): + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-deepseek-from-env") + plugin = LLMProviderPlugin( + provider="deepseek", + model="deepseek-v4-pro", + api_key="", + ) + + result = plugin.provide_llm() + + assert isinstance(result, AnthropicProvider) + assert result._api_key == "sk-deepseek-from-env" + + def test_deepseek_explicit_key_takes_priority_over_env(self, monkeypatch): + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-deepseek-from-env") + plugin = LLMProviderPlugin( + provider="deepseek", + model="deepseek-v4-pro", + api_key="sk-explicit", + ) + + result = plugin.provide_llm() + + assert isinstance(result, AnthropicProvider) + assert result._api_key == "sk-explicit" From 6cb8c0f787ebe69f3fea0dd835fa999b0c0ff511 Mon Sep 17 00:00:00 2001 From: kina0630 Date: Fri, 1 May 2026 22:13:49 +0800 Subject: [PATCH 3/4] docs: mention deepseek provider Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- coding-agent/README.md | 3 +- coding-agent/src/coding_agent/app.py | 2 + coding-agent/src/coding_agent/cli/repl.py | 1 + .../tests/coding_agent/test_cli_pipeline.py | 44 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/coding-agent/README.md b/coding-agent/README.md index 54b19c1e7..98a98487d 100644 --- a/coding-agent/README.md +++ b/coding-agent/README.md @@ -16,7 +16,7 @@ Today the repo contains both: - HTTP server mode via `python -m coding_agent serve` - Hook-driven runtime built from `agentkit` pipeline stages and plugins - Tooling for file reads/writes, grep/glob, shell commands, file patching, planning, web search, and subagents -- Provider support for `openai`, `anthropic`, `copilot`, `kimi`, `kimi-code`, and `kimi-code-anthropic` +- Provider support for `openai`, `anthropic`, `copilot`, `kimi`, `kimi-code`, `kimi-code-anthropic`, and `deepseek` - Approval policies for risky tools and external requests - Dependency-aware parallel tool execution - Topic detection, context summarization, memory grounding, session metrics, skills, and MCP server integration @@ -121,6 +121,7 @@ Provider-specific fallback keys currently supported by the implementation: - `GITHUB_TOKEN` for `copilot` - `MOONSHOT_API_KEY` for `kimi` - `KIMI_CODE_API_KEY` for `kimi-code` and `kimi-code-anthropic` +- `DEEPSEEK_API_KEY` for `deepseek` ## Repository Layout diff --git a/coding-agent/src/coding_agent/app.py b/coding-agent/src/coding_agent/app.py index 8960f6f46..739028bad 100644 --- a/coding-agent/src/coding_agent/app.py +++ b/coding-agent/src/coding_agent/app.py @@ -138,6 +138,8 @@ def create_child_pipeline( resolved_key = os.environ.get("MOONSHOT_API_KEY", "") if not resolved_key and cfg.provider in ("kimi-code", "kimi-code-anthropic"): resolved_key = os.environ.get("KIMI_CODE_API_KEY", "") + if not resolved_key and cfg.provider == "deepseek": + resolved_key = os.environ.get("DEEPSEEK_API_KEY", "") resolved_key = resolved_key or "" approval_cfg = cfg.extra.get("approval", {}) diff --git a/coding-agent/src/coding_agent/cli/repl.py b/coding-agent/src/coding_agent/cli/repl.py index 4d80deb0a..751140420 100644 --- a/coding-agent/src/coding_agent/cli/repl.py +++ b/coding-agent/src/coding_agent/cli/repl.py @@ -208,6 +208,7 @@ def _sync_config_from_managed_session(self, managed_session: Any) -> None: "kimi", "kimi-code", "kimi-code-anthropic", + "deepseek", } if provider_name not in allowed_providers: raise RuntimeError( diff --git a/coding-agent/tests/coding_agent/test_cli_pipeline.py b/coding-agent/tests/coding_agent/test_cli_pipeline.py index f14b47929..361aa68e6 100644 --- a/coding-agent/tests/coding_agent/test_cli_pipeline.py +++ b/coding-agent/tests/coding_agent/test_cli_pipeline.py @@ -381,6 +381,30 @@ def patched_init(self_kb, *args, **kwargs): class TestCliProviderChoices: + def test_top_level_accepts_deepseek_provider(self): + from coding_agent.__main__ import main + + with ( + patch("coding_agent.__main__.load_config") as mock_load_config, + patch("coding_agent.cli.repl.run_repl", AsyncMock()), + patch("coding_agent.__main__.sys.stdout.isatty", return_value=True), + ): + mock_load_config.return_value = MagicMock() + ctx = click.Context(main) + with ctx.scope(cleanup=False): + wrapped_main = getattr(main.callback, "__wrapped__") + wrapped_main( + ctx, + model="deepseek-v4-pro", + provider_name="deepseek", + base_url=None, + api_key=None, + ) + + mock_load_config.assert_called_once_with( + cli_args={"provider": "deepseek", "model": "deepseek-v4-pro"} + ) + def test_top_level_accepts_kimi_code_provider(self): from coding_agent.__main__ import main @@ -491,6 +515,26 @@ def test_run_uses_kimi_code_api_key_env_without_cli_api_key(self, monkeypatch): assert config.api_key is not None assert config.api_key.get_secret_value() == "kimi-env-key" + def test_run_uses_deepseek_api_key_env_without_cli_api_key(self, monkeypatch): + from coding_agent.__main__ import main + + monkeypatch.setenv("DEEPSEEK_API_KEY", "deepseek-env-key") + runner = CliRunner() + + with patch("coding_agent.__main__._run_headless", AsyncMock()) as mock_run: + result = runner.invoke( + main, + ["--provider", "deepseek", "run", "--goal", "test goal"], + ) + + assert result.exit_code == 0 + assert mock_run.await_args is not None + config, goal = mock_run.await_args.args + assert goal == "test goal" + assert config.provider == "deepseek" + assert config.api_key is not None + assert config.api_key.get_secret_value() == "deepseek-env-key" + def test_run_keeps_run_only_options_local_to_subcommand(self): from coding_agent.__main__ import main From 430b890f5c347740e4eb9f9ba018c7e444eb0670 Mon Sep 17 00:00:00 2001 From: kina0630 Date: Fri, 1 May 2026 22:28:44 +0800 Subject: [PATCH 4/4] fix: honor deepseek env fallback Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- coding-agent/src/coding_agent/core/config.py | 2 +- coding-agent/tests/coding_agent/test_cli_pipeline.py | 1 + coding-agent/tests/core/test_config.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/coding-agent/src/coding_agent/core/config.py b/coding-agent/src/coding_agent/core/config.py index ca18ea3a7..ddbf33885 100644 --- a/coding-agent/src/coding_agent/core/config.py +++ b/coding-agent/src/coding_agent/core/config.py @@ -101,7 +101,7 @@ def load_config(cli_args: dict[str, object] | None = None) -> Config: if kimi_code_token: values["api_key"] = kimi_code_token - if values.get("provider") == "deepseek" and "api_key" not in values: + if values.get("provider") == "deepseek" and not values.get("api_key"): deepseek_token = os.environ.get("DEEPSEEK_API_KEY") if deepseek_token: values["api_key"] = deepseek_token diff --git a/coding-agent/tests/coding_agent/test_cli_pipeline.py b/coding-agent/tests/coding_agent/test_cli_pipeline.py index 361aa68e6..0dec68914 100644 --- a/coding-agent/tests/coding_agent/test_cli_pipeline.py +++ b/coding-agent/tests/coding_agent/test_cli_pipeline.py @@ -518,6 +518,7 @@ def test_run_uses_kimi_code_api_key_env_without_cli_api_key(self, monkeypatch): def test_run_uses_deepseek_api_key_env_without_cli_api_key(self, monkeypatch): from coding_agent.__main__ import main + monkeypatch.delenv("AGENT_API_KEY", raising=False) monkeypatch.setenv("DEEPSEEK_API_KEY", "deepseek-env-key") runner = CliRunner() diff --git a/coding-agent/tests/core/test_config.py b/coding-agent/tests/core/test_config.py index 238398cc3..79f1181c4 100644 --- a/coding-agent/tests/core/test_config.py +++ b/coding-agent/tests/core/test_config.py @@ -127,3 +127,15 @@ def test_deepseek_uses_deepseek_api_key_when_agent_api_key_missing( assert c.provider == "deepseek" assert c.api_key is not None assert c.api_key.get_secret_value() == "sk-deepseek-token" + + def test_deepseek_uses_deepseek_api_key_when_agent_api_key_is_empty( + self, monkeypatch + ): + monkeypatch.setenv("AGENT_API_KEY", "") + monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-deepseek-token") + + c = load_config(cli_args={"provider": "deepseek"}) + + assert c.provider == "deepseek" + assert c.api_key is not None + assert c.api_key.get_secret_value() == "sk-deepseek-token"