fix(provider): explicit escape hatch for gateway providerOptions namespacing - #47985
Open
codylandry wants to merge 1 commit into
Open
fix(provider): explicit escape hatch for gateway providerOptions namespacing#47985codylandry wants to merge 1 commit into
codylandry wants to merge 1 commit into
Conversation
…namespacing
ProviderTransform.providerOptions() buckets every non-`gateway` top-level
option under a single key derived from the gateway model-ID prefix
(`model.api.id.split("/")[0]`). When that prefix is an internal routing
alias rather than a recognized AI SDK provider slug, a caller has no way
to address a specific upstream provider's providerOptions namespace
directly (e.g. `openai: { store: false }` or `anthropic: { thinking:
{...} }`) -- it always gets folded into the alias slug's bucket instead,
so the AI SDK provider package reading `providerOptions.openai` (or
`.anthropic`) never sees it.
Add a reserved `options.providerOptions` wrapper. Only a plain-object
value under that key is treated as the explicit escape hatch: it's
extracted before any legacy bucketing logic runs and merged into the
final result verbatim (keyed by whatever name the caller supplies, not
limited to a fixed provider registry) after the legacy result is built.
A non-object value under the same key -- or no `providerOptions` key at
all -- leaves the legacy options bag completely untouched, so every
other key (including one that already happens to share a name with an
upstream provider, or even a non-object value stored under the literal
name `providerOptions` itself) keeps 100% byte-for-byte identical
legacy bucketing behavior.
This was chosen over two alternatives: (1) a key-name/value-shape
heuristic across the existing untyped options bag, rejected because
that bag is a public Record<string, any> end-to-end with no structural
way to distinguish caller intent from a coincidental key name; (2) a
sibling `model.providerOptions` field threaded through the Provider.Model
type, the config schema, and every caller that assembles options --
rejected as disproportionate surface area for a single-purpose
namespace-bucketing fix. The reserved-key reservation is an accepted,
documented compromise: it assumes no caller uses the literal key
`providerOptions` for anything other than this escape hatch, which is
true of every existing caller and documented config example today.
No behavior change for any existing caller: all 561 pre-existing tests
in packages/opencode/test/provider/transform.test.ts pass unmodified.
Added 7 tests: legacy same-named flat option unchanged, explicit
openai/anthropic namespace passthrough, merge-not-clobber with a
same-named legacy slug, canonical (non-aliased) recognized-slug
behavior unchanged, and scalar/array values under the reserved key
falling back to legacy bucketing instead of being silently dropped.
Contributor
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
Contributor
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
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.
Issue for this PR
Closes #47987
Type of change
What does this PR do?
Fixes #47987:
ProviderTransform.providerOptions()(in the@ai-sdk/gatewaybranch) buckets every non-gatewaytop-level option under a single key derived from the gateway model-ID prefix (model.api.id.split("/")[0], viaSLUG_OVERRIDES).When that prefix is an internal routing alias rather than a recognized AI SDK provider slug (e.g. a centrally-configured virtual-model-catalog ID that isn't literally
"openai"or"anthropic"), a caller has no way to address a specific upstream provider'sproviderOptionsnamespace directly. An explicit, already-correctly-namespaced option like:gets folded entirely into the alias slug's bucket instead — so the AI SDK provider package that reads
providerOptions.openainever sees it. This breaks any setup that needs OpenAI-specific options (e.g.store: falsefor zero-data-retention / stateless-reasoning workflows) on a gateway model whose ID isn't already prefixed with a recognized provider slug.Fix: add a reserved
options.providerOptionswrapper as an explicit escape hatch:providerOptionskey at all — leaves the legacy options bag completely untouched, so every other key (including one that already happens to share a name with an upstream provider, or a non-object value literally namedproviderOptions) keeps 100% byte-for-byte identical legacy bucketing behavior.Design alternatives considered:
optionsis a public, fully untypedRecord<string, any>bag end-to-end, so a heuristic based on a coincidental key name can't be made fully unambiguous.model.providerOptionsfield, structurally separate frommodel.options, threaded through the config schema and model type. This is the strictly collision-free option, but requires changing theProvider.Modeltype, the config schema, and every caller that assembles the options object — a much larger surface than this bug warrants.The reserved-key wrapper (this PR) is a deliberate, documented compromise: it reserves the literal key name
providerOptionswithin the existing bag. Repo-wide search found zero existing callers, tests, or documented config examples using that key name today, so the reservation is currently uncontested.How did you verify your code works?
packages/opencode/test/provider/transform.test.tscovering: a legacy flat option namedopenai(no wrapper) still buckets under the model slug unchanged;options.providerOptions.openai/.anthropicpass through at the top level under an unrecognized alias slug; explicit namespace merges with (not clobbers) a same-named legacy-bucketed slug; canonical (non-aliased) recognized-slug behavior stays unchanged; and a scalar/array value under the reserved key falls back to legacy bucketing instead of being silently dropped.devsource (4/7 new tests fail, reproducing the exact bug; 3/7 correctly pass as pins on unchanged behavior) and green after the fix (bun test test/provider/transform.test.ts→ 568 pass, 0 fail, including all 561 pre-existing tests unmodified).tsgo --noEmitacross the full workspace passes (ran automatically via the repo's pre-push hook, 30/30 tasks).Screenshots / recordings
N/A — pure function change, no UI.
Checklist