fix: /model onto Bedrock Mantle keeps SigV4 auth instead of 401ing

Startup (agent_init._init_openai_client) ran configure_bedrock_openai_client_kwargs,
so the aws-sdk sentinel became a SigV4-signing httpx client. Every later client
rebuild — switch_model, fallback restore, credential rotation, request-scoped
clients — went through create_openai_client with bare {api_key, base_url} kwargs,
so the OpenAI SDK sent "Authorization: Bearer aws-sdk" and Mantle answered 401
"Invalid bearer token". Symptom: `hermes --provider bedrock --model
openai.gpt-5.6-terra` works, `/model openai.gpt-5.6-terra` inside the CLI/TUI fails.

Install SigV4 in create_openai_client itself (the single chokepoint every primary
OpenAI-wire client passes through) whenever the base_url is a Mantle host, so all
rebuild paths inherit the fix rather than each remembering to call the adapter.
A real AWS_BEARER_TOKEN_BEDROCK key is left alone (the adapter only rewrites the
aws-sdk / no-key-required placeholders).

Live repro (local HTTP sink capturing the Authorization header after switch_model):
before "Bearer aws-sdk", after "AWS4-HMAC-SHA256 Credential=...".
This commit is contained in:
Teknium
2026-09-10 11:58:07 -07:00
parent 872bafd58d
commit 564aef2946
2 changed files with 48 additions and 0 deletions

View File

@@ -1739,6 +1739,15 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo
# gets its OWN fresh ``httpx.Client`` whose lifetime is tied to the OpenAI client it is passed to. When
# the OpenAI client is closed (rebuild, teardown, credential rotation), the paired ``httpx.Client``
# closes with it, and the next call constructs a fresh one — no stale closed transport can be reused.
# Bedrock Mantle: the ``aws-sdk`` placeholder is a sentinel for IAM-chain auth, not a bearer token.
# Every rebuild from bare ``{api_key, base_url}`` kwargs (switch_model, fallback restore, credential
# rotation, request-scoped clients) must reinstall the SigV4 http_client or Mantle answers 401.
if "bedrock-mantle." in str(client_kwargs.get("base_url") or ""):
from agent.bedrock_adapter import configure_bedrock_openai_client_kwargs
timeout = client_kwargs.get("timeout")
configure_bedrock_openai_client_kwargs(
client_kwargs, timeout=timeout if isinstance(timeout, (int, float)) else None,
)
if "http_client" not in client_kwargs:
keepalive_http = agent._build_keepalive_http_client(client_kwargs.get("base_url", ""), verify=httpx_verify)
if keepalive_http is not None:

View File

@@ -0,0 +1,39 @@
"""A /model switch onto a Bedrock Mantle (OpenAI-wire) model must rebuild the client with SigV4
auth. ``aws-sdk`` is a Hermes sentinel for IAM-chain auth; agent_init installed the SigV4
http_client but switch_model rebuilt bare ``{api_key, base_url}`` kwargs, so the SDK sent
``Authorization: Bearer aws-sdk`` and Mantle answered 401."""
from unittest.mock import MagicMock, patch
from agent.bedrock_adapter import BedrockOpenAISigV4Auth
from agent.context_compressor import ContextCompressor
from run_agent import AIAgent
MANTLE = "https://bedrock-mantle.us-east-1.api.aws/openai/v1"
def _agent() -> AIAgent:
agent = AIAgent.__new__(AIAgent)
agent.model, agent.provider = "claude-opus-4.8", "anthropic"
agent.base_url, agent.api_key, agent.api_mode = "https://api.anthropic.com", "sk-ant", "anthropic_messages"
agent.client, agent._anthropic_client, agent._client_kwargs = None, MagicMock(), {}
agent.quiet_mode, agent._config_context_length, agent._primary_runtime = True, None, {}
agent.context_compressor = ContextCompressor(
model=agent.model, threshold_percent=0.5, base_url=agent.base_url, api_key="sk-ant",
provider="anthropic", quiet_mode=True, config_context_length=None,
)
return agent
@patch("agent.model_metadata.get_model_context_length", return_value=272_000)
def test_switch_to_bedrock_mantle_installs_sigv4_http_client(_ctx, monkeypatch):
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAFAKEFAKEFAKEFAKE")
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "fake-secret")
monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK", raising=False)
agent = _agent()
agent.switch_model("openai.gpt-5.6-terra", "bedrock", api_key="aws-sdk", base_url=MANTLE, api_mode="codex_responses")
auth = getattr(agent.client._client, "auth", None)
assert isinstance(auth, BedrockOpenAISigV4Auth), f"switched client would send Bearer aws-sdk (auth={auth!r})"
assert auth.region == "us-east-1"