diff --git a/src/Actions/Conversations/ItemObjects.php b/src/Actions/Conversations/ItemObjects.php index d91bd37b..1fda7bed 100644 --- a/src/Actions/Conversations/ItemObjects.php +++ b/src/Actions/Conversations/ItemObjects.php @@ -87,6 +87,7 @@ public static function parse(array $outputItems): array 'program_output' => OutputProgramOutput::from($item), 'apply_patch_call' => OutputApplyPatchToolCall::from($item), 'apply_patch_call_output' => ApplyPatchToolCallOutput::from($item), + default => throw new \UnexpectedValueException('Uh oh! We do not recognize this type. Please submit a bug to openai-php/client on GitHub!'), }, $outputItems, ); diff --git a/src/Responses/Responses/Output/OutputWebSearchToolCall.php b/src/Responses/Responses/Output/OutputWebSearchToolCall.php index 82865f3d..0b0a9ff7 100644 --- a/src/Responses/Responses/Output/OutputWebSearchToolCall.php +++ b/src/Responses/Responses/Output/OutputWebSearchToolCall.php @@ -11,9 +11,10 @@ /** * @phpstan-import-type WebSearchActionType from OutputWebSearchAction - * @phpstan-import-type OutputWebSearchToolCallResultType from OutputWebSearchToolCallResult + * @phpstan-import-type OutputWebSearchToolCallImageResultType from OutputWebSearchToolCallImageResult + * @phpstan-import-type OutputWebSearchToolCallTextResultType from OutputWebSearchToolCallTextResult * - * @phpstan-type OutputWebSearchToolCallType array{id: string, status: string, type: 'web_search_call', action?: WebSearchActionType, results?: array} + * @phpstan-type OutputWebSearchToolCallType array{id: string, status: string, type: 'web_search_call', action?: WebSearchActionType, results?: array} * * @implements ResponseContract */ @@ -28,7 +29,7 @@ final class OutputWebSearchToolCall implements ResponseContract /** * @param 'web_search_call' $type - * @param ?array $results + * @param ?array $results */ private function __construct( public readonly string $id, @@ -52,7 +53,10 @@ public static function from(array $attributes): self : null, results: isset($attributes['results']) ? array_map( - static fn (array $result): OutputWebSearchToolCallResult => OutputWebSearchToolCallResult::from($result), + static fn (array $result): OutputWebSearchToolCallImageResult|OutputWebSearchToolCallTextResult => match ($result['type']) { + 'image_result' => OutputWebSearchToolCallImageResult::from($result), + 'text_result' => OutputWebSearchToolCallTextResult::from($result), + }, $attributes['results'], ) : null, @@ -76,7 +80,7 @@ public function toArray(): array if ($this->results !== null) { $data['results'] = array_map( - static fn (OutputWebSearchToolCallResult $result): array => $result->toArray(), + static fn (OutputWebSearchToolCallImageResult|OutputWebSearchToolCallTextResult $result): array => $result->toArray(), $this->results, ); } diff --git a/src/Responses/Responses/Output/OutputWebSearchToolCallResult.php b/src/Responses/Responses/Output/OutputWebSearchToolCallImageResult.php similarity index 76% rename from src/Responses/Responses/Output/OutputWebSearchToolCallResult.php rename to src/Responses/Responses/Output/OutputWebSearchToolCallImageResult.php index 910f1878..78ce4492 100644 --- a/src/Responses/Responses/Output/OutputWebSearchToolCallResult.php +++ b/src/Responses/Responses/Output/OutputWebSearchToolCallImageResult.php @@ -9,14 +9,14 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @phpstan-type OutputWebSearchToolCallResultType array{type: 'image_result', image_url: string, source_website_url: string, thumbnail_url?: string, caption?: string} + * @phpstan-type OutputWebSearchToolCallImageResultType array{type: 'image_result', image_url: string, source_website_url: string, thumbnail_url?: string, caption?: string} * - * @implements ResponseContract + * @implements ResponseContract */ -final class OutputWebSearchToolCallResult implements ResponseContract +final class OutputWebSearchToolCallImageResult implements ResponseContract { /** - * @use ArrayAccessible + * @use ArrayAccessible */ use ArrayAccessible; @@ -34,7 +34,7 @@ private function __construct( ) {} /** - * @param OutputWebSearchToolCallResultType $attributes + * @param OutputWebSearchToolCallImageResultType $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/Output/OutputWebSearchToolCallTextResult.php b/src/Responses/Responses/Output/OutputWebSearchToolCallTextResult.php new file mode 100644 index 00000000..ffc66b95 --- /dev/null +++ b/src/Responses/Responses/Output/OutputWebSearchToolCallTextResult.php @@ -0,0 +1,71 @@ + + */ +final class OutputWebSearchToolCallTextResult implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'text_result' $type + */ + private function __construct( + public readonly string $type, + public readonly ?string $title, + public readonly ?string $url, + public readonly ?string $snippet, + ) {} + + /** + * @param OutputWebSearchToolCallTextResultType $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + title: $attributes['title'] ?? null, + url: $attributes['url'] ?? null, + snippet: $attributes['snippet'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + $data = [ + 'type' => $this->type, + ]; + + if ($this->title !== null) { + $data['title'] = $this->title; + } + + if ($this->url !== null) { + $data['url'] = $this->url; + } + + if ($this->snippet !== null) { + $data['snippet'] = $this->snippet; + } + + return $data; + } +} diff --git a/tests/Responses/Responses/Output/OutputWebSearchToolCall.php b/tests/Responses/Responses/Output/OutputWebSearchToolCall.php index 9c1d245c..a9db899b 100644 --- a/tests/Responses/Responses/Output/OutputWebSearchToolCall.php +++ b/tests/Responses/Responses/Output/OutputWebSearchToolCall.php @@ -1,7 +1,8 @@ url->toBe('https://example.com/news/positive-story'); expect($response->results[0]) - ->toBeInstanceOf(OutputWebSearchToolCallResult::class) + ->toBeInstanceOf(OutputWebSearchToolCallImageResult::class) ->type->toBe('image_result') ->imageUrl->toBe('https://example.com/images/positive-story.jpg') ->thumbnailUrl->toBe('https://example.com/images/positive-story-thumbnail.jpg') @@ -89,7 +90,7 @@ $response = OutputWebSearchToolCall::from($payload); expect($response->results[0]) - ->toBeInstanceOf(OutputWebSearchToolCallResult::class) + ->toBeInstanceOf(OutputWebSearchToolCallImageResult::class) ->thumbnailUrl->toBeNull() ->caption->toBeNull(); @@ -98,6 +99,31 @@ ->toBe($payload); }); +test('from with text search result', function () { + $payload = outputWebSearchToolCall(); + $payload['results'] = [ + [ + 'type' => 'text_result', + 'title' => 'A positive news story', + 'url' => 'https://example.com/news/positive-story', + 'snippet' => 'A short summary of the positive news story.', + ], + ]; + + $response = OutputWebSearchToolCall::from($payload); + + expect($response->results[0]) + ->toBeInstanceOf(OutputWebSearchToolCallTextResult::class) + ->type->toBe('text_result') + ->title->toBe('A positive news story') + ->url->toBe('https://example.com/news/positive-story') + ->snippet->toBe('A short summary of the positive news story.'); + + expect($response->toArray()) + ->toBeArray() + ->toBe($payload); +}); + test('from with action but without query', function () { $payload = outputWebSearchToolCall(); unset($payload['action']['query']);