From bbda5b0ccd941b7229263b5fb5d386d99116d43d Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 6 Jul 2026 14:57:36 +0400 Subject: [PATCH 1/2] Support streaming compaction output --- .../Responses/Streaming/OutputItem.php | 7 +++++-- tests/Fixtures/Responses.php | 5 +++++ .../ResponseOutputItemCompactionDone.txt | 1 + .../Responses/CreateStreamedResponse.php | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/Fixtures/Streams/ResponseOutputItemCompactionDone.txt diff --git a/src/Responses/Responses/Streaming/OutputItem.php b/src/Responses/Responses/Streaming/OutputItem.php index 1223c0e3b..68e037452 100644 --- a/src/Responses/Responses/Streaming/OutputItem.php +++ b/src/Responses/Responses/Streaming/OutputItem.php @@ -10,6 +10,7 @@ use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Responses\Responses\Output\OutputCodeInterpreterToolCall; +use OpenAI\Responses\Responses\Output\OutputCompaction; use OpenAI\Responses\Responses\Output\OutputComputerToolCall; use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall; use OpenAI\Responses\Responses\Output\OutputFunctionToolCall; @@ -34,8 +35,9 @@ * @phpstan-import-type OutputMcpApprovalRequestType from OutputMcpApprovalRequest * @phpstan-import-type OutputMcpCallType from OutputMcpCall * @phpstan-import-type OutputCodeInterpreterToolCallType from OutputCodeInterpreterToolCall + * @phpstan-import-type OutputCompactionType from OutputCompaction * - * @phpstan-type OutputItemType array{type: string, output_index: int, sequence_number: int, item: OutputCodeInterpreterToolCallType|OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType|OutputImageGenerationToolCallType} + * @phpstan-type OutputItemType array{type: string, output_index: int, sequence_number: int, item: OutputCodeInterpreterToolCallType|OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType|OutputMcpListToolsType|OutputMcpApprovalRequestType|OutputMcpCallType|OutputImageGenerationToolCallType|OutputCompactionType} * * @implements ResponseContract */ @@ -53,7 +55,7 @@ private function __construct( public readonly string $type, public readonly int $outputIndex, public readonly int $sequenceNumber, - public readonly OutputMessage|OutputCodeInterpreterToolCall|OutputFileSearchToolCall|OutputFunctionToolCall|OutputWebSearchToolCall|OutputComputerToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall|OutputImageGenerationToolCall $item, + public readonly OutputMessage|OutputCodeInterpreterToolCall|OutputFileSearchToolCall|OutputFunctionToolCall|OutputWebSearchToolCall|OutputComputerToolCall|OutputReasoning|OutputMcpListTools|OutputMcpApprovalRequest|OutputMcpCall|OutputImageGenerationToolCall|OutputCompaction $item, private readonly MetaInformation $meta, ) {} @@ -74,6 +76,7 @@ public static function from(array $attributes, MetaInformation $meta): self 'mcp_approval_request' => OutputMcpApprovalRequest::from($attributes['item']), 'mcp_call' => OutputMcpCall::from($attributes['item']), 'code_interpreter_call' => OutputCodeInterpreterToolCall::from($attributes['item']), + 'compaction' => OutputCompaction::from($attributes['item']), }; return new self( diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 36fd6a2a2..4da44a0f7 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -1042,3 +1042,8 @@ function responseRateLimitsUpdatedEvent() { return fopen(__DIR__.'/Streams/ResponseRateLimitsUpdated.txt', 'r'); } + +function responseOutputItemCompactionDoneEvent() +{ + return fopen(__DIR__.'/Streams/ResponseOutputItemCompactionDone.txt', 'r'); +} diff --git a/tests/Fixtures/Streams/ResponseOutputItemCompactionDone.txt b/tests/Fixtures/Streams/ResponseOutputItemCompactionDone.txt new file mode 100644 index 000000000..c8d409118 --- /dev/null +++ b/tests/Fixtures/Streams/ResponseOutputItemCompactionDone.txt @@ -0,0 +1 @@ +data: {"type":"response.output_item.done","output_index":0,"sequence_number":11,"item":{"id":"cmp_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c","encrypted_content":"encrypted_string_value","type":"compaction","created_by":"user_123"}} diff --git a/tests/Responses/Responses/CreateStreamedResponse.php b/tests/Responses/Responses/CreateStreamedResponse.php index e303d37f7..907243fbd 100644 --- a/tests/Responses/Responses/CreateStreamedResponse.php +++ b/tests/Responses/Responses/CreateStreamedResponse.php @@ -2,6 +2,8 @@ use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Responses\Responses\CreateStreamedResponse; +use OpenAI\Responses\Responses\Output\OutputCompaction; +use OpenAI\Responses\Responses\Streaming\OutputItem; use OpenAI\Responses\Responses\Streaming\RateLimits; use OpenAI\Responses\Responses\Streaming\ReasoningTextDelta; use OpenAI\Responses\Responses\Streaming\ReasoningTextDone; @@ -68,3 +70,18 @@ 'type' => 'response.rate_limits.updated', ]); }); + +test('output item done event with compaction item', function () { + $response = CreateStreamedResponse::fake(responseOutputItemCompactionDoneEvent()); + + expect($response->getIterator()->current()) + ->toBeInstanceOf(CreateStreamedResponse::class) + ->event->toBe('response.output_item.done') + ->response->toBeInstanceOf(OutputItem::class) + ->response->outputIndex->toBe(0) + ->response->sequenceNumber->toBe(11) + ->response->item->toBeInstanceOf(OutputCompaction::class) + ->response->item->id->toBe('cmp_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->response->item->encryptedContent->toBe('encrypted_string_value') + ->response->item->createdBy->toBe('user_123'); +}); From 74ceb3036455cfdbe3da312d4bfdd153e6d8a011 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 20 Jul 2026 13:28:45 +0400 Subject: [PATCH 2/2] Preserve function call namespaces --- .../Output/OutputFunctionToolCall.php | 5 +- tests/Fixtures/Responses.php | 16 +++++++ .../Output/OutputFunctionToolCall.php | 47 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/Responses/Responses/Output/OutputFunctionToolCall.php diff --git a/src/Responses/Responses/Output/OutputFunctionToolCall.php b/src/Responses/Responses/Output/OutputFunctionToolCall.php index 985fa9d26..a566e43b4 100644 --- a/src/Responses/Responses/Output/OutputFunctionToolCall.php +++ b/src/Responses/Responses/Output/OutputFunctionToolCall.php @@ -9,7 +9,7 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @phpstan-type OutputFunctionToolCallType array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'} + * @phpstan-type OutputFunctionToolCallType array{arguments: string, call_id: string, name: string, namespace?: ?string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'} * * @implements ResponseContract */ @@ -33,6 +33,7 @@ private function __construct( public readonly string $type, public readonly string $id, public readonly string $status, + public readonly ?string $namespace, ) {} /** @@ -47,6 +48,7 @@ public static function from(array $attributes): self type: $attributes['type'], id: $attributes['id'], status: $attributes['status'], + namespace: $attributes['namespace'] ?? null, ); } @@ -59,6 +61,7 @@ public function toArray(): array 'arguments' => $this->arguments, 'call_id' => $this->callId, 'name' => $this->name, + 'namespace' => $this->namespace, 'type' => $this->type, 'id' => $this->id, 'status' => $this->status, diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 4da44a0f7..7883adda7 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -726,6 +726,22 @@ function outputCustomToolCall(): array ]; } +/** + * @return array + */ +function outputFunctionToolCall(): array +{ + return [ + 'arguments' => '{"customer_id":"CUST-12345"}', + 'call_id' => 'call_abc123', + 'name' => 'list_open_orders', + 'namespace' => 'crm', + 'type' => 'function_call', + 'id' => 'fc_abc123', + 'status' => 'completed', + ]; +} + /** * @return array */ diff --git a/tests/Responses/Responses/Output/OutputFunctionToolCall.php b/tests/Responses/Responses/Output/OutputFunctionToolCall.php new file mode 100644 index 000000000..05c3dc18a --- /dev/null +++ b/tests/Responses/Responses/Output/OutputFunctionToolCall.php @@ -0,0 +1,47 @@ +toBeInstanceOf(OutputFunctionToolCall::class) + ->arguments->toBe('{"customer_id":"CUST-12345"}') + ->callId->toBe('call_abc123') + ->name->toBe('list_open_orders') + ->namespace->toBe('crm') + ->type->toBe('function_call') + ->id->toBe('fc_abc123') + ->status->toBe('completed'); +}); + +test('from without namespace', function () { + $attributes = outputFunctionToolCall(); + + unset($attributes['namespace']); + + set_error_handler(static fn (int $errno, string $errstr): bool => throw new ErrorException($errstr), E_WARNING); + + try { + $response = OutputFunctionToolCall::from($attributes); + } finally { + restore_error_handler(); + } + + expect($response->namespace)->toBeNull(); +}); + +test('as array accessible', function () { + $response = OutputFunctionToolCall::from(outputFunctionToolCall()); + + expect($response['namespace'])->toBe('crm'); +}); + +test('to array', function () { + $response = OutputFunctionToolCall::from(outputFunctionToolCall()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(outputFunctionToolCall()); +});