mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-25 17:18:11 +00:00
111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
"""Tests for Copilot runtime api_mode resolution."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
|
|
def test_copilot_runtime_api_mode_uses_target_model_over_stale_config_default(monkeypatch):
|
|
"""MoA/fallback slots must derive Copilot api_mode from the slot model.
|
|
|
|
Repro: user's main/default Copilot model is GPT-5.5 (Responses API), but a
|
|
MoA reference slot is Copilot Claude Opus (Chat Completions). Runtime
|
|
resolution previously looked only at model.default and returned
|
|
codex_responses for the Claude slot, causing Copilot to reject it with
|
|
"model ... does not support Responses API".
|
|
"""
|
|
from hermes_cli import runtime_provider as rp
|
|
|
|
monkeypatch.setattr(
|
|
"hermes_cli.models.copilot_model_api_mode",
|
|
lambda model, api_key=None: "codex_responses" if str(model).startswith("gpt-5") else "chat_completions",
|
|
)
|
|
|
|
assert rp._copilot_runtime_api_mode(
|
|
{"provider": "copilot", "default": "gpt-5.5"},
|
|
"token",
|
|
target_model="claude-opus-4.8",
|
|
) == "chat_completions"
|
|
|
|
|
|
def test_copilot_runtime_api_mode_still_uses_default_without_target(monkeypatch):
|
|
from hermes_cli import runtime_provider as rp
|
|
|
|
monkeypatch.setattr(
|
|
"hermes_cli.models.copilot_model_api_mode",
|
|
lambda model, api_key=None: "codex_responses" if str(model).startswith("gpt-5") else "chat_completions",
|
|
)
|
|
|
|
assert rp._copilot_runtime_api_mode(
|
|
{"provider": "copilot", "default": "gpt-5.5"},
|
|
"token",
|
|
) == "codex_responses"
|
|
|
|
|
|
@pytest.mark.parametrize("credential_source", ["pool", "explicit", "env", "hermes-auth-store"])
|
|
@pytest.mark.parametrize(
|
|
("configured_model", "target_model", "expected_mode"),
|
|
[
|
|
("gpt-5.5", "claude-opus-4.8", "chat_completions"),
|
|
("gpt-5.5", "gemini-3-pro-preview", "chat_completions"),
|
|
("claude-opus-4.8", "gpt-5.5", "codex_responses"),
|
|
],
|
|
)
|
|
def test_resolver_routes_copilot_by_target_model_for_every_credential_path(
|
|
monkeypatch,
|
|
credential_source,
|
|
configured_model,
|
|
target_model,
|
|
expected_mode,
|
|
):
|
|
"""The public resolver must propagate the target through every auth path."""
|
|
from hermes_cli import models
|
|
from hermes_cli import runtime_provider as rp
|
|
|
|
model_cfg = {"provider": "copilot", "default": configured_model}
|
|
monkeypatch.setattr(rp, "_get_model_config", lambda: model_cfg)
|
|
monkeypatch.setattr(rp, "resolve_provider", lambda *_args, **_kwargs: "copilot")
|
|
# Keep this a real copilot_model_api_mode decision without making a live
|
|
# catalog request. The API-mode contract is determined by the model family.
|
|
monkeypatch.setattr(models, "fetch_github_model_catalog", lambda **_kwargs: [])
|
|
|
|
kwargs = {"requested": "copilot", "target_model": target_model}
|
|
if credential_source == "pool":
|
|
entry = SimpleNamespace(
|
|
runtime_api_key="pool-token",
|
|
access_token="pool-token",
|
|
runtime_base_url="https://api.githubcopilot.com",
|
|
base_url="https://api.githubcopilot.com",
|
|
source="pool",
|
|
)
|
|
pool = SimpleNamespace(
|
|
has_credentials=lambda: True,
|
|
select=lambda: entry,
|
|
)
|
|
monkeypatch.setattr(rp, "load_pool", lambda _provider: pool)
|
|
elif credential_source == "explicit":
|
|
kwargs["explicit_api_key"] = "explicit-token"
|
|
monkeypatch.setattr(
|
|
rp,
|
|
"load_pool",
|
|
lambda _provider: pytest.fail("explicit credentials must bypass the pool"),
|
|
)
|
|
else:
|
|
monkeypatch.setattr(rp, "load_pool", lambda _provider: None)
|
|
monkeypatch.setattr(
|
|
rp,
|
|
"resolve_api_key_provider_credentials",
|
|
lambda _provider: {
|
|
"api_key": f"{credential_source}-token",
|
|
"base_url": "https://api.githubcopilot.com",
|
|
"source": credential_source,
|
|
},
|
|
)
|
|
|
|
runtime = rp.resolve_runtime_provider(**kwargs)
|
|
|
|
assert runtime["provider"] == "copilot"
|
|
assert runtime["api_mode"] == expected_mode
|
|
assert runtime["source"] == credential_source
|