Files
hermes-agent/tests/hermes_cli/test_copilot_runtime_api_mode_stale.py
Brooklyn Nicholson 3bbe2a1ce2 fix(copilot): recompute api_mode on fallback and stop stale-mode veto (#46527)
Port of #46542 by @MarkVLK onto current main:

- hermes_cli/runtime_provider.py: _copilot_runtime_api_mode must not veto
  a stale configured codex_responses mode for chat-only Copilot models;
  recompute from the live model capabilities.
- agent/transports/codex.py: request summary:"auto" on the GitHub
  Responses API so reasoning summaries surface.
- agent/agent_init.py: recompute api_mode at init-time fallback so the
  fallback provider/model gets its own mode (and the primary-runtime
  snapshot reflects it).
- tests: regression tests for the stale-mode veto, the GitHub Responses
  summary, and the init-time fallback recompute.

Conflicts resolved against main's refactored fallback ladder
(agent/chat_completion_helpers.py) and its expanded transport tests.

Co-authored-by: Mark Vlcek <markvlcek@gmail.com>
Fixes #46527
2026-10-06 23:54:51 -05:00

54 lines
2.8 KiB
Python

"""Regression tests for #46527: Copilot primary api_mode resolution must not
honor a stale/incompatible explicit ``api_mode``.
The gateway resolves and snapshots the primary provider/model at startup via
``resolve_runtime_provider`` -> ``_copilot_runtime_api_mode``. When the config
carries a stale ``api_mode: codex_responses`` (e.g. the Copilot OAuth setup
wrote it for the ``gpt-5.4-mini`` it initially selects, then the user switched
to ``gpt-5-mini``) the old code honored the stale ``codex_responses`` verbatim.
That dispatches gpt-5-mini through the Responses API — which Copilot serves on
chat completions only — and the response comes back with no reasoning/thinking
content (the symptom in #46527).
"""
from hermes_cli.runtime_provider import _copilot_runtime_api_mode
def test_stale_codex_responses_vetoed_for_gpt5_mini():
"""gpt-5-mini + stale api_mode=codex_responses must resolve to chat_completions."""
cfg = {"provider": "copilot", "default": "gpt-5-mini", "api_mode": "codex_responses"}
assert _copilot_runtime_api_mode(cfg, api_key="x") == "chat_completions"
def test_explicit_codex_responses_kept_for_gpt5_variant():
"""gpt-5.4-mini legitimately uses the Responses API on Copilot — an explicit
codex_responses must still be honored (no over-correction)."""
cfg = {"provider": "copilot", "default": "gpt-5.4-mini", "api_mode": "codex_responses"}
assert _copilot_runtime_api_mode(cfg, api_key="x") == "codex_responses"
def test_no_explicit_mode_derives_chat_completions_for_gpt5_mini():
"""Without an explicit api_mode, gpt-5-mini derives chat_completions (unchanged)."""
cfg = {"provider": "copilot", "default": "gpt-5-mini"}
assert _copilot_runtime_api_mode(cfg, api_key="x") == "chat_completions"
def test_explicit_chat_completions_unchanged():
"""An explicit chat_completions stays chat_completions for gpt-5-mini."""
cfg = {"provider": "copilot", "default": "gpt-5-mini", "api_mode": "chat_completions"}
assert _copilot_runtime_api_mode(cfg, api_key="x") == "chat_completions"
def test_stale_mode_from_different_provider_not_honored():
"""When the config's provider doesn't match copilot, the explicit mode is
not honored at all (existing _provider_supports_explicit_api_mode guard),
so gpt-5-mini still derives chat_completions."""
cfg = {"provider": "nous", "default": "gpt-5-mini", "api_mode": "codex_responses"}
assert _copilot_runtime_api_mode(cfg, api_key="x") == "chat_completions"
def test_target_model_beats_stale_default_mode():
"""A /model switch to gpt-5-mini must not inherit a stale codex_responses
recorded for the persisted default."""
cfg = {"provider": "copilot", "default": "gpt-5.4-mini", "api_mode": "codex_responses"}
assert _copilot_runtime_api_mode(cfg, api_key="x", target_model="gpt-5-mini") == "chat_completions"