test_opencode_kimi_oauth_provider.py
1 """Tests for the OpenCode Kimi OAuth provider bridge.""" 2 3 import subprocess 4 from unittest.mock import patch 5 6 from hermes_cli.auth import resolve_external_process_provider_credentials 7 from hermes_cli.model_switch import list_authenticated_providers, switch_model 8 from hermes_cli.runtime_provider import resolve_runtime_provider 9 10 11 _AUTH_LIST = "kimi-for-coding-oauth oauth\n" 12 13 14 def _opencode_auth_completed_process() -> subprocess.CompletedProcess[str]: 15 return subprocess.CompletedProcess( 16 args=["opencode", "auth", "list"], 17 returncode=0, 18 stdout=_AUTH_LIST, 19 stderr="", 20 ) 21 22 23 def test_opencode_kimi_oauth_appears_in_model_picker_when_opencode_auth_exists(monkeypatch): 24 """Hermes /model should list OpenCode Kimi OAuth when OpenCode auth is present.""" 25 for key in ("KIMI_API_KEY", "KIMI_CODING_API_KEY", "KIMI_BASE_URL"): 26 monkeypatch.delenv(key, raising=False) 27 28 with patch("agent.models_dev.fetch_models_dev", return_value={}), \ 29 patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \ 30 patch("subprocess.run", return_value=_opencode_auth_completed_process()): 31 providers = list_authenticated_providers(current_provider="openai-codex", max_models=50) 32 33 opencode_kimi = next((p for p in providers if p["slug"] == "opencode-kimi-oauth"), None) 34 35 assert opencode_kimi is not None 36 assert opencode_kimi["name"] == "OpenCode Kimi OAuth" 37 assert opencode_kimi["models"] == ["kimi-for-coding"] 38 assert opencode_kimi["total_models"] == 1 39 40 41 def test_opencode_kimi_oauth_runtime_uses_opencode_acp(monkeypatch): 42 """Runtime credentials should launch OpenCode ACP without reading OAuth secrets.""" 43 monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False) 44 monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False) 45 monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False) 46 monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False) 47 48 with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \ 49 patch("subprocess.run", return_value=_opencode_auth_completed_process()): 50 creds = resolve_external_process_provider_credentials("opencode-kimi-oauth") 51 52 assert creds["provider"] == "opencode-kimi-oauth" 53 assert creds["api_key"] == "***" 54 assert creds["base_url"] == "acp://opencode" 55 assert creds["command"] == "/home/user/.local/bin/opencode" 56 assert creds["args"] == ["acp"] 57 58 59 def test_opencode_kimi_oauth_resolves_for_agent_runtime(monkeypatch): 60 """The shared runtime resolver should expose command/args to AIAgent.""" 61 monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False) 62 monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False) 63 monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False) 64 monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False) 65 66 with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \ 67 patch("subprocess.run", return_value=_opencode_auth_completed_process()): 68 runtime = resolve_runtime_provider( 69 requested="opencode-kimi-oauth", 70 target_model="kimi-for-coding", 71 ) 72 73 assert runtime["provider"] == "opencode-kimi-oauth" 74 assert runtime["api_mode"] == "chat_completions" 75 assert runtime["base_url"] == "acp://opencode" 76 assert runtime["api_key"] == "***" 77 assert runtime["command"] == "/home/user/.local/bin/opencode" 78 assert runtime["args"] == ["acp"] 79 80 81 def test_switch_model_accepts_opencode_kimi_oauth_provider(monkeypatch): 82 """/model kimi-for-coding --provider opencode-kimi-oauth should switch cleanly.""" 83 monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False) 84 monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False) 85 monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False) 86 monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False) 87 88 with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \ 89 patch("subprocess.run", return_value=_opencode_auth_completed_process()): 90 result = switch_model( 91 "kimi-for-coding", 92 current_provider="openai-codex", 93 current_model="gpt-5.4", 94 explicit_provider="opencode-kimi-oauth", 95 ) 96 97 assert result.success is True 98 assert result.target_provider == "opencode-kimi-oauth" 99 assert result.new_model == "kimi-for-coding" 100 assert result.base_url == "acp://opencode" 101 assert result.api_key == "***" 102 assert result.command == "/home/user/.local/bin/opencode" 103 assert result.args == ["acp"]