From 13c291b4bf13073267e6d88cc77fec46352c1a49 Mon Sep 17 00:00:00 2001 From: Gil Emmanuel Bancud Date: Mon, 6 Oct 2025 23:39:06 +0800 Subject: [PATCH 1/2] feat: Support string|array output in FunctionToolCallOutput - Update FunctionToolCallOutput to accept string|array for output parameter - This enables returning structured file/image references from function calls - Aligns with OpenAI's recent API update for file outputs in tool results - No breaking changes: existing string outputs continue to work --- src/Responses/Responses/Input/FunctionToolCallOutput.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Responses/Responses/Input/FunctionToolCallOutput.php b/src/Responses/Responses/Input/FunctionToolCallOutput.php index 267e7232..e6978355 100644 --- a/src/Responses/Responses/Input/FunctionToolCallOutput.php +++ b/src/Responses/Responses/Input/FunctionToolCallOutput.php @@ -9,7 +9,7 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @phpstan-type FunctionToolCallOutputType array{call_id: string, id: string, output: string, type: 'function_call_output', status: 'in_progress'|'completed'|'incompleted'} + * @phpstan-type FunctionToolCallOutputType array{call_id: string, id: string, output: string|array, type: 'function_call_output', status: 'in_progress'|'completed'|'incompleted'} * * @implements ResponseContract */ @@ -25,11 +25,12 @@ final class FunctionToolCallOutput implements ResponseContract /** * @param 'function_call_output' $type * @param 'in_progress'|'completed'|'incompleted' $status + * @param string|array $output Output can be a string (for text/JSON) or array (for structured content like files/images) */ private function __construct( public readonly string $callId, public readonly string $id, - public readonly string $output, + public readonly string|array $output, public readonly string $type, public readonly string $status, ) {} From 0cfbfe1a6733a5576c99cb1445e0e93d9f1641f6 Mon Sep 17 00:00:00 2001 From: Gil Emmanuel Bancud Date: Tue, 7 Oct 2025 22:36:28 +0800 Subject: [PATCH 2/2] feat: Add typed classes for FunctionToolCallOutput content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dedicated response classes for function tool call output content types (text, image, file) to provide type-safe structured output handling instead of generic arrays. This aligns with OpenAI API specification and enables proper static analysis. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Input/FunctionToolCallOutput.php | 30 +++++++-- .../Input/FunctionToolCallOutputFile.php | 63 +++++++++++++++++++ .../Input/FunctionToolCallOutputImage.php | 60 ++++++++++++++++++ .../Input/FunctionToolCallOutputText.php | 54 ++++++++++++++++ 4 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 src/Responses/Responses/Input/FunctionToolCallOutputFile.php create mode 100644 src/Responses/Responses/Input/FunctionToolCallOutputImage.php create mode 100644 src/Responses/Responses/Input/FunctionToolCallOutputText.php diff --git a/src/Responses/Responses/Input/FunctionToolCallOutput.php b/src/Responses/Responses/Input/FunctionToolCallOutput.php index e6978355..6ca9a76b 100644 --- a/src/Responses/Responses/Input/FunctionToolCallOutput.php +++ b/src/Responses/Responses/Input/FunctionToolCallOutput.php @@ -9,7 +9,11 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @phpstan-type FunctionToolCallOutputType array{call_id: string, id: string, output: string|array, type: 'function_call_output', status: 'in_progress'|'completed'|'incompleted'} + * @phpstan-import-type FunctionToolCallOutputTextType from FunctionToolCallOutputText + * @phpstan-import-type FunctionToolCallOutputImageType from FunctionToolCallOutputImage + * @phpstan-import-type FunctionToolCallOutputFileType from FunctionToolCallOutputFile + * + * @phpstan-type FunctionToolCallOutputType array{call_id: string, id: string, output: string|array, type: 'function_call_output', status: 'in_progress'|'completed'|'incompleted'} * * @implements ResponseContract */ @@ -25,7 +29,7 @@ final class FunctionToolCallOutput implements ResponseContract /** * @param 'function_call_output' $type * @param 'in_progress'|'completed'|'incompleted' $status - * @param string|array $output Output can be a string (for text/JSON) or array (for structured content like files/images) + * @param string|array $output Output can be a string (for text/JSON) or array (for structured content like files/images) */ private function __construct( public readonly string $callId, @@ -40,10 +44,23 @@ private function __construct( */ public static function from(array $attributes): self { + $output = $attributes['output']; + + if (is_array($output)) { + $output = array_map( + fn (array $item): FunctionToolCallOutputText|FunctionToolCallOutputImage|FunctionToolCallOutputFile => match ($item['type']) { + 'input_text' => FunctionToolCallOutputText::from($item), + 'input_image' => FunctionToolCallOutputImage::from($item), + 'input_file' => FunctionToolCallOutputFile::from($item), + }, + $output, + ); + } + return new self( callId: $attributes['call_id'], id: $attributes['id'], - output: $attributes['output'], + output: $output, type: $attributes['type'], status: $attributes['status'], ); @@ -57,7 +74,12 @@ public function toArray(): array return [ 'call_id' => $this->callId, 'id' => $this->id, - 'output' => $this->output, + 'output' => is_array($this->output) + ? array_map( + fn (FunctionToolCallOutputText|FunctionToolCallOutputImage|FunctionToolCallOutputFile $item): array => $item->toArray(), + $this->output, + ) + : $this->output, 'type' => $this->type, 'status' => $this->status, ]; diff --git a/src/Responses/Responses/Input/FunctionToolCallOutputFile.php b/src/Responses/Responses/Input/FunctionToolCallOutputFile.php new file mode 100644 index 00000000..1e0a60fe --- /dev/null +++ b/src/Responses/Responses/Input/FunctionToolCallOutputFile.php @@ -0,0 +1,63 @@ + + */ +final class FunctionToolCallOutputFile implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_file' $type + */ + private function __construct( + public readonly string $type, + public readonly ?string $fileData, + public readonly ?string $fileId, + public readonly ?string $fileUrl, + public readonly ?string $filename, + ) {} + + /** + * @param FunctionToolCallOutputFileType $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + fileData: $attributes['file_data'] ?? null, + fileId: $attributes['file_id'] ?? null, + fileUrl: $attributes['file_url'] ?? null, + filename: $attributes['filename'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return array_filter([ + 'type' => $this->type, + 'file_data' => $this->fileData, + 'file_id' => $this->fileId, + 'file_url' => $this->fileUrl, + 'filename' => $this->filename, + ], fn ($value) => $value !== null); + } +} diff --git a/src/Responses/Responses/Input/FunctionToolCallOutputImage.php b/src/Responses/Responses/Input/FunctionToolCallOutputImage.php new file mode 100644 index 00000000..cb7593f3 --- /dev/null +++ b/src/Responses/Responses/Input/FunctionToolCallOutputImage.php @@ -0,0 +1,60 @@ + + */ +final class FunctionToolCallOutputImage implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_image' $type + */ + private function __construct( + public readonly string $type, + public readonly ?string $detail, + public readonly ?string $fileId, + public readonly ?string $imageUrl, + ) {} + + /** + * @param FunctionToolCallOutputImageType $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + detail: $attributes['detail'] ?? null, + fileId: $attributes['file_id'] ?? null, + imageUrl: $attributes['image_url'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return array_filter([ + 'type' => $this->type, + 'detail' => $this->detail, + 'file_id' => $this->fileId, + 'image_url' => $this->imageUrl, + ], fn ($value) => $value !== null); + } +} diff --git a/src/Responses/Responses/Input/FunctionToolCallOutputText.php b/src/Responses/Responses/Input/FunctionToolCallOutputText.php new file mode 100644 index 00000000..694428ab --- /dev/null +++ b/src/Responses/Responses/Input/FunctionToolCallOutputText.php @@ -0,0 +1,54 @@ + + */ +final class FunctionToolCallOutputText implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_text' $type + */ + private function __construct( + public readonly string $text, + public readonly string $type + ) {} + + /** + * @param FunctionToolCallOutputTextType $attributes + */ + public static function from(array $attributes): self + { + return new self( + text: $attributes['text'], + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'text' => $this->text, + 'type' => $this->type, + ]; + } +}