@iinm/plain-agent 1.3.3 → 1.4.1
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/.config/config.predefined.json +46 -0
- package/README.md +7 -3
- package/package.json +1 -1
- package/src/mcp.mjs +1 -1
- package/src/modelCaller.mjs +3 -0
- package/src/modelDefinition.d.ts +5 -0
- package/src/prompt.mjs +6 -0
- package/src/providers/anthropic.mjs +5 -4
- package/src/providers/bedrock.d.ts +249 -0
- package/src/providers/bedrock.mjs +710 -0
- package/src/providers/openaiCompatible.mjs +5 -4
- package/src/providers/platform/bedrock.mjs +4 -0
- package/src/utils/evalJSONConfig.mjs +24 -0
|
@@ -835,6 +835,52 @@
|
|
|
835
835
|
"model": "qwen/qwen3-next-80b-a3b-thinking-maas"
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
|
+
},
|
|
839
|
+
|
|
840
|
+
{
|
|
841
|
+
"name": "nova-2-lite",
|
|
842
|
+
"variant": "bedrock",
|
|
843
|
+
"platform": {
|
|
844
|
+
"name": "bedrock",
|
|
845
|
+
"variant": "default"
|
|
846
|
+
},
|
|
847
|
+
"model": {
|
|
848
|
+
"format": "bedrock-converse",
|
|
849
|
+
"config": {
|
|
850
|
+
"model": "global.amazon.nova-2-lite-v1:0",
|
|
851
|
+
"enablePromptCaching": true,
|
|
852
|
+
"additionalModelRequestFields": {
|
|
853
|
+
"reasoningConfig": {
|
|
854
|
+
"type": "enabled",
|
|
855
|
+
"maxReasoningEffort": "medium"
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
"name": "claude-haiku-4-5",
|
|
863
|
+
"variant": "thinking-16k-bedrock-converse",
|
|
864
|
+
"platform": {
|
|
865
|
+
"name": "bedrock",
|
|
866
|
+
"variant": "default"
|
|
867
|
+
},
|
|
868
|
+
"model": {
|
|
869
|
+
"format": "bedrock-converse",
|
|
870
|
+
"config": {
|
|
871
|
+
"model": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
872
|
+
"enablePromptCaching": true,
|
|
873
|
+
"inferenceConfig": {
|
|
874
|
+
"max_tokens": 32768
|
|
875
|
+
},
|
|
876
|
+
"additionalModelRequestFields": {
|
|
877
|
+
"reasoning_config": {
|
|
878
|
+
"type": "enabled",
|
|
879
|
+
"budget_tokens": 16384
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
838
884
|
}
|
|
839
885
|
]
|
|
840
886
|
}
|
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ Create the configuration.
|
|
|
50
50
|
"name": "anthropic",
|
|
51
51
|
"variant": "default",
|
|
52
52
|
"apiKey": "FIXME"
|
|
53
|
+
// Or
|
|
54
|
+
// "apiKey": { "$env": "ANTHROPIC_API_KEY" }
|
|
53
55
|
},
|
|
54
56
|
{
|
|
55
57
|
"name": "gemini",
|
|
@@ -603,11 +605,13 @@ policy='{
|
|
|
603
605
|
"Effect": "Allow",
|
|
604
606
|
"Action": [
|
|
605
607
|
"bedrock:InvokeModel",
|
|
606
|
-
"bedrock:InvokeModelWithResponseStream"
|
|
608
|
+
"bedrock:InvokeModelWithResponseStream",
|
|
609
|
+
"bedrock:ListInferenceProfiles"
|
|
607
610
|
],
|
|
608
611
|
"Resource": [
|
|
609
|
-
"arn:aws:bedrock
|
|
610
|
-
"arn:aws:bedrock:*:*:inference-profile/*"
|
|
612
|
+
"arn:aws:bedrock:*:*:foundation-model/*",
|
|
613
|
+
"arn:aws:bedrock:*:*:inference-profile/*",
|
|
614
|
+
"arn:aws:bedrock:*:*:application-inference-profile/*"
|
|
611
615
|
]
|
|
612
616
|
}
|
|
613
617
|
]
|
package/package.json
CHANGED
package/src/mcp.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @import { Client } from "@modelcontextprotocol/client";
|
|
2
3
|
* @import { StructuredToolResultContent, Tool, ToolImplementation } from "./tool";
|
|
3
4
|
* @import { MCPServerConfig } from "./config";
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { mkdir, open } from "node:fs/promises";
|
|
7
8
|
import path from "node:path";
|
|
8
|
-
import { Client } from "@modelcontextprotocol/client";
|
|
9
9
|
import { AGENT_PROJECT_METADATA_DIR } from "./env.mjs";
|
|
10
10
|
import { writeTmpFile } from "./tmpfile.mjs";
|
|
11
11
|
import { noThrow } from "./utils/noThrow.mjs";
|
package/src/modelCaller.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { callAnthropicModel } from "./providers/anthropic.mjs";
|
|
2
|
+
import { callBedrockConverseModel } from "./providers/bedrock.mjs";
|
|
2
3
|
import { createCacheEnabledGeminiModelCaller } from "./providers/gemini.mjs";
|
|
3
4
|
import { callOpenAIModel } from "./providers/openai.mjs";
|
|
4
5
|
import { callOpenAICompatibleModel } from "./providers/openaiCompatible.mjs";
|
|
@@ -25,5 +26,7 @@ export function createModelCaller(modelDef) {
|
|
|
25
26
|
case "openai-messages":
|
|
26
27
|
return (input) =>
|
|
27
28
|
callOpenAICompatibleModel(platform, model.config, input);
|
|
29
|
+
case "bedrock-converse":
|
|
30
|
+
return (input) => callBedrockConverseModel(platform, model.config, input);
|
|
28
31
|
}
|
|
29
32
|
}
|
package/src/modelDefinition.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { AnthropicModelConfig } from "./providers/anthropic";
|
|
|
2
2
|
import { GeminiModelConfig } from "./providers/gemini";
|
|
3
3
|
import { OpenAIModelConfig } from "./providers/openai";
|
|
4
4
|
import { OpenAICompatibleModelConfig } from "./providers/openaiCompatible";
|
|
5
|
+
import { BedrockConverseModelConfig } from "./providers/bedrock";
|
|
5
6
|
|
|
6
7
|
export type ModelDefinition = {
|
|
7
8
|
name: string;
|
|
@@ -70,4 +71,8 @@ export type ModelConfig =
|
|
|
70
71
|
| {
|
|
71
72
|
format: "openai-messages";
|
|
72
73
|
config: OpenAICompatibleModelConfig;
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
format: "bedrock-converse";
|
|
77
|
+
config: BedrockConverseModelConfig;
|
|
73
78
|
};
|
package/src/prompt.mjs
CHANGED
|
@@ -112,6 +112,12 @@ If skill matches task: read full file and apply the workflow
|
|
|
112
112
|
|
|
113
113
|
${skillDescriptions}
|
|
114
114
|
|
|
115
|
+
# Claude Code Compatibility Notes
|
|
116
|
+
|
|
117
|
+
When using a Claude Code-compatible command, agent, or skill, follow these rules:
|
|
118
|
+
- Subagents cannot run in parallel. Delegate to them one at a time.
|
|
119
|
+
- If a Claude Code prompt mentions CLAUDE.md for project rules or conventions, use AGENTS.md instead when CLAUDE.md is absent.
|
|
120
|
+
|
|
115
121
|
# Environment
|
|
116
122
|
|
|
117
123
|
- User name: ${username}
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
|
-
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
9
|
-
import { fromIni } from "@aws-sdk/credential-providers";
|
|
10
|
-
import { HttpRequest } from "@smithy/protocol-http";
|
|
11
|
-
import { SignatureV4 } from "@smithy/signature-v4";
|
|
12
8
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
13
9
|
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
14
10
|
import { getGoogleCloudAccessToken } from "./platform/googleCloud.mjs";
|
|
@@ -112,6 +108,11 @@ export async function callAnthropicModel(
|
|
|
112
108
|
|
|
113
109
|
// bedrock + sso profile
|
|
114
110
|
const runFetchForBedrock = async () => {
|
|
111
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
112
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
113
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
114
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
115
|
+
|
|
115
116
|
const region =
|
|
116
117
|
url.match(/bedrock-runtime\.([\w-]+)\.amazonaws\.com/)?.[1] ?? "";
|
|
117
118
|
const urlParsed = new URL(url);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* Model Configuration */
|
|
2
|
+
export type BedrockConverseModelConfig = {
|
|
3
|
+
model: string;
|
|
4
|
+
inferenceConfig?: {
|
|
5
|
+
maxTokens?: number;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
topP?: number;
|
|
8
|
+
};
|
|
9
|
+
additionalModelRequestFields?: Record<string, unknown>;
|
|
10
|
+
enablePromptCaching?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/* Request */
|
|
14
|
+
export type BedrockConverseRequest = {
|
|
15
|
+
modelId?: string;
|
|
16
|
+
messages: BedrockMessage[];
|
|
17
|
+
system?: BedrockSystemContentBlock[];
|
|
18
|
+
toolConfig?: BedrockToolConfig;
|
|
19
|
+
additionalModelRequestFields?: Record<string, unknown>;
|
|
20
|
+
inferenceConfig?: {
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
temperature?: number;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/* Message */
|
|
27
|
+
export type BedrockMessage = BedrockUserMessage | BedrockAssistantMessage;
|
|
28
|
+
|
|
29
|
+
export type BedrockUserMessage = {
|
|
30
|
+
role: "user";
|
|
31
|
+
content: BedrockContentBlock[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type BedrockAssistantMessage = {
|
|
35
|
+
role: "assistant";
|
|
36
|
+
content: BedrockAssistantContentBlock[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type BedrockSystemContentBlock =
|
|
40
|
+
| {
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
| BedrockCachePointBlock;
|
|
44
|
+
|
|
45
|
+
/* Content Block */
|
|
46
|
+
export type BedrockContentBlock =
|
|
47
|
+
| BedrockTextBlock
|
|
48
|
+
| BedrockImageBlock
|
|
49
|
+
| BedrockToolUseBlock
|
|
50
|
+
| BedrockToolResultBlock
|
|
51
|
+
| BedrockCachePointBlock;
|
|
52
|
+
|
|
53
|
+
export type BedrockAssistantContentBlock =
|
|
54
|
+
| BedrockTextBlock
|
|
55
|
+
| BedrockToolUseBlock
|
|
56
|
+
| BedrockReasoningContentBlock
|
|
57
|
+
| BedrockCachePointBlock;
|
|
58
|
+
|
|
59
|
+
export type BedrockTextBlock = {
|
|
60
|
+
text: string;
|
|
61
|
+
cachePoint?: {
|
|
62
|
+
type: "default";
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type BedrockImageBlock = {
|
|
67
|
+
image: {
|
|
68
|
+
format: "png" | "jpeg" | "gif" | "webp";
|
|
69
|
+
source: {
|
|
70
|
+
bytes?: string; // base64 encoded
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type BedrockToolUseBlock = {
|
|
76
|
+
toolUse: {
|
|
77
|
+
toolUseId: string;
|
|
78
|
+
name: string;
|
|
79
|
+
input: Record<string, unknown>;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type BedrockToolResultBlock = {
|
|
84
|
+
toolResult: {
|
|
85
|
+
toolUseId: string;
|
|
86
|
+
content: BedrockToolResultContent[];
|
|
87
|
+
status?: "success" | "error";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type BedrockToolResultContent =
|
|
92
|
+
| { text: string }
|
|
93
|
+
| { image: BedrockImageBlock["image"] };
|
|
94
|
+
|
|
95
|
+
// Message history type - used when sending back to API
|
|
96
|
+
// Claude Haiku 4.5 and Nova use this format
|
|
97
|
+
// Note: reasoningText and redactedContent are mutually exclusive (union type)
|
|
98
|
+
export type BedrockReasoningContentBlock = {
|
|
99
|
+
reasoningContent:
|
|
100
|
+
| {
|
|
101
|
+
reasoningText: {
|
|
102
|
+
text: string;
|
|
103
|
+
signature?: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
| {
|
|
107
|
+
redactedContent: string; // Base64-encoded binary
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Internal type for accumulating reasoning content during streaming
|
|
112
|
+
// Note: Streaming API uses flat structure (reasoningContent.text, reasoningContent.redactedContent)
|
|
113
|
+
// but message history uses nested structure (reasoningContent.reasoningText.text, reasoningContent.redactedContent)
|
|
114
|
+
export type BedrockReasoningContentAccumulator = {
|
|
115
|
+
reasoningContent: {
|
|
116
|
+
text?: string;
|
|
117
|
+
signature?: string;
|
|
118
|
+
redactedContent?: string; // Base64-encoded binary
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Internal type for accumulating partial content during streaming
|
|
123
|
+
// Note: reasoningContent uses flat structure during streaming, but nested structure in message history
|
|
124
|
+
export type BedrockAssistantContentBlockWithPartial = {
|
|
125
|
+
text?: string;
|
|
126
|
+
toolUse?: {
|
|
127
|
+
toolUseId?: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
input?: unknown;
|
|
130
|
+
};
|
|
131
|
+
reasoningContent?: {
|
|
132
|
+
text?: string;
|
|
133
|
+
signature?: string;
|
|
134
|
+
redactedContent?: string; // Base64-encoded binary
|
|
135
|
+
};
|
|
136
|
+
cachePoint?: {
|
|
137
|
+
type: "default";
|
|
138
|
+
};
|
|
139
|
+
_partialInput?: string;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type BedrockCachePointBlock = {
|
|
143
|
+
cachePoint: {
|
|
144
|
+
type: "default";
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/* Tool Configuration */
|
|
149
|
+
export type BedrockToolConfig = {
|
|
150
|
+
tools: BedrockTool[];
|
|
151
|
+
toolChoice?: BedrockToolChoice;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type BedrockTool = {
|
|
155
|
+
toolSpec: {
|
|
156
|
+
name: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
inputSchema: {
|
|
159
|
+
json: Record<string, unknown>;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export type BedrockToolChoice =
|
|
165
|
+
| { auto: Record<string, never> }
|
|
166
|
+
| { any: Record<string, never> }
|
|
167
|
+
| { tool: { name: string } };
|
|
168
|
+
|
|
169
|
+
/* Response */
|
|
170
|
+
export type BedrockConverseResponse = {
|
|
171
|
+
metrics: {
|
|
172
|
+
latencyMs: number;
|
|
173
|
+
};
|
|
174
|
+
output: {
|
|
175
|
+
message: BedrockAssistantMessage;
|
|
176
|
+
};
|
|
177
|
+
stopReason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence";
|
|
178
|
+
usage: BedrockUsage;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/* Usage */
|
|
182
|
+
export type BedrockUsage = {
|
|
183
|
+
inputTokens: number;
|
|
184
|
+
outputTokens: number;
|
|
185
|
+
totalTokens: number;
|
|
186
|
+
cacheReadInputTokens?: number;
|
|
187
|
+
cacheWriteInputTokens?: number;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/* Stream Event */
|
|
191
|
+
export type BedrockStreamEvent =
|
|
192
|
+
| BedrockStreamMessageStartEvent
|
|
193
|
+
| BedrockStreamContentBlockStartEvent
|
|
194
|
+
| BedrockStreamContentBlockDeltaEvent
|
|
195
|
+
| BedrockStreamContentBlockStopEvent
|
|
196
|
+
| BedrockStreamMessageStopEvent
|
|
197
|
+
| BedrockStreamMetadataEvent;
|
|
198
|
+
|
|
199
|
+
export type BedrockStreamMessageStartEvent = {
|
|
200
|
+
messageStart: {
|
|
201
|
+
requestId: string;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type BedrockStreamContentBlockStartEvent = {
|
|
206
|
+
contentBlockIndex: number;
|
|
207
|
+
start: {
|
|
208
|
+
text?: string;
|
|
209
|
+
toolUse?: {
|
|
210
|
+
toolUseId: string;
|
|
211
|
+
name: string;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type BedrockStreamContentBlockDeltaEvent = {
|
|
217
|
+
contentBlockIndex: number;
|
|
218
|
+
delta: {
|
|
219
|
+
text?: string;
|
|
220
|
+
toolUse?: {
|
|
221
|
+
toolUseId?: string;
|
|
222
|
+
name?: string;
|
|
223
|
+
input?: string; // partial JSON
|
|
224
|
+
};
|
|
225
|
+
reasoningContent?: {
|
|
226
|
+
text?: string;
|
|
227
|
+
signature?: string;
|
|
228
|
+
redactedContent?: string; // Base64-encoded binary
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export type BedrockStreamContentBlockStopEvent = {
|
|
234
|
+
contentBlockStop: {
|
|
235
|
+
contentBlockIndex: number;
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type BedrockStreamMessageStopEvent = {
|
|
240
|
+
stopReason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence";
|
|
241
|
+
additionalModelResponseFields?: Record<string, unknown>;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export type BedrockStreamMetadataEvent = {
|
|
245
|
+
usage: BedrockUsage;
|
|
246
|
+
metrics: {
|
|
247
|
+
latencyMs: number;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ModelInput, Message, AssistantMessage, ModelOutput, PartialMessageContent } from "../model";
|
|
3
|
+
* @import { ToolDefinition } from "../tool";
|
|
4
|
+
* @import { BedrockConverseModelConfig, BedrockMessage, BedrockContentBlock, BedrockAssistantContentBlock, BedrockAssistantContentBlockWithPartial, BedrockTool, BedrockStreamEvent, BedrockConverseRequest, BedrockUsage, BedrockToolResultContent } from "./bedrock";
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { styleText } from "node:util";
|
|
8
|
+
import { noThrow } from "../utils/noThrow.mjs";
|
|
9
|
+
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {import("../modelDefinition").PlatformConfig} platformConfig
|
|
13
|
+
* @param {BedrockConverseModelConfig} modelConfig
|
|
14
|
+
* @param {ModelInput} input
|
|
15
|
+
* @param {number} [retryCount]
|
|
16
|
+
* @returns {Promise<ModelOutput | Error>}
|
|
17
|
+
*/
|
|
18
|
+
export async function callBedrockConverseModel(
|
|
19
|
+
platformConfig,
|
|
20
|
+
modelConfig,
|
|
21
|
+
input,
|
|
22
|
+
retryCount = 0,
|
|
23
|
+
) {
|
|
24
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
25
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
26
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
27
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
28
|
+
|
|
29
|
+
return await noThrow(async () => {
|
|
30
|
+
const messages = convertGenericMessageToBedrockFormat(input.messages);
|
|
31
|
+
const cachedMessages = modelConfig.enablePromptCaching
|
|
32
|
+
? enablePromptCaching(messages)
|
|
33
|
+
: messages;
|
|
34
|
+
const tools = convertGenericToolDefinitionToBedrockFormat(
|
|
35
|
+
input.tools || [],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const url = (() => {
|
|
39
|
+
const baseURL = platformConfig.baseURL;
|
|
40
|
+
if (platformConfig.name !== "bedrock") {
|
|
41
|
+
throw new Error(`Unsupported platform: ${platformConfig.name}`);
|
|
42
|
+
}
|
|
43
|
+
return `${baseURL}/model/${modelConfig.model}/converse-stream`;
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
const region = extractRegionFromBaseURL(platformConfig.baseURL);
|
|
47
|
+
|
|
48
|
+
/** @type {BedrockConverseRequest} */
|
|
49
|
+
const request = {
|
|
50
|
+
messages: cachedMessages,
|
|
51
|
+
...(modelConfig.inferenceConfig && {
|
|
52
|
+
inferenceConfig: modelConfig.inferenceConfig,
|
|
53
|
+
}),
|
|
54
|
+
...(modelConfig.additionalModelRequestFields && {
|
|
55
|
+
additionalModelRequestFields: modelConfig.additionalModelRequestFields,
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Add system messages if present
|
|
60
|
+
const systemMessages = extractSystemMessages(
|
|
61
|
+
input.messages,
|
|
62
|
+
modelConfig.enablePromptCaching,
|
|
63
|
+
);
|
|
64
|
+
if (systemMessages.length > 0) {
|
|
65
|
+
request.system = systemMessages;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add tools if present
|
|
69
|
+
if (tools.length > 0) {
|
|
70
|
+
request.toolConfig = {
|
|
71
|
+
tools: tools,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const payload = JSON.stringify(request);
|
|
76
|
+
|
|
77
|
+
// Sign request with AWS Signature V4
|
|
78
|
+
const signer = new SignatureV4({
|
|
79
|
+
credentials: fromIni({ profile: platformConfig.awsProfile }),
|
|
80
|
+
region,
|
|
81
|
+
service: "bedrock",
|
|
82
|
+
sha256: Sha256,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const urlParsed = new URL(url);
|
|
86
|
+
const { hostname, pathname } = urlParsed;
|
|
87
|
+
|
|
88
|
+
const req = new HttpRequest({
|
|
89
|
+
protocol: "https:",
|
|
90
|
+
method: "POST",
|
|
91
|
+
hostname,
|
|
92
|
+
path: pathname,
|
|
93
|
+
headers: {
|
|
94
|
+
host: hostname,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
},
|
|
97
|
+
body: payload,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const signed = await signer.sign(req);
|
|
101
|
+
|
|
102
|
+
const response = await fetch(url, {
|
|
103
|
+
method: signed.method,
|
|
104
|
+
headers: signed.headers,
|
|
105
|
+
body: signed.body,
|
|
106
|
+
signal: AbortSignal.timeout(120 * 1000),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (response.status !== 200) {
|
|
110
|
+
const errorText = await response.text();
|
|
111
|
+
console.error(
|
|
112
|
+
styleText("red", `Bedrock API error: ${response.status} ${errorText}`),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Retry on throttling or server errors
|
|
116
|
+
if (
|
|
117
|
+
(response.status === 429 ||
|
|
118
|
+
response.status === 502 ||
|
|
119
|
+
response.status === 503) &&
|
|
120
|
+
retryCount < 3
|
|
121
|
+
) {
|
|
122
|
+
const retryInterval = Math.min(2 * 2 ** retryCount, 16);
|
|
123
|
+
console.error(
|
|
124
|
+
styleText(
|
|
125
|
+
"yellow",
|
|
126
|
+
`Retrying in ${retryInterval} seconds... (attempt ${retryCount + 1})`,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
await new Promise((resolve) =>
|
|
130
|
+
setTimeout(resolve, retryInterval * 1000),
|
|
131
|
+
);
|
|
132
|
+
return callBedrockConverseModel(
|
|
133
|
+
platformConfig,
|
|
134
|
+
modelConfig,
|
|
135
|
+
input,
|
|
136
|
+
retryCount + 1,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
throw new Error(`Bedrock API error: ${response.status} ${errorText}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!response.body) {
|
|
144
|
+
throw new Error("Response body is empty");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const reader = response.body.getReader();
|
|
148
|
+
|
|
149
|
+
/** @type {BedrockAssistantContentBlockWithPartial[]} */
|
|
150
|
+
const contentBlocks = [];
|
|
151
|
+
/** @type {Record<number, BedrockAssistantContentBlockWithPartial>} */
|
|
152
|
+
const contentBlockMap = {};
|
|
153
|
+
/** @type {BedrockUsage | undefined} */
|
|
154
|
+
let usage;
|
|
155
|
+
|
|
156
|
+
// Process stream events
|
|
157
|
+
for await (const event of readBedrockStreamEvents(reader)) {
|
|
158
|
+
const bedrockEvent = /** @type {BedrockStreamEvent} */ (event);
|
|
159
|
+
|
|
160
|
+
if (input.onPartialMessageContent) {
|
|
161
|
+
const partialContents = convertBedrockStreamEventToPartialContent(
|
|
162
|
+
bedrockEvent,
|
|
163
|
+
contentBlockMap,
|
|
164
|
+
);
|
|
165
|
+
for (const partialContent of partialContents) {
|
|
166
|
+
input.onPartialMessageContent(partialContent);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Handle Converse API events (flat structure)
|
|
171
|
+
// Check for start event first
|
|
172
|
+
if ("contentBlockIndex" in bedrockEvent && "start" in bedrockEvent) {
|
|
173
|
+
const index = bedrockEvent.contentBlockIndex;
|
|
174
|
+
const start = bedrockEvent.start;
|
|
175
|
+
|
|
176
|
+
if (start.toolUse) {
|
|
177
|
+
contentBlockMap[index] = {
|
|
178
|
+
toolUse: {
|
|
179
|
+
toolUseId: start.toolUse.toolUseId || "",
|
|
180
|
+
name: start.toolUse.name || "",
|
|
181
|
+
input: {},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ("contentBlockIndex" in bedrockEvent && "delta" in bedrockEvent) {
|
|
188
|
+
const index = bedrockEvent.contentBlockIndex;
|
|
189
|
+
const delta = bedrockEvent.delta;
|
|
190
|
+
|
|
191
|
+
// Initialize content block if not exists
|
|
192
|
+
if (!contentBlockMap[index]) {
|
|
193
|
+
if (delta.text !== undefined) {
|
|
194
|
+
contentBlockMap[index] = { text: "" };
|
|
195
|
+
} else if (delta.toolUse) {
|
|
196
|
+
contentBlockMap[index] = {
|
|
197
|
+
toolUse: {
|
|
198
|
+
toolUseId: delta.toolUse.toolUseId || "",
|
|
199
|
+
name: delta.toolUse.name || "",
|
|
200
|
+
input: {},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
} else if (delta.reasoningContent) {
|
|
204
|
+
contentBlockMap[index] = {
|
|
205
|
+
reasoningContent: {
|
|
206
|
+
text: undefined,
|
|
207
|
+
signature: undefined,
|
|
208
|
+
redactedContent: undefined,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const block = contentBlockMap[index];
|
|
215
|
+
|
|
216
|
+
// Accumulate content
|
|
217
|
+
if (block && delta.text !== undefined && "text" in block) {
|
|
218
|
+
block.text += delta.text;
|
|
219
|
+
} else if (
|
|
220
|
+
block &&
|
|
221
|
+
delta.toolUse &&
|
|
222
|
+
"toolUse" in block &&
|
|
223
|
+
block.toolUse
|
|
224
|
+
) {
|
|
225
|
+
// Accumulate tool input as JSON string
|
|
226
|
+
if (!block._partialInput) {
|
|
227
|
+
block._partialInput = "";
|
|
228
|
+
}
|
|
229
|
+
block._partialInput += delta.toolUse.input || "";
|
|
230
|
+
} else if (
|
|
231
|
+
block &&
|
|
232
|
+
delta.reasoningContent &&
|
|
233
|
+
"reasoningContent" in block &&
|
|
234
|
+
block.reasoningContent
|
|
235
|
+
) {
|
|
236
|
+
if (delta.reasoningContent.text) {
|
|
237
|
+
block.reasoningContent.text =
|
|
238
|
+
(block.reasoningContent.text || "") + delta.reasoningContent.text;
|
|
239
|
+
}
|
|
240
|
+
if (delta.reasoningContent.signature) {
|
|
241
|
+
block.reasoningContent.signature = delta.reasoningContent.signature;
|
|
242
|
+
}
|
|
243
|
+
if (delta.reasoningContent.redactedContent) {
|
|
244
|
+
block.reasoningContent.redactedContent =
|
|
245
|
+
delta.reasoningContent.redactedContent;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Handle message stop
|
|
251
|
+
if ("stopReason" in bedrockEvent) {
|
|
252
|
+
// Finalize all content blocks
|
|
253
|
+
for (const [_index, block] of Object.entries(contentBlockMap)) {
|
|
254
|
+
// Parse accumulated tool input JSON
|
|
255
|
+
if (
|
|
256
|
+
block &&
|
|
257
|
+
"toolUse" in block &&
|
|
258
|
+
block.toolUse &&
|
|
259
|
+
block._partialInput
|
|
260
|
+
) {
|
|
261
|
+
try {
|
|
262
|
+
block.toolUse.input = JSON.parse(block._partialInput);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.error(
|
|
265
|
+
styleText(
|
|
266
|
+
"red",
|
|
267
|
+
`Failed to parse tool input JSON for tool "${block.toolUse.name}": ${block._partialInput}`,
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
block.toolUse.input = {
|
|
271
|
+
err: String(err),
|
|
272
|
+
raw: block._partialInput,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
delete block._partialInput;
|
|
276
|
+
}
|
|
277
|
+
contentBlocks.push(block);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Handle metadata
|
|
282
|
+
if ("usage" in bedrockEvent && "metrics" in bedrockEvent) {
|
|
283
|
+
usage = bedrockEvent.usage;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const message =
|
|
288
|
+
convertBedrockContentBlocksToAssistantMessage(contentBlocks);
|
|
289
|
+
|
|
290
|
+
const providerTokenUsage = usage
|
|
291
|
+
? {
|
|
292
|
+
inputTokens: usage.inputTokens,
|
|
293
|
+
outputTokens: usage.outputTokens,
|
|
294
|
+
totalTokens: usage.totalTokens,
|
|
295
|
+
...(usage.cacheReadInputTokens && {
|
|
296
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
297
|
+
}),
|
|
298
|
+
...(usage.cacheWriteInputTokens && {
|
|
299
|
+
cacheWriteInputTokens: usage.cacheWriteInputTokens,
|
|
300
|
+
}),
|
|
301
|
+
}
|
|
302
|
+
: {};
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
message,
|
|
306
|
+
providerTokenUsage,
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {Message[]} messages
|
|
313
|
+
* @returns {BedrockMessage[]}
|
|
314
|
+
*/
|
|
315
|
+
function convertGenericMessageToBedrockFormat(messages) {
|
|
316
|
+
/** @type {BedrockMessage[]} */
|
|
317
|
+
const bedrockMessages = [];
|
|
318
|
+
|
|
319
|
+
for (const message of messages) {
|
|
320
|
+
if (message.role === "system") {
|
|
321
|
+
// System messages handled separately
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (message.role === "user") {
|
|
326
|
+
/** @type {BedrockContentBlock[]} */
|
|
327
|
+
const content = [];
|
|
328
|
+
|
|
329
|
+
for (const part of message.content) {
|
|
330
|
+
if (part.type === "text" && part.text) {
|
|
331
|
+
// Only include non-empty text blocks
|
|
332
|
+
content.push({ text: part.text });
|
|
333
|
+
} else if (part.type === "image") {
|
|
334
|
+
content.push({
|
|
335
|
+
image: {
|
|
336
|
+
format: /** @type {"png" | "jpeg" | "gif" | "webp"} */ (
|
|
337
|
+
part.mimeType.split("/")[1]
|
|
338
|
+
),
|
|
339
|
+
source: {
|
|
340
|
+
bytes: part.data,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
} else if (part.type === "tool_result") {
|
|
345
|
+
/** @type {BedrockToolResultContent[]} */
|
|
346
|
+
const toolResultContent = [];
|
|
347
|
+
for (const resultPart of part.content) {
|
|
348
|
+
if (resultPart.type === "text") {
|
|
349
|
+
toolResultContent.push({ text: resultPart.text });
|
|
350
|
+
} else if (resultPart.type === "image") {
|
|
351
|
+
toolResultContent.push({
|
|
352
|
+
image: {
|
|
353
|
+
format: /** @type {"png" | "jpeg" | "gif" | "webp"} */ (
|
|
354
|
+
resultPart.mimeType.split("/")[1]
|
|
355
|
+
),
|
|
356
|
+
source: {
|
|
357
|
+
bytes: resultPart.data,
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
content.push({
|
|
365
|
+
toolResult: {
|
|
366
|
+
toolUseId: part.toolUseId,
|
|
367
|
+
content: toolResultContent,
|
|
368
|
+
status: part.isError ? "error" : "success",
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
bedrockMessages.push({ role: "user", content });
|
|
375
|
+
} else if (message.role === "assistant") {
|
|
376
|
+
/** @type {BedrockAssistantContentBlock[]} */
|
|
377
|
+
const content = [];
|
|
378
|
+
|
|
379
|
+
for (const part of message.content) {
|
|
380
|
+
if (part.type === "text") {
|
|
381
|
+
content.push({ text: part.text });
|
|
382
|
+
} else if (part.type === "thinking") {
|
|
383
|
+
// Extended thinking requires signature for multi-turn conversations
|
|
384
|
+
const signature = /** @type {string | undefined} */ (
|
|
385
|
+
part.provider?.fields?.signature
|
|
386
|
+
);
|
|
387
|
+
if (signature) {
|
|
388
|
+
content.push({
|
|
389
|
+
reasoningContent: {
|
|
390
|
+
reasoningText: {
|
|
391
|
+
text: part.thinking,
|
|
392
|
+
signature,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
} else if (part.type === "redacted_thinking") {
|
|
398
|
+
// Redacted thinking must be included in message history
|
|
399
|
+
const data = /** @type {string | undefined} */ (
|
|
400
|
+
part.provider?.fields?.data
|
|
401
|
+
);
|
|
402
|
+
if (data) {
|
|
403
|
+
content.push({
|
|
404
|
+
reasoningContent: {
|
|
405
|
+
redactedContent: data,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
} else if (part.type === "tool_use") {
|
|
410
|
+
content.push({
|
|
411
|
+
toolUse: {
|
|
412
|
+
toolUseId: part.toolUseId,
|
|
413
|
+
name: part.toolName,
|
|
414
|
+
input: part.input,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
bedrockMessages.push({ role: "assistant", content });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return bedrockMessages;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @param {Message[]} messages
|
|
429
|
+
* @param {boolean} [enablePromptCaching]
|
|
430
|
+
* @returns {import("./bedrock").BedrockSystemContentBlock[]}
|
|
431
|
+
*/
|
|
432
|
+
function extractSystemMessages(messages, enablePromptCaching = false) {
|
|
433
|
+
/** @type {import("./bedrock").BedrockSystemContentBlock[]} */
|
|
434
|
+
const systemBlocks = [];
|
|
435
|
+
|
|
436
|
+
for (const message of messages) {
|
|
437
|
+
if (message.role === "system") {
|
|
438
|
+
for (const part of message.content) {
|
|
439
|
+
systemBlocks.push({ text: part.text });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Add cache point at the end of system messages if enabled
|
|
445
|
+
if (enablePromptCaching && systemBlocks.length > 0) {
|
|
446
|
+
systemBlocks.push({ cachePoint: { type: "default" } });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return systemBlocks;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* @param {ToolDefinition[]} tools
|
|
454
|
+
* @returns {BedrockTool[]}
|
|
455
|
+
*/
|
|
456
|
+
function convertGenericToolDefinitionToBedrockFormat(tools) {
|
|
457
|
+
return tools.map((tool) => ({
|
|
458
|
+
toolSpec: {
|
|
459
|
+
name: tool.name,
|
|
460
|
+
description: tool.description,
|
|
461
|
+
inputSchema: {
|
|
462
|
+
json: tool.inputSchema,
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @param {BedrockMessage[]} messages
|
|
470
|
+
* @returns {BedrockMessage[]}
|
|
471
|
+
*/
|
|
472
|
+
function enablePromptCaching(messages) {
|
|
473
|
+
// Find user message indices
|
|
474
|
+
const userMessageIndices = messages
|
|
475
|
+
.map((msg, index) => (msg.role === "user" ? index : -1))
|
|
476
|
+
.filter((index) => index !== -1);
|
|
477
|
+
|
|
478
|
+
// Target last two user messages for caching
|
|
479
|
+
const cacheTargetIndices = [
|
|
480
|
+
userMessageIndices.at(-1),
|
|
481
|
+
userMessageIndices.at(-2),
|
|
482
|
+
].filter((index) => index !== undefined);
|
|
483
|
+
|
|
484
|
+
const cachedMessages = messages.map((message, index) => {
|
|
485
|
+
if (cacheTargetIndices.includes(index)) {
|
|
486
|
+
// Add cache point as a separate block at the end
|
|
487
|
+
// Only add to messages without tool results (tool results don't support cachePoint)
|
|
488
|
+
if (message.role === "user") {
|
|
489
|
+
const content = /** @type {BedrockContentBlock[]} */ ([
|
|
490
|
+
...message.content,
|
|
491
|
+
]);
|
|
492
|
+
// Check if content contains toolResult
|
|
493
|
+
const hasToolResult = content.some(
|
|
494
|
+
(block) => "toolResult" in block && block.toolResult,
|
|
495
|
+
);
|
|
496
|
+
if (!hasToolResult) {
|
|
497
|
+
content.push({ cachePoint: { type: "default" } });
|
|
498
|
+
return { ...message, content };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (message.role === "assistant") {
|
|
502
|
+
const content = /** @type {BedrockAssistantContentBlock[]} */ ([
|
|
503
|
+
...message.content,
|
|
504
|
+
]);
|
|
505
|
+
content.push({ cachePoint: { type: "default" } });
|
|
506
|
+
return { ...message, content };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return message;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
return cachedMessages;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @param {BedrockStreamEvent} event
|
|
517
|
+
* @param {Record<number, import("./bedrock").BedrockAssistantContentBlockWithPartial>} contentBlockMap
|
|
518
|
+
* @returns {PartialMessageContent[]}
|
|
519
|
+
*/
|
|
520
|
+
function convertBedrockStreamEventToPartialContent(event, contentBlockMap) {
|
|
521
|
+
/** @type {PartialMessageContent[]} */
|
|
522
|
+
const partialContents = [];
|
|
523
|
+
|
|
524
|
+
// Handle Converse API events (flat structure)
|
|
525
|
+
// Note: Don't send message start event here
|
|
526
|
+
// Each content block will send its own start event
|
|
527
|
+
|
|
528
|
+
// Handle tool use start event
|
|
529
|
+
if ("contentBlockIndex" in event && "start" in event) {
|
|
530
|
+
const index = event.contentBlockIndex;
|
|
531
|
+
const start = event.start;
|
|
532
|
+
|
|
533
|
+
// Send stop event for previous block if exists
|
|
534
|
+
if (index > 0 && contentBlockMap[index - 1]) {
|
|
535
|
+
const prevBlock = contentBlockMap[index - 1];
|
|
536
|
+
const prevType = prevBlock.text
|
|
537
|
+
? "text"
|
|
538
|
+
: prevBlock.toolUse
|
|
539
|
+
? "tool_use"
|
|
540
|
+
: prevBlock.reasoningContent
|
|
541
|
+
? "thinking"
|
|
542
|
+
: "unknown";
|
|
543
|
+
|
|
544
|
+
partialContents.push({
|
|
545
|
+
type: prevType,
|
|
546
|
+
position: "stop",
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (start.toolUse) {
|
|
551
|
+
partialContents.push({
|
|
552
|
+
type: "tool_use",
|
|
553
|
+
position: "start",
|
|
554
|
+
content: JSON.stringify({
|
|
555
|
+
toolUseId: start.toolUse.toolUseId,
|
|
556
|
+
name: start.toolUse.name,
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if ("contentBlockIndex" in event && "delta" in event) {
|
|
563
|
+
const delta = event.delta;
|
|
564
|
+
const index = event.contentBlockIndex;
|
|
565
|
+
|
|
566
|
+
// Check if this is a new block (no entry in contentBlockMap)
|
|
567
|
+
// If so, send stop event for previous block first
|
|
568
|
+
if (!contentBlockMap[index] && index > 0 && contentBlockMap[index - 1]) {
|
|
569
|
+
const prevBlock = contentBlockMap[index - 1];
|
|
570
|
+
const prevType = prevBlock.text
|
|
571
|
+
? "text"
|
|
572
|
+
: prevBlock.toolUse
|
|
573
|
+
? "tool_use"
|
|
574
|
+
: prevBlock.reasoningContent
|
|
575
|
+
? "thinking"
|
|
576
|
+
: "unknown";
|
|
577
|
+
|
|
578
|
+
partialContents.push({
|
|
579
|
+
type: prevType,
|
|
580
|
+
position: "stop",
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (delta.text !== undefined) {
|
|
585
|
+
// Send start event if this is a new text block
|
|
586
|
+
if (!contentBlockMap[index]) {
|
|
587
|
+
partialContents.push({
|
|
588
|
+
type: "text",
|
|
589
|
+
position: "start",
|
|
590
|
+
content: "",
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
partialContents.push({
|
|
594
|
+
type: "text",
|
|
595
|
+
position: "delta",
|
|
596
|
+
content: delta.text,
|
|
597
|
+
});
|
|
598
|
+
} else if (delta.toolUse) {
|
|
599
|
+
// Don't send tool input deltas to onPartialMessageContent
|
|
600
|
+
// Tool input will be shown when tool call is complete
|
|
601
|
+
} else if (delta.reasoningContent) {
|
|
602
|
+
// Send start event if this is a new reasoningContent block
|
|
603
|
+
if (!contentBlockMap[index]) {
|
|
604
|
+
partialContents.push({
|
|
605
|
+
type: "thinking",
|
|
606
|
+
position: "start",
|
|
607
|
+
content: "",
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
// Reasoning content (text or redactedContent)
|
|
611
|
+
if (delta.reasoningContent.text) {
|
|
612
|
+
partialContents.push({
|
|
613
|
+
type: "thinking",
|
|
614
|
+
position: "delta",
|
|
615
|
+
content: delta.reasoningContent.text,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// Note: redactedContent is encrypted, so we don't display it
|
|
619
|
+
// but we still need to track it for the final message
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if ("stopReason" in event) {
|
|
624
|
+
// Message stop event
|
|
625
|
+
const blocks = Object.values(contentBlockMap);
|
|
626
|
+
if (blocks.length > 0) {
|
|
627
|
+
const lastBlock = blocks[blocks.length - 1];
|
|
628
|
+
const type =
|
|
629
|
+
lastBlock && "text" in lastBlock
|
|
630
|
+
? "text"
|
|
631
|
+
: lastBlock && "toolUse" in lastBlock
|
|
632
|
+
? "tool_use"
|
|
633
|
+
: lastBlock && "reasoningContent" in lastBlock
|
|
634
|
+
? "thinking"
|
|
635
|
+
: "unknown";
|
|
636
|
+
|
|
637
|
+
partialContents.push({
|
|
638
|
+
type,
|
|
639
|
+
position: "stop",
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return partialContents;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* @param {BedrockAssistantContentBlockWithPartial[]} contentBlocks
|
|
649
|
+
* @returns {AssistantMessage}
|
|
650
|
+
*/
|
|
651
|
+
function convertBedrockContentBlocksToAssistantMessage(contentBlocks) {
|
|
652
|
+
/** @type {AssistantMessage["content"]} */
|
|
653
|
+
const content = [];
|
|
654
|
+
|
|
655
|
+
for (const block of contentBlocks) {
|
|
656
|
+
if (block.text) {
|
|
657
|
+
// Only include non-empty text blocks
|
|
658
|
+
content.push({
|
|
659
|
+
type: "text",
|
|
660
|
+
text: block.text,
|
|
661
|
+
});
|
|
662
|
+
} else if (block.toolUse) {
|
|
663
|
+
content.push({
|
|
664
|
+
type: "tool_use",
|
|
665
|
+
toolUseId: block.toolUse.toolUseId || "",
|
|
666
|
+
toolName: block.toolUse.name || "",
|
|
667
|
+
input:
|
|
668
|
+
/** @type {Record<string, unknown>} */ (block.toolUse.input) ??
|
|
669
|
+
/** @type {Record<string, unknown>} */ ({}),
|
|
670
|
+
});
|
|
671
|
+
} else if (block.reasoningContent) {
|
|
672
|
+
// Reasoning content
|
|
673
|
+
if (block.reasoningContent.text) {
|
|
674
|
+
content.push({
|
|
675
|
+
type: "thinking",
|
|
676
|
+
thinking: block.reasoningContent.text,
|
|
677
|
+
...(block.reasoningContent.signature && {
|
|
678
|
+
provider: {
|
|
679
|
+
fields: { signature: block.reasoningContent.signature },
|
|
680
|
+
},
|
|
681
|
+
}),
|
|
682
|
+
});
|
|
683
|
+
} else if (block.reasoningContent.redactedContent) {
|
|
684
|
+
content.push({
|
|
685
|
+
type: "redacted_thinking",
|
|
686
|
+
provider: {
|
|
687
|
+
fields: { data: block.reasoningContent.redactedContent },
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return {
|
|
695
|
+
role: "assistant",
|
|
696
|
+
content,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @param {string} baseURL
|
|
702
|
+
* @returns {string}
|
|
703
|
+
*/
|
|
704
|
+
function extractRegionFromBaseURL(baseURL) {
|
|
705
|
+
const match = baseURL.match(/bedrock-runtime\.([^.]+)\.amazonaws\.com/);
|
|
706
|
+
if (!match) {
|
|
707
|
+
throw new Error(`Failed to extract region from baseURL: ${baseURL}`);
|
|
708
|
+
}
|
|
709
|
+
return match[1];
|
|
710
|
+
}
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
|
-
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
9
|
-
import { fromIni } from "@aws-sdk/credential-providers";
|
|
10
|
-
import { HttpRequest } from "@smithy/protocol-http";
|
|
11
|
-
import { SignatureV4 } from "@smithy/signature-v4";
|
|
12
8
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
13
9
|
import { retryOnError } from "../utils/retryOnError.mjs";
|
|
14
10
|
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
@@ -112,6 +108,11 @@ export async function callOpenAICompatibleModel(
|
|
|
112
108
|
|
|
113
109
|
// bedrock + sso profile
|
|
114
110
|
const runFetchForBedrock = async () => {
|
|
111
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
112
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
113
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
114
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
115
|
+
|
|
115
116
|
const region =
|
|
116
117
|
url.match(/bedrock-runtime\.([\w-]+)\.amazonaws\.com/)?.[1] ?? "";
|
|
117
118
|
const urlParsed = new URL(url);
|
|
@@ -44,6 +44,7 @@ export async function* readBedrockStreamEvents(reader) {
|
|
|
44
44
|
try {
|
|
45
45
|
const payloadParsed = JSON.parse(payloadDecoded);
|
|
46
46
|
if (payloadParsed.bytes) {
|
|
47
|
+
// Invoke API format (base64 encoded event)
|
|
47
48
|
const event = Buffer.from(payloadParsed.bytes, "base64").toString(
|
|
48
49
|
"utf-8",
|
|
49
50
|
);
|
|
@@ -56,6 +57,9 @@ export async function* readBedrockStreamEvents(reader) {
|
|
|
56
57
|
`Bedrock message received: ${JSON.stringify(payloadParsed.message)}`,
|
|
57
58
|
),
|
|
58
59
|
);
|
|
60
|
+
} else {
|
|
61
|
+
// Converse API format (direct event data)
|
|
62
|
+
yield payloadParsed;
|
|
59
63
|
}
|
|
60
64
|
} catch (err) {
|
|
61
65
|
if (err instanceof Error) {
|
|
@@ -16,6 +16,30 @@ export function evalJSONConfig(configItem) {
|
|
|
16
16
|
return new RegExp(configItem.$regex);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
if (
|
|
20
|
+
Object.keys(configItem).length === 1 &&
|
|
21
|
+
"$env" in configItem &&
|
|
22
|
+
typeof configItem.$env === "string"
|
|
23
|
+
) {
|
|
24
|
+
const value = process.env[configItem.$env];
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Environment variable '${configItem.$env}' is not defined`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
Object.keys(configItem).length === 1 &&
|
|
35
|
+
"$env" in configItem &&
|
|
36
|
+
typeof configItem.$env !== "string"
|
|
37
|
+
) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`The value of '$env' must be a string, got: ${typeof configItem.$env}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
if (Object.keys(configItem).length === 1 && "$has" in configItem) {
|
|
20
44
|
const pattern = evalJSONConfig(configItem.$has);
|
|
21
45
|
/** @param {unknown} value */
|