@draht/ai 2026.4.26 → 2026.6.11

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.
Files changed (198) hide show
  1. package/dist/api-registry.d.ts +1 -1
  2. package/dist/api-registry.d.ts.map +1 -1
  3. package/dist/api-registry.js.map +1 -1
  4. package/dist/bedrock-provider.d.ts +2 -2
  5. package/dist/bedrock-provider.d.ts.map +1 -1
  6. package/dist/bedrock-provider.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +14 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/env-api-keys.d.ts +10 -1
  11. package/dist/env-api-keys.d.ts.map +1 -1
  12. package/dist/env-api-keys.js +110 -36
  13. package/dist/env-api-keys.js.map +1 -1
  14. package/dist/image-models.d.ts +10 -0
  15. package/dist/image-models.d.ts.map +1 -0
  16. package/dist/image-models.generated.d.ts +485 -0
  17. package/dist/image-models.generated.d.ts.map +1 -0
  18. package/dist/image-models.generated.js +487 -0
  19. package/dist/image-models.generated.js.map +1 -0
  20. package/dist/image-models.js +23 -0
  21. package/dist/image-models.js.map +1 -0
  22. package/dist/images-api-registry.d.ts +14 -0
  23. package/dist/images-api-registry.d.ts.map +1 -0
  24. package/dist/images-api-registry.js +22 -0
  25. package/dist/images-api-registry.js.map +1 -0
  26. package/dist/images.d.ts +4 -0
  27. package/dist/images.d.ts.map +1 -0
  28. package/dist/images.js +14 -0
  29. package/dist/images.js.map +1 -0
  30. package/dist/index.d.ts +31 -25
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +7 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/models.d.ts +5 -8
  35. package/dist/models.d.ts.map +1 -1
  36. package/dist/models.generated.d.ts +5197 -1721
  37. package/dist/models.generated.d.ts.map +1 -1
  38. package/dist/models.generated.js +7156 -5016
  39. package/dist/models.generated.js.map +1 -1
  40. package/dist/models.js +33 -6
  41. package/dist/models.js.map +1 -1
  42. package/dist/oauth.d.ts +1 -1
  43. package/dist/oauth.d.ts.map +1 -1
  44. package/dist/oauth.js.map +1 -1
  45. package/dist/providers/amazon-bedrock.d.ts +19 -1
  46. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  47. package/dist/providers/amazon-bedrock.js +278 -89
  48. package/dist/providers/amazon-bedrock.js.map +1 -1
  49. package/dist/providers/anthropic.d.ts +37 -6
  50. package/dist/providers/anthropic.d.ts.map +1 -1
  51. package/dist/providers/anthropic.js +300 -114
  52. package/dist/providers/anthropic.js.map +1 -1
  53. package/dist/providers/azure-openai-responses.d.ts +1 -1
  54. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  55. package/dist/providers/azure-openai-responses.js +68 -21
  56. package/dist/providers/azure-openai-responses.js.map +1 -1
  57. package/dist/providers/cloudflare.d.ts +13 -0
  58. package/dist/providers/cloudflare.d.ts.map +1 -0
  59. package/dist/providers/cloudflare.js +26 -0
  60. package/dist/providers/cloudflare.js.map +1 -0
  61. package/dist/providers/faux.d.ts +1 -1
  62. package/dist/providers/faux.d.ts.map +1 -1
  63. package/dist/providers/faux.js +1 -0
  64. package/dist/providers/faux.js.map +1 -1
  65. package/dist/providers/github-copilot-headers.d.ts +1 -1
  66. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  67. package/dist/providers/github-copilot-headers.js.map +1 -1
  68. package/dist/providers/google-shared.d.ts +8 -3
  69. package/dist/providers/google-shared.d.ts.map +1 -1
  70. package/dist/providers/google-shared.js +34 -17
  71. package/dist/providers/google-shared.js.map +1 -1
  72. package/dist/providers/google-vertex.d.ts +2 -2
  73. package/dist/providers/google-vertex.d.ts.map +1 -1
  74. package/dist/providers/google-vertex.js +45 -18
  75. package/dist/providers/google-vertex.js.map +1 -1
  76. package/dist/providers/google.d.ts +2 -2
  77. package/dist/providers/google.d.ts.map +1 -1
  78. package/dist/providers/google.js +9 -6
  79. package/dist/providers/google.js.map +1 -1
  80. package/dist/providers/images/openrouter.d.ts +3 -0
  81. package/dist/providers/images/openrouter.d.ts.map +1 -0
  82. package/dist/providers/images/openrouter.js +128 -0
  83. package/dist/providers/images/openrouter.js.map +1 -0
  84. package/dist/providers/images/register-builtins.d.ts +4 -0
  85. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  86. package/dist/providers/images/register-builtins.js +34 -0
  87. package/dist/providers/images/register-builtins.js.map +1 -0
  88. package/dist/providers/mistral.d.ts +4 -1
  89. package/dist/providers/mistral.d.ts.map +1 -1
  90. package/dist/providers/mistral.js +43 -10
  91. package/dist/providers/mistral.js.map +1 -1
  92. package/dist/providers/openai-codex-responses.d.ts +22 -1
  93. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  94. package/dist/providers/openai-codex-responses.js +542 -111
  95. package/dist/providers/openai-codex-responses.js.map +1 -1
  96. package/dist/providers/openai-completions.d.ts +6 -2
  97. package/dist/providers/openai-completions.d.ts.map +1 -1
  98. package/dist/providers/openai-completions.js +446 -227
  99. package/dist/providers/openai-completions.js.map +1 -1
  100. package/dist/providers/openai-prompt-cache.d.ts +3 -0
  101. package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
  102. package/dist/providers/openai-prompt-cache.js +10 -0
  103. package/dist/providers/openai-prompt-cache.js.map +1 -0
  104. package/dist/providers/openai-responses-shared.d.ts +3 -2
  105. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  106. package/dist/providers/openai-responses-shared.js +41 -15
  107. package/dist/providers/openai-responses-shared.js.map +1 -1
  108. package/dist/providers/openai-responses.d.ts +1 -1
  109. package/dist/providers/openai-responses.d.ts.map +1 -1
  110. package/dist/providers/openai-responses.js +85 -40
  111. package/dist/providers/openai-responses.js.map +1 -1
  112. package/dist/providers/register-builtins.d.ts +10 -13
  113. package/dist/providers/register-builtins.d.ts.map +1 -1
  114. package/dist/providers/register-builtins.js +13 -20
  115. package/dist/providers/register-builtins.js.map +1 -1
  116. package/dist/providers/simple-options.d.ts +2 -2
  117. package/dist/providers/simple-options.d.ts.map +1 -1
  118. package/dist/providers/simple-options.js +8 -2
  119. package/dist/providers/simple-options.js.map +1 -1
  120. package/dist/providers/transform-messages.d.ts +1 -1
  121. package/dist/providers/transform-messages.d.ts.map +1 -1
  122. package/dist/providers/transform-messages.js +63 -34
  123. package/dist/providers/transform-messages.js.map +1 -1
  124. package/dist/session-resources.d.ts +4 -0
  125. package/dist/session-resources.d.ts.map +1 -0
  126. package/dist/session-resources.js +22 -0
  127. package/dist/session-resources.js.map +1 -0
  128. package/dist/stream.d.ts +3 -3
  129. package/dist/stream.d.ts.map +1 -1
  130. package/dist/stream.js +14 -2
  131. package/dist/stream.js.map +1 -1
  132. package/dist/types.d.ts +177 -14
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/types.js.map +1 -1
  135. package/dist/utils/abort-signals.d.ts +6 -0
  136. package/dist/utils/abort-signals.d.ts.map +1 -0
  137. package/dist/utils/abort-signals.js +34 -0
  138. package/dist/utils/abort-signals.js.map +1 -0
  139. package/dist/utils/diagnostics.d.ts +19 -0
  140. package/dist/utils/diagnostics.d.ts.map +1 -0
  141. package/dist/utils/diagnostics.js +25 -0
  142. package/dist/utils/diagnostics.js.map +1 -0
  143. package/dist/utils/event-stream.d.ts +3 -3
  144. package/dist/utils/event-stream.d.ts.map +1 -1
  145. package/dist/utils/event-stream.js +2 -2
  146. package/dist/utils/event-stream.js.map +1 -1
  147. package/dist/utils/headers.d.ts +2 -0
  148. package/dist/utils/headers.d.ts.map +1 -0
  149. package/dist/utils/headers.js +8 -0
  150. package/dist/utils/headers.js.map +1 -0
  151. package/dist/utils/json-parse.d.ts +8 -1
  152. package/dist/utils/json-parse.d.ts.map +1 -1
  153. package/dist/utils/json-parse.js +89 -5
  154. package/dist/utils/json-parse.js.map +1 -1
  155. package/dist/utils/node-http-proxy.d.ts +10 -0
  156. package/dist/utils/node-http-proxy.d.ts.map +1 -0
  157. package/dist/utils/node-http-proxy.js +97 -0
  158. package/dist/utils/node-http-proxy.js.map +1 -0
  159. package/dist/utils/oauth/anthropic.d.ts +1 -1
  160. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  161. package/dist/utils/oauth/anthropic.js +1 -1
  162. package/dist/utils/oauth/anthropic.js.map +1 -1
  163. package/dist/utils/oauth/device-code.d.ts +21 -0
  164. package/dist/utils/oauth/device-code.d.ts.map +1 -0
  165. package/dist/utils/oauth/device-code.js +56 -0
  166. package/dist/utils/oauth/device-code.js.map +1 -0
  167. package/dist/utils/oauth/github-copilot.d.ts +3 -3
  168. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  169. package/dist/utils/oauth/github-copilot.js +58 -70
  170. package/dist/utils/oauth/github-copilot.js.map +1 -1
  171. package/dist/utils/oauth/index.d.ts +8 -11
  172. package/dist/utils/oauth/index.d.ts.map +1 -1
  173. package/dist/utils/oauth/index.js +2 -11
  174. package/dist/utils/oauth/index.js.map +1 -1
  175. package/dist/utils/oauth/openai-codex.d.ts +11 -2
  176. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  177. package/dist/utils/oauth/openai-codex.js +187 -73
  178. package/dist/utils/oauth/openai-codex.js.map +1 -1
  179. package/dist/utils/oauth/types.d.ts +18 -1
  180. package/dist/utils/oauth/types.d.ts.map +1 -1
  181. package/dist/utils/oauth/types.js.map +1 -1
  182. package/dist/utils/overflow.d.ts +7 -3
  183. package/dist/utils/overflow.d.ts.map +1 -1
  184. package/dist/utils/overflow.js +25 -3
  185. package/dist/utils/overflow.js.map +1 -1
  186. package/dist/utils/typebox-helpers.d.ts +1 -1
  187. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  188. package/dist/utils/typebox-helpers.js +1 -1
  189. package/dist/utils/typebox-helpers.js.map +1 -1
  190. package/dist/utils/validation.d.ts +1 -1
  191. package/dist/utils/validation.d.ts.map +1 -1
  192. package/dist/utils/validation.js +242 -41
  193. package/dist/utils/validation.js.map +1 -1
  194. package/package.json +15 -16
  195. package/dist/providers/google-gemini-cli.d.ts +0 -74
  196. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  197. package/dist/providers/google-gemini-cli.js +0 -776
  198. package/dist/providers/google-gemini-cli.js.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { BedrockRuntimeClient, BedrockRuntimeServiceException, StopReason as BedrockStopReason, CachePointType, CacheTTL, ConversationRole, ConverseStreamCommand, ImageFormat, ToolResultStatus, } from "@aws-sdk/client-bedrock-runtime";
2
- import { calculateCost, supportsMax } from "../models.js";
2
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
3
+ import { calculateCost } from "../models.js";
3
4
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
4
5
  import { parseStreamingJson } from "../utils/json-parse.js";
6
+ import { createHttpProxyAgentsForTarget } from "../utils/node-http-proxy.js";
5
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
6
8
  import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
7
9
  import { transformMessages } from "./transform-messages.js";
10
+ const EMPTY_TEXT_PLACEHOLDER = "<empty>";
8
11
  export const streamBedrock = (model, context, options = {}) => {
9
12
  const stream = new AssistantMessageEventStream();
10
13
  (async () => {
@@ -29,16 +32,35 @@ export const streamBedrock = (model, context, options = {}) => {
29
32
  const config = {
30
33
  profile: options.profile,
31
34
  };
35
+ const configuredRegion = getConfiguredBedrockRegion(options);
36
+ const hasConfiguredProfile = hasConfiguredBedrockProfile();
37
+ const endpointRegion = getStandardBedrockEndpointRegion(model.baseUrl);
38
+ const useExplicitEndpoint = shouldUseExplicitBedrockEndpoint(model.baseUrl, configuredRegion, hasConfiguredProfile);
39
+ // Only pin standard AWS Bedrock runtime endpoints when no region/profile is configured.
40
+ // This preserves custom endpoints (VPC/proxy) from #3402 without forcing built-in
41
+ // catalog defaults such as us-east-1 to override AWS_REGION/AWS_PROFILE.
42
+ if (useExplicitEndpoint) {
43
+ config.endpoint = model.baseUrl;
44
+ }
45
+ // Resolve bearer token for Bedrock API key auth.
46
+ const bearerToken = options.bearerToken || process.env.AWS_BEARER_TOKEN_BEDROCK || undefined;
47
+ const useBearerToken = bearerToken !== undefined && process.env.AWS_BEDROCK_SKIP_AUTH !== "1";
32
48
  // in Node.js/Bun environment only
33
49
  if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
34
- // Region resolution: explicit option > env vars > SDK default chain.
35
- // When AWS_PROFILE is set, we leave region undefined so the SDK can
36
- // resovle it from aws profile configs. Otherwise fall back to us-east-1.
37
- const explicitRegion = options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
38
- if (explicitRegion) {
39
- config.region = explicitRegion;
50
+ // Region resolution: ARN-embedded > explicit option > env vars > SDK default chain.
51
+ // When the model ID is an inference profile ARN, extract the region from it.
52
+ // This avoids conflicts with AWS_REGION set for other services.
53
+ const arnRegionMatch = model.id.match(/^arn:aws(?:-[a-z0-9-]+)?:bedrock:([a-z0-9-]+):/);
54
+ if (arnRegionMatch) {
55
+ config.region = arnRegionMatch[1];
56
+ }
57
+ else if (configuredRegion) {
58
+ config.region = configuredRegion;
40
59
  }
41
- else if (!process.env.AWS_PROFILE) {
60
+ else if (endpointRegion && useExplicitEndpoint) {
61
+ config.region = endpointRegion;
62
+ }
63
+ else if (!hasConfiguredProfile) {
42
64
  config.region = "us-east-1";
43
65
  }
44
66
  // Support proxies that don't need authentication
@@ -48,42 +70,43 @@ export const streamBedrock = (model, context, options = {}) => {
48
70
  secretAccessKey: "dummy-secret-key",
49
71
  };
50
72
  }
51
- if (process.env.HTTP_PROXY ||
52
- process.env.HTTPS_PROXY ||
53
- process.env.NO_PROXY ||
54
- process.env.http_proxy ||
55
- process.env.https_proxy ||
56
- process.env.no_proxy) {
57
- const nodeHttpHandler = await import("@smithy/node-http-handler");
58
- const proxyAgent = await import("proxy-agent");
59
- const agent = new proxyAgent.ProxyAgent();
73
+ const proxyAgents = createHttpProxyAgentsForTarget(model.baseUrl);
74
+ if (proxyAgents) {
60
75
  // Bedrock runtime uses NodeHttp2Handler by default since v3.798.0, which is based
61
76
  // on `http2` module and has no support for http agent.
62
- // Use NodeHttpHandler to support http agent.
63
- config.requestHandler = new nodeHttpHandler.NodeHttpHandler({
64
- httpAgent: agent,
65
- httpsAgent: agent,
66
- });
77
+ // Use NodeHttpHandler to support HTTP(S) proxy agents.
78
+ config.requestHandler = new NodeHttpHandler(proxyAgents);
67
79
  }
68
80
  else if (process.env.AWS_BEDROCK_FORCE_HTTP1 === "1") {
69
81
  // Some custom endpoints require HTTP/1.1 instead of HTTP/2
70
- const nodeHttpHandler = await import("@smithy/node-http-handler");
71
- config.requestHandler = new nodeHttpHandler.NodeHttpHandler();
82
+ config.requestHandler = new NodeHttpHandler();
72
83
  }
73
84
  }
74
85
  else {
75
86
  // Non-Node environment (browser): fall back to us-east-1 since
76
87
  // there's no config file resolution available.
77
- config.region = options.region || "us-east-1";
88
+ config.region =
89
+ configuredRegion || (endpointRegion && useExplicitEndpoint ? endpointRegion : undefined) || "us-east-1";
90
+ }
91
+ if (useBearerToken) {
92
+ config.token = { token: bearerToken };
93
+ config.authSchemePreference = ["httpBearerAuth"];
78
94
  }
79
95
  try {
80
96
  const client = new BedrockRuntimeClient(config);
97
+ if (options.headers && Object.keys(options.headers).length > 0) {
98
+ addCustomHeadersMiddleware(client, options.headers);
99
+ }
81
100
  const cacheRetention = resolveCacheRetention(options.cacheRetention);
101
+ const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
82
102
  let commandInput = {
83
103
  modelId: model.id,
84
104
  messages: convertMessages(context, model, cacheRetention),
85
105
  system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
86
- inferenceConfig: { maxTokens: options.maxTokens, temperature: options.temperature },
106
+ inferenceConfig: {
107
+ ...(inferenceMaxTokens !== undefined && { maxTokens: inferenceMaxTokens }),
108
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
109
+ },
87
110
  toolConfig: convertToolConfig(context.tools, options.toolChoice),
88
111
  additionalModelRequestFields: buildAdditionalModelRequestFields(model, options),
89
112
  ...(options.requestMetadata !== undefined && { requestMetadata: options.requestMetadata }),
@@ -94,6 +117,13 @@ export const streamBedrock = (model, context, options = {}) => {
94
117
  }
95
118
  const command = new ConverseStreamCommand(commandInput);
96
119
  const response = await client.send(command, { abortSignal: options.signal });
120
+ if (response.$metadata.httpStatusCode !== undefined) {
121
+ const responseHeaders = {};
122
+ if (response.$metadata.requestId) {
123
+ responseHeaders["x-amzn-requestid"] = response.$metadata.requestId;
124
+ }
125
+ await options?.onResponse?.({ status: response.$metadata.httpStatusCode, headers: responseHeaders }, model);
126
+ }
97
127
  for await (const item of response.stream) {
98
128
  if (item.messageStart) {
99
129
  if (item.messageStart.role !== ConversationRole.ASSISTANT) {
@@ -144,6 +174,7 @@ export const streamBedrock = (model, context, options = {}) => {
144
174
  catch (error) {
145
175
  for (const block of output.content) {
146
176
  delete block.index;
177
+ // partialJson is only a streaming scratch buffer; never persist it.
147
178
  delete block.partialJson;
148
179
  }
149
180
  output.stopReason = options.signal?.aborted ? "aborted" : "error";
@@ -167,6 +198,12 @@ const BEDROCK_ERROR_PREFIXES = {
167
198
  ThrottlingException: "Throttling error",
168
199
  ServiceUnavailableException: "Service unavailable",
169
200
  };
201
+ /**
202
+ * Some models reject the account/profile's configured Bedrock data retention mode
203
+ * (e.g. "data retention mode 'default' is not available for this model"). Point
204
+ * users at the AWS docs explaining how to configure a supported mode.
205
+ */
206
+ const BEDROCK_DATA_RETENTION_DOCS_URL = "https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html";
170
207
  /**
171
208
  * Format a Bedrock error with a human-readable prefix.
172
209
  * AWS SDK exceptions (both from `client.send()` and from stream event items)
@@ -176,26 +213,64 @@ const BEDROCK_ERROR_PREFIXES = {
176
213
  */
177
214
  function formatBedrockError(error) {
178
215
  const message = error instanceof Error ? error.message : JSON.stringify(error);
216
+ const dataRetentionHint = /data retention mode/i.test(message)
217
+ ? ` See ${BEDROCK_DATA_RETENTION_DOCS_URL} for supported data retention modes.`
218
+ : "";
179
219
  if (error instanceof BedrockRuntimeServiceException) {
180
220
  const prefix = BEDROCK_ERROR_PREFIXES[error.name] ?? error.name;
181
- return `${prefix}: ${message}`;
221
+ return `${prefix}: ${message}${dataRetentionHint}`;
182
222
  }
183
- return message;
223
+ return `${message}${dataRetentionHint}`;
224
+ }
225
+ /**
226
+ * Header keys that must never be overwritten by caller-supplied headers.
227
+ * `host` and `x-amz-*` participate in the SigV4 canonical request; `authorization`
228
+ * is owned by SigV4 or the bearer-token path (config.token + authSchemePreference).
229
+ * Compared case-insensitively (caller key is lower-cased before lookup).
230
+ */
231
+ const RESERVED_HEADER_EXACT = new Set(["authorization", "host"]);
232
+ function isReservedHeader(key) {
233
+ const lower = key.toLowerCase();
234
+ return lower.startsWith("x-amz-") || RESERVED_HEADER_EXACT.has(lower);
235
+ }
236
+ /**
237
+ * Attach caller-supplied headers to the outgoing Bedrock request via a Smithy
238
+ * `build`-step middleware. The `build` step runs after request serialisation but
239
+ * before SigV4 signing, so injected headers are covered by the signature. Reserved
240
+ * SigV4 / auth headers (`x-amz-*`, `authorization`, `host`) are silently skipped;
241
+ * all other caller headers override any existing same-named header on the request.
242
+ */
243
+ function addCustomHeadersMiddleware(client, headers) {
244
+ const middleware = (next) => async (args) => {
245
+ const request = args.request;
246
+ if (request && typeof request === "object" && "headers" in request) {
247
+ const requestHeaders = request.headers;
248
+ for (const [key, value] of Object.entries(headers)) {
249
+ if (!isReservedHeader(key)) {
250
+ requestHeaders[key] = value;
251
+ }
252
+ }
253
+ }
254
+ return next(args);
255
+ };
256
+ client.middlewareStack.add(middleware, { step: "build", name: "pi-ai-custom-headers", priority: "low" });
184
257
  }
185
258
  export const streamSimpleBedrock = (model, context, options) => {
186
259
  const base = buildBaseOptions(model, options, undefined);
187
260
  if (!options?.reasoning) {
188
261
  return streamBedrock(model, context, { ...base, reasoning: undefined });
189
262
  }
190
- if (model.id.includes("anthropic.claude") || model.id.includes("anthropic/claude")) {
191
- if (supportsAdaptiveThinking(model.id)) {
263
+ if (isAnthropicClaudeModel(model)) {
264
+ if (supportsAdaptiveThinking(model.id, model.name)) {
192
265
  return streamBedrock(model, context, {
193
266
  ...base,
194
267
  reasoning: options.reasoning,
195
268
  thinkingBudgets: options.thinkingBudgets,
196
269
  });
197
270
  }
198
- const adjusted = adjustMaxTokensForThinking(base.maxTokens || 0, model.maxTokens, options.reasoning, options.thinkingBudgets);
271
+ // Undefined means the caller did not request an output cap; let the helper use the model cap.
272
+ // Do not coerce to 0 here, or the thinking budget would become the entire maxTokens value.
273
+ const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets);
199
274
  return streamBedrock(model, context, {
200
275
  ...base,
201
276
  maxTokens: adjusted.maxTokens,
@@ -304,22 +379,43 @@ function handleContentBlockStop(event, blocks, output, stream) {
304
379
  break;
305
380
  case "toolCall":
306
381
  block.arguments = parseStreamingJson(block.partialJson);
382
+ // Finalize in-place and strip the scratch buffer so replay only
383
+ // carries parsed arguments.
307
384
  delete block.partialJson;
308
385
  stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: output });
309
386
  break;
310
387
  }
311
388
  }
312
389
  /**
313
- * Check if the model supports adaptive thinking (Opus 4.6 and Sonnet 4.6).
390
+ * Check if the model supports adaptive thinking (Opus 4.6+, Sonnet 4.6).
391
+ * Checks both model ID and model name to support application inference profiles
392
+ * whose ARNs don't contain the model name.
314
393
  */
315
- function supportsAdaptiveThinking(modelId) {
316
- return (modelId.includes("opus-4-6") ||
317
- modelId.includes("opus-4.6") ||
318
- modelId.includes("sonnet-4-6") ||
319
- modelId.includes("sonnet-4.6"));
394
+ function getModelMatchCandidates(modelId, modelName) {
395
+ const values = modelName ? [modelId, modelName] : [modelId];
396
+ return values.flatMap((value) => {
397
+ const lower = value.toLowerCase();
398
+ return [lower, lower.replace(/[\s_.:]+/g, "-")];
399
+ });
400
+ }
401
+ function supportsAdaptiveThinking(modelId, modelName) {
402
+ const candidates = getModelMatchCandidates(modelId, modelName);
403
+ return candidates.some((s) => s.includes("opus-4-6") ||
404
+ s.includes("opus-4-7") ||
405
+ s.includes("opus-4-8") ||
406
+ s.includes("sonnet-4-6") ||
407
+ s.includes("fable-5"));
320
408
  }
321
- function mapThinkingLevelToEffort(level, modelId) {
322
- const supportsMaxEffort = supportsMax({ id: modelId });
409
+ function supportsNativeXhighEffort(model) {
410
+ const candidates = getModelMatchCandidates(model.id, model.name);
411
+ return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("fable-5"));
412
+ }
413
+ function mapThinkingLevelToEffort(model, level) {
414
+ if (level === "xhigh" && supportsNativeXhighEffort(model))
415
+ return "xhigh";
416
+ const mapped = level ? model.thinkingLevelMap?.[level] : undefined;
417
+ if (typeof mapped === "string")
418
+ return mapped;
323
419
  switch (level) {
324
420
  case "minimal":
325
421
  case "low":
@@ -328,10 +424,6 @@ function mapThinkingLevelToEffort(level, modelId) {
328
424
  return "medium";
329
425
  case "high":
330
426
  return "high";
331
- case "xhigh":
332
- return supportsMaxEffort ? "max" : "high";
333
- case "max":
334
- return supportsMaxEffort ? "max" : "high";
335
427
  default:
336
428
  return "high";
337
429
  }
@@ -349,6 +441,20 @@ function resolveCacheRetention(cacheRetention) {
349
441
  }
350
442
  return "short";
351
443
  }
444
+ /**
445
+ * Check if the model is an Anthropic Claude model on Bedrock.
446
+ * Checks both model ID and model name to support application inference profiles
447
+ * whose ARNs don't contain the model name.
448
+ */
449
+ function isAnthropicClaudeModel(model) {
450
+ const id = model.id.toLowerCase();
451
+ const name = model.name?.toLowerCase() ?? "";
452
+ return (id.includes("anthropic.claude") ||
453
+ id.includes("anthropic/claude") ||
454
+ name.includes("anthropic.claude") ||
455
+ name.includes("anthropic/claude") ||
456
+ name.includes("claude"));
457
+ }
352
458
  /**
353
459
  * Check if the model supports prompt caching.
354
460
  * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, Claude 4.x models
@@ -357,12 +463,14 @@ function resolveCacheRetention(cacheRetention) {
357
463
  * contains the model name, so we can decide locally.
358
464
  *
359
465
  * For application inference profiles (whose ARNs don't contain the model name),
360
- * set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
361
- * have automatic caching and don't need explicit cache points.
466
+ * also checks model.name which is user-controlled via models.json or registerProvider.
467
+ * As a last resort, set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points.
468
+ * Amazon Nova models have automatic caching and don't need explicit cache points.
362
469
  */
363
470
  function supportsPromptCaching(model) {
364
- const id = model.id.toLowerCase();
365
- if (!id.includes("claude")) {
471
+ const candidates = getModelMatchCandidates(model.id, model.name);
472
+ const hasClaudeRef = candidates.some((s) => s.includes("claude"));
473
+ if (!hasClaudeRef) {
366
474
  // Application inference profiles don't contain the model name in the ARN.
367
475
  // Allow users to force cache points via environment variable.
368
476
  if (typeof process !== "undefined" && process.env.AWS_BEDROCK_FORCE_CACHE === "1")
@@ -370,13 +478,13 @@ function supportsPromptCaching(model) {
370
478
  return false;
371
479
  }
372
480
  // Claude 4.x models (opus-4, sonnet-4, haiku-4)
373
- if (id.includes("-4-") || id.includes("-4."))
481
+ if (candidates.some((s) => s.includes("-4-")))
374
482
  return true;
375
483
  // Claude 3.7 Sonnet
376
- if (id.includes("claude-3-7-sonnet"))
484
+ if (candidates.some((s) => s.includes("claude-3-7-sonnet")))
377
485
  return true;
378
486
  // Claude 3.5 Haiku
379
- if (id.includes("claude-3-5-haiku"))
487
+ if (candidates.some((s) => s.includes("claude-3-5-haiku")))
380
488
  return true;
381
489
  return false;
382
490
  }
@@ -385,10 +493,11 @@ function supportsPromptCaching(model) {
385
493
  * Only Anthropic Claude models support the signature field.
386
494
  * Other models (OpenAI, Qwen, Minimax, Moonshot, etc.) reject it with:
387
495
  * "This model doesn't support the reasoningContent.reasoningText.signature field"
496
+ *
497
+ * Checks both model ID and model name to support application inference profiles.
388
498
  */
389
499
  function supportsThinkingSignature(model) {
390
- const id = model.id.toLowerCase();
391
- return id.includes("anthropic.claude") || id.includes("anthropic/claude");
500
+ return isAnthropicClaudeModel(model);
392
501
  }
393
502
  function buildSystemPrompt(systemPrompt, model, cacheRetention) {
394
503
  if (!systemPrompt)
@@ -406,29 +515,65 @@ function normalizeToolCallId(id) {
406
515
  const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
407
516
  return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
408
517
  }
518
+ function createNonBlankTextBlock(text) {
519
+ const sanitized = sanitizeSurrogates(text);
520
+ return sanitized.trim().length === 0 ? undefined : { text: sanitized };
521
+ }
522
+ function createRequiredTextBlock(text) {
523
+ return createNonBlankTextBlock(text) ?? { text: EMPTY_TEXT_PLACEHOLDER };
524
+ }
525
+ function convertToolResultContent(content) {
526
+ const result = [];
527
+ for (const c of content) {
528
+ if (c.type === "image") {
529
+ result.push({ image: createImageBlock(c.mimeType, c.data) });
530
+ }
531
+ else {
532
+ const textBlock = createNonBlankTextBlock(c.text);
533
+ if (textBlock)
534
+ result.push(textBlock);
535
+ }
536
+ }
537
+ if (result.length === 0)
538
+ result.push({ text: EMPTY_TEXT_PLACEHOLDER });
539
+ return result;
540
+ }
409
541
  function convertMessages(context, model, cacheRetention) {
410
542
  const result = [];
411
543
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
412
544
  for (let i = 0; i < transformedMessages.length; i++) {
413
545
  const m = transformedMessages[i];
414
546
  switch (m.role) {
415
- case "user":
547
+ case "user": {
548
+ const content = [];
549
+ if (typeof m.content === "string") {
550
+ content.push(createRequiredTextBlock(m.content));
551
+ }
552
+ else {
553
+ for (const c of m.content) {
554
+ switch (c.type) {
555
+ case "text": {
556
+ const textBlock = createNonBlankTextBlock(c.text);
557
+ if (textBlock)
558
+ content.push(textBlock);
559
+ break;
560
+ }
561
+ case "image":
562
+ content.push({ image: createImageBlock(c.mimeType, c.data) });
563
+ break;
564
+ default:
565
+ continue;
566
+ }
567
+ }
568
+ if (content.length === 0)
569
+ content.push({ text: EMPTY_TEXT_PLACEHOLDER });
570
+ }
416
571
  result.push({
417
572
  role: ConversationRole.USER,
418
- content: typeof m.content === "string"
419
- ? [{ text: sanitizeSurrogates(m.content) }]
420
- : m.content.map((c) => {
421
- switch (c.type) {
422
- case "text":
423
- return { text: sanitizeSurrogates(c.text) };
424
- case "image":
425
- return { image: createImageBlock(c.mimeType, c.data) };
426
- default:
427
- throw new Error("Unknown user content type");
428
- }
429
- }),
573
+ content,
430
574
  });
431
575
  break;
576
+ }
432
577
  case "assistant": {
433
578
  // Skip assistant messages with empty content (e.g., from aborted requests)
434
579
  // Bedrock rejects messages with empty content arrays
@@ -438,20 +583,23 @@ function convertMessages(context, model, cacheRetention) {
438
583
  const contentBlocks = [];
439
584
  for (const c of m.content) {
440
585
  switch (c.type) {
441
- case "text":
586
+ case "text": {
442
587
  // Skip empty text blocks
443
- if (c.text.trim().length === 0)
588
+ const textBlock = createNonBlankTextBlock(c.text);
589
+ if (!textBlock)
444
590
  continue;
445
- contentBlocks.push({ text: sanitizeSurrogates(c.text) });
591
+ contentBlocks.push(textBlock);
446
592
  break;
593
+ }
447
594
  case "toolCall":
448
595
  contentBlocks.push({
449
596
  toolUse: { toolUseId: c.id, name: c.name, input: c.arguments },
450
597
  });
451
598
  break;
452
- case "thinking":
599
+ case "thinking": {
453
600
  // Skip empty thinking blocks
454
- if (c.thinking.trim().length === 0)
601
+ const thinking = sanitizeSurrogates(c.thinking);
602
+ if (thinking.trim().length === 0)
455
603
  continue;
456
604
  // Only Anthropic models support the signature field in reasoningText.
457
605
  // For other models, we omit the signature to avoid errors like:
@@ -461,13 +609,13 @@ function convertMessages(context, model, cacheRetention) {
461
609
  // persisted message lacks a signature, Bedrock rejects the replayed
462
610
  // reasoning block. Fall back to plain text, matching Anthropic.
463
611
  if (!c.thinkingSignature || c.thinkingSignature.trim().length === 0) {
464
- contentBlocks.push({ text: sanitizeSurrogates(c.thinking) });
612
+ contentBlocks.push({ text: thinking });
465
613
  }
466
614
  else {
467
615
  contentBlocks.push({
468
616
  reasoningContent: {
469
617
  reasoningText: {
470
- text: sanitizeSurrogates(c.thinking),
618
+ text: thinking,
471
619
  signature: c.thinkingSignature,
472
620
  },
473
621
  },
@@ -477,13 +625,14 @@ function convertMessages(context, model, cacheRetention) {
477
625
  else {
478
626
  contentBlocks.push({
479
627
  reasoningContent: {
480
- reasoningText: { text: sanitizeSurrogates(c.thinking) },
628
+ reasoningText: { text: thinking },
481
629
  },
482
630
  });
483
631
  }
484
632
  break;
633
+ }
485
634
  default:
486
- throw new Error("Unknown assistant content type");
635
+ continue;
487
636
  }
488
637
  }
489
638
  // Skip if all content blocks were filtered out
@@ -504,9 +653,7 @@ function convertMessages(context, model, cacheRetention) {
504
653
  toolResults.push({
505
654
  toolResult: {
506
655
  toolUseId: m.toolCallId,
507
- content: m.content.map((c) => c.type === "image"
508
- ? { image: createImageBlock(c.mimeType, c.data) }
509
- : { text: sanitizeSurrogates(c.text) }),
656
+ content: convertToolResultContent(m.content),
510
657
  status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
511
658
  },
512
659
  });
@@ -517,9 +664,7 @@ function convertMessages(context, model, cacheRetention) {
517
664
  toolResults.push({
518
665
  toolResult: {
519
666
  toolUseId: nextMsg.toolCallId,
520
- content: nextMsg.content.map((c) => c.type === "image"
521
- ? { image: createImageBlock(c.mimeType, c.data) }
522
- : { text: sanitizeSurrogates(c.text) }),
667
+ content: convertToolResultContent(nextMsg.content),
523
668
  status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
524
669
  },
525
670
  });
@@ -534,7 +679,7 @@ function convertMessages(context, model, cacheRetention) {
534
679
  break;
535
680
  }
536
681
  default:
537
- throw new Error("Unknown message role");
682
+ continue;
538
683
  }
539
684
  }
540
685
  // Add cache point to the last user message for supported Claude models when caching is enabled
@@ -590,15 +735,58 @@ function mapStopReason(reason) {
590
735
  return "error";
591
736
  }
592
737
  }
738
+ function getConfiguredBedrockRegion(options) {
739
+ if (typeof process === "undefined") {
740
+ return options.region;
741
+ }
742
+ return options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || undefined;
743
+ }
744
+ function hasConfiguredBedrockProfile() {
745
+ if (typeof process === "undefined") {
746
+ return false;
747
+ }
748
+ return Boolean(process.env.AWS_PROFILE);
749
+ }
750
+ function getStandardBedrockEndpointRegion(baseUrl) {
751
+ if (!baseUrl) {
752
+ return undefined;
753
+ }
754
+ try {
755
+ const { hostname } = new URL(baseUrl);
756
+ const match = hostname.toLowerCase().match(/^bedrock-runtime(?:-fips)?\.([a-z0-9-]+)\.amazonaws\.com(?:\.cn)?$/);
757
+ return match?.[1];
758
+ }
759
+ catch {
760
+ return undefined;
761
+ }
762
+ }
763
+ function shouldUseExplicitBedrockEndpoint(baseUrl, configuredRegion, hasConfiguredProfile) {
764
+ const endpointRegion = getStandardBedrockEndpointRegion(baseUrl);
765
+ if (!endpointRegion) {
766
+ return true;
767
+ }
768
+ return !configuredRegion && !hasConfiguredProfile;
769
+ }
770
+ function isGovCloudBedrockTarget(model, options) {
771
+ const region = getConfiguredBedrockRegion(options);
772
+ if (region?.toLowerCase().startsWith("us-gov-")) {
773
+ return true;
774
+ }
775
+ const modelId = model.id.toLowerCase();
776
+ return modelId.startsWith("us-gov.") || modelId.startsWith("arn:aws-us-gov:");
777
+ }
593
778
  function buildAdditionalModelRequestFields(model, options) {
594
779
  if (!options.reasoning || !model.reasoning) {
595
780
  return undefined;
596
781
  }
597
- if (model.id.includes("anthropic.claude") || model.id.includes("anthropic/claude")) {
598
- const result = supportsAdaptiveThinking(model.id)
782
+ if (isAnthropicClaudeModel(model)) {
783
+ // GovCloud Bedrock currently rejects the Claude thinking.display field.
784
+ // Omit it there until the GovCloud Converse schema catches up.
785
+ const display = isGovCloudBedrockTarget(model, options) ? undefined : (options.thinkingDisplay ?? "summarized");
786
+ const result = supportsAdaptiveThinking(model.id, model.name)
599
787
  ? {
600
- thinking: { type: "adaptive" },
601
- output_config: { effort: mapThinkingLevelToEffort(options.reasoning, model.id) },
788
+ thinking: { type: "adaptive", ...(display !== undefined ? { display } : {}) },
789
+ output_config: { effort: mapThinkingLevelToEffort(model, options.reasoning) },
602
790
  }
603
791
  : (() => {
604
792
  const defaultBudgets = {
@@ -606,20 +794,21 @@ function buildAdditionalModelRequestFields(model, options) {
606
794
  low: 2048,
607
795
  medium: 8192,
608
796
  high: 16384,
609
- xhigh: 16384, // Older Claude models don't support xhigh, clamp to high budget
610
- max: 16384, // Older Claude models don't support max, clamp to high budget
797
+ xhigh: 16384,
798
+ max: 16384,
611
799
  };
612
- // Custom budgets override defaults (xhigh/max not in ThinkingBudgets, use high)
800
+ // Custom budgets override defaults (xhigh not in ThinkingBudgets, use high)
613
801
  const level = options.reasoning === "xhigh" || options.reasoning === "max" ? "high" : options.reasoning;
614
802
  const budget = options.thinkingBudgets?.[level] ?? defaultBudgets[options.reasoning];
615
803
  return {
616
804
  thinking: {
617
805
  type: "enabled",
618
806
  budget_tokens: budget,
807
+ ...(display !== undefined ? { display } : {}),
619
808
  },
620
809
  };
621
810
  })();
622
- if (!supportsAdaptiveThinking(model.id) && (options.interleavedThinking ?? true)) {
811
+ if (!supportsAdaptiveThinking(model.id, model.name) && (options.interleavedThinking ?? true)) {
623
812
  result.anthropic_beta = ["interleaved-thinking-2025-05-14"];
624
813
  }
625
814
  return result;