Skip to content

Commit b57ec9d

Browse files
test: add regression test for issue #181 (tool response for image) (#371)
* test: add regression test for issue #181 (tool response for image) Co-Authored-By: Robert Yeakel <robert.yeakel@openrouter.ai> * test: add image-data type test case for issue #181 Co-Authored-By: Robert Yeakel <robert.yeakel@openrouter.ai> * test: use exact format from original issue #181 (type: media) Co-Authored-By: Robert Yeakel <robert.yeakel@openrouter.ai> * test: use AI SDK v3 file-data format for issue #181 regression test Co-Authored-By: Robert Yeakel <robert.yeakel@openrouter.ai> * test: remove root cause analysis from issue #181 test comments Co-Authored-By: Robert Yeakel <robert.yeakel@openrouter.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 9952a79 commit b57ec9d

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed

.changeset/cold-kiwis-admire.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* Regression test for GitHub issue #181
3+
* https://github.com/OpenRouterTeam/ai-sdk-provider/issues/181
4+
*
5+
* Issue: "tool response for image not working"
6+
*
7+
* Reported behavior: When a tool returns an image in its response using the AI SDK's
8+
* toModelOutput with multimodal content, the model cannot view or interpret the image.
9+
*
10+
* Observed behavior: Tool results with multimodal content (images) are JSON.stringified.
11+
*/
12+
import { describe, expect, it } from 'vitest';
13+
import { convertToOpenRouterChatMessages } from '@/src/chat/convert-to-openrouter-chat-messages';
14+
15+
describe('Issue #181: Tool response for image not working', () => {
16+
it('should stringify tool result with content type containing image', () => {
17+
const result = convertToOpenRouterChatMessages([
18+
{
19+
role: 'tool',
20+
content: [
21+
{
22+
type: 'tool-result',
23+
toolCallId: 'call-123',
24+
toolName: 'screenshot_tool',
25+
output: {
26+
type: 'content',
27+
value: [
28+
{
29+
type: 'text',
30+
text: 'Here is the screenshot:',
31+
},
32+
{
33+
type: 'file-data',
34+
data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
35+
mediaType: 'image/png',
36+
},
37+
],
38+
},
39+
},
40+
],
41+
},
42+
]);
43+
44+
expect(result).toHaveLength(1);
45+
expect(result[0]).toMatchObject({
46+
role: 'tool',
47+
tool_call_id: 'call-123',
48+
});
49+
50+
expect(typeof result[0]?.content).toBe('string');
51+
52+
const content = result[0]?.content as string;
53+
expect(content).toContain('"type":"text"');
54+
expect(content).toContain('"type":"file-data"');
55+
expect(content).toContain('iVBORw0KGgo'); // Base64 image data
56+
});
57+
58+
it('should stringify tool result with content type containing image URL', () => {
59+
const result = convertToOpenRouterChatMessages([
60+
{
61+
role: 'tool',
62+
content: [
63+
{
64+
type: 'tool-result',
65+
toolCallId: 'call-456',
66+
toolName: 'image_generator',
67+
output: {
68+
type: 'content',
69+
value: [
70+
{
71+
type: 'text',
72+
text: 'Generated image:',
73+
},
74+
{
75+
type: 'file-url',
76+
url: 'https://example.com/generated-image.png',
77+
},
78+
],
79+
},
80+
},
81+
],
82+
},
83+
]);
84+
85+
expect(result).toHaveLength(1);
86+
expect(typeof result[0]?.content).toBe('string');
87+
88+
const content = result[0]?.content as string;
89+
expect(content).toContain('"type":"file-url"');
90+
expect(content).toContain('https://example.com/generated-image.png');
91+
});
92+
93+
it('should handle text-only tool results normally', () => {
94+
const result = convertToOpenRouterChatMessages([
95+
{
96+
role: 'tool',
97+
content: [
98+
{
99+
type: 'tool-result',
100+
toolCallId: 'call-789',
101+
toolName: 'calculator',
102+
output: {
103+
type: 'text',
104+
value: 'The result is 42',
105+
},
106+
},
107+
],
108+
},
109+
]);
110+
111+
expect(result).toHaveLength(1);
112+
expect(result[0]).toMatchObject({
113+
role: 'tool',
114+
tool_call_id: 'call-789',
115+
content: 'The result is 42',
116+
});
117+
});
118+
119+
it('should handle JSON tool results normally', () => {
120+
const result = convertToOpenRouterChatMessages([
121+
{
122+
role: 'tool',
123+
content: [
124+
{
125+
type: 'tool-result',
126+
toolCallId: 'call-abc',
127+
toolName: 'weather_api',
128+
output: {
129+
type: 'json',
130+
value: { temperature: 72, unit: 'F', location: 'San Francisco' },
131+
},
132+
},
133+
],
134+
},
135+
]);
136+
137+
expect(result).toHaveLength(1);
138+
expect(result[0]).toMatchObject({
139+
role: 'tool',
140+
tool_call_id: 'call-abc',
141+
});
142+
143+
const content = result[0]?.content as string;
144+
expect(content).toBe(
145+
'{"temperature":72,"unit":"F","location":"San Francisco"}',
146+
);
147+
});
148+
149+
it('should stringify tool result with file-data type (reproduces issue #181)', () => {
150+
// Reproduces issue #181 using AI SDK v3 format (file-data instead of V2's media type)
151+
const result = convertToOpenRouterChatMessages([
152+
{
153+
role: 'tool',
154+
content: [
155+
{
156+
type: 'tool-result',
157+
toolCallId: 'call-original-issue',
158+
toolName: 'webFetch',
159+
output: {
160+
type: 'content',
161+
value: [
162+
{
163+
type: 'file-data',
164+
mediaType: 'image/jpeg',
165+
// Truncated JPEG from the original issue
166+
data: '/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFgAXAMBIgACEQEDEQH/xAAcAAACAgMBAQAA',
167+
},
168+
],
169+
},
170+
},
171+
],
172+
},
173+
]);
174+
175+
expect(result).toHaveLength(1);
176+
expect(result[0]).toMatchObject({
177+
role: 'tool',
178+
tool_call_id: 'call-original-issue',
179+
});
180+
181+
expect(typeof result[0]?.content).toBe('string');
182+
183+
const content = result[0]?.content as string;
184+
expect(content).toContain('"type":"file-data"');
185+
expect(content).toContain('"mediaType":"image/jpeg"');
186+
expect(content).toContain('/9j/4AAQSkZJRgABAQAAAQABAAD'); // JPEG magic bytes
187+
});
188+
});

0 commit comments

Comments
 (0)