Skip to content

[BUG] Bedrock Mantle base-path table only handles openai.gpt-5.*, mis-routing xai.grok-4.3 (and every third-party model) to /v1 #3654

Closed

Description

@yonib05

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

SDK Language

TypeScript

Strands Version

Reported against current main / latest published @strands-agents/sdk. Exact version not captured from the reporter — the routing defect below is present on main as of this filing.

Language Runtime Version

Not captured from the reporter (Node.js).

Operating System

Not captured from the reporter.

Installation Method

npm

Steps to Reproduce

Reported by a user attempting to reach xai.grok-4.3 through the Bedrock Mantle endpoint in us-east-1 (https://bedrock-mantle.us-east-1.api.aws).

import { OpenAIModel } from '@strands-agents/sdk/models/openai'

const model = new OpenAIModel({
  modelId: 'xai.grok-4.3',
  bedrockMantleConfig: { region: 'us-east-1' },
})

await model.stream([{ role: 'user', content: [{ text: 'hello' }] }])

The model id is valid and servable-by-catalog: GET /v1/models on the same endpoint returns xai.grok-4.3 with "status":"available", so this is not a typo'd or unavailable model id.

The reporter also exercised the three routes directly with curl to isolate the SDK from the service:

Route Payload shape HTTP Response
/v1/chat/completions Chat Completions 400 {"error":{"code":"validation_error","message":"model 'xai.grok-4.3' isn't supported on this route","type":"invalid_request_error"}}
/openai/v1/chat/completions Chat Completions 500 {"error":{"code":"internal_server_error","message":"The server had an error while processing your request. Sorry about that!","type":"server_error"}}
/openai/v1/responses Responses API 500 same internal_server_error body

Control case, same account, same credentials, same session: google.gemma-4-31b returns valid completions on /openai/v1/chat/completions and /openai/v1/responses. That rules out credentials, account permissions, and client code.

Expected Behavior

Two separate expectations, because there are two separate defects here — one ours, one not.

  1. OpenAIModel + bedrockMantleConfig should route a non-OpenAI-family Mantle model id (xai.*, google.*, anthropic.*) to the base path Mantle actually serves it from. It should not construct a request against a route the service will reject with validation_error.
  2. A model advertised as "status":"available" by GET /v1/models should be invocable on at least one route.

Actual Behavior

Defect 1 (SDK, actionable here): the Mantle base-path table only special-cases openai.gpt-5.*, so every third-party model id falls through to /v1 — the route that 400s.

strands-ts/src/models/openai/mantle.ts:

const OPENAI_PATH_MODEL_PREFIXES = ['openai.gpt-5.'] as const

export function bedrockMantleBaseUrl(region: string, modelId: string): string {
  const suffix = OPENAI_PATH_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix)) ? '/openai/v1' : '/v1'
  return `https://bedrock-mantle.${region}.api.aws${suffix}`
}

xai.grok-4.3 does not start with openai.gpt-5., so it resolves to /v1 — and /v1/chat/completions is precisely the route that returns model 'xai.grok-4.3' isn't supported on this route. The SDK cannot currently be pointed at /openai/v1 for this model either, since bedrockMantleConfig rejects a caller-supplied clientConfig.baseURL (validated in mantle.test.ts, "throws when clientConfig.baseURL is set alongside bedrockMantleConfig"). There is no supported escape hatch.

The reporter's own control case is the proof that the table is wrong, not just incomplete: google.gemma-4-31b works on /openai/v1, but the SDK would route it to /v1 for the same reason. So the mis-routing is not specific to Grok — it applies to every non-openai.gpt-5.* id, and it is currently masked only because those models aren't documented as supported yet (#3563).

The comment in mantle.ts states the operative rule correctly ("the base path is keyed by model family, not API surface") but the prefix list encodes only the OpenAI families that existed when it was written.

Defect 2 (Mantle service-side, not fixable in this repo): /openai/v1/* 500s for xai.grok-4.3.

Once routed to /openai/v1, Grok returns internal_server_error on both Chat Completions and Responses, while Gemma succeeds on the same routes with the same credentials. That is a serving-side problem with this specific model, either a catalog mis-registration (advertised available, not actually servable) or a backend fault. Fixing Defect 1 will not make xai.grok-4.3 work; it will change a 400 into a 500 until the service side is resolved. Worth cutting a separate ticket to the Bedrock Mantle service team, and worth confirming Grok is servable before wiring it up here — otherwise SDK-side support cannot be integ-tested.

Additional Context

Parity: the Python SDK has the identical defect at strands-py/src/strands/models/_openai_bedrock.py:

_OPENAI_PATH_MODEL_PREFIXES: tuple[str, ...] = ("openai.gpt-5.",)

def _resolve_mantle_base_path(model_id: str) -> str:
    if model_id.startswith(_OPENAI_PATH_MODEL_PREFIXES):
        return "/openai/v1"
    return "/v1"

Any fix should land in both SDKs to preserve parity.

Related history on this same base-path logic, all of which suggests the prefix-matching approach is fragile enough to be worth replacing rather than extending:

This is the fourth bug filed against Mantle base-URL/route derivation.

Possible Solution

Scoped to Defect 1 only.

The narrow fix is to add the third-party families to the prefix list so they resolve to /openai/v1:

const OPENAI_PATH_MODEL_PREFIXES = ['openai.gpt-5.', 'xai.', 'google.', 'anthropic.'] as const

That is a one-line change, mirrored in Python, and it makes the confirmed-working case (google.gemma-4-31b on /openai/v1) actually reachable through the SDK. But it keeps the design that has now produced four bugs: a hardcoded allowlist of prefixes that must be updated every time Mantle onboards a family, where the failure mode is a confusing service-side 400 rather than a clear client-side error.

Two alternatives worth weighing during triage:

  1. Invert the default. /openai/v1 appears to be the general case and /v1 the exception (openai.gpt-oss-*). Encoding the exceptions instead of the rule shrinks the list that needs maintenance and makes new families work by default.
  2. Fail loudly on unrecognized families. If the model id matches no known Mantle family, throw a client-side error naming the id and the supported prefixes rather than silently building a URL that the service will reject. That converts an opaque validation_error from Mantle into an actionable message, which is what the reporter needed here.

Either way, extending the allowlist without also deciding on the default is likely to produce bug number five.

Also worth considering: whether bedrockMantleConfig's hard rejection of clientConfig.baseURL should relax to a documented override. The validation is sound as a default (it prevents accidentally unsigned or mis-pointed requests), but with no escape hatch, a routing-table gap is a hard block rather than a workaround-able annoyance.

Related Issues

#3563 (feature request: Mantle-API support for Anthropic, Google and Grok models). This issue is the bug half of that request: the routing table silently mis-routes those families today rather than rejecting them, so the failure surfaces as an opaque service 400.

Activity

  1. added
    bugSomething isn't working
    pythonPull requests that update python code
    area-modelRelated to models or model providers
    typescriptPull requests that update typescript code
    on Aug 5, 2026
  2. yonib05 commented on Aug 5, 2026

    @yonib05
    MemberAuthor

    @strandly-the-agent can you please verify this bug?

  3. strandly-the-agent commented on Aug 5, 2026

    @strandly-the-agent
    Collaborator

    🔎 Automated Bug Verification

    Defect 1 reproduced on main (2f90779), in both SDKs. The report's snippet as written issues zero HTTP requests (stream() is an async generator), so I instrumented it to capture the outgoing URL — for modelId: 'xai.grok-4.3' it hits:

    POST https://bedrock-mantle.us-east-1.api.aws/v1/responses
    

    Full matrix: xai.*, google.*, anthropic.* → /v1; only openai.gpt-5.* → /openai/v1 (openai.gpt-oss-120b → /v1, as intended). Python is identical for both OpenAIModel and OpenAIResponsesModel. No escape hatch: clientConfig.baseURL / client_args['base_url'] are both rejected alongside the Mantle config. Confirmed at the unit level too (bedrockMantleBaseUrl / _resolve_mantle_base_path called directly, no stubs), so the URL isn't an artifact of the test harness.

    Defect 2 not verified, and it gates the fix. This sandbox's role has no bedrock-mantle permissions — all four routes returned 401 access_denied — so the 400/500 responses and the google.gemma-4-31b control case rest on the reporter's curl evidence alone. That control case is the whole justification for routing these families to /openai/v1; someone with Mantle access should re-confirm it before landing the prefix-list change, otherwise the "fix" swaps one wrong route for another.

    Reproduction (TypeScript) + captured output
    import { OpenAIModel } from '@strands-agents/sdk/models/openai'
    
    const captured: string[] = []
    const SSE = [
      'event: response.created',
      'data: {"type":"response.created","response":{"id":"resp_1","object":"response","status":"in_progress","output":[]}}',
      '',
      'event: response.completed',
      'data: {"type":"response.completed","response":{"id":"resp_1","object":"response","status":"completed","output":[],"usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}}',
      '',
      '',
    ].join('\n')
    
    // Stub BEFORE constructing the model: the OpenAI JS client captures globalThis.fetch at construction.
    globalThis.fetch = async (url: any, init: any) => {
      captured.push(`${init?.method ?? 'GET'} ${typeof url === 'string' ? url : url?.url}`)
      return new Response(SSE, { status: 200, headers: { 'content-type': 'text/event-stream' } })
    }
    
    const model = new OpenAIModel({
      modelId: 'xai.grok-4.3',
      bedrockMantleConfig: {
        region: 'us-east-1',
        // static dummy creds: the bearer token is a local SigV4 presign, no network needed
        credentials: { accessKeyId: 'AKIAREPRODUMMY000000', secretAccessKey: 'dummy' },
      },
    })
    
    await model.stream([{ role: 'user', content: [{ type: 'textBlock', text: 'hello' }] }])
    console.log('requests after the literal `await model.stream(...)`:', captured.length) // 0
    
    for await (const _ of model.stream([{ role: 'user', content: [{ type: 'textBlock', text: 'hello' }] }])) {
      // drain — stream() is an async generator, it must be iterated to issue the request
    }
    console.log(captured)
    requests after the literal `await model.stream(...)`: 0
    [ 'POST https://bedrock-mantle.us-east-1.api.aws/v1/responses' ]
    

    Routing matrix (same capture, TypeScript, both API surfaces):

    modelId                                    api        captured URL
    xai.grok-4.3                               responses  .../v1/responses
    xai.grok-4.3                               chat       .../v1/chat/completions
    google.gemma-4-31b                         responses  .../v1/responses
    google.gemma-4-31b                         chat       .../v1/chat/completions
    anthropic.claude-sonnet-4-5-20250929-v1:0  responses  .../v1/responses
    anthropic.claude-sonnet-4-5-20250929-v1:0  chat       .../v1/chat/completions
    openai.gpt-5.6-luna                        responses  .../openai/v1/responses
    openai.gpt-5.6-luna                        chat       .../openai/v1/chat/completions
    openai.gpt-oss-120b                        responses  .../v1/responses
    openai.gpt-oss-120b                        chat       .../v1/chat/completions
    
    clientConfig.baseURL override REJECTED -> clientConfig must not contain baseURL when
    bedrockMantleConfig is set; these are derived from the Mantle config automatically.
    

    Python parity (same five ids, httpx.MockTransport capture, PYTHONPATH=strands-py/src):

    _OPENAI_PATH_MODEL_PREFIXES = ('openai.gpt-5.',)
    
    xai.grok-4.3         /v1         OpenAIModel           POST .../v1/chat/completions
    xai.grok-4.3         /v1         OpenAIResponsesModel  POST .../v1/responses
    google.gemma-4-31b   /v1         OpenAIModel           POST .../v1/chat/completions
    openai.gpt-5.6-luna  /openai/v1  OpenAIModel           POST .../openai/v1/chat/completions
    openai.gpt-oss-120b  /v1         OpenAIModel           POST .../v1/chat/completions
    
    client_args['base_url'] override REJECTED -> client_args must not contain ['base_url'] when
    bedrock_mantle_config is set; these are derived from the Mantle config automatically.
    

    Environment: @strands-agents/sdk at main 2f90779 · Node v22.14.0, openai 6.45.0 · CPython 3.12.13 · Linux aarch64 sandbox. Deterministic across repeated runs; independently re-run by a second agent with no other context.

    Version skew: none. The prefix list is unchanged since it was introduced — TS d1fbd75 (#3280, closed #3279) and Python 2399728 (#3032, closed #2629); #3412 touched the file for region validation only. The unit suite asserts openai.gpt-5.* → /openai/v1 and openai.gpt-oss-120b → /v1, and has no case for the third-party families — untested territory rather than a broken test.

    Live probe (blocked): GET /v1/models, POST /v1/chat/completions, POST /openai/v1/chat/completions, POST /openai/v1/responses all returned 401 access_denied (bedrock-mantle:ListModels / bedrock-mantle:CreateInference not allowed for the sandbox role). No AWS resources created.

    Automated triage. This reproduction is a starting point, not a final determination.

  4. yonib05 commented on Aug 5, 2026

    @yonib05
    MemberAuthor

    ✅ Defect 2 does not reproduce — the 500s were transient and are now resolved

    Verified against live Bedrock Mantle in us-east-1 (account with real bedrock-mantle permissions, bearer token minted the same way the SDK does: SigV4 presign of bedrock.amazonaws.com/?Action=CallWithBearerToken).

    xai.grok-4.3 now serves HTTP 200 on both /openai/v1 routes:

    xai.grok-4.3  POST /openai/v1/chat/completions   200   chatcmpl-hfabqdwbyujxkn5ozn5ruzexdocia7ehwvz4ewphwypt7d43yd7a
    xai.grok-4.3  POST /openai/v1/responses          200   resp_tgc3xi77gjhuhenj3jxtcqqrp5g2cm526ueamnclkvb3ozhcprzq
    xai.grok-4.3  POST /v1/chat/completions          400   validation_error: model `xai.grok-4.3` isn't supported on this route
    

    So the internal_server_error the reporter hit on /openai/v1/* was a transient serving-side fault, not a catalog mis-registration. No service ticket needed. The model is genuinely servable, and it is servable on exactly the route the SDK does not send it to.

    The reporter's control case also replicates: google.gemma-4-31b → 200 on both /openai/v1 routes, 400 on /v1. That confirms the evidence @strandly-the-agent flagged as unverified, so the fix is no longer gated on it.

    Net effect: Defect 1 is now the whole bug, and it is the only thing standing between xai.grok-4.3 and working. Grok on Mantle is broken through the SDK purely because of our base-path resolution.

  5. strandly-the-agent commented on Aug 5, 2026

    @strandly-the-agent
    Collaborator

    Thanks — that closes the gap I flagged. One probe is left, and it decides the shape of the fix: does openai.gpt-oss-120b also serve on /openai/v1?

    With grok and gemma now confirmed 200 on /openai/v1 and 400 on /v1, openai.gpt-oss-* is the only known id that needs /v1 — and it is the only Mantle id exercised anywhere in the repo (strands-ts/test/integ/models/openai/mantle.test.node.ts:16, strands-py/tests_integ/models/test_model_mantle.py:16, and both docs snippets all use openai.gpt-oss-120b). So:

    • gpt-oss also 200s on /openai/v1 → delete the table. One base path for everything, and this class of bug cannot recur.
    • gpt-oss 400s on /openai/v1 → it is a genuine exception: invert the default (/openai/v1 unless the id starts with openai.gpt-oss-), so new families work by default and only the exception list needs maintenance.

    Extending the allowlist is the one option that's wrong either way. Note the current prefix is 'openai.gpt-5.' with the trailing dot — it only matches the gpt-5.x form, so an OpenAI id in any other shape already falls through to /v1 alongside every un-listed third-party family.

    Whichever way it goes, worth pairing the fix with an integ test on a non-gpt-oss id — google.gemma-4-31b is now a validated target. Integ coverage being 100% gpt-oss is exactly why this gap went unnoticed for four issues.

  6. yonib05 commented on Aug 5, 2026

    @yonib05
    MemberAuthor

    Fix up in #3666

    Verified against live Mantle in us-east-1, and the investigation changed the fix.

    The prefix-list fix I proposed in this issue was wrong. Adding 'xai.', 'google.', 'anthropic.' to OPENAI_PATH_MODEL_PREFIXES would have traded one bug for another, because the base path is a property of the individual model, not of its vendor prefix:

    Model Served from
    google.gemma-4-31b /openai/v1
    google.gemma-3-27b-it /v1

    Same prefix, different base paths. A google. prefix would have mis-routed all three Gemma 3 models. #3666 uses an explicit id set instead, with prefixes kept only for families that have no /v1 members.

    Derived by invoking all 55 models listed in us-east-1 across both base paths and both API surfaces. Anthropic turned out to be a third case: those models are served from /anthropic/v1/messages, a different protocol reached via AnthropicModel, so they answer on neither OpenAI-compatible path and are out of scope for this table.

    On catching this early. Route support is not discoverable: GET /v1/models reports status but not routing, and there is no /openai/v1/models, so any table here is hand-maintained and goes stale on each Mantle onboarding. #3666 adds an integ test to each SDK that replays the probe against the live catalog and fails naming any mis-routed id. I confirmed it works by reverting the table to its pre-fix state:

    AssertionError: Mantle serves these models from the base path the SDK does not use.
    Update _OPENAI_PATH_MODEL_IDS in strands/models/_openai_bedrock.py (and the TypeScript
    mirror in strands-ts/src/models/openai/mantle.ts): {'xai.grok-4.3': '/v1'}
    

    Both SDKs fixed. All 11 table ids plus 6 /v1 controls verified end to end through Python and TypeScript.

    @strandly-the-agent thanks for flagging that the control case was unverified: that turned out to be the load-bearing detail, and probing it properly is what surfaced the Gemma 3/4 split.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-modelRelated to models or model providersbugSomething isn't workingpythonPull requests that update python codetypescriptPull requests that update typescript code

    Type

    Fields

    Language

    TypeScript

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions