Skip to content

Commit c8aab0a

Browse files
authored
fix (provider/anthropic): revert cd458a8 (#8536)
## Background cd458a8 did not fix the underlying issue and caused #8516 ## Summary Revert #cd458a8c1667df86e6987a1f2e06159823453864 ## Related Issues #8516 #8474
1 parent a0a725f commit c8aab0a

File tree

3 files changed

+12
-264
lines changed

3 files changed

+12
-264
lines changed

.changeset/early-bugs-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/anthropic': patch
3+
---
4+
5+
fix (provider/anthropic): revert cd458a8c1667df86e6987a1f2e06159823453864

packages/anthropic/src/convert-to-anthropic-messages-prompt.test.ts

Lines changed: 2 additions & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ describe('tool messages', () => {
372372
});
373373
});
374374

375-
it('should combine tool and user messages with tool_result first', async () => {
375+
it('should combine user and tool messages', async () => {
376376
const result = await convertToAnthropicMessagesPrompt({
377377
prompt: [
378378
{
@@ -409,13 +409,8 @@ describe('tool messages', () => {
409409
tool_use_id: 'tool-call-1',
410410
is_error: undefined,
411411
content: JSON.stringify({ test: 'This is a tool message' }),
412-
cache_control: undefined,
413-
},
414-
{
415-
type: 'text',
416-
text: 'This is a user message',
417-
cache_control: undefined,
418412
},
413+
{ type: 'text', text: 'This is a user message' },
419414
],
420415
},
421416
],
@@ -425,84 +420,6 @@ describe('tool messages', () => {
425420
});
426421
});
427422

428-
it('should place tool_result before user text in combined messages', async () => {
429-
// Ensures tool_result parts appear first in combined user messages
430-
const result = await convertToAnthropicMessagesPrompt({
431-
prompt: [
432-
{
433-
role: 'user',
434-
content: [{ type: 'text', text: 'generate 10 items' }],
435-
},
436-
{
437-
role: 'assistant',
438-
content: [
439-
{
440-
type: 'tool-call',
441-
toolCallId: 'tool-example-123',
442-
toolName: 'json',
443-
input: {
444-
message: 'generate 10 items',
445-
},
446-
},
447-
{
448-
type: 'text',
449-
text: 'I generated code for 10 items.',
450-
},
451-
],
452-
},
453-
{
454-
role: 'tool',
455-
content: [
456-
{
457-
type: 'tool-result',
458-
toolCallId: 'tool-example-123',
459-
toolName: 'json',
460-
output: {
461-
type: 'json',
462-
value: {
463-
code: 'export const code = () => [...]',
464-
packageJson: '{}',
465-
},
466-
},
467-
},
468-
],
469-
},
470-
{
471-
role: 'user',
472-
content: [{ type: 'text', text: 'generate 100 items' }],
473-
},
474-
],
475-
sendReasoning: true,
476-
warnings: [],
477-
});
478-
479-
// The key fix: tool_result and user content are combined in a single message
480-
// but tool_result appears FIRST to satisfy Claude's validation requirements
481-
expect(result.prompt.messages).toHaveLength(3);
482-
expect(result.prompt.messages[0].role).toBe('user');
483-
expect(result.prompt.messages[1].role).toBe('assistant');
484-
expect(result.prompt.messages[2].role).toBe('user'); // combined tool_result + user message
485-
486-
// Verify tool_result comes before user text in the combined message
487-
expect(result.prompt.messages[2].content).toEqual([
488-
{
489-
type: 'tool_result',
490-
tool_use_id: 'tool-example-123',
491-
is_error: undefined,
492-
content: JSON.stringify({
493-
code: 'export const code = () => [...]',
494-
packageJson: '{}',
495-
}),
496-
cache_control: undefined,
497-
},
498-
{
499-
type: 'text',
500-
text: 'generate 100 items',
501-
cache_control: undefined,
502-
},
503-
]);
504-
});
505-
506423
it('should handle tool result with content parts', async () => {
507424
const result = await convertToAnthropicMessagesPrompt({
508425
prompt: [
@@ -573,170 +490,6 @@ describe('tool messages', () => {
573490
}
574491
`);
575492
});
576-
577-
it('should place tool_result before user text in combined message', async () => {
578-
// Combines tool_result and user text in a single message (preserving role alternation)
579-
// but tool_result parts appear first, satisfying Claude's validation requirements
580-
581-
const result = await convertToAnthropicMessagesPrompt({
582-
prompt: [
583-
{
584-
role: 'tool',
585-
content: [
586-
{
587-
type: 'tool-result',
588-
toolName: 'analyze-tool',
589-
toolCallId: 'tool-call-123',
590-
output: {
591-
type: 'json',
592-
value: { analysis: 'Tool execution completed' },
593-
},
594-
},
595-
],
596-
},
597-
{
598-
role: 'user',
599-
content: [
600-
{ type: 'text', text: 'Thanks! Now please provide more details.' },
601-
],
602-
},
603-
],
604-
sendReasoning: true,
605-
warnings: [],
606-
});
607-
608-
// Fixed behavior: still combines in single message, but tool_result comes first
609-
// This satisfies Claude's "tool_result immediately after tool_use" requirement
610-
expect(result).toEqual({
611-
prompt: {
612-
messages: [
613-
{
614-
role: 'user',
615-
content: [
616-
{
617-
type: 'tool_result',
618-
tool_use_id: 'tool-call-123',
619-
is_error: undefined,
620-
content: JSON.stringify({
621-
analysis: 'Tool execution completed',
622-
}),
623-
},
624-
{
625-
type: 'text',
626-
text: 'Thanks! Now please provide more details.',
627-
},
628-
],
629-
},
630-
],
631-
system: undefined,
632-
},
633-
betas: new Set(),
634-
});
635-
});
636-
637-
it('should place multiple tool_result parts before user text in correct order', async () => {
638-
// Test multiple tool_use scenario: tool_result parts should appear first
639-
// and maintain the same order as their corresponding tool_use ids
640-
const result = await convertToAnthropicMessagesPrompt({
641-
prompt: [
642-
{
643-
role: 'tool',
644-
content: [
645-
{
646-
type: 'tool-result',
647-
toolName: 'search-tool',
648-
toolCallId: 'search-123',
649-
output: { type: 'text', value: 'Search result' },
650-
},
651-
{
652-
type: 'tool-result',
653-
toolName: 'analyze-tool',
654-
toolCallId: 'analyze-456',
655-
output: { type: 'json', value: { status: 'complete' } },
656-
},
657-
],
658-
},
659-
{
660-
role: 'user',
661-
content: [
662-
{
663-
type: 'text',
664-
text: 'Great! What do you think about these results?',
665-
},
666-
],
667-
},
668-
],
669-
sendReasoning: false,
670-
warnings: [],
671-
});
672-
673-
expect(result.prompt.messages).toHaveLength(1);
674-
expect(result.prompt.messages[0]).toEqual({
675-
role: 'user',
676-
content: [
677-
{
678-
type: 'tool_result',
679-
tool_use_id: 'search-123',
680-
content: 'Search result',
681-
is_error: undefined,
682-
},
683-
{
684-
type: 'tool_result',
685-
tool_use_id: 'analyze-456',
686-
content: JSON.stringify({ status: 'complete' }),
687-
is_error: undefined,
688-
},
689-
{ type: 'text', text: 'Great! What do you think about these results?' },
690-
],
691-
});
692-
});
693-
694-
it('should place error tool_result parts before user text', async () => {
695-
// Test error tool results: should also appear first even when is_error: true
696-
const result = await convertToAnthropicMessagesPrompt({
697-
prompt: [
698-
{
699-
role: 'tool',
700-
content: [
701-
{
702-
type: 'tool-result',
703-
toolName: 'failing-tool',
704-
toolCallId: 'fail-789',
705-
output: { type: 'error-text', value: 'Tool execution failed' },
706-
},
707-
],
708-
},
709-
{
710-
role: 'user',
711-
content: [
712-
{
713-
type: 'text',
714-
text: 'The tool failed. Can you try a different approach?',
715-
},
716-
],
717-
},
718-
],
719-
sendReasoning: false,
720-
warnings: [],
721-
});
722-
723-
expect(result.prompt.messages).toHaveLength(1);
724-
expect(result.prompt.messages[0]).toEqual({
725-
role: 'user',
726-
content: [
727-
{
728-
type: 'tool_result',
729-
tool_use_id: 'fail-789',
730-
content: 'Tool execution failed',
731-
is_error: true,
732-
},
733-
{
734-
type: 'text',
735-
text: 'The tool failed. Can you try a different approach?',
736-
},
737-
],
738-
});
739-
});
740493
});
741494

742495
describe('assistant messages', () => {

packages/anthropic/src/convert-to-anthropic-messages-prompt.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -311,21 +311,7 @@ export async function convertToAnthropicMessagesPrompt({
311311
}
312312
}
313313

314-
// Reorder content parts to ensure tool_result blocks appear first
315-
// This satisfies Claude's "tool_result immediately after tool_use" validation
316-
// while preserving the intentional message combining from #2067 (role alternation fix #2047)
317-
const toolResultParts = anthropicContent.filter(
318-
part => part.type === 'tool_result',
319-
);
320-
const otherParts = anthropicContent.filter(
321-
part => part.type !== 'tool_result',
322-
);
323-
const reorderedContent =
324-
toolResultParts.length > 0
325-
? [...toolResultParts, ...otherParts]
326-
: anthropicContent;
327-
328-
messages.push({ role: 'user', content: reorderedContent });
314+
messages.push({ role: 'user', content: anthropicContent });
329315

330316
break;
331317
}
@@ -577,6 +563,7 @@ function groupIntoBlocks(
577563
currentBlock = { type: 'system', messages: [] };
578564
blocks.push(currentBlock);
579565
}
566+
580567
currentBlock.messages.push(message);
581568
break;
582569
}
@@ -585,6 +572,7 @@ function groupIntoBlocks(
585572
currentBlock = { type: 'assistant', messages: [] };
586573
blocks.push(currentBlock);
587574
}
575+
588576
currentBlock.messages.push(message);
589577
break;
590578
}
@@ -593,6 +581,7 @@ function groupIntoBlocks(
593581
currentBlock = { type: 'user', messages: [] };
594582
blocks.push(currentBlock);
595583
}
584+
596585
currentBlock.messages.push(message);
597586
break;
598587
}
@@ -601,6 +590,7 @@ function groupIntoBlocks(
601590
currentBlock = { type: 'user', messages: [] };
602591
blocks.push(currentBlock);
603592
}
593+
604594
currentBlock.messages.push(message);
605595
break;
606596
}

0 commit comments

Comments
 (0)