diff --git a/src/Providers/AWS/MessageMapper.php b/src/Providers/AWS/MessageMapper.php index 15ac372e..33c49bf4 100644 --- a/src/Providers/AWS/MessageMapper.php +++ b/src/Providers/AWS/MessageMapper.php @@ -15,6 +15,7 @@ use function array_map; use function array_merge; use function array_filter; +use function array_values; class MessageMapper implements MessageMapperInterface { @@ -90,7 +91,7 @@ protected function mapMessage(Message $message): array */ protected function mapBlocks(array $blocks): array { - return array_filter(array_map($this->mapContentBlock(...), $blocks)); + return array_values(array_filter(array_map($this->mapContentBlock(...), $blocks))); } protected function mapContentBlock(ContentBlockInterface $block): ?array diff --git a/src/Providers/Anthropic/MessageMapper.php b/src/Providers/Anthropic/MessageMapper.php index 7751ae70..34c55ec7 100644 --- a/src/Providers/Anthropic/MessageMapper.php +++ b/src/Providers/Anthropic/MessageMapper.php @@ -55,7 +55,7 @@ protected function mapMessage(Message $message): array protected function mapBlocks(array $blocks): array { - return array_filter(array_map($this->mapSingleBlock(...), $blocks)); + return array_values(array_filter(array_map($this->mapSingleBlock(...), $blocks))); } protected function mapSingleBlock(ContentBlockInterface $block): ?array diff --git a/src/Providers/Gemini/MessageMapper.php b/src/Providers/Gemini/MessageMapper.php index 7d2e1ea1..fe6559e4 100644 --- a/src/Providers/Gemini/MessageMapper.php +++ b/src/Providers/Gemini/MessageMapper.php @@ -25,6 +25,7 @@ use function array_filter; use function array_map; +use function array_values; class MessageMapper implements MessageMapperInterface { @@ -59,7 +60,7 @@ protected function mapMessage(Message $message): array protected function mapBlocks(array $blocks): array { - return array_filter(array_map($this->mapContentBlock(...), $blocks)); + return array_values(array_filter(array_map($this->mapContentBlock(...), $blocks))); } protected function mapContentBlock(ContentBlock $block): ?array diff --git a/src/Providers/Mistral/MessageMapper.php b/src/Providers/Mistral/MessageMapper.php index eef3a111..5e882cac 100644 --- a/src/Providers/Mistral/MessageMapper.php +++ b/src/Providers/Mistral/MessageMapper.php @@ -24,6 +24,7 @@ use function array_filter; use function array_map; +use function array_values; use function json_encode; class MessageMapper implements MessageMapperInterface @@ -58,7 +59,7 @@ protected function mapMessage(Message $message): void protected function mapBlocks(array $blocks): array { - return array_filter(array_map($this->mapContentBlock(...), $blocks)); + return array_values(array_filter(array_map($this->mapContentBlock(...), $blocks))); } protected function mapContentBlock(ContentBlockInterface $block): ?array diff --git a/src/Providers/OpenAI/MessageMapper.php b/src/Providers/OpenAI/MessageMapper.php index 956cc9c1..1f6a2038 100644 --- a/src/Providers/OpenAI/MessageMapper.php +++ b/src/Providers/OpenAI/MessageMapper.php @@ -24,6 +24,7 @@ use function array_is_list; use function array_map; use function array_merge; +use function array_values; use function json_encode; class MessageMapper implements MessageMapperInterface @@ -64,7 +65,7 @@ protected function mapMessage(Message $message): array protected function mapBlocks(array $blocks): array { - return array_filter(array_map($this->mapContentBlock(...), $blocks)); + return array_values(array_filter(array_map($this->mapContentBlock(...), $blocks))); } protected function mapContentBlock(ContentBlockInterface $block): ?array diff --git a/src/Providers/OpenAI/Responses/MessageMapper.php b/src/Providers/OpenAI/Responses/MessageMapper.php index 500213ab..62861ae6 100644 --- a/src/Providers/OpenAI/Responses/MessageMapper.php +++ b/src/Providers/OpenAI/Responses/MessageMapper.php @@ -22,6 +22,7 @@ use function array_filter; use function array_map; use function array_merge; +use function array_values; use function json_encode; class MessageMapper implements MessageMapperInterface @@ -62,10 +63,10 @@ protected function mapMessage(Message $message): void */ protected function mapBlocks(array $blocks, bool $isUser): array { - return array_filter(array_map( + return array_values(array_filter(array_map( fn (ContentBlockInterface $item): ?array => $this->mapContentBlock($item, $isUser), $blocks - )); + ))); } protected function mapContentBlock(ContentBlockInterface $block, bool $isUser): ?array diff --git a/tests/Providers/MessageMapperReindexingTest.php b/tests/Providers/MessageMapperReindexingTest.php new file mode 100644 index 00000000..382e98ea --- /dev/null +++ b/tests/Providers/MessageMapperReindexingTest.php @@ -0,0 +1,123 @@ +> $expectedBlocks + */ + #[DataProvider('mapperProvider')] + public function test_filtered_blocks_serialize_as_lists( + MessageMapperInterface $mapper, + AssistantMessage $message, + string $blocksKey, + array $expectedBlocks, + ): void { + $payload = json_decode(json_encode($mapper->map([$message]), JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR); + + $this->assertIsArray($payload[0][$blocksKey]); + $this->assertTrue(array_is_list($payload[0][$blocksKey])); + $this->assertSame($expectedBlocks, $payload[0][$blocksKey]); + } + + /** + * @return iterable>}> + */ + public static function mapperProvider(): iterable + { + yield 'openai responses reasoning then text' => [ + new OpenAIResponsesMessageMapper(), + new AssistantMessage([ + new ReasoningContent('Thinking'), + new TextContent('Hello'), + ]), + 'content', + [ + ['type' => 'output_text', 'text' => 'Hello'], + ], + ]; + + yield 'openai reasoning then text' => [ + new OpenAIMessageMapper(), + new AssistantMessage([ + new ReasoningContent('Thinking'), + new TextContent('Hello'), + ]), + 'content', + [ + ['type' => 'text', 'text' => 'Hello'], + ], + ]; + + yield 'anthropic unsupported image id then text' => [ + new AnthropicMessageMapper(), + new AssistantMessage([ + new ImageContent('file_123', SourceType::ID), + new TextContent('Hello'), + ]), + 'content', + [ + ['type' => 'text', 'text' => 'Hello'], + ], + ]; + + yield 'mistral unsupported base64 file then text' => [ + new MistralMessageMapper(), + new AssistantMessage([ + new FileContent('ZmFrZQ==', SourceType::BASE64, 'application/pdf', 'test.pdf'), + new TextContent('Hello'), + ]), + 'content', + [ + ['type' => 'text', 'text' => 'Hello'], + ], + ]; + + yield 'gemini unsupported image id then text' => [ + new GeminiMessageMapper(), + new AssistantMessage([ + new ImageContent('file_123', SourceType::ID, 'image/png'), + new TextContent('Hello'), + ]), + 'parts', + [ + ['text' => 'Hello'], + ], + ]; + + yield 'aws unsupported image then text' => [ + new AWSMessageMapper(), + new AssistantMessage([ + new ImageContent('https://example.com/image.png', SourceType::URL), + new TextContent('Hello'), + ]), + 'content', + [ + ['text' => 'Hello'], + ], + ]; + } +}