@openclaw/amazon-bedrock-provider 2026.7.1 → 2026.7.2-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,8 @@
1
1
  //#region extensions/amazon-bedrock/aws-credential-refresh.ts
2
+ /**
3
+ * AWS shared config cache refresh helpers for Bedrock. They nudge the AWS SDK
4
+ * to re-read profile/SSO config when no static credentials are present.
5
+ */
2
6
  function hasStaticAwsCredentialEnv(env) {
3
7
  return Boolean(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY);
4
8
  }
@@ -7,13 +11,11 @@ function shouldRefreshAwsSharedConfigCacheForBedrock(env) {
7
11
  if (env.AWS_BEDROCK_SKIP_AUTH === "1" || env.AWS_BEARER_TOKEN_BEDROCK) return false;
8
12
  return !hasStaticAwsCredentialEnv(env);
9
13
  }
10
- async function loadSharedIniFileLoader() {
11
- return await import("@smithy/shared-ini-file-loader");
12
- }
13
14
  /** Refresh Smithy shared config files when Bedrock needs default-chain credentials. */
14
15
  async function refreshAwsSharedConfigCacheForBedrock(env = process.env) {
15
16
  if (!shouldRefreshAwsSharedConfigCacheForBedrock(env)) return;
16
- await (await loadSharedIniFileLoader()).loadSharedConfigFiles({ ignoreCache: true });
17
+ const { loadSharedConfigFiles } = await import("@smithy/shared-ini-file-loader");
18
+ await loadSharedConfigFiles({ ignoreCache: true });
17
19
  }
18
20
  //#endregion
19
21
  export { refreshAwsSharedConfigCacheForBedrock };
@@ -0,0 +1,29 @@
1
+ import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
2
+ //#region extensions/amazon-bedrock/control-plane.ts
3
+ const BEDROCK_CONTROL_PLANE_REQUEST_TIMEOUT_MS = 3e4;
4
+ async function loadBedrockControlPlaneSdk() {
5
+ const { BedrockClient, GetInferenceProfileCommand, ListFoundationModelsCommand, ListInferenceProfilesCommand } = await import("@aws-sdk/client-bedrock");
6
+ return {
7
+ createClient: (region) => new BedrockClient(region ? { region } : {}),
8
+ createGetInferenceProfileCommand: (input) => new GetInferenceProfileCommand(input),
9
+ createListFoundationModelsCommand: () => new ListFoundationModelsCommand({}),
10
+ createListInferenceProfilesCommand: (input) => new ListInferenceProfilesCommand(input)
11
+ };
12
+ }
13
+ async function runBedrockControlPlaneRequest(params) {
14
+ const { signal, cleanup } = buildTimeoutAbortSignal({
15
+ timeoutMs: BEDROCK_CONTROL_PLANE_REQUEST_TIMEOUT_MS,
16
+ signal: params.signal,
17
+ operation: params.operation
18
+ });
19
+ try {
20
+ signal?.throwIfAborted();
21
+ const response = await params.send({ abortSignal: signal });
22
+ signal?.throwIfAborted();
23
+ return response;
24
+ } finally {
25
+ cleanup();
26
+ }
27
+ }
28
+ //#endregion
29
+ export { loadBedrockControlPlaneSdk, runBedrockControlPlaneRequest };
package/dist/discovery.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
2
+ import { loadBedrockControlPlaneSdk, runBedrockControlPlaneRequest } from "./control-plane.js";
2
3
  import { resolveBedrockConfigApiKey } from "./discovery-shared.js";
3
4
  import { resolveBedrockNativeThinkingLevelMap } from "./thinking-policy.js";
4
5
  import { resolveClaudeFable5ModelIdentity, resolveClaudeModelIdentity, resolveClaudeMythos5ModelIdentity, resolveClaudeSonnet5ModelIdentity, supportsClaudeAdaptiveThinking } from "openclaw/plugin-sdk/provider-model-shared";
@@ -119,33 +120,6 @@ const DEFAULT_COST = {
119
120
  cacheRead: 0,
120
121
  cacheWrite: 0
121
122
  };
122
- async function loadBedrockDiscoverySdk() {
123
- const { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } = await import("@aws-sdk/client-bedrock");
124
- return {
125
- createClient: (region) => new BedrockClient({ region }),
126
- createListFoundationModelsCommand: () => new ListFoundationModelsCommand({}),
127
- createListInferenceProfilesCommand: (input) => new ListInferenceProfilesCommand(input)
128
- };
129
- }
130
- function createInjectedClientDiscoverySdk() {
131
- class ListFoundationModelsCommand {
132
- constructor(input = {}) {
133
- this.input = input;
134
- }
135
- }
136
- class ListInferenceProfilesCommand {
137
- constructor(input = {}) {
138
- this.input = input;
139
- }
140
- }
141
- return {
142
- createClient() {
143
- throw new Error("clientFactory is required for injected Bedrock discovery commands");
144
- },
145
- createListFoundationModelsCommand: () => new ListFoundationModelsCommand({}),
146
- createListInferenceProfilesCommand: (input) => new ListInferenceProfilesCommand(input)
147
- };
148
- }
149
123
  const discoveryCache = /* @__PURE__ */ new Map();
150
124
  let hasLoggedBedrockError = false;
151
125
  function normalizeProviderFilter(filter) {
@@ -248,7 +222,11 @@ async function fetchInferenceProfileSummaries(client, createListInferenceProfile
248
222
  const profiles = [];
249
223
  let nextToken;
250
224
  do {
251
- const response = await client.send(createListInferenceProfilesCommand({ nextToken }));
225
+ const command = createListInferenceProfilesCommand({ nextToken });
226
+ const response = await runBedrockControlPlaneRequest({
227
+ operation: "Bedrock ListInferenceProfiles",
228
+ send: (options) => client.send(command, options)
229
+ });
252
230
  for (const summary of response.inferenceProfileSummaries ?? []) profiles.push(summary);
253
231
  nextToken = response.nextToken;
254
232
  } while (nextToken);
@@ -328,44 +306,51 @@ async function discoverBedrockModels(params) {
328
306
  }
329
307
  if (cached) discoveryCache.delete(cacheKey);
330
308
  }
331
- const sdk = params.clientFactory ? createInjectedClientDiscoverySdk() : await loadBedrockDiscoverySdk();
309
+ const sdk = await loadBedrockControlPlaneSdk();
332
310
  const clientFactory = params.clientFactory ?? ((region) => sdk.createClient(region));
333
311
  if (!params.clientFactory) await refreshAwsSharedConfigCacheForBedrock();
334
312
  const client = clientFactory(params.region);
335
313
  const discoveryPromise = (async () => {
336
- const [rawFoundationResponse, profileSummaries] = await Promise.all([client.send(sdk.createListFoundationModelsCommand()), fetchInferenceProfileSummaries(client, (input) => sdk.createListInferenceProfilesCommand(input))]);
337
- const foundationResponse = rawFoundationResponse;
338
- const discovered = [];
339
- const seenIds = /* @__PURE__ */ new Set();
340
- const foundationModels = /* @__PURE__ */ new Map();
341
- for (const summary of foundationResponse.modelSummaries ?? []) {
342
- if (!shouldIncludeSummary(summary, providerFilter)) continue;
343
- const def = toModelDefinition(summary, {
314
+ try {
315
+ const foundationCommand = sdk.createListFoundationModelsCommand();
316
+ const [foundationResponse, profileSummaries] = await Promise.all([runBedrockControlPlaneRequest({
317
+ operation: "Bedrock ListFoundationModels",
318
+ send: (options) => client.send(foundationCommand, options)
319
+ }), fetchInferenceProfileSummaries(client, (input) => sdk.createListInferenceProfilesCommand(input))]);
320
+ const discovered = [];
321
+ const seenIds = /* @__PURE__ */ new Set();
322
+ const foundationModels = /* @__PURE__ */ new Map();
323
+ for (const summary of foundationResponse.modelSummaries ?? []) {
324
+ if (!shouldIncludeSummary(summary, providerFilter)) continue;
325
+ const def = toModelDefinition(summary, {
326
+ contextWindow: defaultContextWindow,
327
+ maxTokens: defaultMaxTokens
328
+ });
329
+ discovered.push(def);
330
+ const normalizedId = normalizeLowercaseStringOrEmpty(def.id);
331
+ seenIds.add(normalizedId);
332
+ foundationModels.set(normalizedId, def);
333
+ }
334
+ const inferenceProfiles = resolveInferenceProfiles(profileSummaries, {
344
335
  contextWindow: defaultContextWindow,
345
336
  maxTokens: defaultMaxTokens
346
- });
347
- discovered.push(def);
348
- const normalizedId = normalizeLowercaseStringOrEmpty(def.id);
349
- seenIds.add(normalizedId);
350
- foundationModels.set(normalizedId, def);
351
- }
352
- const inferenceProfiles = resolveInferenceProfiles(profileSummaries, {
353
- contextWindow: defaultContextWindow,
354
- maxTokens: defaultMaxTokens
355
- }, providerFilter, foundationModels);
356
- for (const profile of inferenceProfiles) {
357
- const normalizedId = normalizeLowercaseStringOrEmpty(profile.id);
358
- if (!seenIds.has(normalizedId)) {
359
- discovered.push(profile);
360
- seenIds.add(normalizedId);
337
+ }, providerFilter, foundationModels);
338
+ for (const profile of inferenceProfiles) {
339
+ const normalizedId = normalizeLowercaseStringOrEmpty(profile.id);
340
+ if (!seenIds.has(normalizedId)) {
341
+ discovered.push(profile);
342
+ seenIds.add(normalizedId);
343
+ }
361
344
  }
345
+ return discovered.toSorted((a, b) => {
346
+ const aGlobal = a.id.startsWith("global.") ? 0 : 1;
347
+ const bGlobal = b.id.startsWith("global.") ? 0 : 1;
348
+ if (aGlobal !== bGlobal) return aGlobal - bGlobal;
349
+ return a.name.localeCompare(b.name);
350
+ });
351
+ } finally {
352
+ client.destroy();
362
353
  }
363
- return discovered.toSorted((a, b) => {
364
- const aGlobal = a.id.startsWith("global.") ? 0 : 1;
365
- const bGlobal = b.id.startsWith("global.") ? 0 : 1;
366
- if (aGlobal !== bGlobal) return aGlobal - bGlobal;
367
- return a.name.localeCompare(b.name);
368
- });
369
354
  })();
370
355
  if (refreshIntervalSeconds > 0) {
371
356
  const expiresAt = resolveExpiresAtMsFromDurationSeconds(refreshIntervalSeconds, { nowMs: now });
@@ -104,25 +104,18 @@ function inferFamily(modelId) {
104
104
  if (id.startsWith("twelvelabs.")) return "twelvelabs";
105
105
  return "titan-v1";
106
106
  }
107
- let sdkCache = null;
108
- let credentialProviderSdkCache;
107
+ let sdkPromise = null;
108
+ let credentialProviderPromise = null;
109
109
  async function loadSdk() {
110
- if (sdkCache) return sdkCache;
111
110
  try {
112
- sdkCache = await import("@aws-sdk/client-bedrock-runtime");
113
- return sdkCache;
111
+ return await (sdkPromise ??= import("@aws-sdk/client-bedrock-runtime"));
114
112
  } catch {
113
+ sdkPromise = null;
115
114
  throw new Error("No API key found for provider bedrock: @aws-sdk/client-bedrock-runtime is not installed. Install it with: npm install @aws-sdk/client-bedrock-runtime");
116
115
  }
117
116
  }
118
- async function loadCredentialProviderSdk() {
119
- if (credentialProviderSdkCache !== void 0) return credentialProviderSdkCache;
120
- try {
121
- credentialProviderSdkCache = await import("@aws-sdk/credential-provider-node");
122
- } catch {
123
- credentialProviderSdkCache = null;
124
- }
125
- return credentialProviderSdkCache;
117
+ function loadDefaultCredentialProvider() {
118
+ return credentialProviderPromise ??= import("@aws-sdk/credential-provider-node").then(({ defaultProvider }) => defaultProvider).catch(() => null);
126
119
  }
127
120
  const MODEL_PREFIX_RE = /^(?:bedrock|amazon-bedrock|aws)\//;
128
121
  const REGION_RE = /bedrock-runtime\.([a-z0-9-]+)\./;
@@ -223,6 +216,7 @@ const testing = {
223
216
  parseSingle,
224
217
  stripInferenceProfilePrefix
225
218
  };
219
+ if (process.env.VITEST === "true") Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockEmbeddingTestApi"), testing);
226
220
  async function createBedrockEmbeddingProvider(options) {
227
221
  const client = resolveBedrockEmbeddingClient(options);
228
222
  const { BedrockRuntimeClient, InvokeModelCommand } = await loadSdk();
@@ -295,13 +289,13 @@ function resolveBedrockEmbeddingClient(options) {
295
289
  dimensions
296
290
  };
297
291
  }
298
- async function hasAwsCredentials(env = process.env, loadCredentialProvider = loadCredentialProviderSdk) {
292
+ async function hasAwsCredentials(env = process.env, loadCredentialProvider = loadDefaultCredentialProvider) {
299
293
  if (env.AWS_ACCESS_KEY_ID?.trim() && env.AWS_SECRET_ACCESS_KEY?.trim()) return true;
300
294
  if (env.AWS_BEARER_TOKEN_BEDROCK?.trim()) return true;
301
- const credentialProviderSdk = await loadCredentialProvider();
302
- if (!credentialProviderSdk) return false;
295
+ const defaultProvider = await loadCredentialProvider();
296
+ if (!defaultProvider) return false;
303
297
  try {
304
- const credentials = await credentialProviderSdk.defaultProvider({
298
+ const credentials = await defaultProvider({
305
299
  timeout: 1e3,
306
300
  maxRetries: 0
307
301
  })();
@@ -311,4 +305,4 @@ async function hasAwsCredentials(env = process.env, loadCredentialProvider = loa
311
305
  }
312
306
  }
313
307
  //#endregion
314
- export { DEFAULT_BEDROCK_EMBEDDING_MODEL, testing as __testing, testing, createBedrockEmbeddingProvider, hasAwsCredentials };
308
+ export { DEFAULT_BEDROCK_EMBEDDING_MODEL, createBedrockEmbeddingProvider, hasAwsCredentials };
@@ -1,12 +1,13 @@
1
1
  import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
2
2
  import { supportsBedrockPromptCaching } from "./bedrock-options.js";
3
+ import { loadBedrockControlPlaneSdk, runBedrockControlPlaneRequest } from "./control-plane.js";
3
4
  import { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey } from "./discovery-shared.js";
4
5
  import { bedrockMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
5
6
  import { isLatestAdaptiveBedrockModelRef, isOpus47OrNewerBedrockModelRef, resolveBedrockClaudeThinkingProfile, resolveBedrockNativeThinkingLevelMap, supportsBedrockNativeMaxEffort } from "./thinking-policy.js";
6
7
  import { streamBedrock, streamSimpleBedrock } from "./stream.runtime.js";
7
8
  import { registerApiProvider, streamSimple } from "openclaw/plugin-sdk/llm";
8
9
  import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
9
- import { ANTHROPIC_BY_MODEL_REPLAY_HOOKS, normalizeProviderId, resolveClaudeFable5ModelIdentity, resolveClaudeModelIdentity, resolveClaudeMythos5ModelIdentity, resolveClaudeSonnet5ModelIdentity } from "openclaw/plugin-sdk/provider-model-shared";
10
+ import { buildProviderReplayFamilyHooks, normalizeProviderId, resolveClaudeFable5ModelIdentity, resolveClaudeModelIdentity, resolveClaudeMythos5ModelIdentity, resolveClaudeSonnet5ModelIdentity } from "openclaw/plugin-sdk/provider-model-shared";
10
11
  import { streamWithPayloadPatch } from "openclaw/plugin-sdk/provider-stream-shared";
11
12
  //#region extensions/amazon-bedrock/register.sync.runtime.ts
12
13
  function normalizeBedrockResolvedModel({ modelId, model }) {
@@ -129,18 +130,24 @@ function resolvedModelSupportsCaching(modelArn) {
129
130
  return supportsBedrockPromptCaching(modelArn);
130
131
  }
131
132
  const appProfileTraitsCache = /* @__PURE__ */ new Map();
132
- async function createBedrockControlPlane(region) {
133
- await refreshAwsSharedConfigCacheForBedrock();
134
- const { BedrockClient, GetInferenceProfileCommand } = await import("@aws-sdk/client-bedrock");
135
- const client = new BedrockClient(region ? { region } : {});
136
- return { getInferenceProfile: async (input) => await client.send(new GetInferenceProfileCommand(input)) };
137
- }
138
- async function resolveAppProfileTraits(modelId, fallbackRegion) {
133
+ async function resolveAppProfileTraits(modelId, fallbackRegion, signal) {
139
134
  const cached = appProfileTraitsCache.get(modelId);
140
135
  if (cached) return cached;
136
+ let client;
141
137
  try {
142
- const models = (await (await createBedrockControlPlane(extractRegionFromArn(modelId) ?? fallbackRegion)).getInferenceProfile({ inferenceProfileIdentifier: modelId })).models ?? [];
143
- const modelArns = models.map((m) => m.modelArn ?? "");
138
+ signal?.throwIfAborted();
139
+ const region = extractRegionFromArn(modelId) ?? fallbackRegion;
140
+ const sdk = await loadBedrockControlPlaneSdk();
141
+ signal?.throwIfAborted();
142
+ const controlPlaneClient = sdk.createClient(region);
143
+ client = controlPlaneClient;
144
+ const command = sdk.createGetInferenceProfileCommand({ inferenceProfileIdentifier: modelId });
145
+ const models = (await runBedrockControlPlaneRequest({
146
+ operation: "Bedrock GetInferenceProfile",
147
+ signal,
148
+ send: (options) => controlPlaneClient.send(command, options)
149
+ })).models ?? [];
150
+ const modelArns = models.map((model) => model.modelArn ?? "");
144
151
  const traits = {
145
152
  cacheEligible: models.length > 0 && modelArns.every((modelArn) => resolvedModelSupportsCaching(modelArn)),
146
153
  omitTemperature: modelArns.some(isOpus47OrNewerBedrockModelRef)
@@ -148,10 +155,13 @@ async function resolveAppProfileTraits(modelId, fallbackRegion) {
148
155
  appProfileTraitsCache.set(modelId, traits);
149
156
  return traits;
150
157
  } catch {
158
+ signal?.throwIfAborted();
151
159
  return {
152
160
  cacheEligible: isAnthropicBedrockModel(modelId),
153
161
  omitTemperature: isOpus47OrNewerBedrockModelRef(modelId)
154
162
  };
163
+ } finally {
164
+ client?.destroy();
155
165
  }
156
166
  }
157
167
  function hasCachePoint(blocks) {
@@ -173,9 +183,8 @@ function injectBedrockCachePoints(payload, cacheRetention) {
173
183
  const system = payload.system;
174
184
  if (Array.isArray(system) && system.length > 0 && !hasCachePoint(system)) system.push(point);
175
185
  const messages = payload.messages;
176
- if (Array.isArray(messages) && messages.length > 0) for (let i = messages.length - 1; i >= 0; i--) {
177
- const msg = messages[i];
178
- if (msg.role === "user" && Array.isArray(msg.content)) {
186
+ if (Array.isArray(messages) && messages.length > 0) {
187
+ for (const msg of messages.toReversed()) if (msg.role === "user" && Array.isArray(msg.content)) {
179
188
  if (!hasCachePoint(msg.content)) msg.content.push(point);
180
189
  break;
181
190
  }
@@ -200,7 +209,7 @@ function registerAmazonBedrockPlugin(api) {
200
209
  /ModelStreamErrorException.*(?:Input is too long|too many input tokens)/i
201
210
  ];
202
211
  const deprecatedTemperatureValidationRe = /ValidationException[\s\S]*(?:invalid_request_error[\s\S]*)?temperature[\s\S]*deprecated|ValidationException[\s\S]*deprecated[\s\S]*temperature/i;
203
- const anthropicByModelReplayHooks = ANTHROPIC_BY_MODEL_REPLAY_HOOKS;
212
+ const anthropicByModelReplayHooks = buildProviderReplayFamilyHooks({ family: "anthropic-by-model" });
204
213
  const startupPluginConfig = api.pluginConfig ?? {};
205
214
  registerApiProvider({
206
215
  api: "bedrock-converse-stream",
@@ -237,7 +246,10 @@ function registerAmazonBedrockPlugin(api) {
237
246
  return {
238
247
  ...options,
239
248
  onPayload: async (payload, payloadModel) => {
249
+ const signal = options.signal;
250
+ signal?.throwIfAborted();
240
251
  await refreshAwsSharedConfigCacheForBedrock();
252
+ signal?.throwIfAborted();
241
253
  return originalOnPayload?.(payload, payloadModel);
242
254
  }
243
255
  };
@@ -351,7 +363,7 @@ function registerAmazonBedrockPlugin(api) {
351
363
  if (shouldPatchMaxThinking) patchMaxThinkingEffort(payloadRecord);
352
364
  if (shouldOmitTemperature) omitUnsupportedClaudePayloadTemperature(payloadRecord);
353
365
  else if (mayNeedTemperatureTrait) {
354
- if ((await resolveAppProfileTraits(modelId, region)).omitTemperature) omitUnsupportedClaudePayloadTemperature(payloadRecord);
366
+ if ((await resolveAppProfileTraits(modelId, region, merged.signal)).omitTemperature) omitUnsupportedClaudePayloadTemperature(payloadRecord);
355
367
  }
356
368
  }
357
369
  return originalOnPayload?.(payload, payloadModel);
@@ -361,7 +373,7 @@ function registerAmazonBedrockPlugin(api) {
361
373
  return underlying(streamModel, context, withAwsCredentialRefreshOnPayload({
362
374
  ...merged,
363
375
  onPayload: async (payload, payloadModel) => {
364
- const traits = await resolveAppProfileTraits(modelId, region);
376
+ const traits = await resolveAppProfileTraits(modelId, region, merged.signal);
365
377
  if (payload && typeof payload === "object") {
366
378
  const payloadRecord = payload;
367
379
  if (traits.cacheEligible) injectBedrockCachePoints(payloadRecord, cacheRetention);
@@ -5,6 +5,8 @@ import { requiresClaudeMandatoryAdaptiveThinking, resolveClaudeFable5ModelIdenti
5
5
  import { applyAnthropicRefusal, createDeferredEventBuffer, notifyLlmRequestActivity } from "openclaw/plugin-sdk/provider-stream-shared";
6
6
  import { BedrockRuntimeClient, BedrockRuntimeServiceException, CachePointType, CacheTTL, ConversationRole, ConverseStreamCommand, ImageFormat, StopReason, ToolResultStatus } from "@aws-sdk/client-bedrock-runtime";
7
7
  import { NodeHttpHandler } from "@smithy/node-http-handler";
8
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
9
+ import { describeToolResultMediaPlaceholder } from "openclaw/plugin-sdk/provider-transport-runtime";
8
10
  //#region extensions/amazon-bedrock/stream.runtime.ts
9
11
  /**
10
12
  * Amazon Bedrock Converse streaming runtime. It maps OpenClaw messages/tools,
@@ -284,7 +286,7 @@ function handleContentBlockDelta(event, blocks, output, stream) {
284
286
  };
285
287
  output.content.push(newBlock);
286
288
  index = blocks.length - 1;
287
- block = blocks[index];
289
+ block = newBlock;
288
290
  stream.push({
289
291
  type: "text_start",
290
292
  contentIndex: index,
@@ -375,7 +377,6 @@ function handleContentBlockStop(event, blocks, output, stream) {
375
377
  });
376
378
  break;
377
379
  case "toolCall":
378
- block.arguments = parseStreamingJson(block.partialJson);
379
380
  delete block.partialJson;
380
381
  stream.push({
381
382
  type: "toolcall_end",
@@ -474,11 +475,26 @@ function normalizeToolCallId(id) {
474
475
  const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
475
476
  return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
476
477
  }
478
+ function createBedrockToolResult(message) {
479
+ const content = [];
480
+ for (const block of message.content) {
481
+ if (block.type === "text") {
482
+ content.push({ text: sanitizeSurrogates(block.text) });
483
+ continue;
484
+ }
485
+ if (describeToolResultMediaPlaceholder([block])) content.push({ image: createImageBlock(block.mimeType, block.data) });
486
+ }
487
+ return { toolResult: {
488
+ toolUseId: message.toolCallId,
489
+ content: content.length > 0 ? content : [{ text: "(no output)" }],
490
+ status: message.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS
491
+ } };
492
+ }
477
493
  function convertMessages(context, model, cacheRetention) {
478
494
  const result = [];
479
495
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
480
496
  for (let i = 0; i < transformedMessages.length; i++) {
481
- const m = transformedMessages[i];
497
+ const m = expectDefined(transformedMessages[i], "message conversion index is in bounds");
482
498
  switch (m.role) {
483
499
  case "user": {
484
500
  const content = [];
@@ -541,19 +557,12 @@ function convertMessages(context, model, cacheRetention) {
541
557
  }
542
558
  case "toolResult": {
543
559
  const toolResults = [];
544
- toolResults.push({ toolResult: {
545
- toolUseId: m.toolCallId,
546
- content: m.content.map((c) => c.type === "image" ? { image: createImageBlock(c.mimeType, c.data) } : { text: sanitizeSurrogates(c.text) }),
547
- status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS
548
- } });
560
+ toolResults.push(createBedrockToolResult(m));
549
561
  let j = i + 1;
550
- while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
551
- const nextMsg = transformedMessages[j];
552
- toolResults.push({ toolResult: {
553
- toolUseId: nextMsg.toolCallId,
554
- content: nextMsg.content.map((c) => c.type === "image" ? { image: createImageBlock(c.mimeType, c.data) } : { text: sanitizeSurrogates(c.text) }),
555
- status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS
556
- } });
562
+ while (true) {
563
+ const nextMsg = transformedMessages.at(j);
564
+ if (nextMsg?.role !== "toolResult") break;
565
+ toolResults.push(createBedrockToolResult(nextMsg));
557
566
  j++;
558
567
  }
559
568
  i = j - 1;
@@ -567,7 +576,7 @@ function convertMessages(context, model, cacheRetention) {
567
576
  }
568
577
  }
569
578
  if (cacheRetention !== "none" && supportsPromptCaching(model) && result.length > 0) {
570
- const lastMessage = result[result.length - 1];
579
+ const lastMessage = expectDefined(result.at(-1), "non-empty converted message list");
571
580
  if (lastMessage.role === ConversationRole.USER && lastMessage.content) lastMessage.content.push({ cachePoint: {
572
581
  type: CachePointType.DEFAULT,
573
582
  ...cacheRetention === "long" ? { ttl: CacheTTL.ONE_HOUR } : {}
@@ -703,5 +712,6 @@ const testing = {
703
712
  resolveSimpleBedrockOptions,
704
713
  shouldUseExplicitBedrockEndpoint
705
714
  };
715
+ if (process.env.VITEST === "true") Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockStreamTestApi"), testing);
706
716
  //#endregion
707
- export { streamBedrock, streamSimpleBedrock, testing };
717
+ export { streamBedrock, streamSimpleBedrock };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/amazon-bedrock-provider",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/amazon-bedrock-provider",
9
- "version": "2026.7.1",
9
+ "version": "2026.7.2-beta.2",
10
10
  "dependencies": {
11
11
  "@aws-sdk/client-bedrock": "3.1078.0",
12
12
  "@aws-sdk/client-bedrock-runtime": "3.1078.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/amazon-bedrock-provider",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,10 +25,10 @@
25
25
  "minHostVersion": ">=2026.5.12-beta.1"
26
26
  },
27
27
  "compat": {
28
- "pluginApi": ">=2026.7.1"
28
+ "pluginApi": ">=2026.7.2-beta.2"
29
29
  },
30
30
  "build": {
31
- "openclawVersion": "2026.7.1",
31
+ "openclawVersion": "2026.7.2-beta.2",
32
32
  "bundledDist": false
33
33
  },
34
34
  "release": {
@@ -46,7 +46,7 @@
46
46
  "README.md"
47
47
  ],
48
48
  "peerDependencies": {
49
- "openclaw": ">=2026.7.1"
49
+ "openclaw": ">=2026.7.2-beta.2"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "openclaw": {