@draht/ai 2026.5.12 → 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.
- package/dist/api-registry.d.ts +1 -1
- package/dist/api-registry.d.ts.map +1 -1
- package/dist/api-registry.js.map +1 -1
- package/dist/bedrock-provider.d.ts +2 -2
- package/dist/bedrock-provider.d.ts.map +1 -1
- package/dist/bedrock-provider.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts +10 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +110 -36
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.d.ts +10 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.generated.d.ts +485 -0
- package/dist/image-models.generated.d.ts.map +1 -0
- package/dist/image-models.generated.js +487 -0
- package/dist/image-models.generated.js.map +1 -0
- package/dist/image-models.js +23 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +4 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +31 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +5 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +4665 -1252
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +4877 -2833
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +33 -6
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +19 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +278 -89
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +37 -6
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +300 -114
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +1 -1
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +68 -21
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +13 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +26 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +1 -0
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +1 -1
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +8 -3
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +34 -17
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +45 -18
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +2 -2
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +9 -6
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts +3 -0
- package/dist/providers/images/openrouter.d.ts.map +1 -0
- package/dist/providers/images/openrouter.js +128 -0
- package/dist/providers/images/openrouter.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/mistral.d.ts +4 -1
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +43 -10
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +22 -1
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +542 -111
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +6 -2
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +447 -229
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-prompt-cache.d.ts +3 -0
- package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
- package/dist/providers/openai-prompt-cache.js +10 -0
- package/dist/providers/openai-prompt-cache.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +3 -2
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +41 -15
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +85 -40
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/register-builtins.d.ts +10 -13
- package/dist/providers/register-builtins.d.ts.map +1 -1
- package/dist/providers/register-builtins.js +13 -20
- package/dist/providers/register-builtins.js.map +1 -1
- package/dist/providers/simple-options.d.ts +2 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +8 -2
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +63 -34
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts +3 -3
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +14 -2
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +177 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/abort-signals.d.ts +6 -0
- package/dist/utils/abort-signals.d.ts.map +1 -0
- package/dist/utils/abort-signals.js +34 -0
- package/dist/utils/abort-signals.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts +3 -3
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +2 -2
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +8 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +89 -5
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/node-http-proxy.d.ts +10 -0
- package/dist/utils/node-http-proxy.d.ts.map +1 -0
- package/dist/utils/node-http-proxy.js +97 -0
- package/dist/utils/node-http-proxy.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +1 -1
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +1 -1
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts +21 -0
- package/dist/utils/oauth/device-code.d.ts.map +1 -0
- package/dist/utils/oauth/device-code.js +56 -0
- package/dist/utils/oauth/device-code.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +3 -3
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +58 -70
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -11
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +2 -11
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +11 -2
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +187 -73
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +18 -1
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/overflow.d.ts +7 -3
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +25 -3
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +1 -1
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +242 -41
- package/dist/utils/validation.js.map +1 -1
- package/package.json +14 -15
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -776
- 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 {
|
|
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
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
config.region =
|
|
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 (
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
63
|
-
config.requestHandler = new
|
|
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
|
-
|
|
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 =
|
|
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: {
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
322
|
-
const
|
|
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
|
-
*
|
|
361
|
-
*
|
|
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
|
|
365
|
-
|
|
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 (
|
|
481
|
+
if (candidates.some((s) => s.includes("-4-")))
|
|
374
482
|
return true;
|
|
375
483
|
// Claude 3.7 Sonnet
|
|
376
|
-
if (
|
|
484
|
+
if (candidates.some((s) => s.includes("claude-3-7-sonnet")))
|
|
377
485
|
return true;
|
|
378
486
|
// Claude 3.5 Haiku
|
|
379
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
588
|
+
const textBlock = createNonBlankTextBlock(c.text);
|
|
589
|
+
if (!textBlock)
|
|
444
590
|
continue;
|
|
445
|
-
contentBlocks.push(
|
|
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
|
-
|
|
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:
|
|
612
|
+
contentBlocks.push({ text: thinking });
|
|
465
613
|
}
|
|
466
614
|
else {
|
|
467
615
|
contentBlocks.push({
|
|
468
616
|
reasoningContent: {
|
|
469
617
|
reasoningText: {
|
|
470
|
-
text:
|
|
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:
|
|
628
|
+
reasoningText: { text: thinking },
|
|
481
629
|
},
|
|
482
630
|
});
|
|
483
631
|
}
|
|
484
632
|
break;
|
|
633
|
+
}
|
|
485
634
|
default:
|
|
486
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
598
|
-
|
|
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
|
|
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,
|
|
610
|
-
max: 16384,
|
|
797
|
+
xhigh: 16384,
|
|
798
|
+
max: 16384,
|
|
611
799
|
};
|
|
612
|
-
// Custom budgets override defaults (xhigh
|
|
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;
|