fix(runtime): emit camelCase OpenAI-compatible provider option keys - #3321
Conversation
The AI SDK resolves openai-compatible providerOptions under the camelCase alias of the provider name and flags raw dashed keys as deprecated on every doGenerate. Derive the key via toCamelCase for all openai-compatible identities (extends apache#2463, which only covered custom relay slugs) and fix the three direct dashed emissions. The open-responses path is unchanged: that SDK resolves only the raw name. Closes apache#1430 Generated-by: Claude Code (Fable 5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Astro-Han
left a comment
There was a problem hiding this comment.
Approving. The change is correct, and it is correct for a reason worth stating: it draws the raw-versus-camelCase line exactly where the two SDKs actually differ, rather than applying one rule everywhere.
I verified the substance rather than taking the premise on trust:
- The
toCamelCaseadded here is semantically identical to the SDK's own (@ai-sdk/openai-compatible@3.0.30,dist/index.js:19-21) — same/[_-]([a-z])/gregex, same replacement. So the emitted key always matches the alias the SDK resolves, including the edge cases where they both decline to transform, such as a relay slug likerelay-1where the character after the dash is not a lowercase letter. - Both option paths resolve either spelling, so nothing is dropped. Schema options are parsed three times and merged — under
"openaiCompatible", the raw name and the camelCase alias (dist/index.js:505-519). Passthrough fields are spread from both keys and then filtered against the chat-options shape (dist/index.js:569-577). That second one is what makesvolcengine-ark'sthinkingandcloudflare-workers-ai'schat_template_kwargssafe to rekey, and it is the less obvious of the two. - The deprecation warning fires precisely when the raw key is present and differs from its alias (
warnIfDeprecatedProviderOptionsKey,dist/index.js:29-40), so removing the dashed key removes the warning at its cause rather than suppressing it. - Leaving
model-factory.ts:543on the raw name is right, not an oversight.@ai-sdk/open-responses@2.0.28setsproviderOptionsName: providerNameverbatim (dist/index.js:936) and never camelCases, so the Responses lane must stay raw. The comment already says so; I confirmed it against the pinned package. - The three providers whose namespaces change are all constructed through
createOpenAICompatible—github-copilotexplicitly atmodel-factory.ts:116-117, andvolcengine-ark,zai-coding-planand the rest via the generic path at:189withname: openAiCompatibleProviderName(...), since their registry adapters are{ kind: 'openai-compatible', name: 'provider' }. So every rekeyed identity is one the SDK aliases.
The new capture test is the right kind: it asserts reasoning_effort in the actual request body and that no deprecated warning comes back, which is the defect this PR closes rather than a proxy for it.
Two P3s below — a docstring note and a test gap. Neither blocks; both are about making the coupling to the SDK explicit rather than incidental.
One coordination note, not a finding. packages/runtime/src/model-factory.ts is contested right now: #3255 and #3300 both touch buildFamilyWire's openai-compatible case, and #3255 introduces a third provider-key resolver in model-runtime.ts that must stay raw for the Responses lane. Whichever of these lands last should re-check that the raw and camelCase resolvers are still distinguished on purpose — the refactor here makes openAiCompatibleProviderName the single raw source, which is the right base for that reconciliation.
AI disclosure: reviewed with Claude Code. I read the pinned @ai-sdk/openai-compatible@3.0.30 and @ai-sdk/open-responses@2.0.28 sources myself to confirm the alias resolution, the passthrough spread and the deprecation trigger, checked each rewritten provider's registry adapter, and grepped every providerMetadata reader in the runtime packages. The approval is mine and rests on those checks.
Address review: note in the providerOptions-key docstring that the camelCase alias also selects the SDK's response metadata namespace, assert the capture test's providerMetadata comes back under the camelCase key, and add a wire-level capture for volcengine-ark's passthrough thinking object - the lane the existing reasoningEffort captures do not exercise. Generated-by: Claude Code (Fable 5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
likun666661
left a comment
There was a problem hiding this comment.
Reviewed the current head. The issue is correctly scoped to the deprecated OpenAI-compatible provider-options namespace; the Chat camelCase alias and Responses raw-name paths remain intentionally distinct. Wire-level coverage preserves both schema and passthrough fields, and the providerMetadata namespace change has no current raw-provider consumer. Approved.
Summary
Every doGenerate through a built-in dashed OpenAI-compatible provider (
zai-coding-plan,volcengine-ark, …) logs an AI SDK deprecation warning: options are keyed by the raw provider name, while the SDK's canonical key is its camelCase alias. This derives the key via the #2463toCamelCasehelper for all openai-compatible identities and fixes the three direct dashed emissions (volcengine-ark,cloudflare-workers-ai, thegithub-copilotchat lane). The open-responses path is unchanged — that SDK resolves only the raw name.Fixes #1430
Verification
packages/runtimesuite: 2977 tests pass; with the fix reverted, 8 fail (the updated namespace pins plus a new doGenerate capture test assertingreasoning_effortreaches the request body with zerodeprecatedwarnings)typecheck,lint, andformat:checkpassnode-pty-write-lifecycletimes out on my machine (spawnSync ETIMEDOUT) — reproduced identically on unmodified mainAI use
Select exactly one:
Tool(s) and scope: Claude Code (Fable 5) — implementation and tests, under my direction and review. The commit carries a Generated-by trailer.
Checklist
Does this PR entail a change in behavior?
🤖 Generated with Claude Code