Skip to content

Commit c018d84

Browse files
committed
fix: refresh Bedrock profile credentials live
1 parent a35067f commit c018d84

7 files changed

Lines changed: 252 additions & 112 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ Docs: https://docs.openclaw.ai
145145
- CLI backends: keep versioned OAuth identity matches reusable when auth profile ids rotate, so Claude CLI sessions do not reset and lose continuity during same-account OAuth refresh/profile alias changes. Fixes #78541.
146146
- Model providers: normalize APNG sniffed PNG uploads, preserve Gemini 3 tool-call thought-signature replay with documented fallback signatures, accept legacy `__env__:VAR` custom-provider keys, and repair snake_case tool-call transcript sanitization. Fixes #51881, #48915, #77566, and #42858.
147147
- Telegram/models: parse provider ids containing dots in `/models` callback buttons so `hf.co` model lists render as inline keyboard buttons. Fixes #38745.
148+
- Amazon Bedrock: refresh shared AWS profile/config file credentials before Bedrock model, discovery, and embedding requests so long-running Gateway processes pick up renewed profile credentials without restart. Fixes #77551.
148149
- Anthropic: reject uppercase provider-prefixed forward-compat model ids locally instead of sending malformed dynamic ids upstream. Fixes #73715.
149150
- OpenAI/embeddings: pass configured output dimensionality through single and batched embedding requests so memory embedding indexes can request smaller vectors. Fixes #55126.
150151
- CLI/infer: normalize HEIC/HEIF image files to JPEG before model-run requests, avoiding providers that reject Apple image container formats. Fixes #50081.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
type SharedIniFileLoader = {
2+
loadSharedConfigFiles(init?: { ignoreCache?: boolean }): Promise<unknown>;
3+
};
4+
5+
let sharedIniFileLoaderForTest: SharedIniFileLoader | null | undefined;
6+
7+
function hasStaticAwsCredentialEnv(env: NodeJS.ProcessEnv): boolean {
8+
return Boolean(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY);
9+
}
10+
11+
export function shouldRefreshAwsSharedConfigCacheForBedrock(env: NodeJS.ProcessEnv): boolean {
12+
if (env.AWS_BEDROCK_SKIP_AUTH === "1" || env.AWS_BEARER_TOKEN_BEDROCK) {
13+
return false;
14+
}
15+
return !hasStaticAwsCredentialEnv(env);
16+
}
17+
18+
async function loadSharedIniFileLoader(): Promise<SharedIniFileLoader> {
19+
if (sharedIniFileLoaderForTest !== undefined) {
20+
if (!sharedIniFileLoaderForTest) {
21+
throw new Error("AWS shared INI file loader unavailable");
22+
}
23+
return sharedIniFileLoaderForTest;
24+
}
25+
return (await import("@smithy/shared-ini-file-loader")) as SharedIniFileLoader;
26+
}
27+
28+
export async function refreshAwsSharedConfigCacheForBedrock(
29+
env: NodeJS.ProcessEnv = process.env,
30+
): Promise<void> {
31+
if (!shouldRefreshAwsSharedConfigCacheForBedrock(env)) {
32+
return;
33+
}
34+
const loader = await loadSharedIniFileLoader();
35+
await loader.loadSharedConfigFiles({ ignoreCache: true });
36+
}
37+
38+
export function setAwsSharedIniFileLoaderForTest(
39+
loader: SharedIniFileLoader | null | undefined,
40+
): void {
41+
sharedIniFileLoaderForTest = loader;
42+
}

extensions/amazon-bedrock/discovery.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
normalizeLowercaseStringOrEmpty,
1515
normalizeOptionalLowercaseString,
1616
} from "openclaw/plugin-sdk/text-runtime";
17+
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
1718
import { resolveBedrockConfigApiKey } from "./discovery-shared.js";
1819

1920
const log = createSubsystemLogger("bedrock-discovery");
@@ -481,6 +482,9 @@ export async function discoverBedrockModels(params: {
481482
? createInjectedClientDiscoverySdk()
482483
: await loadBedrockDiscoverySdk();
483484
const clientFactory = params.clientFactory ?? ((region: string) => sdk.createClient(region));
485+
if (!params.clientFactory) {
486+
await refreshAwsSharedConfigCacheForBedrock();
487+
}
484488
const client = clientFactory(params.region);
485489

486490
const discoveryPromise = (async () => {

extensions/amazon-bedrock/embedding-provider.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type MemoryEmbeddingProviderCreateOptions,
66
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
77
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
8+
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
89

910
// ---------------------------------------------------------------------------
1011
// Types & constants
@@ -263,7 +264,6 @@ export async function createBedrockEmbeddingProvider(
263264
): Promise<{ provider: MemoryEmbeddingProvider; client: BedrockEmbeddingClient }> {
264265
const client = resolveBedrockEmbeddingClient(options);
265266
const { BedrockRuntimeClient, InvokeModelCommand } = await loadSdk();
266-
const sdk = new BedrockRuntimeClient({ region: client.region });
267267
const spec = resolveSpec(client.model);
268268
const family = spec?.family ?? inferFamily(client.model);
269269

@@ -275,15 +275,21 @@ export async function createBedrockEmbeddingProvider(
275275
});
276276

277277
const invoke = async (body: string): Promise<string> => {
278-
const res = await sdk.send(
279-
new InvokeModelCommand({
280-
modelId: client.model,
281-
body,
282-
contentType: "application/json",
283-
accept: "application/json",
284-
}),
285-
);
286-
return new TextDecoder().decode(res.body);
278+
await refreshAwsSharedConfigCacheForBedrock();
279+
const sdk = new BedrockRuntimeClient({ region: client.region });
280+
try {
281+
const res = await sdk.send(
282+
new InvokeModelCommand({
283+
modelId: client.model,
284+
body,
285+
contentType: "application/json",
286+
accept: "application/json",
287+
}),
288+
);
289+
return new TextDecoder().decode(res.body);
290+
} finally {
291+
sdk.destroy();
292+
}
287293
};
288294

289295
const isCohere = family === "cohere-v3" || family === "cohere-v4";

0 commit comments

Comments
 (0)