From 386f310f19bb60774edb6b73e095889025060e40 Mon Sep 17 00:00:00 2001 From: mrsibe Date: Wed, 30 Sep 2026 13:24:15 +0800 Subject: [PATCH] chore(ai): rename the openai-compatible provider options key AI SDK 7 deprecates the kebab-case `providerOptions` key for `@ai-sdk/openai-compatible`. The provider still reads it, but it logs a deprecation warning on every call and files `providerMetadata` under the old key. The constant already served as both the provider name and the options key, so it now holds the camelCase form and both follow from it. Verified against the real provider with a capturing fetch, same request in all three cases: key='openai-compatible' reasoning_effort=low + DeprecationWarning key='openaiCompatible' reasoning_effort=low (no warning) key= no reasoning field Nothing in this repository reads `providerMetadata` (a repository-wide search for it in `src/` returns nothing), so moving the key from `providerMetadata['openai-compatible']` to `providerMetadata['openaiCompatible']` has no UI, logging or debug consumer. `test/connectionCapabilities.test.ts` now pins the metadata key anyway, through a mocked SSE response, so a revert to the deprecated key fails a test instead of only warning. Verified: typecheck clean, 472/472 tests pass, `npm run build` OK. No dependency, native addon, packaging or schema change, so no packaged smoke was needed. --- .../models/protocols/openaiCompletions.ts | 10 ++++- test/connectionCapabilities.test.ts | 43 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/models/protocols/openaiCompletions.ts b/src/main/models/protocols/openaiCompletions.ts index 9d81582..14636c8 100644 --- a/src/main/models/protocols/openaiCompletions.ts +++ b/src/main/models/protocols/openaiCompletions.ts @@ -11,7 +11,15 @@ import type { ModelConnection } from '../../../shared/types/connection' import type { ProtocolAdapter } from './types' import { listOpenAICompatibleModels } from './modelListing' -export const PROVIDER_OPTIONS_KEY = 'openai-compatible' +/** + * The provider's name, and the key its `providerOptions` are namespaced under. + * + * AI SDK 7 wants the camelCase form. Passing `'openai-compatible'` still works, but + * it emits a deprecation warning on every call and lands `providerMetadata` under + * the kebab-case key. This value is what both the provider name and the options + * key resolve from, so nothing has to be kept in sync by hand. + */ +export const PROVIDER_OPTIONS_KEY = 'openaiCompatible' function createProvider(connection: ModelConnection) { return createOpenAICompatible({ diff --git a/test/connectionCapabilities.test.ts b/test/connectionCapabilities.test.ts index 269d3a2..00b207d 100644 --- a/test/connectionCapabilities.test.ts +++ b/test/connectionCapabilities.test.ts @@ -1,8 +1,13 @@ import { test } from 'node:test' import assert from 'node:assert/strict' import { MockLanguageModelV3, simulateReadableStream } from 'ai/test' +import { streamText } from 'ai' +import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { ModelClient } from '../src/main/models/ModelClient.ts' -import { openaiCompletionsAdapter } from '../src/main/models/protocols/openaiCompletions.ts' +import { + openaiCompletionsAdapter, + PROVIDER_OPTIONS_KEY +} from '../src/main/models/protocols/openaiCompletions.ts' import { openaiResponsesAdapter } from '../src/main/models/protocols/openaiResponses.ts' import { anthropicMessagesAdapter } from '../src/main/models/protocols/anthropicMessages.ts' import { googleGenerativeAiAdapter } from '../src/main/models/protocols/googleGenerativeAi.ts' @@ -27,7 +32,7 @@ test('openai-compatible connections translate effort into reasoningEffort', () = assert.deepEqual( openaiCompletionsAdapter.chatProviderOptions?.({ ...base, reasoningEffort: 'low' }), { - 'openai-compatible': { reasoningEffort: 'low' } + openaiCompatible: { reasoningEffort: 'low' } } ) assert.equal(openaiCompletionsAdapter.chatProviderOptions?.({ ...base }), undefined) @@ -142,7 +147,7 @@ test('the reasoning configuration reaches the provider request', async () => { await drain(client) assert.deepEqual(model.doStreamCalls[0]?.providerOptions, { - 'openai-compatible': { reasoningEffort: 'low' } + openaiCompatible: { reasoningEffort: 'low' } }) }) @@ -156,3 +161,35 @@ test('a connection with no reasoning metadata sends no provider options', async 'a connection that declared no reasoning settings was given some anyway' ) }) + +test('provider metadata is namespaced under the camelCase key', async () => { + // The provider picks the `providerMetadata` key from the key the caller sends in + // `providerOptions`. Sending the deprecated kebab-case key would put + // `providerMetadata['openai-compatible']` in front of every consumer and log a + // deprecation warning on each call, so this pins the camelCase form. + const sse = [ + 'data: {"id":"1","object":"chat.completion.chunk","created":0,"model":"m","choices":[{"index":0,"delta":{"role":"assistant","content":"ok"},"finish_reason":null}]}\n\n', + 'data: {"id":"1","object":"chat.completion.chunk","created":0,"model":"m","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}\n\n', + 'data: [DONE]\n\n' + ].join('') + + const provider = createOpenAICompatible({ + name: PROVIDER_OPTIONS_KEY, + baseURL: 'https://example.invalid/v1', + apiKey: 'test', + fetch: async () => + new Response(sse, { status: 200, headers: { 'content-type': 'text/event-stream' } }) + }) + + const result = streamText({ + model: provider('test-model'), + prompt: 'hi', + providerOptions: { [PROVIDER_OPTIONS_KEY]: { reasoningEffort: 'low' } } + }) + for await (const chunk of result.textStream) { + void chunk + } + + assert.deepEqual(Object.keys((await result.providerMetadata) ?? {}), [PROVIDER_OPTIONS_KEY]) + assert.equal(PROVIDER_OPTIONS_KEY, 'openaiCompatible') +})