@iinm/plain-agent 1.8.2 → 1.8.4
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/README.md +6 -2
- package/bin/plain +1 -1
- package/config/config.predefined.json +1 -1
- package/config/prompts.predefined/shortcuts/configure.md +1 -1
- package/dist/main.mjs +473 -0
- package/dist/main.mjs.map +7 -0
- package/package.json +5 -7
- package/src/agent.d.ts +0 -52
- package/src/agent.mjs +0 -204
- package/src/agentLoop.mjs +0 -419
- package/src/agentState.mjs +0 -41
- package/src/claudeCodePlugin.mjs +0 -164
- package/src/cliArgs.mjs +0 -175
- package/src/cliBatch.mjs +0 -144
- package/src/cliCommands.mjs +0 -283
- package/src/cliCompleter.mjs +0 -227
- package/src/cliCost.mjs +0 -309
- package/src/cliFormatter.mjs +0 -413
- package/src/cliInteractive.mjs +0 -526
- package/src/cliInterruptTransform.mjs +0 -51
- package/src/cliMuteTransform.mjs +0 -26
- package/src/cliPasteTransform.mjs +0 -183
- package/src/config.d.ts +0 -36
- package/src/config.mjs +0 -197
- package/src/context/loadAgentRoles.mjs +0 -283
- package/src/context/loadPrompts.mjs +0 -324
- package/src/context/loadUserMessageContext.mjs +0 -147
- package/src/costTracker.mjs +0 -210
- package/src/env.mjs +0 -44
- package/src/main.mjs +0 -278
- package/src/mcpClient.mjs +0 -351
- package/src/mcpIntegration.mjs +0 -160
- package/src/model.d.ts +0 -109
- package/src/modelCaller.mjs +0 -32
- package/src/modelDefinition.d.ts +0 -92
- package/src/prompt.mjs +0 -138
- package/src/providers/anthropic.d.ts +0 -248
- package/src/providers/anthropic.mjs +0 -587
- package/src/providers/bedrock.d.ts +0 -249
- package/src/providers/bedrock.mjs +0 -700
- package/src/providers/gemini.d.ts +0 -208
- package/src/providers/gemini.mjs +0 -754
- package/src/providers/openai.d.ts +0 -281
- package/src/providers/openai.mjs +0 -544
- package/src/providers/openaiCompatible.d.ts +0 -147
- package/src/providers/openaiCompatible.mjs +0 -652
- package/src/providers/platform/awsSigV4.mjs +0 -184
- package/src/providers/platform/azure.mjs +0 -42
- package/src/providers/platform/bedrock.mjs +0 -78
- package/src/providers/platform/googleCloud.mjs +0 -34
- package/src/subagent.mjs +0 -265
- package/src/tmpfile.mjs +0 -27
- package/src/tool.d.ts +0 -74
- package/src/toolExecutor.mjs +0 -236
- package/src/toolInputValidator.mjs +0 -183
- package/src/toolUseApprover.mjs +0 -99
- package/src/tools/askURL.mjs +0 -209
- package/src/tools/askWeb.mjs +0 -208
- package/src/tools/compactContext.d.ts +0 -4
- package/src/tools/compactContext.mjs +0 -87
- package/src/tools/delegateToSubagent.d.ts +0 -4
- package/src/tools/delegateToSubagent.mjs +0 -48
- package/src/tools/execCommand.d.ts +0 -22
- package/src/tools/execCommand.mjs +0 -200
- package/src/tools/patchFile.d.ts +0 -4
- package/src/tools/patchFile.mjs +0 -133
- package/src/tools/reportAsSubagent.d.ts +0 -3
- package/src/tools/reportAsSubagent.mjs +0 -44
- package/src/tools/tmuxCommand.d.ts +0 -14
- package/src/tools/tmuxCommand.mjs +0 -194
- package/src/tools/writeFile.d.ts +0 -4
- package/src/tools/writeFile.mjs +0 -56
- package/src/usageStore.mjs +0 -167
- package/src/utils/evalJSONConfig.mjs +0 -72
- package/src/utils/matchValue.d.ts +0 -6
- package/src/utils/matchValue.mjs +0 -40
- package/src/utils/noThrow.mjs +0 -31
- package/src/utils/notify.mjs +0 -29
- package/src/utils/parseFileRange.mjs +0 -18
- package/src/utils/readFileRange.mjs +0 -33
- package/src/utils/retryOnError.mjs +0 -41
- package/src/voiceInput.mjs +0 -61
- package/src/voiceInputGemini.mjs +0 -105
- package/src/voiceInputOpenAI.mjs +0 -104
- package/src/voiceInputSession.mjs +0 -543
- package/src/voiceToggleKey.mjs +0 -62
|
@@ -1,700 +0,0 @@
|
|
|
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 { loadAwsCredentials, signAwsRequest } from "./platform/awsSigV4.mjs";
|
|
10
|
-
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {import("../modelDefinition").PlatformConfig} platformConfig
|
|
14
|
-
* @param {BedrockConverseModelConfig} modelConfig
|
|
15
|
-
* @param {ModelInput} input
|
|
16
|
-
* @param {number} [retryCount]
|
|
17
|
-
* @returns {Promise<ModelOutput | Error>}
|
|
18
|
-
*/
|
|
19
|
-
export async function callBedrockConverseModel(
|
|
20
|
-
platformConfig,
|
|
21
|
-
modelConfig,
|
|
22
|
-
input,
|
|
23
|
-
retryCount = 0,
|
|
24
|
-
) {
|
|
25
|
-
return await noThrow(async () => {
|
|
26
|
-
const messages = convertGenericMessageToBedrockFormat(input.messages);
|
|
27
|
-
const cachedMessages = modelConfig.enablePromptCaching
|
|
28
|
-
? enablePromptCaching(messages)
|
|
29
|
-
: messages;
|
|
30
|
-
const tools = convertGenericToolDefinitionToBedrockFormat(
|
|
31
|
-
input.tools || [],
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const url = (() => {
|
|
35
|
-
const baseURL = platformConfig.baseURL;
|
|
36
|
-
if (platformConfig.name !== "bedrock") {
|
|
37
|
-
throw new Error(`Unsupported platform: ${platformConfig.name}`);
|
|
38
|
-
}
|
|
39
|
-
return `${baseURL}/model/${modelConfig.model}/converse-stream`;
|
|
40
|
-
})();
|
|
41
|
-
|
|
42
|
-
const region = extractRegionFromBaseURL(platformConfig.baseURL);
|
|
43
|
-
|
|
44
|
-
/** @type {BedrockConverseRequest} */
|
|
45
|
-
const request = {
|
|
46
|
-
messages: cachedMessages,
|
|
47
|
-
...(modelConfig.inferenceConfig && {
|
|
48
|
-
inferenceConfig: modelConfig.inferenceConfig,
|
|
49
|
-
}),
|
|
50
|
-
...(modelConfig.additionalModelRequestFields && {
|
|
51
|
-
additionalModelRequestFields: modelConfig.additionalModelRequestFields,
|
|
52
|
-
}),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Add system messages if present
|
|
56
|
-
const systemMessages = extractSystemMessages(
|
|
57
|
-
input.messages,
|
|
58
|
-
modelConfig.enablePromptCaching,
|
|
59
|
-
);
|
|
60
|
-
if (systemMessages.length > 0) {
|
|
61
|
-
request.system = systemMessages;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Add tools if present
|
|
65
|
-
if (tools.length > 0) {
|
|
66
|
-
request.toolConfig = {
|
|
67
|
-
tools: tools,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const payload = JSON.stringify(request);
|
|
72
|
-
|
|
73
|
-
// Sign request with AWS Signature V4
|
|
74
|
-
const credentials = await loadAwsCredentials(platformConfig.awsProfile);
|
|
75
|
-
const urlParsed = new URL(url);
|
|
76
|
-
const { hostname, pathname } = urlParsed;
|
|
77
|
-
|
|
78
|
-
const signed = signAwsRequest(
|
|
79
|
-
{
|
|
80
|
-
method: "POST",
|
|
81
|
-
hostname,
|
|
82
|
-
path: pathname,
|
|
83
|
-
headers: {
|
|
84
|
-
host: hostname,
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
},
|
|
87
|
-
body: payload,
|
|
88
|
-
},
|
|
89
|
-
{ region, service: "bedrock", credentials },
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const response = await fetch(url, {
|
|
93
|
-
method: signed.method,
|
|
94
|
-
headers: signed.headers,
|
|
95
|
-
body: signed.body,
|
|
96
|
-
signal: AbortSignal.timeout(8 * 60 * 1000),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (response.status !== 200) {
|
|
100
|
-
const errorText = await response.text();
|
|
101
|
-
console.error(
|
|
102
|
-
styleText("red", `Bedrock API error: ${response.status} ${errorText}`),
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// Retry on throttling or server errors
|
|
106
|
-
if (
|
|
107
|
-
(response.status === 429 ||
|
|
108
|
-
response.status === 502 ||
|
|
109
|
-
response.status === 503) &&
|
|
110
|
-
retryCount < 3
|
|
111
|
-
) {
|
|
112
|
-
const retryInterval = Math.min(2 * 2 ** retryCount, 16);
|
|
113
|
-
console.error(
|
|
114
|
-
styleText(
|
|
115
|
-
"yellow",
|
|
116
|
-
`Retrying in ${retryInterval} seconds... (attempt ${retryCount + 1})`,
|
|
117
|
-
),
|
|
118
|
-
);
|
|
119
|
-
await new Promise((resolve) =>
|
|
120
|
-
setTimeout(resolve, retryInterval * 1000),
|
|
121
|
-
);
|
|
122
|
-
return callBedrockConverseModel(
|
|
123
|
-
platformConfig,
|
|
124
|
-
modelConfig,
|
|
125
|
-
input,
|
|
126
|
-
retryCount + 1,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
throw new Error(`Bedrock API error: ${response.status} ${errorText}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!response.body) {
|
|
134
|
-
throw new Error("Response body is empty");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const reader = response.body.getReader();
|
|
138
|
-
|
|
139
|
-
/** @type {BedrockAssistantContentBlockWithPartial[]} */
|
|
140
|
-
const contentBlocks = [];
|
|
141
|
-
/** @type {Record<number, BedrockAssistantContentBlockWithPartial>} */
|
|
142
|
-
const contentBlockMap = {};
|
|
143
|
-
/** @type {BedrockUsage | undefined} */
|
|
144
|
-
let usage;
|
|
145
|
-
|
|
146
|
-
// Process stream events
|
|
147
|
-
for await (const event of readBedrockStreamEvents(reader)) {
|
|
148
|
-
const bedrockEvent = /** @type {BedrockStreamEvent} */ (event);
|
|
149
|
-
|
|
150
|
-
if (input.onPartialMessageContent) {
|
|
151
|
-
const partialContents = convertBedrockStreamEventToPartialContent(
|
|
152
|
-
bedrockEvent,
|
|
153
|
-
contentBlockMap,
|
|
154
|
-
);
|
|
155
|
-
for (const partialContent of partialContents) {
|
|
156
|
-
input.onPartialMessageContent(partialContent);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Handle Converse API events (flat structure)
|
|
161
|
-
// Check for start event first
|
|
162
|
-
if ("contentBlockIndex" in bedrockEvent && "start" in bedrockEvent) {
|
|
163
|
-
const index = bedrockEvent.contentBlockIndex;
|
|
164
|
-
const start = bedrockEvent.start;
|
|
165
|
-
|
|
166
|
-
if (start.toolUse) {
|
|
167
|
-
contentBlockMap[index] = {
|
|
168
|
-
toolUse: {
|
|
169
|
-
toolUseId: start.toolUse.toolUseId || "",
|
|
170
|
-
name: start.toolUse.name || "",
|
|
171
|
-
input: {},
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if ("contentBlockIndex" in bedrockEvent && "delta" in bedrockEvent) {
|
|
178
|
-
const index = bedrockEvent.contentBlockIndex;
|
|
179
|
-
const delta = bedrockEvent.delta;
|
|
180
|
-
|
|
181
|
-
// Initialize content block if not exists
|
|
182
|
-
if (!contentBlockMap[index]) {
|
|
183
|
-
if (delta.text !== undefined) {
|
|
184
|
-
contentBlockMap[index] = { text: "" };
|
|
185
|
-
} else if (delta.toolUse) {
|
|
186
|
-
contentBlockMap[index] = {
|
|
187
|
-
toolUse: {
|
|
188
|
-
toolUseId: delta.toolUse.toolUseId || "",
|
|
189
|
-
name: delta.toolUse.name || "",
|
|
190
|
-
input: {},
|
|
191
|
-
},
|
|
192
|
-
};
|
|
193
|
-
} else if (delta.reasoningContent) {
|
|
194
|
-
contentBlockMap[index] = {
|
|
195
|
-
reasoningContent: {
|
|
196
|
-
text: undefined,
|
|
197
|
-
signature: undefined,
|
|
198
|
-
redactedContent: undefined,
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const block = contentBlockMap[index];
|
|
205
|
-
|
|
206
|
-
// Accumulate content
|
|
207
|
-
if (block && delta.text !== undefined && "text" in block) {
|
|
208
|
-
block.text += delta.text;
|
|
209
|
-
} else if (
|
|
210
|
-
block &&
|
|
211
|
-
delta.toolUse &&
|
|
212
|
-
"toolUse" in block &&
|
|
213
|
-
block.toolUse
|
|
214
|
-
) {
|
|
215
|
-
// Accumulate tool input as JSON string
|
|
216
|
-
if (!block._partialInput) {
|
|
217
|
-
block._partialInput = "";
|
|
218
|
-
}
|
|
219
|
-
block._partialInput += delta.toolUse.input || "";
|
|
220
|
-
} else if (
|
|
221
|
-
block &&
|
|
222
|
-
delta.reasoningContent &&
|
|
223
|
-
"reasoningContent" in block &&
|
|
224
|
-
block.reasoningContent
|
|
225
|
-
) {
|
|
226
|
-
if (delta.reasoningContent.text) {
|
|
227
|
-
block.reasoningContent.text =
|
|
228
|
-
(block.reasoningContent.text || "") + delta.reasoningContent.text;
|
|
229
|
-
}
|
|
230
|
-
if (delta.reasoningContent.signature) {
|
|
231
|
-
block.reasoningContent.signature = delta.reasoningContent.signature;
|
|
232
|
-
}
|
|
233
|
-
if (delta.reasoningContent.redactedContent) {
|
|
234
|
-
block.reasoningContent.redactedContent =
|
|
235
|
-
delta.reasoningContent.redactedContent;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Handle message stop
|
|
241
|
-
if ("stopReason" in bedrockEvent) {
|
|
242
|
-
// Finalize all content blocks
|
|
243
|
-
for (const [_index, block] of Object.entries(contentBlockMap)) {
|
|
244
|
-
// Parse accumulated tool input JSON
|
|
245
|
-
if (
|
|
246
|
-
block &&
|
|
247
|
-
"toolUse" in block &&
|
|
248
|
-
block.toolUse &&
|
|
249
|
-
block._partialInput
|
|
250
|
-
) {
|
|
251
|
-
try {
|
|
252
|
-
block.toolUse.input = JSON.parse(block._partialInput);
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.error(
|
|
255
|
-
styleText(
|
|
256
|
-
"red",
|
|
257
|
-
`Failed to parse tool input JSON for tool "${block.toolUse.name}": ${block._partialInput}`,
|
|
258
|
-
),
|
|
259
|
-
);
|
|
260
|
-
block.toolUse.input = {
|
|
261
|
-
err: String(err),
|
|
262
|
-
raw: block._partialInput,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
delete block._partialInput;
|
|
266
|
-
}
|
|
267
|
-
contentBlocks.push(block);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Handle metadata
|
|
272
|
-
if ("usage" in bedrockEvent && "metrics" in bedrockEvent) {
|
|
273
|
-
usage = bedrockEvent.usage;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const message =
|
|
278
|
-
convertBedrockContentBlocksToAssistantMessage(contentBlocks);
|
|
279
|
-
|
|
280
|
-
const providerTokenUsage = usage
|
|
281
|
-
? {
|
|
282
|
-
inputTokens: usage.inputTokens,
|
|
283
|
-
outputTokens: usage.outputTokens,
|
|
284
|
-
totalTokens: usage.totalTokens,
|
|
285
|
-
...(usage.cacheReadInputTokens && {
|
|
286
|
-
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
287
|
-
}),
|
|
288
|
-
...(usage.cacheWriteInputTokens && {
|
|
289
|
-
cacheWriteInputTokens: usage.cacheWriteInputTokens,
|
|
290
|
-
}),
|
|
291
|
-
}
|
|
292
|
-
: {};
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
message,
|
|
296
|
-
providerTokenUsage,
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @param {Message[]} messages
|
|
303
|
-
* @returns {BedrockMessage[]}
|
|
304
|
-
*/
|
|
305
|
-
function convertGenericMessageToBedrockFormat(messages) {
|
|
306
|
-
/** @type {BedrockMessage[]} */
|
|
307
|
-
const bedrockMessages = [];
|
|
308
|
-
|
|
309
|
-
for (const message of messages) {
|
|
310
|
-
if (message.role === "system") {
|
|
311
|
-
// System messages handled separately
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (message.role === "user") {
|
|
316
|
-
/** @type {BedrockContentBlock[]} */
|
|
317
|
-
const content = [];
|
|
318
|
-
|
|
319
|
-
for (const part of message.content) {
|
|
320
|
-
if (part.type === "text" && part.text) {
|
|
321
|
-
// Only include non-empty text blocks
|
|
322
|
-
content.push({ text: part.text });
|
|
323
|
-
} else if (part.type === "image") {
|
|
324
|
-
content.push({
|
|
325
|
-
image: {
|
|
326
|
-
format: /** @type {"png" | "jpeg" | "gif" | "webp"} */ (
|
|
327
|
-
part.mimeType.split("/")[1]
|
|
328
|
-
),
|
|
329
|
-
source: {
|
|
330
|
-
bytes: part.data,
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
});
|
|
334
|
-
} else if (part.type === "tool_result") {
|
|
335
|
-
/** @type {BedrockToolResultContent[]} */
|
|
336
|
-
const toolResultContent = [];
|
|
337
|
-
for (const resultPart of part.content) {
|
|
338
|
-
if (resultPart.type === "text") {
|
|
339
|
-
toolResultContent.push({ text: resultPart.text });
|
|
340
|
-
} else if (resultPart.type === "image") {
|
|
341
|
-
toolResultContent.push({
|
|
342
|
-
image: {
|
|
343
|
-
format: /** @type {"png" | "jpeg" | "gif" | "webp"} */ (
|
|
344
|
-
resultPart.mimeType.split("/")[1]
|
|
345
|
-
),
|
|
346
|
-
source: {
|
|
347
|
-
bytes: resultPart.data,
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
content.push({
|
|
355
|
-
toolResult: {
|
|
356
|
-
toolUseId: part.toolUseId,
|
|
357
|
-
content: toolResultContent,
|
|
358
|
-
status: part.isError ? "error" : "success",
|
|
359
|
-
},
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
bedrockMessages.push({ role: "user", content });
|
|
365
|
-
} else if (message.role === "assistant") {
|
|
366
|
-
/** @type {BedrockAssistantContentBlock[]} */
|
|
367
|
-
const content = [];
|
|
368
|
-
|
|
369
|
-
for (const part of message.content) {
|
|
370
|
-
if (part.type === "text") {
|
|
371
|
-
content.push({ text: part.text });
|
|
372
|
-
} else if (part.type === "thinking") {
|
|
373
|
-
// Extended thinking requires signature for multi-turn conversations
|
|
374
|
-
const signature = /** @type {string | undefined} */ (
|
|
375
|
-
part.provider?.fields?.signature
|
|
376
|
-
);
|
|
377
|
-
if (signature) {
|
|
378
|
-
content.push({
|
|
379
|
-
reasoningContent: {
|
|
380
|
-
reasoningText: {
|
|
381
|
-
text: part.thinking,
|
|
382
|
-
signature,
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
} else if (part.type === "redacted_thinking") {
|
|
388
|
-
// Redacted thinking must be included in message history
|
|
389
|
-
const data = /** @type {string | undefined} */ (
|
|
390
|
-
part.provider?.fields?.data
|
|
391
|
-
);
|
|
392
|
-
if (data) {
|
|
393
|
-
content.push({
|
|
394
|
-
reasoningContent: {
|
|
395
|
-
redactedContent: data,
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
} else if (part.type === "tool_use") {
|
|
400
|
-
content.push({
|
|
401
|
-
toolUse: {
|
|
402
|
-
toolUseId: part.toolUseId,
|
|
403
|
-
name: part.toolName,
|
|
404
|
-
input: part.input,
|
|
405
|
-
},
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
bedrockMessages.push({ role: "assistant", content });
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return bedrockMessages;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* @param {Message[]} messages
|
|
419
|
-
* @param {boolean} [enablePromptCaching]
|
|
420
|
-
* @returns {import("./bedrock").BedrockSystemContentBlock[]}
|
|
421
|
-
*/
|
|
422
|
-
function extractSystemMessages(messages, enablePromptCaching = false) {
|
|
423
|
-
/** @type {import("./bedrock").BedrockSystemContentBlock[]} */
|
|
424
|
-
const systemBlocks = [];
|
|
425
|
-
|
|
426
|
-
for (const message of messages) {
|
|
427
|
-
if (message.role === "system") {
|
|
428
|
-
for (const part of message.content) {
|
|
429
|
-
systemBlocks.push({ text: part.text });
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Add cache point at the end of system messages if enabled
|
|
435
|
-
if (enablePromptCaching && systemBlocks.length > 0) {
|
|
436
|
-
systemBlocks.push({ cachePoint: { type: "default" } });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return systemBlocks;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* @param {ToolDefinition[]} tools
|
|
444
|
-
* @returns {BedrockTool[]}
|
|
445
|
-
*/
|
|
446
|
-
function convertGenericToolDefinitionToBedrockFormat(tools) {
|
|
447
|
-
return tools.map((tool) => ({
|
|
448
|
-
toolSpec: {
|
|
449
|
-
name: tool.name,
|
|
450
|
-
description: tool.description,
|
|
451
|
-
inputSchema: {
|
|
452
|
-
json: tool.inputSchema,
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
}));
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* @param {BedrockMessage[]} messages
|
|
460
|
-
* @returns {BedrockMessage[]}
|
|
461
|
-
*/
|
|
462
|
-
function enablePromptCaching(messages) {
|
|
463
|
-
// Find user message indices
|
|
464
|
-
const userMessageIndices = messages
|
|
465
|
-
.map((msg, index) => (msg.role === "user" ? index : -1))
|
|
466
|
-
.filter((index) => index !== -1);
|
|
467
|
-
|
|
468
|
-
// Target last two user messages for caching
|
|
469
|
-
const cacheTargetIndices = [
|
|
470
|
-
userMessageIndices.at(-1),
|
|
471
|
-
userMessageIndices.at(-2),
|
|
472
|
-
].filter((index) => index !== undefined);
|
|
473
|
-
|
|
474
|
-
const cachedMessages = messages.map((message, index) => {
|
|
475
|
-
if (cacheTargetIndices.includes(index)) {
|
|
476
|
-
// Add cache point as a separate block at the end
|
|
477
|
-
// Only add to messages without tool results (tool results don't support cachePoint)
|
|
478
|
-
if (message.role === "user") {
|
|
479
|
-
const content = /** @type {BedrockContentBlock[]} */ ([
|
|
480
|
-
...message.content,
|
|
481
|
-
]);
|
|
482
|
-
// Check if content contains toolResult
|
|
483
|
-
const hasToolResult = content.some(
|
|
484
|
-
(block) => "toolResult" in block && block.toolResult,
|
|
485
|
-
);
|
|
486
|
-
if (!hasToolResult) {
|
|
487
|
-
content.push({ cachePoint: { type: "default" } });
|
|
488
|
-
return { ...message, content };
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (message.role === "assistant") {
|
|
492
|
-
const content = /** @type {BedrockAssistantContentBlock[]} */ ([
|
|
493
|
-
...message.content,
|
|
494
|
-
]);
|
|
495
|
-
content.push({ cachePoint: { type: "default" } });
|
|
496
|
-
return { ...message, content };
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return message;
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
return cachedMessages;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* @param {BedrockStreamEvent} event
|
|
507
|
-
* @param {Record<number, import("./bedrock").BedrockAssistantContentBlockWithPartial>} contentBlockMap
|
|
508
|
-
* @returns {PartialMessageContent[]}
|
|
509
|
-
*/
|
|
510
|
-
function convertBedrockStreamEventToPartialContent(event, contentBlockMap) {
|
|
511
|
-
/** @type {PartialMessageContent[]} */
|
|
512
|
-
const partialContents = [];
|
|
513
|
-
|
|
514
|
-
// Handle Converse API events (flat structure)
|
|
515
|
-
// Note: Don't send message start event here
|
|
516
|
-
// Each content block will send its own start event
|
|
517
|
-
|
|
518
|
-
// Handle tool use start event
|
|
519
|
-
if ("contentBlockIndex" in event && "start" in event) {
|
|
520
|
-
const index = event.contentBlockIndex;
|
|
521
|
-
const start = event.start;
|
|
522
|
-
|
|
523
|
-
// Send stop event for previous block if exists
|
|
524
|
-
if (index > 0 && contentBlockMap[index - 1]) {
|
|
525
|
-
const prevBlock = contentBlockMap[index - 1];
|
|
526
|
-
const prevType = prevBlock.text
|
|
527
|
-
? "text"
|
|
528
|
-
: prevBlock.toolUse
|
|
529
|
-
? "tool_use"
|
|
530
|
-
: prevBlock.reasoningContent
|
|
531
|
-
? "thinking"
|
|
532
|
-
: "unknown";
|
|
533
|
-
|
|
534
|
-
partialContents.push({
|
|
535
|
-
type: prevType,
|
|
536
|
-
position: "stop",
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (start.toolUse) {
|
|
541
|
-
partialContents.push({
|
|
542
|
-
type: "tool_use",
|
|
543
|
-
position: "start",
|
|
544
|
-
content: JSON.stringify({
|
|
545
|
-
toolUseId: start.toolUse.toolUseId,
|
|
546
|
-
name: start.toolUse.name,
|
|
547
|
-
}),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if ("contentBlockIndex" in event && "delta" in event) {
|
|
553
|
-
const delta = event.delta;
|
|
554
|
-
const index = event.contentBlockIndex;
|
|
555
|
-
|
|
556
|
-
// Check if this is a new block (no entry in contentBlockMap)
|
|
557
|
-
// If so, send stop event for previous block first
|
|
558
|
-
if (!contentBlockMap[index] && index > 0 && contentBlockMap[index - 1]) {
|
|
559
|
-
const prevBlock = contentBlockMap[index - 1];
|
|
560
|
-
const prevType = prevBlock.text
|
|
561
|
-
? "text"
|
|
562
|
-
: prevBlock.toolUse
|
|
563
|
-
? "tool_use"
|
|
564
|
-
: prevBlock.reasoningContent
|
|
565
|
-
? "thinking"
|
|
566
|
-
: "unknown";
|
|
567
|
-
|
|
568
|
-
partialContents.push({
|
|
569
|
-
type: prevType,
|
|
570
|
-
position: "stop",
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (delta.text !== undefined) {
|
|
575
|
-
// Send start event if this is a new text block
|
|
576
|
-
if (!contentBlockMap[index]) {
|
|
577
|
-
partialContents.push({
|
|
578
|
-
type: "text",
|
|
579
|
-
position: "start",
|
|
580
|
-
content: "",
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
partialContents.push({
|
|
584
|
-
type: "text",
|
|
585
|
-
position: "delta",
|
|
586
|
-
content: delta.text,
|
|
587
|
-
});
|
|
588
|
-
} else if (delta.toolUse) {
|
|
589
|
-
// Don't send tool input deltas to onPartialMessageContent
|
|
590
|
-
// Tool input will be shown when tool call is complete
|
|
591
|
-
} else if (delta.reasoningContent) {
|
|
592
|
-
// Send start event if this is a new reasoningContent block
|
|
593
|
-
if (!contentBlockMap[index]) {
|
|
594
|
-
partialContents.push({
|
|
595
|
-
type: "thinking",
|
|
596
|
-
position: "start",
|
|
597
|
-
content: "",
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
// Reasoning content (text or redactedContent)
|
|
601
|
-
if (delta.reasoningContent.text) {
|
|
602
|
-
partialContents.push({
|
|
603
|
-
type: "thinking",
|
|
604
|
-
position: "delta",
|
|
605
|
-
content: delta.reasoningContent.text,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
// Note: redactedContent is encrypted, so we don't display it
|
|
609
|
-
// but we still need to track it for the final message
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if ("stopReason" in event) {
|
|
614
|
-
// Message stop event
|
|
615
|
-
const blocks = Object.values(contentBlockMap);
|
|
616
|
-
if (blocks.length > 0) {
|
|
617
|
-
const lastBlock = blocks[blocks.length - 1];
|
|
618
|
-
const type =
|
|
619
|
-
lastBlock && "text" in lastBlock
|
|
620
|
-
? "text"
|
|
621
|
-
: lastBlock && "toolUse" in lastBlock
|
|
622
|
-
? "tool_use"
|
|
623
|
-
: lastBlock && "reasoningContent" in lastBlock
|
|
624
|
-
? "thinking"
|
|
625
|
-
: "unknown";
|
|
626
|
-
|
|
627
|
-
partialContents.push({
|
|
628
|
-
type,
|
|
629
|
-
position: "stop",
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return partialContents;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* @param {BedrockAssistantContentBlockWithPartial[]} contentBlocks
|
|
639
|
-
* @returns {AssistantMessage}
|
|
640
|
-
*/
|
|
641
|
-
function convertBedrockContentBlocksToAssistantMessage(contentBlocks) {
|
|
642
|
-
/** @type {AssistantMessage["content"]} */
|
|
643
|
-
const content = [];
|
|
644
|
-
|
|
645
|
-
for (const block of contentBlocks) {
|
|
646
|
-
if (block.text) {
|
|
647
|
-
// Only include non-empty text blocks
|
|
648
|
-
content.push({
|
|
649
|
-
type: "text",
|
|
650
|
-
text: block.text,
|
|
651
|
-
});
|
|
652
|
-
} else if (block.toolUse) {
|
|
653
|
-
content.push({
|
|
654
|
-
type: "tool_use",
|
|
655
|
-
toolUseId: block.toolUse.toolUseId || "",
|
|
656
|
-
toolName: block.toolUse.name || "",
|
|
657
|
-
input:
|
|
658
|
-
/** @type {Record<string, unknown>} */ (block.toolUse.input) ??
|
|
659
|
-
/** @type {Record<string, unknown>} */ ({}),
|
|
660
|
-
});
|
|
661
|
-
} else if (block.reasoningContent) {
|
|
662
|
-
// Reasoning content
|
|
663
|
-
if (block.reasoningContent.text) {
|
|
664
|
-
content.push({
|
|
665
|
-
type: "thinking",
|
|
666
|
-
thinking: block.reasoningContent.text,
|
|
667
|
-
...(block.reasoningContent.signature && {
|
|
668
|
-
provider: {
|
|
669
|
-
fields: { signature: block.reasoningContent.signature },
|
|
670
|
-
},
|
|
671
|
-
}),
|
|
672
|
-
});
|
|
673
|
-
} else if (block.reasoningContent.redactedContent) {
|
|
674
|
-
content.push({
|
|
675
|
-
type: "redacted_thinking",
|
|
676
|
-
provider: {
|
|
677
|
-
fields: { data: block.reasoningContent.redactedContent },
|
|
678
|
-
},
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
return {
|
|
685
|
-
role: "assistant",
|
|
686
|
-
content,
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* @param {string} baseURL
|
|
692
|
-
* @returns {string}
|
|
693
|
-
*/
|
|
694
|
-
function extractRegionFromBaseURL(baseURL) {
|
|
695
|
-
const match = baseURL.match(/bedrock-runtime\.([^.]+)\.amazonaws\.com/);
|
|
696
|
-
if (!match) {
|
|
697
|
-
throw new Error(`Failed to extract region from baseURL: ${baseURL}`);
|
|
698
|
-
}
|
|
699
|
-
return match[1];
|
|
700
|
-
}
|