mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: truncate AWS Bedrock toolUseId to 64 characters (#10902)
This commit is contained in:
parent
3877d02498
commit
f9a3a178db
2 changed files with 222 additions and 6 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import { convertToBedrockConverseMessages } from "../bedrock-converse-format"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ContentBlock, ToolResultContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
import { OPENAI_CALL_ID_MAX_LENGTH } from "../../../utils/tool-id"
|
||||
|
||||
describe("convertToBedrockConverseMessages", () => {
|
||||
it("converts simple text messages correctly", () => {
|
||||
|
|
@ -341,4 +342,218 @@ describe("convertToBedrockConverseMessages", () => {
|
|||
const textBlock = result[0].content[0] as ContentBlock
|
||||
expect(textBlock).toEqual({ text: "Hello world" })
|
||||
})
|
||||
|
||||
describe("toolUseId sanitization for Bedrock 64-char limit", () => {
|
||||
it("truncates toolUseId longer than 64 characters in tool_use blocks", () => {
|
||||
const longId = "a".repeat(100)
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: longId,
|
||||
name: "read_file",
|
||||
input: { path: "test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
const toolBlock = result[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolUse" in toolBlock && toolBlock.toolUse && toolBlock.toolUse.toolUseId) {
|
||||
expect(toolBlock.toolUse.toolUseId.length).toBeLessThanOrEqual(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
expect(toolBlock.toolUse.toolUseId.length).toBe(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
expect(toolBlock.toolUse.toolUseId).toContain("_")
|
||||
} else {
|
||||
expect.fail("Expected tool use block not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("truncates toolUseId longer than 64 characters in tool_result blocks with string content", () => {
|
||||
const longId = "b".repeat(100)
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: longId,
|
||||
content: "Result content",
|
||||
} as any,
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
const resultBlock = result[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolResult" in resultBlock && resultBlock.toolResult && resultBlock.toolResult.toolUseId) {
|
||||
expect(resultBlock.toolResult.toolUseId.length).toBeLessThanOrEqual(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
expect(resultBlock.toolResult.toolUseId.length).toBe(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
expect(resultBlock.toolResult.toolUseId).toContain("_")
|
||||
} else {
|
||||
expect.fail("Expected tool result block not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("truncates toolUseId longer than 64 characters in tool_result blocks with array content", () => {
|
||||
const longId = "c".repeat(100)
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: longId,
|
||||
content: [{ type: "text", text: "Result content" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
const resultBlock = result[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolResult" in resultBlock && resultBlock.toolResult && resultBlock.toolResult.toolUseId) {
|
||||
expect(resultBlock.toolResult.toolUseId.length).toBeLessThanOrEqual(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
expect(resultBlock.toolResult.toolUseId.length).toBe(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
} else {
|
||||
expect.fail("Expected tool result block not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("keeps toolUseId unchanged when under 64 characters", () => {
|
||||
const shortId = "short-id-123"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: shortId,
|
||||
name: "read_file",
|
||||
input: { path: "test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
const toolBlock = result[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolUse" in toolBlock && toolBlock.toolUse) {
|
||||
expect(toolBlock.toolUse.toolUseId).toBe(shortId)
|
||||
} else {
|
||||
expect.fail("Expected tool use block not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("produces consistent truncated IDs for the same input", () => {
|
||||
const longId = "d".repeat(100)
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: longId,
|
||||
name: "read_file",
|
||||
input: { path: "test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result1 = convertToBedrockConverseMessages(messages)
|
||||
const result2 = convertToBedrockConverseMessages(messages)
|
||||
|
||||
const toolBlock1 = result1[0]?.content?.[0] as ContentBlock
|
||||
const toolBlock2 = result2[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolUse" in toolBlock1 && toolBlock1.toolUse && "toolUse" in toolBlock2 && toolBlock2.toolUse) {
|
||||
expect(toolBlock1.toolUse.toolUseId).toBe(toolBlock2.toolUse.toolUseId)
|
||||
} else {
|
||||
expect.fail("Expected tool use blocks not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("produces different truncated IDs for different long inputs", () => {
|
||||
const longId1 = "e".repeat(100)
|
||||
const longId2 = "f".repeat(100)
|
||||
|
||||
const messages1: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "tool_use", id: longId1, name: "read_file", input: {} }],
|
||||
},
|
||||
]
|
||||
const messages2: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "tool_use", id: longId2, name: "read_file", input: {} }],
|
||||
},
|
||||
]
|
||||
|
||||
const result1 = convertToBedrockConverseMessages(messages1)
|
||||
const result2 = convertToBedrockConverseMessages(messages2)
|
||||
|
||||
const toolBlock1 = result1[0]?.content?.[0] as ContentBlock
|
||||
const toolBlock2 = result2[0]?.content?.[0] as ContentBlock
|
||||
|
||||
if ("toolUse" in toolBlock1 && toolBlock1.toolUse && "toolUse" in toolBlock2 && toolBlock2.toolUse) {
|
||||
expect(toolBlock1.toolUse.toolUseId).not.toBe(toolBlock2.toolUse.toolUseId)
|
||||
} else {
|
||||
expect.fail("Expected tool use blocks not found")
|
||||
}
|
||||
})
|
||||
|
||||
it("matching tool_use and tool_result IDs are both truncated consistently", () => {
|
||||
const longId = "g".repeat(100)
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: longId,
|
||||
name: "read_file",
|
||||
input: { path: "test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: longId,
|
||||
content: "File contents",
|
||||
} as any,
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToBedrockConverseMessages(messages)
|
||||
|
||||
const toolUseBlock = result[0]?.content?.[0] as ContentBlock
|
||||
const toolResultBlock = result[1]?.content?.[0] as ContentBlock
|
||||
|
||||
if (
|
||||
"toolUse" in toolUseBlock &&
|
||||
toolUseBlock.toolUse &&
|
||||
toolUseBlock.toolUse.toolUseId &&
|
||||
"toolResult" in toolResultBlock &&
|
||||
toolResultBlock.toolResult &&
|
||||
toolResultBlock.toolResult.toolUseId
|
||||
) {
|
||||
expect(toolUseBlock.toolUse.toolUseId).toBe(toolResultBlock.toolResult.toolUseId)
|
||||
expect(toolUseBlock.toolUse.toolUseId.length).toBeLessThanOrEqual(OPENAI_CALL_ID_MAX_LENGTH)
|
||||
} else {
|
||||
expect.fail("Expected tool use and result blocks not found")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ConversationRole, Message, ContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
import { sanitizeOpenAiCallId } from "../../utils/tool-id"
|
||||
|
||||
interface BedrockMessageContent {
|
||||
type: "text" | "image" | "video" | "tool_use" | "tool_result"
|
||||
|
|
@ -90,7 +91,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
// Native-only: keep input as JSON object for Bedrock's toolUse format
|
||||
return {
|
||||
toolUse: {
|
||||
toolUseId: messageBlock.id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.id || ""),
|
||||
name: messageBlock.name || "",
|
||||
input: messageBlock.input || {},
|
||||
},
|
||||
|
|
@ -104,7 +105,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
if (typeof messageBlock.content === "string") {
|
||||
return {
|
||||
toolResult: {
|
||||
toolUseId: messageBlock.tool_use_id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.tool_use_id || ""),
|
||||
content: [
|
||||
{
|
||||
text: messageBlock.content,
|
||||
|
|
@ -118,7 +119,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
if (Array.isArray(messageBlock.content)) {
|
||||
return {
|
||||
toolResult: {
|
||||
toolUseId: messageBlock.tool_use_id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.tool_use_id || ""),
|
||||
content: messageBlock.content.map((item) => ({
|
||||
text: typeof item === "string" ? item : item.text || String(item),
|
||||
})),
|
||||
|
|
@ -132,7 +133,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
if (messageBlock.output && typeof messageBlock.output === "string") {
|
||||
return {
|
||||
toolResult: {
|
||||
toolUseId: messageBlock.tool_use_id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.tool_use_id || ""),
|
||||
content: [
|
||||
{
|
||||
text: messageBlock.output,
|
||||
|
|
@ -146,7 +147,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
if (Array.isArray(messageBlock.output)) {
|
||||
return {
|
||||
toolResult: {
|
||||
toolUseId: messageBlock.tool_use_id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.tool_use_id || ""),
|
||||
content: messageBlock.output.map((part) => {
|
||||
if (typeof part === "object" && "text" in part) {
|
||||
return { text: part.text }
|
||||
|
|
@ -165,7 +166,7 @@ export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Me
|
|||
// Default case
|
||||
return {
|
||||
toolResult: {
|
||||
toolUseId: messageBlock.tool_use_id || "",
|
||||
toolUseId: sanitizeOpenAiCallId(messageBlock.tool_use_id || ""),
|
||||
content: [
|
||||
{
|
||||
text: String(messageBlock.output || ""),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue