fix(amazon-bedrock): ignore blank region env overrides (#110676)

* fix(amazon-bedrock): ignore blank region env overrides

* fix(amazon-bedrock): normalize region envs across runtimes

Co-authored-by: ZengWen-DT <ceng.wen@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Wynne668
2026-07-18 22:38:51 +01:00
committed by GitHub
co-authored by Peter Steinberger
parent fc777a35bb
commit 7e4a76a6d0
8 changed files with 141 additions and 9 deletions
@@ -710,6 +710,38 @@ describe("bedrock mantle discovery", () => {
objectArgAt(mockFetch, 0, 1);
});
it.each([
{
name: "the fallback region when the primary env is blank",
env: { AWS_REGION: " ", AWS_DEFAULT_REGION: "eu-west-1" },
expectedRegion: "eu-west-1",
},
{
name: "the default region when both env values are blank",
env: { AWS_REGION: "", AWS_DEFAULT_REGION: "\t" },
expectedRegion: "us-east-1",
},
])("uses $name", async ({ env, expectedRegion }) => {
const mockFetch = vi
.fn()
.mockResolvedValue(
modelDiscoveryResponse({ data: [{ id: "openai.gpt-oss-120b", object: "model" }] }),
);
const provider = await resolveImplicitMantleProvider({
env: {
AWS_BEARER_TOKEN_BEDROCK: MANTLE_IAM_TOKEN_MARKER,
...env,
} as NodeJS.ProcessEnv,
fetchFn: mockFetch as unknown as typeof fetch,
});
expect(provider?.baseUrl).toBe(`https://bedrock-mantle.${expectedRegion}.api.aws/v1`);
expect(stringArgAt(mockFetch, 0, 0)).toBe(
`https://bedrock-mantle.${expectedRegion}.api.aws/v1/models`,
);
});
// ---------------------------------------------------------------------------
// Provider merging
// ---------------------------------------------------------------------------
@@ -13,7 +13,10 @@ import type {
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
const log = createSubsystemLogger("bedrock-mantle-discovery");
@@ -116,7 +119,11 @@ const iamTokenCache = new Map<string, { token: string; expiresAt: number }>();
const IAM_TOKEN_TTL_MS = 7200_000; // Matches the 2h token lifetime we request below.
function resolveMantleRegion(env: NodeJS.ProcessEnv): string {
return env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1";
return (
normalizeOptionalString(env.AWS_REGION) ??
normalizeOptionalString(env.AWS_DEFAULT_REGION) ??
"us-east-1"
);
}
function getCachedIamTokenEntry(
@@ -795,6 +795,34 @@ describe("bedrock discovery", () => {
expect(sendMock).toHaveBeenCalledTimes(2);
});
it.each([
{
name: "secondary region when the primary env override is blank",
env: { AWS_REGION: " ", AWS_DEFAULT_REGION: "eu-west-1" },
expectedRegion: "eu-west-1",
},
{
name: "plugin default when both region env overrides are blank",
env: { AWS_REGION: "", AWS_DEFAULT_REGION: " " },
expectedRegion: "us-east-1",
},
{
name: "primary region when both env overrides are nonblank",
env: { AWS_REGION: "ap-southeast-2", AWS_DEFAULT_REGION: "eu-west-1" },
expectedRegion: "ap-southeast-2",
},
])("uses $name", async ({ env, expectedRegion }) => {
mockSingleActiveSummary();
const provider = await resolveImplicitBedrockProvider({
pluginConfig: { discovery: { enabled: true } },
env,
clientFactory,
});
expect(provider?.baseUrl).toBe(`https://bedrock-runtime.${expectedRegion}.amazonaws.com`);
});
// Ported from #65449 by @alickgithub2 — extended to also cover apac. prefix
it("resolves au. and apac. prefixes for regional inference profiles", async () => {
sendMock
+6 -1
View File
@@ -28,6 +28,7 @@ import {
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
import {
@@ -675,7 +676,11 @@ export async function resolveImplicitBedrockProvider(params: {
return null;
}
const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1";
const region =
discoveryConfig?.region ??
normalizeOptionalString(env.AWS_REGION) ??
normalizeOptionalString(env.AWS_DEFAULT_REGION) ??
"us-east-1";
const models = await discoverBedrockModels({
region,
config: discoveryConfig,
@@ -1,8 +1,45 @@
// Amazon Bedrock tests cover embedding provider plugin behavior.
import { describe, expect, it, vi } from "vitest";
import { hasAwsCredentials } from "./embedding-provider.js";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createBedrockEmbeddingProvider, hasAwsCredentials } from "./embedding-provider.js";
import { embeddingTesting as testing } from "./test-support.js";
afterEach(() => {
vi.unstubAllEnvs();
});
describe("bedrock embedding region resolution", () => {
it.each([
{
name: "secondary region when the primary env override is blank",
primary: " ",
secondary: "eu-west-1",
expected: "eu-west-1",
},
{
name: "plugin default when both env overrides are blank",
primary: "",
secondary: " ",
expected: "us-east-1",
},
{
name: "primary region when both env overrides are nonblank",
primary: "ap-southeast-2",
secondary: "eu-west-1",
expected: "ap-southeast-2",
},
])("uses $name", async ({ primary, secondary, expected }) => {
vi.stubEnv("AWS_REGION", primary);
vi.stubEnv("AWS_DEFAULT_REGION", secondary);
const { client } = await createBedrockEmbeddingProvider({
config: {},
model: "",
});
expect(client.region).toBe(expected);
});
});
describe("hasAwsCredentials", () => {
it("accepts static AWS key credentials without loading the credential chain", async () => {
const loadCredentialProvider = vi.fn();
@@ -11,6 +11,7 @@ import {
import {
asOptionalRecord as asRecord,
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
@@ -406,8 +407,8 @@ function resolveBedrockEmbeddingClient(
const region =
regionFromUrl(options.remote?.baseUrl) ??
regionFromUrl(providerConfig?.baseUrl) ??
process.env.AWS_REGION ??
process.env.AWS_DEFAULT_REGION ??
normalizeOptionalString(process.env.AWS_REGION) ??
normalizeOptionalString(process.env.AWS_DEFAULT_REGION) ??
"us-east-1";
let dimensions: number | undefined;
@@ -238,6 +238,20 @@ describe("Bedrock profile endpoint resolution", () => {
ambientRegion: "eu-west-1",
expectedRegion: "eu-west-1",
},
{
name: "blank primary region with a fallback env",
modelId: "amazon.nova-micro-v1:0",
ambientRegion: " ",
fallbackRegion: "eu-west-1",
expectedRegion: "eu-west-1",
},
{
name: "blank region env vars",
modelId: "amazon.nova-micro-v1:0",
ambientRegion: " ",
fallbackRegion: "\t",
expectedRegion: "us-east-1",
},
{
name: "application inference-profile ARN",
modelId: "arn:aws:bedrock:us-west-2:123456789012:application-inference-profile/profile-abc",
@@ -260,8 +274,11 @@ describe("Bedrock profile endpoint resolution", () => {
},
])(
"resolves $name to $expectedRegion",
async ({ modelId, ambientRegion, explicitRegion, expectedRegion }) => {
async ({ modelId, ambientRegion, fallbackRegion, explicitRegion, expectedRegion }) => {
vi.stubEnv("AWS_REGION", ambientRegion);
if (fallbackRegion !== undefined) {
vi.stubEnv("AWS_DEFAULT_REGION", fallbackRegion);
}
await expect(
captureClientRegion(
+6 -1
View File
@@ -69,6 +69,7 @@ import {
notifyLlmRequestActivity,
} from "openclaw/plugin-sdk/provider-stream-shared";
import { describeToolResultMediaPlaceholder } from "openclaw/plugin-sdk/provider-transport-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import { supportsBedrockPromptCaching, type BedrockOptions } from "./bedrock-options.js";
import { supportsBedrockNativeMaxEffort } from "./thinking-policy.js";
@@ -1038,7 +1039,11 @@ function getConfiguredBedrockRegion(options: BedrockOptions): string | undefined
return options.region;
}
return options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || undefined;
return (
options.region ||
normalizeOptionalString(process.env.AWS_REGION) ||
normalizeOptionalString(process.env.AWS_DEFAULT_REGION)
);
}
function hasConfiguredBedrockProfile(options: BedrockOptions): boolean {