@openclaw/amazon-bedrock-provider 2026.7.2-beta.7 → 2026.8.1-beta.3

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.
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey } from "./discovery-shared.js";
2
- import { discoverBedrockModels, resetBedrockDiscoveryCacheForTest, resolveImplicitBedrockProvider } from "./discovery.js";
3
- export { discoverBedrockModels, mergeImplicitBedrockProvider, resetBedrockDiscoveryCacheForTest, resolveBedrockConfigApiKey, resolveImplicitBedrockProvider };
2
+ import { discoverBedrockModels, resolveImplicitBedrockProvider } from "./discovery.js";
3
+ export { discoverBedrockModels, mergeImplicitBedrockProvider, resolveBedrockConfigApiKey, resolveImplicitBedrockProvider };
package/dist/discovery.js CHANGED
@@ -278,11 +278,6 @@ function resolveInferenceProfiles(profiles, defaults, providerFilter, foundation
278
278
  }
279
279
  return discovered;
280
280
  }
281
- /** Reset Bedrock discovery cache for tests. */
282
- function resetBedrockDiscoveryCacheForTest() {
283
- discoveryCache.clear();
284
- hasLoggedBedrockError = false;
285
- }
286
281
  /** Discover Bedrock models and inference profiles for one region/config. */
287
282
  async function discoverBedrockModels(params) {
288
283
  const refreshIntervalSeconds = Math.max(0, Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS));
@@ -400,4 +395,4 @@ async function resolveImplicitBedrockProvider(params) {
400
395
  };
401
396
  }
402
397
  //#endregion
403
- export { discoverBedrockModels, resetBedrockDiscoveryCacheForTest, resolveImplicitBedrockProvider };
398
+ export { discoverBedrockModels, resolveImplicitBedrockProvider };
@@ -118,7 +118,7 @@ function loadDefaultCredentialProvider() {
118
118
  return credentialProviderPromise ??= import("@aws-sdk/credential-provider-node").then(({ defaultProvider }) => defaultProvider).catch(() => null);
119
119
  }
120
120
  const MODEL_PREFIX_RE = /^(?:bedrock|amazon-bedrock|aws)\//;
121
- const REGION_RE = /bedrock-runtime\.([a-z0-9-]+)\./;
121
+ const REGION_RE = /bedrock-runtime(?:-fips)?\.([a-z0-9-]+)\./;
122
122
  function normalizeBedrockEmbeddingModel(model) {
123
123
  const trimmed = model.trim();
124
124
  return trimmed ? trimmed.replace(MODEL_PREFIX_RE, "") : DEFAULT_BEDROCK_EMBEDDING_MODEL;
@@ -211,15 +211,9 @@ function parseCohereBatch(family, raw) {
211
211
  }
212
212
  return asNumberArrayBatch(embeddings);
213
213
  }
214
- const testing = {
215
- parseCohereBatch,
216
- parseSingle,
217
- stripInferenceProfilePrefix
218
- };
219
- if (process.env.VITEST === "true") Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockEmbeddingTestApi"), testing);
220
214
  async function createBedrockEmbeddingProvider(options) {
221
- const client = resolveBedrockEmbeddingClient(options);
222
215
  const { BedrockRuntimeClient, InvokeModelCommand } = await loadSdk();
216
+ const client = resolveBedrockEmbeddingClient(options, BedrockRuntimeClient);
223
217
  const spec = resolveSpec(client.model);
224
218
  const family = spec?.family ?? inferFamily(client.model);
225
219
  debugEmbeddingsLog("memory embeddings: bedrock client", {
@@ -230,7 +224,12 @@ async function createBedrockEmbeddingProvider(options) {
230
224
  });
231
225
  const invoke = async (body, signal) => {
232
226
  await refreshAwsSharedConfigCacheForBedrock();
233
- const sdk = new BedrockRuntimeClient({ region: client.region });
227
+ const sdk = new BedrockRuntimeClient({
228
+ region: client.region,
229
+ endpoint: client.endpoint,
230
+ useFipsEndpoint: client.useFipsEndpoint,
231
+ useDualstackEndpoint: client.useDualstackEndpoint
232
+ });
234
233
  try {
235
234
  const res = await sdk.send(new InvokeModelCommand({
236
235
  modelId: client.model,
@@ -273,11 +272,41 @@ async function createBedrockEmbeddingProvider(options) {
273
272
  client
274
273
  };
275
274
  }
276
- function resolveBedrockEmbeddingClient(options) {
275
+ function resolveBedrockEmbeddingClient(options, BedrockRuntimeClient) {
277
276
  const model = normalizeBedrockEmbeddingModel(options.model);
278
277
  const spec = resolveSpec(model);
279
278
  const providerConfig = options.config.models?.providers?.["amazon-bedrock"];
279
+ let endpoint = normalizeOptionalString(options.remote?.baseUrl) ?? normalizeOptionalString(providerConfig?.baseUrl);
280
+ let useFipsEndpoint;
281
+ let useDualstackEndpoint;
280
282
  const region = regionFromUrl(options.remote?.baseUrl) ?? regionFromUrl(providerConfig?.baseUrl) ?? normalizeOptionalString(process.env.AWS_REGION) ?? normalizeOptionalString(process.env.AWS_DEFAULT_REGION) ?? "us-east-1";
283
+ if (endpoint) {
284
+ const sdk = new BedrockRuntimeClient({ region });
285
+ try {
286
+ const normalizedEndpoint = new URL(endpoint).href;
287
+ for (const fips of [false, true]) {
288
+ for (const dualstack of [false, true]) {
289
+ const endpointModes = {
290
+ Region: region,
291
+ UseFIPS: fips,
292
+ UseDualStack: dualstack
293
+ };
294
+ try {
295
+ if (sdk.config.endpointProvider(endpointModes).url.href !== normalizedEndpoint) continue;
296
+ } catch {
297
+ continue;
298
+ }
299
+ endpoint = void 0;
300
+ useFipsEndpoint = fips || void 0;
301
+ useDualstackEndpoint = dualstack || void 0;
302
+ break;
303
+ }
304
+ if (!endpoint) break;
305
+ }
306
+ } finally {
307
+ sdk.destroy();
308
+ }
309
+ }
281
310
  let dimensions;
282
311
  if (options.outputDimensionality != null) {
283
312
  if (spec?.validDims && !spec.validDims.includes(options.outputDimensionality)) throw new Error(`Invalid dimensions ${options.outputDimensionality} for ${model}. Valid values: ${spec.validDims.join(", ")}`);
@@ -286,7 +315,10 @@ function resolveBedrockEmbeddingClient(options) {
286
315
  return {
287
316
  region,
288
317
  model,
289
- dimensions
318
+ dimensions,
319
+ ...endpoint ? { endpoint } : {},
320
+ ...useFipsEndpoint ? { useFipsEndpoint } : {},
321
+ ...useDualstackEndpoint ? { useDualstackEndpoint } : {}
290
322
  };
291
323
  }
292
324
  async function hasAwsCredentials(env = process.env, loadCredentialProvider = loadDefaultCredentialProvider) {
@@ -29,7 +29,8 @@ const bedrockMemoryEmbeddingProviderAdapter = {
29
29
  provider: "bedrock",
30
30
  region: client.region,
31
31
  model: client.model,
32
- dimensions: client.dimensions
32
+ dimensions: client.dimensions,
33
+ ...client.endpoint ? { endpoint: client.endpoint } : {}
33
34
  }
34
35
  }
35
36
  };
@@ -5,9 +5,10 @@ import { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey } from "./disc
5
5
  import { bedrockMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
6
6
  import { isLatestAdaptiveBedrockModelRef, isOpus47OrNewerBedrockModelRef, resolveBedrockClaudeThinkingProfile, resolveBedrockNativeThinkingLevelMap, supportsBedrockNativeMaxEffort } from "./thinking-policy.js";
7
7
  import { streamSimpleBedrock } from "./stream.runtime.js";
8
+ import { adaptMemoryEmbeddingProviderAdapter } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
8
9
  import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
9
10
  import { buildProviderReplayFamilyHooks, normalizeProviderId, resolveClaudeFable5ModelIdentity, resolveClaudeModelIdentity, resolveClaudeMythos5ModelIdentity, resolveClaudeOpus5ModelIdentity, resolveClaudeSonnet5ModelIdentity } from "openclaw/plugin-sdk/provider-model-shared";
10
- import { streamWithPayloadPatch } from "openclaw/plugin-sdk/provider-stream-shared";
11
+ import { createPayloadPatchStreamWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
11
12
  //#region extensions/amazon-bedrock/register.sync.runtime.ts
12
13
  function normalizeBedrockResolvedModel({ modelId, model }) {
13
14
  const thinkingLevelMap = resolveBedrockNativeThinkingLevelMap(modelId, model.params);
@@ -68,28 +69,23 @@ function resolveBedrockServiceTier(extraParams, warn) {
68
69
  warn(`ignoring invalid Bedrock service_tier param: ${raw}`);
69
70
  }
70
71
  function createBedrockServiceTierWrapper(underlying, serviceTier) {
71
- return (model, context, options) => {
72
- if (model.api !== "bedrock-converse-stream") return underlying(model, context, options);
73
- return streamWithPayloadPatch(underlying, model, context, options, (payloadObj) => {
74
- payloadObj.serviceTier ??= { type: serviceTier };
75
- });
76
- };
72
+ return createPayloadPatchStreamWrapper(underlying, ({ payload }) => {
73
+ payload.serviceTier ??= { type: serviceTier };
74
+ }, { shouldPatch: ({ model }) => model.api === "bedrock-converse-stream" });
77
75
  }
78
76
  function createGuardrailWrapStreamFn(innerWrapStreamFn, guardrailConfig) {
79
77
  return (ctx) => {
80
78
  const inner = innerWrapStreamFn(ctx);
81
79
  if (!inner) return inner;
82
- return (model, context, options) => {
83
- return streamWithPayloadPatch(inner, model, context, options, (payload) => {
84
- const gc = {
85
- guardrailIdentifier: guardrailConfig.guardrailIdentifier,
86
- guardrailVersion: guardrailConfig.guardrailVersion
87
- };
88
- if (guardrailConfig.streamProcessingMode) gc.streamProcessingMode = guardrailConfig.streamProcessingMode;
89
- if (guardrailConfig.trace) gc.trace = guardrailConfig.trace;
90
- payload.guardrailConfig = gc;
91
- });
92
- };
80
+ return createPayloadPatchStreamWrapper(inner, ({ payload }) => {
81
+ const gc = {
82
+ guardrailIdentifier: guardrailConfig.guardrailIdentifier,
83
+ guardrailVersion: guardrailConfig.guardrailVersion
84
+ };
85
+ if (guardrailConfig.streamProcessingMode) gc.streamProcessingMode = guardrailConfig.streamProcessingMode;
86
+ if (guardrailConfig.trace) gc.trace = guardrailConfig.trace;
87
+ payload.guardrailConfig = gc;
88
+ });
93
89
  };
94
90
  }
95
91
  function sharedRuntimeWouldInjectCachePoints(modelId) {
@@ -220,7 +216,7 @@ function registerAmazonBedrockPlugin(api) {
220
216
  function resolveCurrentPluginConfig(config) {
221
217
  return resolvePluginConfigObject(config, providerId) ?? (config ? void 0 : startupPluginConfig);
222
218
  }
223
- api.registerMemoryEmbeddingProvider(bedrockMemoryEmbeddingProviderAdapter);
219
+ api.registerEmbeddingProvider(adaptMemoryEmbeddingProviderAdapter(bedrockMemoryEmbeddingProviderAdapter));
224
220
  const baseWrapStreamFn = ({ modelId, model, streamFn }) => {
225
221
  const modelRef = {
226
222
  id: modelId,
@@ -2,13 +2,13 @@ import { supportsBedrockPromptCaching } from "./bedrock-options.js";
2
2
  import { supportsBedrockNativeMaxEffort } from "./thinking-policy.js";
3
3
  import { requiresClaudeMandatoryAdaptiveThinking, resolveClaudeFable5ModelIdentity, resolveClaudeModelIdentity, resolveClaudeMythos5ModelIdentity, resolveClaudeOpus5ModelIdentity, resolveClaudeSonnet5ModelIdentity, supportsClaudeAdaptiveThinking, supportsClaudeNativeXhighEffort } from "openclaw/plugin-sdk/provider-model-shared";
4
4
  import { applyAnthropicRefusal, createDeferredEventBuffer, notifyLlmRequestActivity } from "openclaw/plugin-sdk/provider-stream-shared";
5
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
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
8
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
9
9
  import { AssistantMessageEventStream, adjustMaxTokensForThinking, buildBaseOptions, calculateCost, clampReasoning, createHttpProxyAgentsForTarget, parseStreamingJson, sanitizeSurrogates, transformMessages } from "openclaw/plugin-sdk/llm";
10
10
  import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
11
- import { describeToolResultMediaPlaceholder } from "openclaw/plugin-sdk/provider-transport-runtime";
11
+ import { describeToolResultMediaPlaceholder, finalizeTerminalToolCallArguments, notifyProviderHttpMetadata } from "openclaw/plugin-sdk/provider-transport-runtime";
12
12
  //#region extensions/amazon-bedrock/stream.runtime.ts
13
13
  /**
14
14
  * Amazon Bedrock Converse streaming runtime. It maps OpenClaw messages/tools,
@@ -72,6 +72,8 @@ const streamBedrock = (model, context, options = {}) => {
72
72
  timestamp: Date.now()
73
73
  };
74
74
  const blocks = output.content;
75
+ const pendingToolCallEnds = [];
76
+ const redactedReasoningChunks = /* @__PURE__ */ new Map();
75
77
  const fable5 = usesClaudeFable5BedrockContract(model);
76
78
  const refusalBuffer = usesClaudeStreamingRefusalBedrockContract(model) ? createDeferredEventBuffer(stream, () => notifyLlmRequestActivity(options.signal)) : void 0;
77
79
  const eventSink = refusalBuffer ?? stream;
@@ -100,8 +102,9 @@ const streamBedrock = (model, context, options = {}) => {
100
102
  config.token = { token: bearerToken };
101
103
  config.authSchemePreference = ["httpBearerAuth"];
102
104
  }
105
+ let client;
103
106
  try {
104
- const client = new BedrockRuntimeClient(config);
107
+ client = new BedrockRuntimeClient(config);
105
108
  const cacheRetention = resolveCacheRetention(options.cacheRetention);
106
109
  const additionalModelRequestFields = buildAdditionalModelRequestFields(model, options);
107
110
  const thinking = additionalModelRequestFields?.thinking;
@@ -123,24 +126,32 @@ const streamBedrock = (model, context, options = {}) => {
123
126
  if (nextCommandInput !== void 0) commandInput = nextCommandInput;
124
127
  const command = new ConverseStreamCommand(commandInput);
125
128
  const response = await client.send(command, { abortSignal: options.signal });
129
+ const responseIterator = response.stream[Symbol.asyncIterator]();
126
130
  if (response.$metadata.httpStatusCode !== void 0) {
127
131
  const responseHeaders = {};
128
132
  if (response.$metadata.requestId) responseHeaders["x-amzn-requestid"] = response.$metadata.requestId;
129
- await options?.onResponse?.({
130
- status: response.$metadata.httpStatusCode,
131
- headers: responseHeaders
132
- }, model);
133
+ await notifyProviderHttpMetadata({
134
+ options,
135
+ response: {
136
+ status: response.$metadata.httpStatusCode,
137
+ headers: responseHeaders
138
+ },
139
+ model,
140
+ cancelStream: async () => {
141
+ await responseIterator.return?.();
142
+ }
143
+ });
133
144
  }
134
145
  let sawMessageStop = false;
135
- for await (const item of response.stream) if (item.messageStart) {
146
+ for await (const item of { [Symbol.asyncIterator]: () => responseIterator }) if (item.messageStart) {
136
147
  if (item.messageStart.role !== ConversationRole.ASSISTANT) throw new Error("Unexpected assistant message start but got user message start instead");
137
148
  eventSink.push({
138
149
  type: "start",
139
150
  partial: output
140
151
  });
141
152
  } else if (item.contentBlockStart) handleContentBlockStart(item.contentBlockStart, blocks, output, eventSink);
142
- else if (item.contentBlockDelta) handleContentBlockDelta(item.contentBlockDelta, blocks, output, eventSink);
143
- else if (item.contentBlockStop) handleContentBlockStop(item.contentBlockStop, blocks, output, eventSink);
153
+ else if (item.contentBlockDelta) handleContentBlockDelta(item.contentBlockDelta, blocks, output, eventSink, redactedReasoningChunks);
154
+ else if (item.contentBlockStop) handleContentBlockStop(item.contentBlockStop, blocks, output, eventSink, redactedReasoningChunks, pendingToolCallEnds);
144
155
  else if (item.messageStop) {
145
156
  sawMessageStop = true;
146
157
  if (item.messageStop.stopReason === "refusal") applyAnthropicRefusal(output, readBedrockStopDetails(item.messageStop.additionalModelResponseFields), model.provider);
@@ -155,9 +166,11 @@ const streamBedrock = (model, context, options = {}) => {
155
166
  else if (item.validationException) throw item.validationException;
156
167
  else if (item.throttlingException) throw item.throttlingException;
157
168
  else if (item.serviceUnavailableException) throw item.serviceUnavailableException;
158
- if (refusalBuffer && !sawMessageStop) throw new Error("Bedrock stream ended before messageStop");
169
+ if (!sawMessageStop) throw new Error("Bedrock stream ended before messageStop");
159
170
  if (options.signal?.aborted) throw new Error("Request was aborted");
160
171
  if (output.stopReason === "error" || output.stopReason === "aborted") throw new Error(output.errorMessage ?? "An unknown error occurred");
172
+ for (const block of blocks) if (block.index !== void 0 && block.type !== "toolCall") handleContentBlockStop({ contentBlockIndex: block.index }, blocks, output, eventSink, redactedReasoningChunks, pendingToolCallEnds);
173
+ flushPendingBedrockToolCalls(pendingToolCallEnds, blocks, output, eventSink);
161
174
  refusalBuffer?.flush();
162
175
  stream.push({
163
176
  type: "done",
@@ -166,6 +179,7 @@ const streamBedrock = (model, context, options = {}) => {
166
179
  });
167
180
  stream.end();
168
181
  } catch (error) {
182
+ output.content = output.content.filter((block) => block.type !== "toolCall");
169
183
  for (const block of output.content) {
170
184
  delete block.index;
171
185
  delete block.partialJson;
@@ -182,6 +196,8 @@ const streamBedrock = (model, context, options = {}) => {
182
196
  error: output
183
197
  });
184
198
  stream.end();
199
+ } finally {
200
+ client?.destroy();
185
201
  }
186
202
  })();
187
203
  return stream;
@@ -269,11 +285,12 @@ function handleContentBlockStart(event, blocks, output, stream) {
269
285
  const index = event.contentBlockIndex;
270
286
  const start = event.start;
271
287
  if (start?.toolUse) {
288
+ const startArguments = isRecord(start.toolUse) ? start.toolUse.input : void 0;
272
289
  const block = {
273
290
  type: "toolCall",
274
291
  id: start.toolUse.toolUseId || "",
275
292
  name: start.toolUse.name || "",
276
- arguments: {},
293
+ arguments: isRecord(startArguments) ? startArguments : {},
277
294
  partialJson: "",
278
295
  index
279
296
  };
@@ -285,7 +302,7 @@ function handleContentBlockStart(event, blocks, output, stream) {
285
302
  });
286
303
  }
287
304
  }
288
- function handleContentBlockDelta(event, blocks, output, stream) {
305
+ function handleContentBlockDelta(event, blocks, output, stream, redactedReasoningChunks) {
289
306
  const contentBlockIndex = event.contentBlockIndex;
290
307
  const delta = event.delta;
291
308
  let index = blocks.findIndex((b) => b.index === contentBlockIndex);
@@ -354,6 +371,13 @@ function handleContentBlockDelta(event, blocks, output, stream) {
354
371
  });
355
372
  }
356
373
  if (delta.reasoningContent.signature) thinkingBlock.thinkingSignature = (thinkingBlock.thinkingSignature || "") + delta.reasoningContent.signature;
374
+ if (delta.reasoningContent.redactedContent) {
375
+ const chunks = redactedReasoningChunks.get(contentBlockIndex);
376
+ if (chunks) chunks.push(delta.reasoningContent.redactedContent);
377
+ else redactedReasoningChunks.set(contentBlockIndex, [delta.reasoningContent.redactedContent]);
378
+ thinkingBlock.thinking = "[Reasoning redacted]";
379
+ thinkingBlock.redacted = true;
380
+ }
357
381
  }
358
382
  }
359
383
  }
@@ -363,17 +387,25 @@ function handleMetadata(event, model, output) {
363
387
  output.usage.output = event.usage.outputTokens || 0;
364
388
  output.usage.cacheRead = event.usage.cacheReadInputTokens || 0;
365
389
  output.usage.cacheWrite = event.usage.cacheWriteInputTokens || 0;
366
- output.usage.totalTokens = event.usage.totalTokens || output.usage.input + output.usage.output;
390
+ const promptTokens = output.usage.input + output.usage.cacheRead + output.usage.cacheWrite;
391
+ output.usage.totalTokens = Math.max(event.usage.totalTokens || 0, promptTokens + output.usage.output);
392
+ output.usage.contextUsage = {
393
+ state: "available",
394
+ promptTokens,
395
+ totalTokens: promptTokens + output.usage.output
396
+ };
397
+ const cacheWrite1h = event.usage.cacheDetails?.reduce((total, detail) => detail.ttl === CacheTTL.ONE_HOUR ? total + (detail.inputTokens ?? 0) : total, 0);
398
+ if (cacheWrite1h) output.usage.cacheWrite1h = cacheWrite1h;
367
399
  calculateCost(model, output.usage);
368
400
  }
369
401
  }
370
- function handleContentBlockStop(event, blocks, output, stream) {
402
+ function handleContentBlockStop(event, blocks, output, stream, redactedReasoningChunks, pendingToolCallEnds) {
371
403
  const index = blocks.findIndex((b) => b.index === event.contentBlockIndex);
372
404
  const block = blocks[index];
373
405
  if (!block) return;
374
- delete block.index;
375
406
  switch (block.type) {
376
407
  case "text":
408
+ delete block.index;
377
409
  stream.push({
378
410
  type: "text_end",
379
411
  contentIndex: index,
@@ -382,6 +414,16 @@ function handleContentBlockStop(event, blocks, output, stream) {
382
414
  });
383
415
  break;
384
416
  case "thinking":
417
+ delete block.index;
418
+ if (block.redacted) {
419
+ const chunks = redactedReasoningChunks.get(event.contentBlockIndex);
420
+ if (chunks) {
421
+ let opaqueReasoning = "";
422
+ for (const chunk of chunks) for (const byte of chunk) opaqueReasoning += String.fromCharCode(byte);
423
+ block.thinkingSignature = btoa(opaqueReasoning);
424
+ redactedReasoningChunks.delete(event.contentBlockIndex);
425
+ }
426
+ }
385
427
  stream.push({
386
428
  type: "thinking_end",
387
429
  contentIndex: index,
@@ -390,16 +432,27 @@ function handleContentBlockStop(event, blocks, output, stream) {
390
432
  });
391
433
  break;
392
434
  case "toolCall":
393
- delete block.partialJson;
394
- stream.push({
395
- type: "toolcall_end",
396
- contentIndex: index,
397
- toolCall: block,
398
- partial: output
435
+ delete block.index;
436
+ pendingToolCallEnds.push({
437
+ block,
438
+ contentIndex: index
399
439
  });
400
440
  break;
401
441
  }
402
442
  }
443
+ function flushPendingBedrockToolCalls(pending, blocks, output, stream) {
444
+ if (blocks.some((block) => block.type === "toolCall" && block.index !== void 0)) throw new Error("Provider completed stream with an incomplete tool call");
445
+ finalizeTerminalToolCallArguments(pending.map(({ block }) => block), (block) => block.partialJson && block.partialJson.length > 0 ? block.partialJson : block.arguments);
446
+ for (const toolCall of pending) {
447
+ delete toolCall.block.partialJson;
448
+ stream.push({
449
+ type: "toolcall_end",
450
+ contentIndex: toolCall.contentIndex,
451
+ toolCall: toolCall.block,
452
+ partial: output
453
+ });
454
+ }
455
+ }
403
456
  function resolveClaudeProfileNameModelId(modelName) {
404
457
  const normalized = modelName?.trim().toLowerCase().replace(/[\s_.:]+/g, "-") ?? "";
405
458
  if (!normalized.includes("claude")) return;
@@ -495,16 +548,17 @@ function createBedrockToolResult(message) {
495
548
  content.push({ text: sanitizeSurrogates(block.text) });
496
549
  continue;
497
550
  }
498
- if (describeToolResultMediaPlaceholder([block])) content.push({ image: createImageBlock(block.mimeType, block.data) });
551
+ if (block.type === "image" && describeToolResultMediaPlaceholder([block])) content.push({ image: createImageBlock(block.mimeType, block.data) });
499
552
  }
500
553
  return { toolResult: {
501
554
  toolUseId: message.toolCallId,
502
- content: content.length > 0 ? content : [{ text: "(no output)" }],
555
+ content: content.length > 0 ? content : [{ text: describeToolResultMediaPlaceholder(message.content) ?? "(no output)" }],
503
556
  status: message.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS
504
557
  } };
505
558
  }
506
559
  function convertMessages(context, model, cacheRetention) {
507
560
  const result = [];
561
+ let firstVolatileMessageIndex;
508
562
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
509
563
  for (let i = 0; i < transformedMessages.length; i++) {
510
564
  const m = expectDefined(transformedMessages[i], "message conversion index is in bounds");
@@ -522,6 +576,7 @@ function convertMessages(context, model, cacheRetention) {
522
576
  default: continue;
523
577
  }
524
578
  if (content.length === 0) continue;
579
+ if (m.runtimeContextCarrier === true && firstVolatileMessageIndex === void 0) firstVolatileMessageIndex = result.length;
525
580
  result.push({
526
581
  role: ConversationRole.USER,
527
582
  content
@@ -544,6 +599,12 @@ function convertMessages(context, model, cacheRetention) {
544
599
  } });
545
600
  break;
546
601
  case "thinking": {
602
+ if (c.redacted) {
603
+ if (!supportsThinkingSignature(model)) continue;
604
+ if (!c.thinkingSignature) throw new Error("Bedrock redacted reasoning block is missing its opaque signature");
605
+ contentBlocks.push({ reasoningContent: { redactedContent: decodeBedrockBase64(c.thinkingSignature, "Bedrock redacted reasoning block has a malformed opaque signature") } });
606
+ break;
607
+ }
547
608
  const thinkingSignature = c.thinkingSignature;
548
609
  const normalizedThinkingSignature = thinkingSignature?.trim();
549
610
  const supportsSignature = supportsThinkingSignature(model);
@@ -588,9 +649,9 @@ function convertMessages(context, model, cacheRetention) {
588
649
  default: continue;
589
650
  }
590
651
  }
591
- if (cacheRetention !== "none" && supportsPromptCaching(model) && result.length > 0) {
592
- const lastMessage = expectDefined(result.at(-1), "non-empty converted message list");
593
- if (lastMessage.role === ConversationRole.USER && lastMessage.content) lastMessage.content.push({ cachePoint: {
652
+ if (cacheRetention !== "none" && supportsPromptCaching(model) && result.at(-1)?.role === ConversationRole.USER) {
653
+ const cacheAnchor = result.findLast((message, index) => message.role === ConversationRole.USER && (firstVolatileMessageIndex === void 0 || index < firstVolatileMessageIndex));
654
+ if (cacheAnchor?.content) cacheAnchor.content.push({ cachePoint: {
594
655
  type: CachePointType.DEFAULT,
595
656
  ...cacheRetention === "long" ? { ttl: CacheTTL.ONE_HOUR } : {}
596
657
  } });
@@ -602,7 +663,7 @@ function convertToolConfig(tools, toolChoice) {
602
663
  const bedrockTools = tools.map((tool) => ({ toolSpec: {
603
664
  name: tool.name,
604
665
  description: tool.description,
605
- inputSchema: { json: tool.parameters }
666
+ inputSchema: { json: { ...tool.parameters } }
606
667
  } }));
607
668
  let bedrockToolChoice;
608
669
  switch (toolChoice) {
@@ -720,26 +781,18 @@ function createImageBlock(mimeType, data) {
720
781
  break;
721
782
  default: throw new Error(`Unknown image type: ${mimeType}`);
722
783
  }
784
+ return {
785
+ source: { bytes: decodeBedrockBase64(data, "Amazon Bedrock image content has malformed base64") },
786
+ format
787
+ };
788
+ }
789
+ function decodeBedrockBase64(data, errorMessage) {
723
790
  const canonicalBase64 = canonicalizeBase64(data);
724
- if (!canonicalBase64) throw new Error("Amazon Bedrock image content has malformed base64");
791
+ if (!canonicalBase64) throw new Error(errorMessage);
725
792
  const binaryString = atob(canonicalBase64);
726
793
  const bytes = new Uint8Array(binaryString.length);
727
794
  for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
728
- return {
729
- source: { bytes },
730
- format
731
- };
795
+ return bytes;
732
796
  }
733
- /** Test-only hooks for Bedrock runtime conversion and endpoint policy. */
734
- const testing = {
735
- buildAdditionalModelRequestFields,
736
- convertMessages,
737
- getConfiguredBedrockRegion,
738
- hasConfiguredBedrockProfile,
739
- mapThinkingLevelToEffort,
740
- resolveSimpleBedrockOptions,
741
- shouldUseExplicitBedrockEndpoint
742
- };
743
- if (process.env.VITEST === "true") Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockStreamTestApi"), testing);
744
797
  //#endregion
745
798
  export { streamSimpleBedrock };
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "contracts": {
16
- "memoryEmbeddingProviders": ["bedrock"]
16
+ "embeddingProviders": ["bedrock"]
17
17
  },
18
18
  "configSchema": {
19
19
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/amazon-bedrock-provider",
3
- "version": "2026.7.2-beta.7",
3
+ "version": "2026.8.1-beta.3",
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.2-beta.7"
28
+ "pluginApi": ">=2026.8.1-beta.3"
29
29
  },
30
30
  "build": {
31
- "openclawVersion": "2026.7.2-beta.7",
31
+ "openclawVersion": "2026.8.1-beta.3",
32
32
  "bundledDist": false
33
33
  },
34
34
  "release": {
@@ -45,7 +45,7 @@
45
45
  "README.md"
46
46
  ],
47
47
  "peerDependencies": {
48
- "openclaw": ">=2026.7.2-beta.7"
48
+ "openclaw": ">=2026.8.1-beta.3"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "openclaw": {