feat: bump bedrock and add proper mantle support for openai models through aws bedrock (#30464)
This commit is contained in:
parent
2a33addd29
commit
9f42bd4a85
12 changed files with 305 additions and 92 deletions
|
|
@ -57,7 +57,7 @@
|
|||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.21.0",
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.107",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
"@ai-sdk/azure": "3.0.49",
|
||||
"@ai-sdk/cerebras": "2.0.41",
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.82",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@aws-sdk/credential-providers": "3.1057.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
|
|
|
|||
|
|
@ -100,10 +100,13 @@ function googleVertexAnthropicBaseURL(project: string | undefined, location: str
|
|||
|
||||
type BundledSDK = {
|
||||
languageModel(modelId: string): LanguageModelV3
|
||||
chat?: (modelId: string) => LanguageModelV3
|
||||
responses?: (modelId: string) => LanguageModelV3
|
||||
}
|
||||
|
||||
const BUNDLED_PROVIDERS: Record<string, () => Promise<(opts: any) => BundledSDK>> = {
|
||||
"@ai-sdk/amazon-bedrock": () => import("@ai-sdk/amazon-bedrock").then((m) => m.createAmazonBedrock),
|
||||
"@ai-sdk/amazon-bedrock/mantle": () => import("@ai-sdk/amazon-bedrock/mantle").then((m) => m.createBedrockMantle),
|
||||
"@ai-sdk/anthropic": () => import("@ai-sdk/anthropic").then((m) => m.createAnthropic),
|
||||
"@ai-sdk/azure": () => import("@ai-sdk/azure").then((m) => m.createAzure),
|
||||
"@ai-sdk/google": () => import("@ai-sdk/google").then((m) => m.createGoogleGenerativeAI),
|
||||
|
|
@ -130,7 +133,7 @@ const BUNDLED_PROVIDERS: Record<string, () => Promise<(opts: any) => BundledSDK>
|
|||
"venice-ai-sdk-provider": () => import("venice-ai-sdk-provider").then((m) => m.createVenice),
|
||||
}
|
||||
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>, model?: Model) => Promise<any>
|
||||
type CustomVarsLoader = (options: Record<string, any>) => Record<string, string>
|
||||
type CustomDiscoverModels = () => Promise<Record<string, Model>>
|
||||
type CustomLoader = (provider: Info) => Effect.Effect<{
|
||||
|
|
@ -156,6 +159,12 @@ function selectAzureLanguageModel(sdk: any, modelID: string, useChat: boolean) {
|
|||
return sdk.languageModel(modelID)
|
||||
}
|
||||
|
||||
function selectBedrockMantleLanguageModel(sdk: BundledSDK, modelID: string) {
|
||||
if (modelID === "openai.gpt-oss-safeguard-20b" || modelID === "openai.gpt-oss-safeguard-120b")
|
||||
return sdk.chat?.(modelID) ?? sdk.languageModel(modelID)
|
||||
return sdk.responses?.(modelID) ?? sdk.languageModel(modelID)
|
||||
}
|
||||
|
||||
function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
return {
|
||||
anthropic: () =>
|
||||
|
|
@ -331,7 +340,9 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
|||
return {
|
||||
autoload: true,
|
||||
options: providerOptions,
|
||||
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
|
||||
async getModel(sdk: any, modelID: string, options?: Record<string, any>, model?: Model) {
|
||||
if (model?.api.npm === "@ai-sdk/amazon-bedrock/mantle") return selectBedrockMantleLanguageModel(sdk, modelID)
|
||||
|
||||
// Skip region prefixing if model already has a cross-region inference profile prefix
|
||||
// Models from models.dev may already include prefixes like us., eu., global., etc.
|
||||
const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
|
||||
|
|
@ -1618,7 +1629,9 @@ export const layer = Layer.effect(
|
|||
|
||||
// Strip openai itemId metadata following what codex does
|
||||
if (
|
||||
(model.api.npm === "@ai-sdk/openai" || model.api.npm === "@ai-sdk/azure") &&
|
||||
(model.api.npm === "@ai-sdk/openai" ||
|
||||
model.api.npm === "@ai-sdk/azure" ||
|
||||
model.api.npm === "@ai-sdk/amazon-bedrock/mantle") &&
|
||||
opts.body &&
|
||||
opts.method === "POST"
|
||||
) {
|
||||
|
|
@ -1725,10 +1738,15 @@ export const layer = Layer.effect(
|
|||
async () => {
|
||||
const sdk = await resolveSDK(model, s, envs)
|
||||
const language = s.modelLoaders[model.providerID]
|
||||
? await s.modelLoaders[model.providerID](sdk, model.api.id, {
|
||||
...provider.options,
|
||||
...model.options,
|
||||
})
|
||||
? await s.modelLoaders[model.providerID](
|
||||
sdk,
|
||||
model.api.id,
|
||||
{
|
||||
...provider.options,
|
||||
...model.options,
|
||||
},
|
||||
model,
|
||||
)
|
||||
: sdk.languageModel(model.api.id)
|
||||
s.models.set(key, language)
|
||||
return language
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ function sdkKey(npm: string): string | undefined {
|
|||
return "azure"
|
||||
case "@ai-sdk/openai":
|
||||
return "openai"
|
||||
case "@ai-sdk/amazon-bedrock/mantle":
|
||||
return "openai"
|
||||
case "@ai-sdk/amazon-bedrock":
|
||||
return "bedrock"
|
||||
case "@ai-sdk/anthropic":
|
||||
|
|
@ -815,6 +817,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||
},
|
||||
]),
|
||||
)
|
||||
case "@ai-sdk/amazon-bedrock/mantle":
|
||||
case "@ai-sdk/openai": {
|
||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
|
||||
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
||||
|
|
@ -1021,7 +1024,8 @@ export function options(input: {
|
|||
if (
|
||||
input.model.providerID === "openai" ||
|
||||
input.model.api.npm === "@ai-sdk/openai" ||
|
||||
input.model.api.npm === "@ai-sdk/github-copilot"
|
||||
input.model.api.npm === "@ai-sdk/github-copilot" ||
|
||||
input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle"
|
||||
) {
|
||||
result["store"] = false
|
||||
}
|
||||
|
|
@ -1107,7 +1111,7 @@ export function options(input: {
|
|||
if (!input.model.api.id.includes("gpt-5-pro")) {
|
||||
result["reasoningEffort"] = "medium"
|
||||
result["reasoningSummary"] = "auto"
|
||||
if (input.model.api.npm === "@ai-sdk/openai") {
|
||||
if (input.model.api.npm === "@ai-sdk/openai" || input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle") {
|
||||
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
|
|||
const supportsMediaInToolResult = (attachment: { mime: string }) => {
|
||||
if (model.api.npm === "@ai-sdk/anthropic") return true
|
||||
if (model.api.npm === "@ai-sdk/openai") return true
|
||||
if (model.api.npm === "@ai-sdk/amazon-bedrock/mantle") return true
|
||||
if (model.api.npm === "@ai-sdk/amazon-bedrock") return attachment.mime.startsWith("image/")
|
||||
if (model.api.npm === "@ai-sdk/xai") return attachment.mime.startsWith("image/")
|
||||
if (model.api.npm === "@ai-sdk/google-vertex/anthropic") return true
|
||||
|
|
|
|||
|
|
@ -33,6 +33,20 @@ afterEach(async () => {
|
|||
|
||||
const list = Provider.use.list()
|
||||
|
||||
const mantleModelConfig = {
|
||||
provider: { npm: "@ai-sdk/amazon-bedrock/mantle" },
|
||||
limit: { context: 272_000, output: 32_000 },
|
||||
modalities: {
|
||||
input: ["text", "image", "pdf"] as Array<"text" | "image" | "pdf">,
|
||||
output: ["text"] as Array<"text">,
|
||||
},
|
||||
}
|
||||
|
||||
const mantleOpenAIModelConfig = {
|
||||
...mantleModelConfig,
|
||||
provider: { npm: "@ai-sdk/amazon-bedrock/mantle", api: "https://bedrock-mantle.us-east-2.api.aws/openai/v1" },
|
||||
}
|
||||
|
||||
const withAuthJson = (contents: string) =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(async () => {
|
||||
|
|
@ -94,6 +108,65 @@ it.instance(
|
|||
{ config: { provider: { "amazon-bedrock": { options: { region: "eu-west-1" } } } } },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"Bedrock Mantle: GPT-5.5 uses Responses API and OpenAI base path",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
yield* set("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token")
|
||||
const model = yield* Provider.use.getModel(ProviderV2.ID.amazonBedrock, ProviderV2.ModelID.make("openai.gpt-5.5"))
|
||||
const language = yield* Provider.use.getLanguage(model)
|
||||
expect((language as { provider: string }).provider).toBe("bedrock-mantle.responses")
|
||||
expect((language as { modelId: string }).modelId).toBe("openai.gpt-5.5")
|
||||
expect(
|
||||
(language as unknown as { config: { url: (input: { path: string; modelId: string }) => string } }).config.url({
|
||||
path: "/responses",
|
||||
modelId: "openai.gpt-5.5",
|
||||
}),
|
||||
).toBe("https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
provider: {
|
||||
"amazon-bedrock": {
|
||||
options: { region: "us-east-2" },
|
||||
models: { "openai.gpt-5.5": mantleOpenAIModelConfig },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"Bedrock Mantle: GPT OSS safeguard uses Chat Completions and Mantle base path",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
yield* set("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token")
|
||||
const model = yield* Provider.use.getModel(
|
||||
ProviderV2.ID.amazonBedrock,
|
||||
ProviderV2.ModelID.make("openai.gpt-oss-safeguard-120b"),
|
||||
)
|
||||
const language = yield* Provider.use.getLanguage(model)
|
||||
expect((language as { provider: string }).provider).toBe("bedrock-mantle.chat")
|
||||
expect((language as { modelId: string }).modelId).toBe("openai.gpt-oss-safeguard-120b")
|
||||
expect(
|
||||
(language as unknown as { config: { url: (input: { path: string; modelId: string }) => string } }).config.url({
|
||||
path: "/chat/completions",
|
||||
modelId: "openai.gpt-oss-safeguard-120b",
|
||||
}),
|
||||
).toBe("https://bedrock-mantle.us-east-1.api.aws/v1/chat/completions")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
provider: {
|
||||
"amazon-bedrock": {
|
||||
options: { region: "us-east-1" },
|
||||
models: { "openai.gpt-oss-safeguard-120b": mantleModelConfig },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"Bedrock: config profile takes precedence over AWS_PROFILE env var",
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -274,6 +274,25 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
|
|||
expect(result.include).toEqual(["reasoning.encrypted_content"])
|
||||
})
|
||||
|
||||
test("Bedrock Mantle gpt-5.5 uses OpenAI Responses defaults", () => {
|
||||
const model = {
|
||||
...createGpt5Model("openai.gpt-5.5"),
|
||||
id: "amazon-bedrock/openai.gpt-5.5",
|
||||
providerID: "amazon-bedrock",
|
||||
api: {
|
||||
id: "openai.gpt-5.5",
|
||||
url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
|
||||
npm: "@ai-sdk/amazon-bedrock/mantle",
|
||||
},
|
||||
}
|
||||
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
|
||||
expect(result.store).toBe(false)
|
||||
expect(result.reasoningEffort).toBe("medium")
|
||||
expect(result.reasoningSummary).toBe("auto")
|
||||
expect(result.include).toEqual(["reasoning.encrypted_content"])
|
||||
expect(result.textVerbosity).toBe("low")
|
||||
})
|
||||
|
||||
test("gpt-5.1 should have textVerbosity set to low", () => {
|
||||
const model = createGpt5Model("gpt-5.1")
|
||||
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
|
||||
|
|
@ -562,6 +581,21 @@ describe("ProviderTransform.providerOptions", () => {
|
|||
})
|
||||
})
|
||||
|
||||
test("maps Bedrock Mantle provider options to OpenAI namespace", () => {
|
||||
const model = createModel({
|
||||
providerID: "amazon-bedrock",
|
||||
api: {
|
||||
id: "openai.gpt-5.5",
|
||||
url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
|
||||
npm: "@ai-sdk/amazon-bedrock/mantle",
|
||||
},
|
||||
})
|
||||
|
||||
expect(ProviderTransform.providerOptions(model, { reasoningEffort: "medium" })).toEqual({
|
||||
openai: { reasoningEffort: "medium" },
|
||||
})
|
||||
})
|
||||
|
||||
test("uses groq slug for groq models", () => {
|
||||
const model = createModel({
|
||||
providerID: "vercel",
|
||||
|
|
@ -3163,6 +3197,28 @@ describe("ProviderTransform.variants", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("@ai-sdk/amazon-bedrock/mantle", () => {
|
||||
test("gpt-5.5 returns OpenAI-style reasoning variants", () => {
|
||||
const model = createMockModel({
|
||||
id: "openai.gpt-5.5",
|
||||
providerID: "amazon-bedrock",
|
||||
api: {
|
||||
id: "openai.gpt-5.5",
|
||||
url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
|
||||
npm: "@ai-sdk/amazon-bedrock/mantle",
|
||||
},
|
||||
release_date: "2026-04-23",
|
||||
})
|
||||
const result = ProviderTransform.variants(model)
|
||||
expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"])
|
||||
expect(result.medium).toEqual({
|
||||
reasoningEffort: "medium",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("@ai-sdk/anthropic", () => {
|
||||
for (const testCase of [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue