diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 62a6395c9d6d..7dfe31308a43 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -1477,6 +1477,7 @@ export type ModelCompatibility = { requireFinishReason?: boolean requireAssistantAfterTool?: boolean supportsPromptCacheKey?: boolean + supportsThinkingBlockBinding?: boolean } export type ModelCost = { diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index d831e1b1f872..ec62425e31e0 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -168,7 +168,26 @@ export function providerID(input: string) { } function migrateModel(info: typeof ConfigProviderV1.Model.Type) { - const settings = info.options && ConfigProviderOptionsV1.model(info.options) + const disableThinkingBlockBinding = + info.options?.thinking?.blockBinding === false || info.options?.reasoningConfig?.blockBinding === false + const options = info.options && { ...info.options } + + // Move the legacy opt-out to compatibility without mutating the input. + if (options && disableThinkingBlockBinding) { + for (const key of ["thinking", "reasoningConfig"]) { + if (options[key]?.blockBinding !== false) continue + + const { blockBinding, ...rest } = options[key] + if (Object.keys(rest).length) { + options[key] = rest + continue + } + delete options[key] + } + } + + const settings = options && ConfigProviderOptionsV1.model(options) + const compatibility = Model.compatibility(info.interleaved) const costs = info.cost && [ { input: info.cost.input, @@ -199,7 +218,9 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) { modelID: info.id, family: info.family, name: info.name, - compatibility: Model.compatibility(info.interleaved), + compatibility: disableThinkingBlockBinding + ? { ...compatibility, supportsThinkingBlockBinding: false } + : compatibility, package: info.provider?.npm ? Provider.aisdk(info.provider.npm) : undefined, settings: info.provider?.api ? { ...settings, baseURL: info.provider.api } : settings, capabilities, diff --git a/packages/core/test/config/normalization.test.ts b/packages/core/test/config/normalization.test.ts index 488156afe64b..5cba5bf7fff8 100644 --- a/packages/core/test/config/normalization.test.ts +++ b/packages/core/test/config/normalization.test.ts @@ -508,6 +508,53 @@ describe("ConfigNormalize", () => { ]) }) + test("migrates the legacy thinking block-binding opt-out into model compatibility", () => { + const result = normalized({ + provider: { + gateway: { + models: { + anthropic: { options: { thinking: { type: "adaptive", blockBinding: false }, effort: "high" } }, + bedrock: { options: { reasoningConfig: { blockBinding: false } } }, + both: { + options: { + thinking: { type: "adaptive", blockBinding: false }, + reasoningConfig: { type: "adaptive", blockBinding: false }, + }, + }, + untouched: { options: { thinking: { type: "adaptive" } } }, + }, + }, + }, + }) + expect(result.encoded.providers).toMatchObject({ + gateway: { + models: { + anthropic: { + compatibility: { supportsThinkingBlockBinding: false }, + settings: { thinking: { type: "adaptive" }, effort: "high" }, + }, + bedrock: { compatibility: { supportsThinkingBlockBinding: false }, settings: {} }, + both: { + compatibility: { supportsThinkingBlockBinding: false }, + settings: { thinking: { type: "adaptive" }, reasoningConfig: { type: "adaptive" } }, + }, + untouched: { settings: { thinking: { type: "adaptive" } } }, + }, + }, + }) + expect(result.encoded.providers).not.toHaveProperty(["gateway", "models", "bedrock", "settings", "reasoningConfig"]) + expect(result.encoded.providers).not.toHaveProperty(["gateway", "models", "both", "settings", "thinking", "blockBinding"]) + expect(result.encoded.providers).not.toHaveProperty([ + "gateway", + "models", + "both", + "settings", + "reasoningConfig", + "blockBinding", + ]) + expect(result.encoded.providers).not.toHaveProperty(["gateway", "models", "untouched", "compatibility"]) + }) + test("invalid legacy provider overlays skip only that provider", () => { const result = normalized({ provider: { diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index b52018f9271b..8b5b91036c7d 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -80,6 +80,7 @@ export const Compatibility = Schema.Struct({ requireFinishReason: Schema.Boolean.pipe(optional), requireAssistantAfterTool: Schema.Boolean.pipe(optional), supportsPromptCacheKey: Schema.Boolean.pipe(optional), + supportsThinkingBlockBinding: Schema.Boolean.pipe(optional), }).annotate({ identifier: "Model.Compatibility" }) export interface Capabilities extends Schema.Schema.Type {}