fix(bedrock): consolidated — model-ID detection, context length priority, aux aws_sdk, stale-connection eviction, interrupt rebuild - #15184
Merged
Conversation
…#12295) Bedrock model IDs use dots as namespace separators (anthropic.claude-opus-4-7, us.anthropic.claude-sonnet-4-5-v1:0), not version separators. normalize_model_name() was unconditionally converting all dots to hyphens, producing invalid IDs that Bedrock rejects with HTTP 400/404. This affected both the main agent loop (partially mitigated by _anthropic_preserve_dots in run_agent.py) and all auxiliary client calls (compression, session_search, vision, etc.) which go through _AnthropicCompletionsAdapter and never pass preserve_dots=True. Fix: add _is_bedrock_model_id() to detect Bedrock namespace prefixes (anthropic., us., eu., ap., jp., global.) and skip dot-to-hyphen conversion for these IDs regardless of the preserve_dots flag.
…ndpoint probe
## Problem
`get_model_context_length()` in `agent/model_metadata.py` had a resolution
order bug that caused every Bedrock model to fall back to the 128K default
context length instead of reaching the static Bedrock table (200K for
Claude, etc.).
The root cause: `bedrock-runtime.<region>.amazonaws.com` is not listed in
`_URL_TO_PROVIDER`, so `_is_known_provider_base_url()` returned False.
The resolution order then ran the custom-endpoint probe (step 2) *before*
the Bedrock branch (step 4b), which:
1. Treated Bedrock as a custom endpoint (via `_is_custom_endpoint`).
2. Called `fetch_endpoint_model_metadata()` → `GET /models` on the
bedrock-runtime URL (Bedrock doesn't serve this shape).
3. Fell through to `return DEFAULT_FALLBACK_CONTEXT` (128K) at the
"probe-down" branch — never reaching the Bedrock static table.
Result: users on Bedrock saw 128K context for Claude models that
actually support 200K on Bedrock, causing premature auto-compression.
## Fix
Promote the Bedrock branch from step 4b to step 1b, so it runs *before*
the custom-endpoint probe at step 2. The static table in
`bedrock_adapter.py::get_bedrock_context_length()` is the authoritative
source for Bedrock (the ListFoundationModels API doesn't expose context
window sizes), so there's no reason to probe `/models` first.
The original step 4b is replaced with a one-line breadcrumb comment
pointing to the new location, to make the resolution-order docstring
accurate.
## Changes
- `agent/model_metadata.py`
- Add step 1b: Bedrock static-table branch (unchanged predicate, moved).
- Remove dead step 4b block, replace with breadcrumb comment.
- Update resolution-order docstring to include step 1b.
- `tests/agent/test_model_metadata.py`
- New `TestBedrockContextResolution` class (3 tests):
- `test_bedrock_provider_returns_static_table_before_probe`:
confirms `provider="bedrock"` hits the static table and does NOT
call `fetch_endpoint_model_metadata` (regression guard).
- `test_bedrock_url_without_provider_hint`: confirms the
`bedrock-runtime.*.amazonaws.com` host match works without an
explicit `provider=` hint.
- `test_non_bedrock_url_still_probes`: confirms the probe still
fires for genuinely-custom endpoints (no over-reach).
## Testing
pytest tests/agent/test_model_metadata.py -q
# 83 passed in 1.95s (3 new + 80 existing)
## Risk
Very low.
- Predicate is identical to the original step 4b — no behaviour change
for non-Bedrock paths.
- Original step 4b was dead code for the user-facing case (always hit
the 128K fallback first), so removing it cannot regress behaviour.
- Bedrock path now short-circuits before any network I/O — faster too.
- `ImportError` fall-through preserved so users without `boto3`
installed are unaffected.
## Related
- This is a prerequisite for accurate context-window accounting on
Bedrock — the fix for #14710 (stale-connection client eviction)
depends on correct context sizing to know when to compress.
Signed-off-by: Andre Kurait <andrekurait@gmail.com>
Bedrock's aws_sdk auth_type had no matching branch in resolve_provider_client(), causing it to fall through to the "unhandled auth_type" warning and return (None, None). This broke all auxiliary tasks (compression, memory, summarization) for Bedrock users — the main conversation loop worked fine, but background context management silently failed. Add an aws_sdk branch that creates an AnthropicAuxiliaryClient via build_anthropic_bedrock_client(), using boto3's default credential chain (IAM roles, SSO, env vars, instance metadata). Default auxiliary model is Haiku for cost efficiency. Closes #13919
## Problem
When a pooled HTTPS connection to the Bedrock runtime goes stale (NAT
timeout, VPN flap, server-side TCP RST, proxy idle cull), the next
Converse call surfaces as one of:
* botocore.exceptions.ConnectionClosedError / ReadTimeoutError /
EndpointConnectionError / ConnectTimeoutError
* urllib3.exceptions.ProtocolError
* A bare AssertionError raised from inside urllib3 or botocore
(internal connection-pool invariant check)
The agent loop retries the request 3x, but the cached boto3 client in
_bedrock_runtime_client_cache is reused across retries — so every
attempt hits the same dead connection pool and fails identically.
Only a process restart clears the cache and lets the user keep working.
The bare-AssertionError variant is particularly user-hostile because
str(AssertionError()) is an empty string, so the retry banner shows:
⚠️ API call failed: AssertionError
📝 Error:
with no hint of what went wrong.
## Fix
Add two helpers to agent/bedrock_adapter.py:
* is_stale_connection_error(exc) — classifies exceptions that
indicate dead-client/dead-socket state. Matches botocore
ConnectionError + HTTPClientError subtrees, urllib3
ProtocolError / NewConnectionError, and AssertionError
raised from a frame whose module name starts with urllib3.,
botocore., or boto3.. Application-level AssertionErrors are
intentionally excluded.
* invalidate_runtime_client(region) — per-region counterpart to
the existing reset_client_cache(). Evicts a single cached
client so the next call rebuilds it (and its connection pool).
Wire both into the Converse call sites:
* call_converse() / call_converse_stream() in
bedrock_adapter.py (defense-in-depth for any future caller)
* The two direct client.converse(**kwargs) /
client.converse_stream(**kwargs) call sites in run_agent.py
(the paths the agent loop actually uses)
On a stale-connection exception, the client is evicted and the
exception re-raised unchanged. The agent's existing retry loop then
builds a fresh client on the next attempt and recovers without
requiring a process restart.
## Tests
tests/agent/test_bedrock_adapter.py gets three new classes (14 tests):
* TestInvalidateRuntimeClient — per-region eviction correctness;
non-cached region returns False.
* TestIsStaleConnectionError — classifies botocore
ConnectionClosedError / EndpointConnectionError /
ReadTimeoutError, urllib3 ProtocolError, library-internal
AssertionError (both urllib3.* and botocore.* frames), and
correctly ignores application-level AssertionError and
unrelated exceptions (ValueError, KeyError).
* TestCallConverseInvalidatesOnStaleError — end-to-end: stale
error evicts the cached client, non-stale error (validation)
leaves it alone, successful call leaves it cached.
All 116 tests in test_bedrock_adapter.py pass.
Signed-off-by: Andre Kurait <andrekurait@gmail.com>
…rupt Three interrupt-recovery sites in run_agent.py rebuilt self._anthropic_client with build_anthropic_client(self._anthropic_api_key, ...) unconditionally. When provider=bedrock + api_mode=anthropic_messages (AnthropicBedrock SDK path), self._anthropic_api_key is the sentinel 'aws-sdk' — build_anthropic_client doesn't accept that and the rebuild either crashed or produced a non-functional client. Extract a _rebuild_anthropic_client() helper that dispatches to build_anthropic_bedrock_client(region) when provider='bedrock', falling back to build_anthropic_client() for native Anthropic and other anthropic_messages providers (MiniMax, Kimi, Alibaba, etc.). Three inline rebuild sites now call the helper. Partial salvage of #14680 by @bsgdigital — only the _rebuild_anthropic_client helper. The normalize_model_name Bedrock-prefix piece was subsumed by #14664, and the aux client aws_sdk branch was subsumed by #14770 (both in the same salvage PR as this commit).
This was referenced Apr 24, 2026
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidated salvage of 5 AWS Bedrock PRs — model-ID normalization, context length resolution, auxiliary-client support, stale-connection recovery, and interrupt-rebuild correctness. All changes verified safe against @kshitijk4poor's BedrockTransport refactor (#57411fca2); none touch the
bedrock_converseapi_mode transport path, they operate on complementary / orthogonal layers (string normalization, metadata, auxiliary client, client-cache eviction, interrupt handlers). Attribution preserved via rebase-merge.Changes
agent/anthropic_adapter.py(Bedrock model-ID detection in normalize_model_name) —_is_bedrock_model_id()helper detects both bare Bedrock IDs (anthropic.claude-opus-4-7) and regional inference-profile IDs (us.,eu.,ap.,jp.,global.).normalize_model_name()preserves them verbatim instead of converting dots → hyphens (which Bedrock rejects withHTTP 400 The provided model identifier is invalid). Fixes Bedrock Claude inference-profile IDs get dot-collapsed to hyphens -> BadRequestError 400 #12295. From @qike-ms (fix(anthropic): auto-detect Bedrock model IDs in normalize_model_name (#12295) #14664).agent/model_metadata.py(Bedrock context length priority) —get_model_context_length()now consults the Bedrock static table (inbedrock_adapter.py) BEFORE the custom-endpoint probe at resolution step 2.bedrock-runtime.<region>.amazonaws.comwas previously treated as an unknown custom endpoint, failed the/modelsprobe (Bedrock doesn't expose that shape), and fell through to the 128K default. Moved to step 1b. From @AndreKurait (fix(bedrock): resolve context length via static table before custom-endpoint probe #14721).agent/auxiliary_client.py(aws_sdk auth_type branch) — Before this change,resolve_provider_client()had NO Bedrock path at all, so every auxiliary call (compression, vision, session summarization) for Bedrock users fell through to "unhandled auth_type" and returned(None, None). Newaws_sdkbranch wrapsAnthropicBedrockinAnthropicAuxiliaryClientviahas_aws_credentials()+resolve_bedrock_region()+build_anthropic_bedrock_client(). Fixes [Bug]: auxiliary_client が Bedrock (aws_sdk) 認証に未対応 — context compression / summarization が動作しない #13919. From @Tranquil-Flow (fix(agent): handle aws_sdk auth type in resolve_provider_client #14770).agent/bedrock_adapter.py+run_agent.py(stale-connection eviction) — boto3 caches its HTTPS connection pool inside the client object. When a pooled connection dies (NAT timeout, VPN flap, server-side TCP RST, proxy idle cull), reuse surfaces asbotocore.exceptions.ConnectionClosedError,urllib3.exceptions.ProtocolError, or a bare urllib3AssertionError. Retrying with the same cached client reproduces the failure until the process restarts.invalidate_runtime_client(region)evicts the cached client per-region so the next attempt builds a fresh one;is_stale_connection_error()narrowly matches this failure class. Wired intocall_converse/call_converse_streaminbedrock_adapter.pyAND the directclient.converse(...)call inrun_agent.py(the path BedrockTransport feeds into via__bedrock_converse__sentinel). From @AndreKurait (fix(bedrock): evict cached boto3 client on stale-connection errors #14710).run_agent.py(Bedrock-aware interrupt rebuild) —_rebuild_anthropic_client()helper dispatches tobuild_anthropic_bedrock_client(region)whenprovider == "bedrock", falling back tobuild_anthropic_client()for native Anthropic and otheranthropic_messagesproviders. Three inline rebuild sites (interrupt handlers) previously calledbuild_anthropic_client(self._anthropic_api_key, ...)unconditionally, which failed for Bedrock because_anthropic_api_keyis the sentinel"aws-sdk". From @bsgdigital (fix(bedrock): inference profile ID preservation + correct client rebuild on interrupt #14680 — partial salvage, only the rebuild helper).Credit
_rebuild_anthropic_clienthelper only)Validation
569/569 passing.
Compatibility with BedrockTransport (#57411fca2)
Audit confirmed none of these candidates break Kxee's transport work:
normalize_model_name()string utilmodel_metadata.get_model_context_length()aws_sdkbranchbedrock_converseANDanthropic_messages+provider=bedrockclient.converse(...)call in run_agent.py__bedrock_converse__sentinel_rebuild_anthropic_client()onself._anthropic_clientapi_mode="anthropic_messages"+provider="bedrock"(AnthropicBedrock SDK path), NOTbedrock_converseConflict resolutions
kwargs["model"] == "anthropic.claude-opus-4-7"(dots preserved); PR's stale assertion expected the old mangled"global-anthropic-claude-opus-4-7". Kept HEAD's assertion + the new aux-client Bedrock integration tests from the PR.-n; dropped thenormalize_model_namechanges (duplicated fix(anthropic): auto-detect Bedrock model IDs in normalize_model_name (#12295) #14664) and theauxiliary_client.pyaws_sdk branch (duplicated fix(agent): handle aws_sdk auth type in resolve_provider_client #14770 — would have been a second dead code branch at line 2037). Kept only the_rebuild_anthropic_clienthelper refactor. Committed with--author=bsgdigitalto preserve GitHub PR authorship (original commit was authored as "Hermes Local hermes@local" — openclaw environment).Not included
anthropic.prefix PLUS a cleaner_is_bedrock_model_id()helper. Will close with credit.