diff --git a/packages/coding-agent/src/config/model-resolver.ts b/packages/coding-agent/src/config/model-resolver.ts index f52c70607..e9b84120f 100644 --- a/packages/coding-agent/src/config/model-resolver.ts +++ b/packages/coding-agent/src/config/model-resolver.ts @@ -200,15 +200,19 @@ const AMAZON_BEDROCK_PROVIDER = "amazon-bedrock"; const BEDROCK_INFERENCE_PROFILE_ARN = /^arn:aws(?:-[a-z]+)*:bedrock:[a-z0-9-]+:[0-9]*:(?:application-inference-profile|inference-profile)\/[a-z0-9][a-z0-9._:-]*$/i; -function resolveBedrockInferenceProfileReference( - provider: string, +function hasBedrockInferenceProfileThinkingSuffix(modelId: string): boolean { + const { base, level } = splitThinkingSuffix(modelId); + return level !== undefined && BEDROCK_INFERENCE_PROFILE_ARN.test(base.trim()); +} + +function resolveBedrockInferenceProfileModelId( modelId: string, availableModels: readonly Model[], ): Model | undefined { - if (provider.toLowerCase() !== AMAZON_BEDROCK_PROVIDER) return undefined; - const requestedId = modelId.trim(); - if (!BEDROCK_INFERENCE_PROFILE_ARN.test(requestedId)) return undefined; + if (hasBedrockInferenceProfileThinkingSuffix(requestedId) || !BEDROCK_INFERENCE_PROFILE_ARN.test(requestedId)) { + return undefined; + } const providerModels = availableModels.filter(model => model.provider.toLowerCase() === AMAZON_BEDROCK_PROVIDER); const defaultModelId = DEFAULT_MODEL_PER_PROVIDER[AMAZON_BEDROCK_PROVIDER]; @@ -223,6 +227,15 @@ function resolveBedrockInferenceProfileReference( }; } +function resolveBedrockInferenceProfileReference( + provider: string, + modelId: string, + availableModels: readonly Model[], +): Model | undefined { + if (provider.toLowerCase() !== AMAZON_BEDROCK_PROVIDER) return undefined; + return resolveBedrockInferenceProfileModelId(modelId, availableModels); +} + const UPSTREAM_ROUTING_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i; /** @@ -502,6 +515,7 @@ function findExactCanonicalModelMatch( * The single model-matching engine. Tries, in order: * 1. exact `provider/id` reference (variant-alias and OpenRouter routed/date * fallbacks included), + * 2. exact canonical id (coalesces provider variants), * 3. exact bare id (preference-ranked), * 4. retired effort-tier variant alias (collapsed catalog entries), @@ -534,6 +548,11 @@ function matchModel( return pickPreferredModel(exactMatches, context); } + const bedrockInferenceProfile = resolveBedrockInferenceProfileModelId(modelPattern, availableModels); + if (bedrockInferenceProfile) { + return bedrockInferenceProfile; + } + // Retired effort-tier variant ids (bare, no provider prefix) resolve to // their collapsed logical model; models from the providers whose table // declared the alias win ties. Auto-derived `X-thinking` pairs resolve diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index 76426f488..73d2d26b5 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -883,7 +883,7 @@ describe("resolveCliModel", () => { expect(result.model?.id).toBe("z-ai/glm-4.7-20251222:nitro"); }); - test("accepts Bedrock inference profile ARNs as explicit provider model ids", () => { + test("accepts Bedrock inference profile ARNs and preserves thinking suffixes", () => { const defaultBedrockModel = buildModel({ id: "us.anthropic.claude-opus-4-8", name: "Claude Opus 4.8 (US)", @@ -898,19 +898,30 @@ describe("resolveCliModel", () => { }); const profileArn = "arn:aws:bedrock:us-east-2:1234567890:application-inference-profile/company-opus-48"; - const result = resolveCliModel({ + const baseResult = resolveCliModel({ cliProvider: "amazon-bedrock", cliModel: profileArn, modelRegistry: { getAll: () => [defaultBedrockModel], }, }); + const offResult = resolveCliModel({ + cliProvider: "amazon-bedrock", + cliModel: `${profileArn}:off`, + modelRegistry: { + getAll: () => [defaultBedrockModel], + }, + }); - expect(result.error).toBeUndefined(); - expect(result.model?.provider).toBe("amazon-bedrock"); - expect(result.model?.api).toBe("bedrock-converse-stream"); - expect(result.model?.id).toBe(profileArn); - expect(result.model?.name).toBe("Bedrock inference profile"); + expect(baseResult.error).toBeUndefined(); + expect(baseResult.model?.provider).toBe("amazon-bedrock"); + expect(baseResult.model?.api).toBe("bedrock-converse-stream"); + expect(baseResult.model?.id).toBe(profileArn); + expect(baseResult.model?.name).toBe("Bedrock inference profile"); + expect(baseResult.thinkingLevel).toBeUndefined(); + expect(offResult.error).toBeUndefined(); + expect(offResult.model?.id).toBe(profileArn); + expect(offResult.thinkingLevel).toBe("off"); }); test("returns a clear error when there are no models", () => {