TanStack AI version
0.40.0
Framework/Library version
Tanstack Start
Describe the bug and the steps to reproduce it
First and most importantly: I really love this initiative and project. Amazing like anything that gets the Tanstack touch!
Second: I realise patching the lib is probably not what you had in mind and that the @tanstack/ai-bedrock is a very new package still under development. What i hope here is to bring forward some of the findings I've done with using your package as well as some specifics about the bedrock api that I have tried to wrap my head around (but in typical aws fashion, that is probably not possible).
Third, the issue:
Package: @tanstack/ai-bedrock@0.1.2
Summary
buildBaseURL returns a single path per endpoint:
endpoint === 'mantle'
? `https://bedrock-mantle.${region}.api.aws/v1`
: `https://bedrock-runtime.${region}.amazonaws.com/openai/v1`
On the bedrock-mantle endpoint, though, AWS serves different models on different paths. The hardcoded /v1 is correct only for a subset of models. For Gemma the path is /openai/v1; for Claude it's /anthropic/v1/messages. Sending a request to the wrong path returns a confusing, misleading 401 ... is not enabled for this account (access_denied) rather than a 404/"wrong path" error, which makes this very hard to diagnose.
Evidence (AWS model cards → "Programmatic Access")
| Model ID |
Endpoint |
In-Region endpoint URL (verbatim from the model card) |
google.gemma-4-31b |
bedrock-mantle |
https://bedrock-mantle.{region}.api.aws/openai/v1 |
anthropic.claude-haiku-4-5 |
bedrock-mantle |
https://bedrock-mantle.{region}.api.aws/anthropic/v1/messages |
deepseek.* |
bedrock-mantle |
https://bedrock-mantle.{region}.api.aws/v1 |
The Gemma card explicitly calls this out: "This model is available on the openai/v1/... path on the bedrock-mantle endpoint. This is different from the v1/... path used by other models."
Reproduction
const adapter = bedrockText('google.gemma-4-31b', {
api: 'chat',
endpoint: 'mantle',
region: 'eu-central-1',
})
// → POSTs to https://bedrock-mantle.eu-central-1.api.aws/v1/chat/completions
// → 401 "... is not enabled for this account (access_denied)"
The SigV4 signing service (bedrock-mantle) is correct — only the path is wrong. Overriding baseURL to https://bedrock-mantle.eu-central-1.api.aws/openai/v1 makes the exact same call succeed.
Broader design concerns surfaced while adding our own models
We're pinning + patching model-catalog.generated.ts to add models that ship faster than the catalog updates. Doing that exposed a few gaps beyond the path bug:
-
The catalog encodes apis (converse/chat/responses) but not the endpoint or the URL path. There's nothing to derive the correct base URL from, so buildBaseURL can only be a per-endpoint constant — which is wrong for any model whose mantle path isn't /v1. The catalog seems like the right place to carry, per model, which endpoint(s), which API(s), and which path.
-
api: 'chat' can't represent Claude-on-mantle. Claude's mantle path is /anthropic/v1/messages — the Anthropic Messages API, not OpenAI Chat Completions. So "chat over mantle" differs by model in both path and wire format, and there's no api value that captures the Messages shape.
-
region is decoupled from the model id, but AWS couples them. The catalog lists geo-prefixed ids (e.g. us.anthropic.claude-...) as first-class models, yet bedrockText(model, { region }) accepts any region. bedrockText('us.anthropic.claude-haiku-4-5-20251001-v1:0', { region: 'eu-central-1' }) typechecks but is contradictory (a US geo-inference profile invoked from an EU region). Per-model region/geo availability (us. / eu. / global. / in-region) isn't captured, so invalid combinations only fail as a remote error.
Suggested directions
1. Make the generated catalog carry endpoint / path / region
Have the generated catalog compile, per model, everything needed to build a correct request without the caller guessing:
- supported
endpoint(s): runtime / mantle
- supported
api(s) per endpoint (ideally including wire format, since mantle-Claude is Anthropic Messages, not OpenAI chat)
- the URL path per (endpoint, api):
/v1, /openai/v1, /anthropic/v1/messages
- region / geo availability
Then buildBaseURL and adapter selection can be catalog-driven rather than per-endpoint constants, and invalid (model, endpoint, api, region) combinations can fail at the type level or with a clear local error instead of a remote 401 access_denied.
2. A first-class way to declare a custom model (so users don't patch the lib)
Today the only way to use a model the catalog doesn't ship yet is to edit model-catalog.generated.ts — which for a pinned dependency means patching the package (bun patch / patch-package). That's brittle: the patch is version-pinned and silently stops applying on upgrade, and it can't be caught by typecheck. The catalog is both the type source (the model-id union via IdsWhere<'chat'>) and the runtime behavior source (apis/modalities, and — per this issue — ideally the path), so a custom model has to supply both, and there's currently no public seam to do it. New Bedrock models ship faster than a catalog release (this is how we hit Gemma and Kimi before they were in the generated catalog), so an escape hatch matters.
Options that would remove the need to patch:
(a) Accept an inline model descriptor at the call site. Let bedrockText take either a catalog key (as today, with full inference) or a fully-specified object for anything not in the catalog:
bedrockText(
{
id: 'google.gemma-4-31b',
endpoint: 'mantle',
api: 'chat',
path: '/openai/v1', // per-model, from the model card
input: ['text'],
output: ['text'],
},
{ region: 'eu-central-1' },
)
When a descriptor is passed, skip the catalog lookup and use its fields directly. This also self-serves the path bug above — users set the correct path without waiting for a catalog release.
(b) A defineBedrockModel({...}) helper that returns a typed, reusable model object to pass to bedrockText (nice for sharing a custom model across a codebase).
(c) A registry + module augmentation: registerBedrockModels([...]) that merges entries into the runtime catalog, plus an exported interface consumers can declare module-augment to extend the id union — so custom ids typecheck without editing generated files.
Any of these lets teams adopt just-shipped models without a lockfile-pinned patch that breaks on the next upgrade, while keeping the fully-inferred experience for catalog models.
Workaround (today)
bedrockText('google.gemma-4-31b', {
api: 'chat',
endpoint: 'mantle',
region: 'eu-central-1',
baseURL: `https://bedrock-mantle.${region}.api.aws/openai/v1`,
})
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
see above
Screenshots or Videos (Optional)
not applicable
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct
TanStack AI version
0.40.0
Framework/Library version
Tanstack Start
Describe the bug and the steps to reproduce it
First and most importantly: I really love this initiative and project. Amazing like anything that gets the Tanstack touch!
Second: I realise patching the lib is probably not what you had in mind and that the @tanstack/ai-bedrock is a very new package still under development. What i hope here is to bring forward some of the findings I've done with using your package as well as some specifics about the bedrock api that I have tried to wrap my head around (but in typical aws fashion, that is probably not possible).
Third, the issue:
Package:
@tanstack/ai-bedrock@0.1.2Summary
buildBaseURLreturns a single path per endpoint:On the
bedrock-mantleendpoint, though, AWS serves different models on different paths. The hardcoded/v1is correct only for a subset of models. For Gemma the path is/openai/v1; for Claude it's/anthropic/v1/messages. Sending a request to the wrong path returns a confusing, misleading401 ... is not enabled for this account (access_denied)rather than a 404/"wrong path" error, which makes this very hard to diagnose.Evidence (AWS model cards → "Programmatic Access")
google.gemma-4-31bbedrock-mantlehttps://bedrock-mantle.{region}.api.aws/openai/v1anthropic.claude-haiku-4-5bedrock-mantlehttps://bedrock-mantle.{region}.api.aws/anthropic/v1/messagesdeepseek.*bedrock-mantlehttps://bedrock-mantle.{region}.api.aws/v1The Gemma card explicitly calls this out: "This model is available on the
openai/v1/...path on thebedrock-mantleendpoint. This is different from thev1/...path used by other models."Reproduction
The SigV4 signing service (
bedrock-mantle) is correct — only the path is wrong. OverridingbaseURLtohttps://bedrock-mantle.eu-central-1.api.aws/openai/v1makes the exact same call succeed.Broader design concerns surfaced while adding our own models
We're pinning + patching
model-catalog.generated.tsto add models that ship faster than the catalog updates. Doing that exposed a few gaps beyond the path bug:The catalog encodes
apis(converse/chat/responses) but not the endpoint or the URL path. There's nothing to derive the correct base URL from, sobuildBaseURLcan only be a per-endpoint constant — which is wrong for any model whose mantle path isn't/v1. The catalog seems like the right place to carry, per model, which endpoint(s), which API(s), and which path.api: 'chat'can't represent Claude-on-mantle. Claude's mantle path is/anthropic/v1/messages— the Anthropic Messages API, not OpenAI Chat Completions. So "chat over mantle" differs by model in both path and wire format, and there's noapivalue that captures the Messages shape.regionis decoupled from the model id, but AWS couples them. The catalog lists geo-prefixed ids (e.g.us.anthropic.claude-...) as first-class models, yetbedrockText(model, { region })accepts any region.bedrockText('us.anthropic.claude-haiku-4-5-20251001-v1:0', { region: 'eu-central-1' })typechecks but is contradictory (a US geo-inference profile invoked from an EU region). Per-model region/geo availability (us./eu./global./ in-region) isn't captured, so invalid combinations only fail as a remote error.Suggested directions
1. Make the generated catalog carry endpoint / path / region
Have the generated catalog compile, per model, everything needed to build a correct request without the caller guessing:
endpoint(s):runtime/mantleapi(s) per endpoint (ideally including wire format, since mantle-Claude is Anthropic Messages, not OpenAI chat)/v1,/openai/v1,/anthropic/v1/messagesThen
buildBaseURLand adapter selection can be catalog-driven rather than per-endpoint constants, and invalid(model, endpoint, api, region)combinations can fail at the type level or with a clear local error instead of a remote401 access_denied.2. A first-class way to declare a custom model (so users don't patch the lib)
Today the only way to use a model the catalog doesn't ship yet is to edit
model-catalog.generated.ts— which for a pinned dependency means patching the package (bun patch/patch-package). That's brittle: the patch is version-pinned and silently stops applying on upgrade, and it can't be caught by typecheck. The catalog is both the type source (the model-id union viaIdsWhere<'chat'>) and the runtime behavior source (apis/modalities, and — per this issue — ideally the path), so a custom model has to supply both, and there's currently no public seam to do it. New Bedrock models ship faster than a catalog release (this is how we hit Gemma and Kimi before they were in the generated catalog), so an escape hatch matters.Options that would remove the need to patch:
(a) Accept an inline model descriptor at the call site. Let
bedrockTexttake either a catalog key (as today, with full inference) or a fully-specified object for anything not in the catalog:When a descriptor is passed, skip the catalog lookup and use its fields directly. This also self-serves the path bug above — users set the correct path without waiting for a catalog release.
(b) A
defineBedrockModel({...})helper that returns a typed, reusable model object to pass tobedrockText(nice for sharing a custom model across a codebase).(c) A registry + module augmentation:
registerBedrockModels([...])that merges entries into the runtime catalog, plus an exported interface consumers candeclare module-augment to extend the id union — so custom ids typecheck without editing generated files.Any of these lets teams adopt just-shipped models without a lockfile-pinned patch that breaks on the next upgrade, while keeping the fully-inferred experience for catalog models.
Workaround (today)
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
see above
Screenshots or Videos (Optional)
not applicable
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct