refactor: flatten provider settings across core and the AI package - #48429
Merged
Conversation
Catalog settings are flat, but native @opencode/ai packages read request options from a nested providerOptions object. Only the AI SDK mapping path lifted flat keys, so a model on a native package silently dropped reasoningEffort and every other request setting, including the variants models.dev generates, while a config that already wrote providerOptions was nested a second time on the aisdk: path. Provider.nativeSettings now does the lift once for every native package: connection keys stay on top, everything else moves into providerOptions, and a nested providerOptions merges instead of wrapping. AISDKNative.map uses it in place of its per-package whitelists and keeps only the real vocabulary translations for Bedrock, OpenRouter, Azure, and Mantle.
Drop the connection-key list: core knows nothing about a package's shape. The flat bag is offered to the package as-is and again as providerOptions, excluding only the credentials core injects, the ProviderPackage.Settings base keys, and opencode transport keys. Each side reads the names it declares, so new package settings work without core changes. A legacy nested providerOptions is flattened with flat keys taking precedence. AISDKNative.map now emits flat settings and keeps only spelling translations between AI SDK and native packages.
Only OpenRouter forwards unknown request options to the wire, so the constructor-only exclusion belongs with the other OpenRouter keys.
Type the AI SDK settings whose spelling differs from the native package with a lenient schema decoded at the top of AISDKNative.map, replacing the per-field isRecord and typeof checks. nativeSettings takes Provider.Settings and its comment states the reason it exists.
model(modelID, settings) takes one flat object. Each entrypoint destructures the connection keys it declares and passes everything else to the route as providerOptions, so there is no nested providerOptions at the package boundary and new package settings need no changes in core. Core's nativeSettings shrinks to dropping opencode transport keys and flattening a legacy nested providerOptions.
There is no providerOptions concept in opencode settings.
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.
Problem
opencode settings are one flat bag everywhere (config, models.dev, catalog, variants). Native
@opencode/aipackages read request options from a nestedproviderOptionsobject insidemodel(id, settings). Only the legacyaisdk:mapping path lifted flat keys into that object, with a hand-maintained whitelist per npm package.So the same intent had multiple spellings and which one worked depended on the package prefix:
aisdk:@ai-sdk/openai@opencode/ai/providers/openaisettings.reasoningEffortmediumsettings.providerOptions.reasoningEffortThe first failure also hit models.dev variants: overriding a provider's package to a native one in config made every generated reasoning variant a no-op.
Change
There is no
providerOptionsconcept at any boundary opencode owns. Settings are flat in config, in the catalog, and at the AI package entrypoint.@opencode/aimodel(modelID, settings)takes one flat object. Each of the 37 entrypoints destructures the connection keys it declares (apiKey,baseURL,region,resourceName, …) and passes the rest to the route asproviderOptions.Settingstypes becomeProviderPackage.Settings & <ProtocolOptionsInput> & { connection keys }, so autocomplete and type-checking are unchanged and the auth-exclusivity type tests still hold. Internalconfigure(...)/Configshapes are untouched; only the package entrypoint is flat. Package guide updated.packages/coreProvider.nativeSettingsdrops opencode transport keys (timeout,chunkTimeout,fetch), which only the AI SDK runtime consumes. That is all; core knows no package key names.AISDKNative.mapis purely the legacy translator: package mapping plus the spellings that differ between AI SDK and native packages (Bedrock connection keys and Converse body fields, OpenRouter attribution headers, Azure/Mantle chat-vs-responses selection). The legacy AI SDK shape is decoded once with a lenient schema at the top ofmap, replacing 31 per-fieldisRecord/typeofchecks. ~400 → ~200 lines.ModelResolverappliesnativeSettingsonce on the native path and loads whatever package the catalog names.Tests
Existing tests updated to the flat shape (
packages/ai/test/provider-package.test.ts,native-providers,meta,groq; coreaisdk-native,model-resolver). One smallnativeSettingscase inprovider.test.ts.packages/ai: 1300 pass.packages/coreaffected suites: 385 pass.Behavior notes
store: "yes") makes the OpenAI-family option schema decode return nothing for that request. The oldaisdk:whitelists masked this; it was already native-path behavior. Making that decode lenient per field is a separate@opencode/aichange.providerOptions, soapiKey/baseURL/headers/bodynever reach it.Follow-up (not in this PR)
Register native packages and native-vocabulary variants at catalog ingest (models.dev, config) so the catalog itself is native and the legacy translator runs only at those entry points; add best-guess default variants for config-defined models with none.