Skip to content

Commit d062770

Browse files
committed
fix: resolve AI_InvalidPromptError in OpenRouter AI SDK tool results
- Fixed tool result output format to use typed object { type: 'text', value: string } instead of plain string to satisfy AI SDK validation schema - Added tool name resolution by building a map of tool call IDs to names - Updated tests to reflect new output format
1 parent a6136d0 commit d062770

File tree

3 files changed

+74
-9
lines changed

3 files changed

+74
-9
lines changed

src/api/providers/openrouter.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
type ModelRecord,
77
openRouterDefaultModelId,
88
openRouterDefaultModelInfo,
9-
NATIVE_TOOL_DEFAULTS,
109
OPENROUTER_DEFAULT_PROVIDER_NAME,
1110
DEEP_SEEK_DEFAULT_TEMPERATURE,
1211
} from "@roo-code/types"
@@ -183,7 +182,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
183182
}
184183

185184
// Apply tool preferences for models accessed through routers
186-
info = applyRouterToolPreferences(id, { ...NATIVE_TOOL_DEFAULTS, ...info })
185+
info = applyRouterToolPreferences(id, info)
187186

188187
const isDeepSeekR1 = id.startsWith("deepseek/deepseek-r1") || id === "perplexity/sonar-reasoning"
189188

src/api/transform/__tests__/ai-sdk.spec.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,19 @@ describe("AI SDK conversion utilities", () => {
7373
})
7474
})
7575

76-
it("converts tool results into separate tool messages", () => {
76+
it("converts tool results into separate tool messages with resolved tool names", () => {
7777
const messages: Anthropic.Messages.MessageParam[] = [
78+
{
79+
role: "assistant",
80+
content: [
81+
{
82+
type: "tool_use",
83+
id: "call_123",
84+
name: "read_file",
85+
input: { path: "test.ts" },
86+
},
87+
],
88+
},
7889
{
7990
role: "user",
8091
content: [
@@ -89,15 +100,56 @@ describe("AI SDK conversion utilities", () => {
89100

90101
const result = convertToAiSdkMessages(messages)
91102

92-
expect(result).toHaveLength(1)
103+
expect(result).toHaveLength(2)
93104
expect(result[0]).toEqual({
105+
role: "assistant",
106+
content: [
107+
{
108+
type: "tool-call",
109+
toolCallId: "call_123",
110+
toolName: "read_file",
111+
args: { path: "test.ts" },
112+
},
113+
],
114+
})
115+
expect(result[1]).toEqual({
94116
role: "tool",
95117
content: [
96118
{
97119
type: "tool-result",
98120
toolCallId: "call_123",
99-
toolName: "",
100-
output: "Tool result content",
121+
toolName: "read_file",
122+
output: { type: "text", value: "Tool result content" },
123+
},
124+
],
125+
})
126+
})
127+
128+
it("uses unknown_tool for tool results without matching tool call", () => {
129+
const messages: Anthropic.Messages.MessageParam[] = [
130+
{
131+
role: "user",
132+
content: [
133+
{
134+
type: "tool_result",
135+
tool_use_id: "call_orphan",
136+
content: "Orphan result",
137+
},
138+
],
139+
},
140+
]
141+
142+
const result = convertToAiSdkMessages(messages)
143+
144+
expect(result).toHaveLength(1)
145+
expect(result[0]).toEqual({
146+
role: "tool",
147+
content: [
148+
{
149+
type: "tool-result",
150+
toolCallId: "call_orphan",
151+
toolName: "unknown_tool",
152+
output: { type: "text", value: "Orphan result" },
101153
},
102154
],
103155
})

src/api/transform/ai-sdk.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ import type { ApiStreamChunk } from "./stream"
1818
export function convertToAiSdkMessages(messages: Anthropic.Messages.MessageParam[]): CoreMessage[] {
1919
const coreMessages: CoreMessage[] = []
2020

21+
// First pass: build a map of tool call IDs to tool names from assistant messages
22+
const toolCallIdToName = new Map<string, string>()
23+
for (const message of messages) {
24+
if (message.role === "assistant" && typeof message.content !== "string") {
25+
for (const part of message.content) {
26+
if (part.type === "tool_use") {
27+
toolCallIdToName.set(part.id, part.name)
28+
}
29+
}
30+
}
31+
}
32+
2133
for (const message of messages) {
2234
if (typeof message.content === "string") {
2335
coreMessages.push({
@@ -33,7 +45,7 @@ export function convertToAiSdkMessages(messages: Anthropic.Messages.MessageParam
3345
type: "tool-result"
3446
toolCallId: string
3547
toolName: string
36-
output: unknown
48+
output: { type: "text"; value: string }
3749
}> = []
3850

3951
for (const part of message.content) {
@@ -60,11 +72,13 @@ export function convertToAiSdkMessages(messages: Anthropic.Messages.MessageParam
6072
})
6173
.join("\n") ?? ""
6274
}
75+
// Look up the tool name from the tool call ID
76+
const toolName = toolCallIdToName.get(part.tool_use_id) ?? "unknown_tool"
6377
toolResults.push({
6478
type: "tool-result",
6579
toolCallId: part.tool_use_id,
66-
toolName: "", // Will be filled from context
67-
output: content || "(empty)",
80+
toolName,
81+
output: { type: "text", value: content || "(empty)" },
6882
})
6983
}
7084
}

0 commit comments

Comments
 (0)