Skip to content

Commit 2b07e51

Browse files
iBotPeachestrandbert37
authored andcommitted
feat(OpenAI): Add streaming for ImageGeneration (Responses API) (openai-php#602)
* feat(OpenAI): Add streaming for ImageGeneration (Responses API) * fix(OpenAI): `model` is missing on stream and partial images is default 0 * test(OpenAI): image generation steaming test * fix(OpenAI): add output item (Image Generation) * fix(OpenAI): add output item (Image Generation) * test(OpenAI): enhanced stream testing * fix(OpenAI): image generation streaming fixes * fix(OpenAI): proper type * chore(OpenAI): align test to stream mock * fix(OpenAI): result is missing on partial image failure # Conflicts: # src/Responses/Responses/CreateResponse.php # src/Responses/Responses/CreateStreamedResponse.php # src/Responses/Responses/RetrieveResponse.php # src/Responses/Responses/Streaming/OutputItem.php
1 parent 4d70ddd commit 2b07e51

11 files changed

+305
-15
lines changed

src/Responses/Responses/CreateResponse.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OpenAI\Responses\Responses\Output\OutputMcpApprovalRequest;
1616
use OpenAI\Responses\Responses\Output\OutputMcpCall;
1717
use OpenAI\Responses\Responses\Output\OutputMcpListTools;
18+
use OpenAI\Responses\Responses\Output\OutputImageGenerationToolCall;
1819
use OpenAI\Responses\Responses\Output\OutputMessage;
1920
use OpenAI\Responses\Responses\Output\OutputMessageContentOutputText;
2021
use OpenAI\Responses\Responses\Output\OutputReasoning;
@@ -37,6 +38,7 @@
3738
* @phpstan-import-type OutputMessageType from OutputMessage
3839
* @phpstan-import-type OutputReasoningType from OutputReasoning
3940
* @phpstan-import-type OutputWebSearchToolCallType from OutputWebSearchToolCall
41+
* @phpstan-import-type OutputImageGenerationToolCallType from OutputImageGenerationToolCall
4042
* @phpstan-import-type OutputMcpListToolsType from OutputMcpListTools
4143
* @phpstan-import-type OutputMcpApprovalRequestType from OutputMcpApprovalRequest
4244
* @phpstan-import-type OutputMcpCallType from OutputMcpCall
@@ -55,7 +57,7 @@
5557
*
5658
* @phpstan-type ToolChoiceType 'none'|'auto'|'required'|FunctionToolChoiceType|HostedToolChoiceType
5759
* @phpstan-type ToolsType array<int, ComputerUseToolType|FileSearchToolType|FunctionToolType|WebSearchToolType|ImageGenerationToolType|RemoteMcpToolType>
58-
* @phpstan-type OutputType array<int, OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType>
60+
* @phpstan-type OutputType array<int, OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputImageGenerationToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType>
5961
* @phpstan-type CreateResponseType array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: ErrorType|null, incomplete_details: IncompleteDetailsType|null, instructions: string|null, max_output_tokens: int|null, model: string, output: OutputType, output_text: string|null, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ReasoningType|null, store: bool, temperature: float|null, text: ResponseFormatType, tool_choice: ToolChoiceType, tools: ToolsType, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: UsageType|null, user: string|null, metadata: array<string, string>|null}
6062
*
6163
* @implements ResponseContract<CreateResponseType>
@@ -73,7 +75,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati
7375
/**
7476
* @param 'response' $object
7577
* @param 'completed'|'failed'|'in_progress'|'incomplete' $status
76-
* @param array<int, OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall> $output
78+
* @param array<int, OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall|OutputImageGenerationToolCall> $output
7779
* @param array<int, ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool|ImageGenerationTool|RemoteMcpTool> $tools
7880
* @param 'auto'|'disabled'|null $truncation
7981
* @param array<string, string> $metadata
@@ -112,7 +114,7 @@ private function __construct(
112114
public static function from(array $attributes, MetaInformation $meta): self
113115
{
114116
$output = array_map(
115-
fn (array $output): OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall => match ($output['type']) {
117+
fn (array $output): OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall|OutputImageGenerationToolCall => match ($output['type']) {
116118
'message' => OutputMessage::from($output),
117119
'file_search_call' => OutputFileSearchToolCall::from($output),
118120
'function_call' => OutputFunctionToolCall::from($output),
@@ -122,6 +124,7 @@ public static function from(array $attributes, MetaInformation $meta): self
122124
'mcp_list_tools' => OutputMcpListTools::from($output),
123125
'mcp_approval_request' => OutputMcpApprovalRequest::from($output),
124126
'mcp_call' => OutputMcpCall::from($output),
127+
'image_generation_call' => OutputImageGenerationToolCall::from($output),
125128
},
126129
$attributes['output'],
127130
);
@@ -213,7 +216,7 @@ public function toArray(): array
213216
'metadata' => $this->metadata ?? [],
214217
'model' => $this->model,
215218
'output' => array_map(
216-
fn (OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall $output): array => $output->toArray(),
219+
fn (OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputImageGenerationToolCall|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall $output): array => $output->toArray(),
217220
$this->output
218221
),
219222
'parallel_tool_calls' => $this->parallelToolCalls,

src/Responses/Responses/CreateStreamedResponse.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use OpenAI\Responses\Responses\Streaming\FileSearchCall;
1313
use OpenAI\Responses\Responses\Streaming\FunctionCallArgumentsDelta;
1414
use OpenAI\Responses\Responses\Streaming\FunctionCallArgumentsDone;
15+
use OpenAI\Responses\Responses\Streaming\ImageGenerationPart;
16+
use OpenAI\Responses\Responses\Streaming\ImageGenerationPartialImage;
1517
use OpenAI\Responses\Responses\Streaming\McpCall;
1618
use OpenAI\Responses\Responses\Streaming\McpCallArgumentsDelta;
1719
use OpenAI\Responses\Responses\Streaming\McpCallArgumentsDone;
@@ -44,7 +46,7 @@ final class CreateStreamedResponse implements ResponseContract
4446

4547
private function __construct(
4648
public readonly string $event,
47-
public readonly CreateResponse|OutputItem|ContentPart|OutputTextDelta|OutputTextAnnotationAdded|OutputTextDone|RefusalDelta|RefusalDone|FunctionCallArgumentsDelta|FunctionCallArgumentsDone|FileSearchCall|WebSearchCall|ReasoningSummaryPart|ReasoningSummaryTextDelta|ReasoningSummaryTextDone|McpListTools|McpCall|McpCallArgumentsDelta|McpCallArgumentsDone|Error $response,
49+
public readonly CreateResponse|OutputItem|ContentPart|OutputTextDelta|OutputTextAnnotationAdded|OutputTextDone|RefusalDelta|RefusalDone|FunctionCallArgumentsDelta|FunctionCallArgumentsDone|FileSearchCall|WebSearchCall|ReasoningSummaryPart|ReasoningSummaryTextDelta|ReasoningSummaryTextDone|McpListTools|McpCall|McpCallArgumentsDelta|McpCallArgumentsDone|ImageGenerationPart|ImageGenerationPartialImage|Error $response,
4850
) {}
4951

5052
/**
@@ -93,6 +95,10 @@ public static function from(array $attributes): self
9395
'response.mcp_call_arguments.delta' => McpCallArgumentsDelta::from($attributes, $meta), // @phpstan-ignore-line
9496
'response.mcp_call.arguments.done',
9597
'response.mcp_call_arguments.done' => McpCallArgumentsDone::from($attributes, $meta), // @phpstan-ignore-line
98+
'response.image_generation_call.completed',
99+
'response.image_generation_call.generating',
100+
'response.image_generation_call.in_progress' => ImageGenerationPart::from($attributes, $meta), // @phpstan-ignore-line
101+
'response.image_generation_call.partial_image' => ImageGenerationPartialImage::from($attributes, $meta), // @phpstan-ignore-line
96102
'error' => Error::from($attributes, $meta), // @phpstan-ignore-line
97103
default => throw new UnknownEventException('Unknown Responses streaming event: '.$event),
98104
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Responses\Output;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Responses\Concerns\ArrayAccessible;
9+
use OpenAI\Testing\Responses\Concerns\Fakeable;
10+
11+
/**
12+
* @phpstan-type OutputImageGenerationToolCallType array{id: string, result?: string|null, status: string, type: 'image_generation_call'}
13+
*
14+
* @implements ResponseContract<OutputImageGenerationToolCallType>
15+
*/
16+
final class OutputImageGenerationToolCall implements ResponseContract
17+
{
18+
/**
19+
* @use ArrayAccessible<OutputImageGenerationToolCallType>
20+
*/
21+
use ArrayAccessible;
22+
23+
use Fakeable;
24+
25+
/**
26+
* @param 'image_generation_call' $type
27+
*/
28+
private function __construct(
29+
public readonly string $id,
30+
public readonly ?string $result,
31+
public readonly string $status,
32+
public readonly string $type,
33+
) {}
34+
35+
/**
36+
* @param OutputImageGenerationToolCallType $attributes
37+
*/
38+
public static function from(array $attributes): self
39+
{
40+
return new self(
41+
id: $attributes['id'],
42+
result: $attributes['result'] ?? null,
43+
status: $attributes['status'],
44+
type: $attributes['type'],
45+
);
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
public function toArray(): array
52+
{
53+
return [
54+
'id' => $this->id,
55+
'result' => $this->result,
56+
'status' => $this->status,
57+
'type' => $this->type,
58+
];
59+
}
60+
}

src/Responses/Responses/RetrieveResponse.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OpenAI\Responses\Responses\Output\OutputComputerToolCall;
1313
use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall;
1414
use OpenAI\Responses\Responses\Output\OutputFunctionToolCall;
15+
use OpenAI\Responses\Responses\Output\OutputImageGenerationToolCall;
1516
use OpenAI\Responses\Responses\Output\OutputMcpApprovalRequest;
1617
use OpenAI\Responses\Responses\Output\OutputMcpCall;
1718
use OpenAI\Responses\Responses\Output\OutputMcpListTools;
@@ -39,6 +40,7 @@
3940
* @phpstan-import-type OutputMcpListToolsType from OutputMcpListTools
4041
* @phpstan-import-type OutputMcpApprovalRequestType from OutputMcpApprovalRequest
4142
* @phpstan-import-type OutputMcpCallType from OutputMcpCall
43+
* @phpstan-import-type OutputImageGenerationToolCallType from OutputImageGenerationToolCall
4244
* @phpstan-import-type ComputerUseToolType from ComputerUseTool
4345
* @phpstan-import-type FileSearchToolType from FileSearchTool
4446
* @phpstan-import-type ImageGenerationToolType from ImageGenerationTool
@@ -54,7 +56,7 @@
5456
*
5557
* @phpstan-type ToolChoiceType 'none'|'auto'|'required'|FunctionToolChoiceType|HostedToolChoiceType
5658
* @phpstan-type ToolsType array<int, ComputerUseToolType|FileSearchToolType|FunctionToolType|WebSearchToolType|ImageGenerationToolType|RemoteMcpToolType>
57-
* @phpstan-type OutputType array<int, OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType>
59+
* @phpstan-type OutputType array<int, OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputImageGenerationToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType>
5860
* @phpstan-type RetrieveResponseType array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: ErrorType|null, incomplete_details: IncompleteDetailsType|null, instructions: string|null, max_output_tokens: int|null, model: string, output: OutputType, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ReasoningType|null, store: bool, temperature: float|null, text: ResponseFormatType, tool_choice: ToolChoiceType, tools: ToolsType, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: UsageType|null, user: string|null, metadata: array<string, string>|null}
5961
*
6062
* @implements ResponseContract<RetrieveResponseType>
@@ -72,7 +74,7 @@ final class RetrieveResponse implements ResponseContract, ResponseHasMetaInforma
7274
/**
7375
* @param 'response' $object
7476
* @param 'completed'|'failed'|'in_progress'|'incomplete' $status
75-
* @param array<int, OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall> $output
77+
* @param array<int, OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputImageGenerationToolCall|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall> $output
7678
* @param array<int, ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool|ImageGenerationTool|RemoteMcpTool> $tools
7779
* @param 'auto'|'disabled'|null $truncation
7880
* @param array<string, string> $metadata
@@ -110,7 +112,7 @@ private function __construct(
110112
public static function from(array $attributes, MetaInformation $meta): self
111113
{
112114
$output = array_map(
113-
fn (array $output): OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall => match ($output['type']) {
115+
fn (array $output): OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputImageGenerationToolCall|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall => match ($output['type']) {
114116
'message' => OutputMessage::from($output),
115117
'file_search_call' => OutputFileSearchToolCall::from($output),
116118
'function_call' => OutputFunctionToolCall::from($output),
@@ -120,6 +122,7 @@ public static function from(array $attributes, MetaInformation $meta): self
120122
'mcp_list_tools' => OutputMcpListTools::from($output),
121123
'mcp_approval_request' => OutputMcpApprovalRequest::from($output),
122124
'mcp_call' => OutputMcpCall::from($output),
125+
'image_generation_call' => OutputImageGenerationToolCall::from($output),
123126
},
124127
$attributes['output'],
125128
);
@@ -198,7 +201,7 @@ public function toArray(): array
198201
'metadata' => $this->metadata ?? [],
199202
'model' => $this->model,
200203
'output' => array_map(
201-
fn (OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputMcpListTools|OutputMcpCall|OutputMcpApprovalRequest $output): array => $output->toArray(),
204+
fn (OutputMessage|OutputComputerToolCall|OutputFileSearchToolCall|OutputWebSearchToolCall|OutputFunctionToolCall|OutputReasoning|OutputImageGenerationToolCall|OutputMcpListTools|OutputMcpCall|OutputMcpApprovalRequest $output): array => $output->toArray(),
202205
$this->output
203206
),
204207
'parallel_tool_calls' => $this->parallelToolCalls,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Responses\Streaming;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Contracts\ResponseHasMetaInformationContract;
9+
use OpenAI\Responses\Concerns\ArrayAccessible;
10+
use OpenAI\Responses\Concerns\HasMetaInformation;
11+
use OpenAI\Responses\Meta\MetaInformation;
12+
use OpenAI\Testing\Responses\Concerns\Fakeable;
13+
14+
/**
15+
* @phpstan-type ImageGenerationPartType array{output_index: int, item_id: string, sequence_number: int}
16+
*
17+
* @implements ResponseContract<ImageGenerationPartType>
18+
*/
19+
final class ImageGenerationPart implements ResponseContract, ResponseHasMetaInformationContract
20+
{
21+
/**
22+
* @use ArrayAccessible<ImageGenerationPartType>
23+
*/
24+
use ArrayAccessible;
25+
26+
use Fakeable;
27+
use HasMetaInformation;
28+
29+
private function __construct(
30+
public readonly int $outputIndex,
31+
public readonly string $itemId,
32+
public readonly int $sequenceNumber,
33+
private readonly MetaInformation $meta,
34+
) {}
35+
36+
/**
37+
* @param ImageGenerationPartType $attributes
38+
*/
39+
public static function from(array $attributes, MetaInformation $meta): self
40+
{
41+
return new self(
42+
outputIndex: $attributes['output_index'],
43+
itemId: $attributes['item_id'],
44+
sequenceNumber: $attributes['sequence_number'],
45+
meta: $meta,
46+
);
47+
}
48+
49+
/**
50+
* {@inheritDoc}
51+
*/
52+
public function toArray(): array
53+
{
54+
return [
55+
'output_index' => $this->outputIndex,
56+
'item_id' => $this->itemId,
57+
'sequence_number' => $this->sequenceNumber,
58+
];
59+
}
60+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Responses\Streaming;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Contracts\ResponseHasMetaInformationContract;
9+
use OpenAI\Responses\Concerns\ArrayAccessible;
10+
use OpenAI\Responses\Concerns\HasMetaInformation;
11+
use OpenAI\Responses\Meta\MetaInformation;
12+
use OpenAI\Testing\Responses\Concerns\Fakeable;
13+
14+
/**
15+
* @phpstan-type ImageGenerationPartialImageType array{output_index: int, item_id: string, sequence_number: int, partial_image_index: int, partial_image_b64: string}
16+
*
17+
* @implements ResponseContract<ImageGenerationPartialImageType>
18+
*/
19+
final class ImageGenerationPartialImage implements ResponseContract, ResponseHasMetaInformationContract
20+
{
21+
/**
22+
* @use ArrayAccessible<ImageGenerationPartialImageType>
23+
*/
24+
use ArrayAccessible;
25+
26+
use Fakeable;
27+
use HasMetaInformation;
28+
29+
private function __construct(
30+
public readonly int $outputIndex,
31+
public readonly string $itemId,
32+
public readonly int $sequenceNumber,
33+
public readonly int $partialImageIndex,
34+
public readonly string $partialImageB64,
35+
private readonly MetaInformation $meta,
36+
) {}
37+
38+
/**
39+
* @param ImageGenerationPartialImageType $attributes
40+
*/
41+
public static function from(array $attributes, MetaInformation $meta): self
42+
{
43+
return new self(
44+
outputIndex: $attributes['output_index'],
45+
itemId: $attributes['item_id'],
46+
sequenceNumber: $attributes['sequence_number'],
47+
partialImageIndex: $attributes['partial_image_index'],
48+
partialImageB64: $attributes['partial_image_b64'],
49+
meta: $meta,
50+
);
51+
}
52+
53+
/**
54+
* {@inheritDoc}
55+
*/
56+
public function toArray(): array
57+
{
58+
return [
59+
'output_index' => $this->outputIndex,
60+
'item_id' => $this->itemId,
61+
'sequence_number' => $this->sequenceNumber,
62+
'partial_image_index' => $this->partialImageIndex,
63+
'partial_image_b64' => $this->partialImageB64,
64+
];
65+
}
66+
}

0 commit comments

Comments
 (0)