From d9f8bbb969c0b3cb40e9ec6c17206e81065a2b33 Mon Sep 17 00:00:00 2001 From: momostafa Date: Sat, 22 Mar 2025 15:11:53 +0100 Subject: [PATCH 01/60] Add support for Responses API Full support to the new Responses API https://platform.openai.com/docs/api-reference/responses --- src/Client.php | 12 ++ src/CreateResponse.php | 151 ++++++++++++++++++++++ src/CreateStreamedResponse.php | 42 +++++++ src/Data.php | 70 +++++++++++ src/DeleteResponse.php | 71 +++++++++++ src/ListInputItemsResponse.php | 99 +++++++++++++++ src/ListResponse.php | 99 +++++++++++++++ src/Payload.php | 220 +++++++++++++++++++++++++++++++++ src/ResourceUri.php | 101 +++++++++++++++ src/ResponseObject.php | 206 ++++++++++++++++++++++++++++++ src/Responses.php | 130 +++++++++++++++++++ src/ResponsesContract.php | 61 +++++++++ src/RetrieveResponse.php | 161 ++++++++++++++++++++++++ 13 files changed, 1423 insertions(+) create mode 100644 src/CreateResponse.php create mode 100644 src/CreateStreamedResponse.php create mode 100644 src/Data.php create mode 100644 src/DeleteResponse.php create mode 100644 src/ListInputItemsResponse.php create mode 100644 src/ListResponse.php create mode 100644 src/Payload.php create mode 100644 src/ResourceUri.php create mode 100644 src/ResponseObject.php create mode 100644 src/Responses.php create mode 100644 src/ResponsesContract.php create mode 100644 src/RetrieveResponse.php diff --git a/src/Client.php b/src/Client.php index c2547e13..7145a73e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -21,6 +21,7 @@ use OpenAI\Resources\Images; use OpenAI\Resources\Models; use OpenAI\Resources\Moderations; +use OpenAI\Resources\Responses; use OpenAI\Resources\Threads; use OpenAI\Resources\VectorStores; @@ -34,6 +35,16 @@ public function __construct(private readonly TransporterContract $transporter) // .. } + /** + * Manage responses to assist models with tasks. + * + * @see https://platform.openai.com/docs/api-reference/responses + */ + public function responses(): Responses + { + return new Responses($this->transporter); + } + /** * Given a prompt, the model will return one or more predicted completions, and can also return the probabilities * of alternative tokens at each position. @@ -186,4 +197,5 @@ public function vectorStores(): VectorStoresContract { return new VectorStores($this->transporter); } + } diff --git a/src/CreateResponse.php b/src/CreateResponse.php new file mode 100644 index 00000000..caa91b53 --- /dev/null +++ b/src/CreateResponse.php @@ -0,0 +1,151 @@ +, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> + */ +final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + + use Fakeable; + use HasMetaInformation; + + private function __construct( + /** + * The response id. + */ + public readonly string $id, + + /** + * The object type, which is always response. + */ + public readonly string $object, + + /** + * The Unix timestamp (in seconds) when the response was created. + */ + public readonly int $createdAt, + + /** + * The status of the response. + */ + public readonly string $status, + + /** + * The model used for response. + */ + public readonly string $model, + + /** + * The output of the response. + * + * @var array}>}> + */ + public readonly array $output, + + /** + * Set of 16 key-value pairs that can be attached to the object. + * This can be useful for storing additional information about the object in a structured format. + */ + public readonly array $metadata, + + /** + * The input for the response. + * + * @var string|array + */ + public readonly string|array $input = [], + + public readonly ?array $error = null, + public readonly ?array $incompleteDetails = null, + public readonly ?string $instructions = null, + public readonly ?int $maxOutputTokens = null, + public readonly bool $parallelToolCalls = false, + public readonly ?string $previousResponseId = null, + ) { + } + + /** + * @param array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes + */ + public static function from(array $attributes, array $meta): self + { + + return new self( + $attributes['id'], + $attributes['object'], + $attributes['created_at'], + $attributes['status'], + $attributes['model'], + $attributes['output'], + $attributes['metadata'] ?? [], + $attributes['input'] ?? [], + $attributes['error'] ?? null, + $attributes['incomplete_details'] ?? null, + $attributes['instructions'] ?? null, + $attributes['max_output_tokens'] ?? null, + $attributes['parallel_tool_calls'] ?? false, + $attributes['previous_response_id'] ?? null, + $attributes['reasoning'] ?? [], + $attributes['store'] ?? false, + $attributes['temperature'] ?? null, + $attributes['text'] ?? [], + $attributes['tools'] ?? [], + $attributes['top_p'] ?? null, + $attributes['truncation'] ?? 'disabled', + $attributes['usage'] ?? null, + $attributes['user'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->id; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'created_at' => $this->createdAt, + 'status' => $this->status, + 'model' => $this->model, + 'output' => $this->output, + 'metadata' => $this->metadata, + 'input' => $this->input, + 'error' => $this->error, + 'incomplete_details' => $this->incompleteDetails, + 'instructions' => $this->instructions, + 'max_output_tokens' => $this->maxOutputTokens, + 'parallel_tool_calls' => $this->parallelToolCalls, + 'previous_response_id' => $this->previousResponseId, + 'reasoning' => $this->reasoning, + 'store' => $this->store, + 'temperature' => $this->temperature, + 'text' => $this->text, + 'tools' => $this->tools, + 'top_p' => $this->topP, + 'truncation' => $this->truncation, + 'usage' => $this->usage, + 'user' => $this->user, + ]; + } +} \ No newline at end of file diff --git a/src/CreateStreamedResponse.php b/src/CreateStreamedResponse.php new file mode 100644 index 00000000..95ee0f80 --- /dev/null +++ b/src/CreateStreamedResponse.php @@ -0,0 +1,42 @@ + + */ +final class CreateStreamedResponse extends StreamResponse implements ResponseStreamContract +{ + protected static function partialResponseClass(): string + { + return CreatedPartialResponse::class; + } + + /** + * @return \Generator + */ + public static function fromResponse(ResponseInterface $response): \Generator + { + foreach (self::decodeStream($response) as $line) { + yield CreatedPartialResponse::from(json_decode($line, true, 512, JSON_THROW_ON_ERROR), []); + } + } + + /** + * @param callable(CreatedPartialResponse): void $callback + */ + public static function stream(ResponseInterface $response, callable $callback): void + { + foreach (self::decodeStream($response) as $line) { + $callback(CreatedPartialResponse::from(json_decode($line, true, 512, JSON_THROW_ON_ERROR), [])); + } + } +} \ No newline at end of file diff --git a/src/Data.php b/src/Data.php new file mode 100644 index 00000000..6aad4293 --- /dev/null +++ b/src/Data.php @@ -0,0 +1,70 @@ + + */ +final class Data implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use HasMetaInformation; + + private function __construct( + /** + * The input id. + */ + public readonly string $id, + + /** + * The object type, which is always input. + */ + public readonly string $object, + + /** + * The content of the input. + */ + public readonly string $content, + ) { + } + + /** + * @param array{id: string, object: string, content: string} $attributes + */ + public static function from(array $attributes, array $meta): self + { + return new self( + $attributes['id'], + $attributes['object'], + $attributes['content'], + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->id; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'content' => $this->content, + ]; + } +} \ No newline at end of file diff --git a/src/DeleteResponse.php b/src/DeleteResponse.php new file mode 100644 index 00000000..9f184799 --- /dev/null +++ b/src/DeleteResponse.php @@ -0,0 +1,71 @@ + + */ +final class DeleteResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use HasMetaInformation; + + private function __construct( + /** + * The response id. + */ + public readonly string $id, + + /** + * The object type, which is always response. + */ + public readonly string $object, + + /** + * Whether the response was successfully deleted. + */ + public readonly bool $deleted, + ) { + } + + /** + * @param array{id: string, object: string, deleted: bool} $attributes + */ + public static function from(array $attributes, array $meta): self + { + return new self( + $attributes['id'], + $attributes['object'], + $attributes['deleted'], + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->id; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'deleted' => $this->deleted, + ]; + } +} \ No newline at end of file diff --git a/src/ListInputItemsResponse.php b/src/ListInputItemsResponse.php new file mode 100644 index 00000000..9969c0a8 --- /dev/null +++ b/src/ListInputItemsResponse.php @@ -0,0 +1,99 @@ +, first_id: ?string, last_id: ?string, has_more: bool}> + */ +final class ListInputItemsResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use HasMetaInformation; + + /** + * @var array + */ + public readonly array $data; + + private function __construct( + /** + * The object type, which is always "list". + */ + public readonly string $object, + + /** + * The list of input items. + * + * @var array + */ + array $data, + + /** + * The first input item ID in the list. + */ + public readonly ?string $firstId, + + /** + * The last input item ID in the list. + */ + public readonly ?string $lastId, + + /** + * Whether there are more input items available beyond this list. + */ + public readonly bool $hasMore, + ) { + $this->data = $data; + } + + /** + * @param array{object: string, data: array, first_id: ?string, last_id: ?string, has_more: bool} $attributes + */ + public static function from(array $attributes, array $meta): self + { + $data = array_map(static function (array $inputItem): Data { + /** @var array{id: string, object: string, content: string} $inputItem */ + return Data::from($inputItem, []); + }, $attributes['data']); + + return new self( + $attributes['object'], + $data, + $attributes['first_id'] ?? null, + $attributes['last_id'] ?? null, + $attributes['has_more'], + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->object; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map(static fn (Data $inputItem): array => $inputItem->toArray(), $this->data), + 'first_id' => $this->firstId, + 'last_id' => $this->lastId, + 'has_more' => $this->hasMore, + ]; + } +} \ No newline at end of file diff --git a/src/ListResponse.php b/src/ListResponse.php new file mode 100644 index 00000000..4ce7170b --- /dev/null +++ b/src/ListResponse.php @@ -0,0 +1,99 @@ +, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> + */ +final class ListResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use HasMetaInformation; + + /** + * @var array + */ + public readonly array $data; + + private function __construct( + /** + * The object type, which is always "list". + */ + public readonly string $object, + + /** + * The list of responses. + * + * @var array + */ + array $data, + + /** + * The first response ID in the list. + */ + public readonly ?string $firstId, + + /** + * The last response ID in the list. + */ + public readonly ?string $lastId, + + /** + * Whether there are more responses available beyond this list. + */ + public readonly bool $hasMore, + ) { + $this->data = $data; + } + + /** + * @param array{object: string, data: array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool} $attributes + */ + public static function from(array $attributes, array $meta): self + { + $data = array_map(static function (array $response): ResponseObject { + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}},tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $response */ + return ResponseObject::from($response, []); + }, $attributes['data']); + + return new self( + $attributes['object'], + $data, + $attributes['first_id'] ?? null, + $attributes['last_id'] ?? null, + $attributes['has_more'], + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->object; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map(static fn (ResponseObject $response): array => $response->toArray(), $this->data), + 'first_id' => $this->firstId, + 'last_id' => $this->lastId, + 'has_more' => $this->hasMore, + ]; + } +} \ No newline at end of file diff --git a/src/Payload.php b/src/Payload.php new file mode 100644 index 00000000..2aafcfd3 --- /dev/null +++ b/src/Payload.php @@ -0,0 +1,220 @@ + $parameters + */ + private function __construct( + private readonly ContentType $contentType, + private readonly Method $method, + private readonly ResourceUri $uri, + private readonly array $parameters = [], + ) { + // .. + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function listInputItems(string $resource, string $id, array $parameters = []): self + { + $contentType = ContentType::JSON; + $method = Method::GET; + $uri = ResourceUri::listInputItems($resource, $id); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function list(string $resource, array $parameters = []): self + { + $contentType = ContentType::JSON; + $method = Method::GET; + $uri = ResourceUri::list($resource); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function retrieve(string $resource, string $id, string $suffix = '', array $parameters = []): self + { + $contentType = ContentType::JSON; + $method = Method::GET; + $uri = ResourceUri::retrieve($resource, $id, $suffix); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function modify(string $resource, string $id, array $parameters = []): self + { + $contentType = ContentType::JSON; + $method = Method::POST; + $uri = ResourceUri::modify($resource, $id); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + */ + public static function retrieveContent(string $resource, string $id): self + { + $contentType = ContentType::JSON; + $method = Method::GET; + $uri = ResourceUri::retrieveContent($resource, $id); + + return new self($contentType, $method, $uri); + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function create(string $resource, array $parameters): self + { + $contentType = ContentType::JSON; + $method = Method::POST; + $uri = ResourceUri::create($resource); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function upload(string $resource, array $parameters): self + { + $contentType = ContentType::MULTIPART; + $method = Method::POST; + $uri = ResourceUri::upload($resource); + + return new self($contentType, $method, $uri, $parameters); + } + + /** + * Creates a new Payload value object from the given parameters. + */ + public static function cancel(string $resource, string $id): self + { + $contentType = ContentType::JSON; + $method = Method::POST; + $uri = ResourceUri::cancel($resource, $id); + + return new self($contentType, $method, $uri); + } + + /** + * Creates a new Payload value object from the given parameters. + */ + public static function delete(string $resource, string $id): self + { + $contentType = ContentType::JSON; + $method = Method::DELETE; + $uri = ResourceUri::delete($resource, $id); + + return new self($contentType, $method, $uri); + } + + /** + * Creates a new Psr 7 Request instance. + */ + public function toRequest(BaseUri $baseUri, Headers $headers, QueryParams $queryParams): RequestInterface + { + $psr17Factory = new Psr17Factory; + + $body = null; + + $uri = $baseUri->toString().$this->uri->toString(); + + $queryParams = $queryParams->toArray(); + if ($this->method === Method::GET) { + $queryParams = [...$queryParams, ...$this->parameters]; + } + + if ($queryParams !== []) { + $uri .= '?'.http_build_query($queryParams); + } + + $headers = $headers->withContentType($this->contentType); + + if ($this->method === Method::POST) { + if ($this->contentType === ContentType::MULTIPART) { + $streamBuilder = new MultipartStreamBuilder($psr17Factory); + + /** @var array> $parameters */ + $parameters = $this->parameters; + + foreach ($parameters as $key => $value) { + if (is_int($value) || is_float($value) || is_bool($value)) { + $value = (string) $value; + } + + if (is_array($value)) { + foreach ($value as $nestedValue) { + $streamBuilder->addResource($key.'[]', $nestedValue); + } + + continue; + } + + $streamBuilder->addResource($key, $value); + } + + $body = $streamBuilder->build(); + + $headers = $headers->withContentType($this->contentType, '; boundary='.$streamBuilder->getBoundary()); + } else { + $body = $psr17Factory->createStream(json_encode($this->parameters, JSON_THROW_ON_ERROR)); + } + } + + $request = $psr17Factory->createRequest($this->method->value, $uri); + + if ($body instanceof StreamInterface) { + $request = $request->withBody($body); + } + + foreach ($headers->toArray() as $name => $value) { + $request = $request->withHeader($name, $value); + } + + return $request; + } +} diff --git a/src/ResourceUri.php b/src/ResourceUri.php new file mode 100644 index 00000000..1819d93e --- /dev/null +++ b/src/ResourceUri.php @@ -0,0 +1,101 @@ +uri; + } +} diff --git a/src/ResponseObject.php b/src/ResponseObject.php new file mode 100644 index 00000000..d22353b2 --- /dev/null +++ b/src/ResponseObject.php @@ -0,0 +1,206 @@ +, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, metadata: array}> + */ +final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use HasMetaInformation; + + /** + * @var array + */ + + private function __construct( + /** + * The response id. + */ + public readonly string $id, + + /** + * The object type, which is always model.response. + */ + public readonly string $object, + + /** + * The Unix timestamp (in seconds) when the response was created. + */ + public readonly int $createdAt, + + /** + * The response id. + */ + public readonly string $responseId, + + /** + * The status of the response, which can be “pending”, “processing”, “complete”, “error”, or “cancelled”. + */ + public readonly string $status, + + + /** + * The usage information for the response. + */ + public readonly array $usage, + + /** + * The error information for the response, if any. + */ + public readonly ?array $error, + + /** + * The incomplete details information for the response, if any. + */ + public readonly ?array $incompleteDetails, + + /** + * The text format information for the response. + */ + public readonly array $text, + + /** + * The reasoning information for the response. + */ + public readonly array $reasoning, + + /** + * The instructions for the response, if any. + */ + public readonly ?string $instructions, + + /** + * Whether parallel tool calls were used for the response. + */ + public readonly bool $parallelToolCalls, + + /** + * The tools used for the response. + */ + public readonly array $tools, + + /** + * The tool choice for the response. + */ + public readonly string $toolChoice, + + /** + * The top_p value for the response. + */ + public readonly ?float $topP, + + /** + * The temperature value for the response. + */ + public readonly ?float $temperature, + + /** + * The maximum output tokens for the response, if any. + */ + public readonly ?int $maxOutputTokens, + + /** + * Whether the response was stored. + */ + public readonly bool $store, + + /** + * The user ID associated with the response, if any. + */ + public readonly ?string $user, + + /** + * The ID of the previous response, if any. + */ + public readonly ?string $previousResponseId, + + + /** + * Set of 16 key-value pairs that can be attached to the object. + * This can be useful for storing additional information about the object in a structured format. + */ + public readonly array $metadata, + ) { + } + + /** + * @param array{id: string, object: string, created_at: int, response_id: string, status: string, array{index: int, content: string}>, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, metadata: array} $attributes + */ + public static function from(array $attributes, array $meta): self + { + + + return new self( + $attributes['id'], + $attributes['object'], + $attributes['created_at'], + $attributes['response_id'], + $attributes['status'], + $attributes['usage'], + $attributes['error'], + $attributes['incomplete_details'], + $attributes['text'], + $attributes['reasoning'], + $attributes['instructions'], + $attributes['parallel_tool_calls'], + $attributes['tools'], + $attributes['tool_choice'], + $attributes['top_p'], + $attributes['temperature'], + $attributes['max_output_tokens'], + $attributes['store'], + $attributes['user'], + $attributes['previous_response_id'], + $attributes['metadata'] ?? [], + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->id; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'createdAt' => $this->createdAt, + 'response_id' => $this->responseId, + 'status' => $this->status, + 'usage' => $this->usage, + 'error' => $this->error, + 'incomplete_details' => $this->incompleteDetails, + 'text' => $this->text, + 'reasoning' => $this->reasoning, + 'instructions' => $this->instructions, + 'parallel_tool_calls' => $this->parallelToolCalls, + 'tools' => $this->tools, + 'tool_choice' => $this->toolChoice, + 'top_p' => $this->topP, + 'temperature' => $this->temperature, + 'max_output_tokens' => $this->maxOutputTokens, + 'store' => $this->store, + 'user' => $this->user, + 'previous_response_id' => $this->previousResponseId, + 'metadata' => $this->metadata, + ]; + } +} \ No newline at end of file diff --git a/src/Responses.php b/src/Responses.php new file mode 100644 index 00000000..1d353873 --- /dev/null +++ b/src/Responses.php @@ -0,0 +1,130 @@ +ensureNotStreamed($parameters); + + $payload = Payload::create('responses', $parameters); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return CreateResponse::from($response->data(), $metaData); + + } + + /** + * {@inheritDoc} + * + * @return \OpenAI\Responses\Responses\CreateStreamedResponse + */ + public function createStreamed(array $parameters): CreateStreamedResponse + { + + $payload = Payload::createStreamed('responses', $parameters); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return CreateStreamedResponse::from($response->data(), $metaData); + } + + /** + * {@inheritDoc} + */ + public function retrieve(string $id): RetrieveResponse + { + $payload = Payload::retrieve('responses', $id); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return RetrieveResponse::from($response->data(), $metaData); + + } + + /** + * {@inheritDoc} + */ + public function delete(string $id): DeleteResponse + { + $payload = Payload::delete('responses', $id); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return DeleteResponse::from($response->data(), $metaData); + + } + + /** + * {@inheritDoc} + * attribute input_items is only used for ListInputItemsResponse and not for create response + */ + public function listInputItems(string $id, array $parameters): ListInputItemsResponse + { + $payload = Payload::listInputItems('responses', $id , $parameters); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return ListInputItemsResponse::from($response->data(), $metaData); + + } + + + /** + * {@inheritDoc} + */ + public function list(array $parameters): ListResponse + { + $payload = Payload::list('responses', $parameters); + + /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + $response = $this->transporter->requestObject($payload); + + $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + + return ListResponse::from($response->data(), $metaData); + + } + +} \ No newline at end of file diff --git a/src/ResponsesContract.php b/src/ResponsesContract.php new file mode 100644 index 00000000..f52b2ff1 --- /dev/null +++ b/src/ResponsesContract.php @@ -0,0 +1,61 @@ +, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> + */ +final class RetrieveResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +{ + use ArrayAccessible; + use Fakeable; + use HasMetaInformation; + + private function __construct( + /** + * The response id. + */ + public readonly string $id, + + /** + * The object type, which is always response. + */ + public readonly string $object, + + /** + * The Unix timestamp (in seconds) when the response was created. + */ + public readonly int $createdAt, + + /** + * The status of the response. + */ + public readonly string $status, + + /** + * The model used for response. + */ + public readonly string $model, + + /** + * The output of the response. + * + * @var array}>}> + */ + public readonly array $output, + + /** + * Set of 16 key-value pairs that can be attached to the object. + * This can be useful for storing additional information about the object in a structured format. + */ + public readonly array $metadata, + + /** + * The input for the response. + * + * @var string|array + */ + public readonly string|array $input = [], + + public readonly ?array $error = null, + public readonly ?array $incompleteDetails = null, + public readonly ?string $instructions = null, + public readonly ?int $maxOutputTokens = null, + public readonly bool $parallelToolCalls = false, + public readonly ?string $previousResponseId = null, + public readonly array $reasoning = [], + public readonly bool $store = false, + public readonly ?float $temperature = null, + public readonly array $text = [], + public readonly string $toolChoice = 'auto', + public readonly array $tools = [], + public readonly ?float $topP = null, + public readonly string $truncation = 'disabled', + public readonly ?array $usage = null, + public readonly ?string $user = null, + ) { + } + + /** + * @param array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes + */ + public static function from(array $attributes, array $meta): self + { + return new self( + $attributes['id'], + $attributes['object'], + $attributes['created_at'], + $attributes['status'], + $attributes['model'], + $attributes['output'], + $attributes['metadata'] ?? [], + $attributes['input'] ?? [], + $attributes['error'] ?? null, + $attributes['incomplete_details'] ?? null, + $attributes['instructions'] ?? null, + $attributes['max_output_tokens'] ?? null, + $attributes['parallel_tool_calls'] ?? false, + $attributes['previous_response_id'] ?? null, + $attributes['reasoning'] ?? [], + $attributes['store'] ?? false, + $attributes['temperature'] ?? null, + $attributes['text'] ?? [], + $attributes['toolChoice'] ?? 'auto', + $attributes['tools'] ?? [], + $attributes['topP'] ?? null, + $attributes['truncation'] ?? 'disabled', + $attributes['usage'] ?? null, + $attributes['user'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toString(): string + { + return $this->id; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'createdAt' => $this->createdAt, + 'status' => $this->status, + 'model' => $this->model, + 'output' => $this->output, + 'metadata' => $this->metadata, + 'input' => $this->input, + 'error' => $this->error, + 'incomplete_details' => $this->incompleteDetails, + 'instructions' => $this->instructions, + 'max_output_tokens' => $this->maxOutputTokens, + 'parallel_tool_calls' => $this->parallelToolCalls, + 'previous_response_id' => $this->previousResponseId, + 'reasoning' => $this->reasoning, + 'store' => $this->store, + 'temperature' => $this->temperature, + 'text' => $this->text, + 'tool_choice' => $this->toolChoice, + 'tools' => $this->tools, + 'top_p' => $this->topP, + 'truncation' => $this->truncation, + 'usage' => $this->usage, + 'user' => $this->user, + ]; + } +} \ No newline at end of file From 3cea3dc7fd01126bc8699256dde4746d5a6abbe3 Mon Sep 17 00:00:00 2001 From: momostafa Date: Tue, 25 Mar 2025 23:58:06 +0100 Subject: [PATCH 02/60] Updated miss placed files that occurred during first upload --- src/Client.php | 2 +- .../Resources}/ResponsesContract.php | 2 +- src/Payload.php | 220 ------------------ src/ResourceUri.php | 101 -------- src/{ => Resources}/Responses.php | 0 .../Responses}/CreateResponse.php | 0 .../Responses}/CreateStreamedResponse.php | 0 .../Responses}/DeleteResponse.php | 0 src/{ => Responses/Responses/Input}/Data.php | 0 .../Responses}/ListInputItemsResponse.php | 0 .../Responses}/ListResponse.php | 0 .../Responses}/ResponseObject.php | 0 .../Responses}/RetrieveResponse.php | 0 src/ValueObjects/ResourceUri.php | 10 +- src/ValueObjects/Transporter/Payload.php | 16 +- 15 files changed, 26 insertions(+), 325 deletions(-) rename src/{ => Contracts/Resources}/ResponsesContract.php (99%) delete mode 100644 src/Payload.php delete mode 100644 src/ResourceUri.php rename src/{ => Resources}/Responses.php (100%) rename src/{ => Responses/Responses}/CreateResponse.php (100%) rename src/{ => Responses/Responses}/CreateStreamedResponse.php (100%) rename src/{ => Responses/Responses}/DeleteResponse.php (100%) rename src/{ => Responses/Responses/Input}/Data.php (100%) rename src/{ => Responses/Responses}/ListInputItemsResponse.php (100%) rename src/{ => Responses/Responses}/ListResponse.php (100%) rename src/{ => Responses/Responses}/ResponseObject.php (100%) rename src/{ => Responses/Responses}/RetrieveResponse.php (100%) diff --git a/src/Client.php b/src/Client.php index 7145a73e..8ac67732 100644 --- a/src/Client.php +++ b/src/Client.php @@ -198,4 +198,4 @@ public function vectorStores(): VectorStoresContract return new VectorStores($this->transporter); } -} +} \ No newline at end of file diff --git a/src/ResponsesContract.php b/src/Contracts/Resources/ResponsesContract.php similarity index 99% rename from src/ResponsesContract.php rename to src/Contracts/Resources/ResponsesContract.php index f52b2ff1..caf5ae4a 100644 --- a/src/ResponsesContract.php +++ b/src/Contracts/Resources/ResponsesContract.php @@ -58,4 +58,4 @@ public function listInputItems(string $responseId, array $parameters): ListInput * @see https://platform.openai.com/docs/api-reference/responses/list */ public function list(array $parameters): ListResponse; -} +} \ No newline at end of file diff --git a/src/Payload.php b/src/Payload.php deleted file mode 100644 index 2aafcfd3..00000000 --- a/src/Payload.php +++ /dev/null @@ -1,220 +0,0 @@ - $parameters - */ - private function __construct( - private readonly ContentType $contentType, - private readonly Method $method, - private readonly ResourceUri $uri, - private readonly array $parameters = [], - ) { - // .. - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function listInputItems(string $resource, string $id, array $parameters = []): self - { - $contentType = ContentType::JSON; - $method = Method::GET; - $uri = ResourceUri::listInputItems($resource, $id); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function list(string $resource, array $parameters = []): self - { - $contentType = ContentType::JSON; - $method = Method::GET; - $uri = ResourceUri::list($resource); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function retrieve(string $resource, string $id, string $suffix = '', array $parameters = []): self - { - $contentType = ContentType::JSON; - $method = Method::GET; - $uri = ResourceUri::retrieve($resource, $id, $suffix); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function modify(string $resource, string $id, array $parameters = []): self - { - $contentType = ContentType::JSON; - $method = Method::POST; - $uri = ResourceUri::modify($resource, $id); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - */ - public static function retrieveContent(string $resource, string $id): self - { - $contentType = ContentType::JSON; - $method = Method::GET; - $uri = ResourceUri::retrieveContent($resource, $id); - - return new self($contentType, $method, $uri); - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function create(string $resource, array $parameters): self - { - $contentType = ContentType::JSON; - $method = Method::POST; - $uri = ResourceUri::create($resource); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function upload(string $resource, array $parameters): self - { - $contentType = ContentType::MULTIPART; - $method = Method::POST; - $uri = ResourceUri::upload($resource); - - return new self($contentType, $method, $uri, $parameters); - } - - /** - * Creates a new Payload value object from the given parameters. - */ - public static function cancel(string $resource, string $id): self - { - $contentType = ContentType::JSON; - $method = Method::POST; - $uri = ResourceUri::cancel($resource, $id); - - return new self($contentType, $method, $uri); - } - - /** - * Creates a new Payload value object from the given parameters. - */ - public static function delete(string $resource, string $id): self - { - $contentType = ContentType::JSON; - $method = Method::DELETE; - $uri = ResourceUri::delete($resource, $id); - - return new self($contentType, $method, $uri); - } - - /** - * Creates a new Psr 7 Request instance. - */ - public function toRequest(BaseUri $baseUri, Headers $headers, QueryParams $queryParams): RequestInterface - { - $psr17Factory = new Psr17Factory; - - $body = null; - - $uri = $baseUri->toString().$this->uri->toString(); - - $queryParams = $queryParams->toArray(); - if ($this->method === Method::GET) { - $queryParams = [...$queryParams, ...$this->parameters]; - } - - if ($queryParams !== []) { - $uri .= '?'.http_build_query($queryParams); - } - - $headers = $headers->withContentType($this->contentType); - - if ($this->method === Method::POST) { - if ($this->contentType === ContentType::MULTIPART) { - $streamBuilder = new MultipartStreamBuilder($psr17Factory); - - /** @var array> $parameters */ - $parameters = $this->parameters; - - foreach ($parameters as $key => $value) { - if (is_int($value) || is_float($value) || is_bool($value)) { - $value = (string) $value; - } - - if (is_array($value)) { - foreach ($value as $nestedValue) { - $streamBuilder->addResource($key.'[]', $nestedValue); - } - - continue; - } - - $streamBuilder->addResource($key, $value); - } - - $body = $streamBuilder->build(); - - $headers = $headers->withContentType($this->contentType, '; boundary='.$streamBuilder->getBoundary()); - } else { - $body = $psr17Factory->createStream(json_encode($this->parameters, JSON_THROW_ON_ERROR)); - } - } - - $request = $psr17Factory->createRequest($this->method->value, $uri); - - if ($body instanceof StreamInterface) { - $request = $request->withBody($body); - } - - foreach ($headers->toArray() as $name => $value) { - $request = $request->withHeader($name, $value); - } - - return $request; - } -} diff --git a/src/ResourceUri.php b/src/ResourceUri.php deleted file mode 100644 index 1819d93e..00000000 --- a/src/ResourceUri.php +++ /dev/null @@ -1,101 +0,0 @@ -uri; - } -} diff --git a/src/Responses.php b/src/Resources/Responses.php similarity index 100% rename from src/Responses.php rename to src/Resources/Responses.php diff --git a/src/CreateResponse.php b/src/Responses/Responses/CreateResponse.php similarity index 100% rename from src/CreateResponse.php rename to src/Responses/Responses/CreateResponse.php diff --git a/src/CreateStreamedResponse.php b/src/Responses/Responses/CreateStreamedResponse.php similarity index 100% rename from src/CreateStreamedResponse.php rename to src/Responses/Responses/CreateStreamedResponse.php diff --git a/src/DeleteResponse.php b/src/Responses/Responses/DeleteResponse.php similarity index 100% rename from src/DeleteResponse.php rename to src/Responses/Responses/DeleteResponse.php diff --git a/src/Data.php b/src/Responses/Responses/Input/Data.php similarity index 100% rename from src/Data.php rename to src/Responses/Responses/Input/Data.php diff --git a/src/ListInputItemsResponse.php b/src/Responses/Responses/ListInputItemsResponse.php similarity index 100% rename from src/ListInputItemsResponse.php rename to src/Responses/Responses/ListInputItemsResponse.php diff --git a/src/ListResponse.php b/src/Responses/Responses/ListResponse.php similarity index 100% rename from src/ListResponse.php rename to src/Responses/Responses/ListResponse.php diff --git a/src/ResponseObject.php b/src/Responses/Responses/ResponseObject.php similarity index 100% rename from src/ResponseObject.php rename to src/Responses/Responses/ResponseObject.php diff --git a/src/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php similarity index 100% rename from src/RetrieveResponse.php rename to src/Responses/Responses/RetrieveResponse.php diff --git a/src/ValueObjects/ResourceUri.php b/src/ValueObjects/ResourceUri.php index 12e05b45..880ce0c5 100644 --- a/src/ValueObjects/ResourceUri.php +++ b/src/ValueObjects/ResourceUri.php @@ -35,6 +35,14 @@ public static function upload(string $resource): self return new self($resource); } + /** + * Creates a new ResourceUri value object that list the input items for the given resource. + */ + public static function listInputItems(string $resource, string $id): self + { + return new self("{$resource}/{$id}/input_items"); + } + /** * Creates a new ResourceUri value object that lists the given resource. */ @@ -90,4 +98,4 @@ public function toString(): string { return $this->uri; } -} +} \ No newline at end of file diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index 24118c56..18dd1426 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -32,6 +32,20 @@ private function __construct( // .. } + /** + * Creates a new Payload value object from the given parameters. + * + * @param array $parameters + */ + public static function listInputItems(string $resource, string $id, array $parameters = []): self + { + $contentType = ContentType::JSON; + $method = Method::GET; + $uri = ResourceUri::listInputItems($resource, $id); + + return new self($contentType, $method, $uri, $parameters); + } + /** * Creates a new Payload value object from the given parameters. * @@ -203,4 +217,4 @@ public function toRequest(BaseUri $baseUri, Headers $headers, QueryParams $query return $request; } -} +} \ No newline at end of file From 99a3ed5e6f8b8446f5f8e1fdb039ecd1c5046bab Mon Sep 17 00:00:00 2001 From: momostafa Date: Thu, 27 Mar 2025 22:36:17 +0100 Subject: [PATCH 03/60] Heavy refactoring to match codebase pattern, added testing files --- src/Contracts/Resources/ResponsesContract.php | 29 +++-- src/Resources/Responses.php | 104 +++++++----------- src/Responses/Responses/CreateResponse.php | 49 +++++---- .../Responses/CreateResponseUsage.php | 38 +++++++ .../Responses/CreateStreamedResponse.php | 67 +++++++---- src/Responses/Responses/DeleteResponse.php | 26 +++-- src/Responses/Responses/Input/Data.php | 70 ------------ src/Responses/Responses/ListInputItems.php | 77 +++++++++++++ .../Responses/ListInputItemsResponse.php | 99 ----------------- src/Responses/Responses/ListResponse.php | 99 ----------------- src/Responses/Responses/ResponseObject.php | 44 +++----- src/Responses/Responses/RetrieveResponse.php | 49 ++++----- src/Testing/ClientFake.php | 8 ++ .../Resources/ResponsesTestResource.php | 47 ++++++++ .../Responses/CreateResponseFixture.php | 64 +++++++++++ .../CreateStreamedResponseFixture.txt | 9 ++ .../Responses/DeleteResponseFixture.php | 14 +++ .../Responses/ListInputItemsFixture.php | 28 +++++ .../Responses/RetrieveResponseFixture.php | 63 +++++++++++ src/ValueObjects/ResourceUri.php | 8 -- src/ValueObjects/Transporter/Payload.php | 14 --- 21 files changed, 527 insertions(+), 479 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseUsage.php delete mode 100644 src/Responses/Responses/Input/Data.php create mode 100644 src/Responses/Responses/ListInputItems.php delete mode 100644 src/Responses/Responses/ListInputItemsResponse.php delete mode 100644 src/Responses/Responses/ListResponse.php create mode 100644 src/Testing/Resources/ResponsesTestResource.php create mode 100644 src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/Responses/CreateStreamedResponseFixture.txt create mode 100644 src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php create mode 100644 src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php diff --git a/src/Contracts/Resources/ResponsesContract.php b/src/Contracts/Resources/ResponsesContract.php index caf5ae4a..79a93f40 100644 --- a/src/Contracts/Resources/ResponsesContract.php +++ b/src/Contracts/Resources/ResponsesContract.php @@ -7,10 +7,9 @@ use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Responses\Responses\DeleteResponse; use OpenAI\Responses\Responses\RetrieveResponse; -use OpenAI\Responses\Responses\ListResponse; -use OpenAI\Responses\Responses\ListInputItemsResponse; +use OpenAI\Responses\StreamResponse; use OpenAI\Responses\Responses\CreateStreamedResponse; -use OpenAI\Contracts\StringableContract; +use OpenAI\Responses\Responses\ListInputItems; /** * @internal @@ -18,7 +17,7 @@ interface ResponsesContract { /** - * Create a response. + * Create a response. * * @see https://platform.openai.com/docs/api-reference/responses/create */ @@ -28,34 +27,32 @@ public function create(array $parameters): CreateResponse; * Create a streamed response. * * @see https://platform.openai.com/docs/api-reference/responses/create + * + * @param array $parameters + * @return StreamResponse */ - public function createStreamed(array $parameters): CreateStreamedResponse; + public function createStreamed(array $parameters): StreamResponse; /** * Retrieve a response. * * @see https://platform.openai.com/docs/api-reference/responses/retrieve */ - public function retrieve(string $responseId): RetrieveResponse; + public function retrieve(string $id): RetrieveResponse; /** * Delete a response. * * @see https://platform.openai.com/docs/api-reference/responses/delete */ - public function delete(string $responseId): DeleteResponse; + public function delete(string $id): DeleteResponse; /** - * Lists the input items for a response. + * List input items for a response. * * @see https://platform.openai.com/docs/api-reference/responses/input-items - */ - public function listInputItems(string $responseId, array $parameters): ListInputItemsResponse; - - /** - * Returns a list of responses. * - * @see https://platform.openai.com/docs/api-reference/responses/list + * @param array $parameters */ - public function list(array $parameters): ListResponse; -} \ No newline at end of file + public function list(string $id, array $parameters = []): ListInputItems; +} diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 1d353873..cf8d17dd 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -10,23 +10,22 @@ use OpenAI\Responses\StreamResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\Responses\Responses\DeleteResponse; -use OpenAI\Responses\Responses\ListInputItemsResponse; -use OpenAI\Responses\Responses\ListResponse; use OpenAI\Responses\Responses\RetrieveResponse; -use OpenAI\Resources\Concerns\Transportable; -use OpenAI\Contracts\StringableContract; -use OpenAI\Responses\Responses\PartialResponses\CreatedPartialResponse; // Import PartialResponse +use OpenAI\Responses\Responses\ListInputItems; -/** - * @internal - */ final class Responses implements ResponsesContract { use Concerns\Streamable; use Concerns\Transportable; - /** - * {@inheritDoc} + /** + * Creates a model response. Provide text or image inputs to generate text or JSON outputs. + * Have the model call your own custom code or use built-in tools like web search or file search + * to use your own data as input for the model's response. + * + * @see https://platform.openai.com/docs/api-reference/responses/create + * + * @param array $parameters */ public function create(array $parameters): CreateResponse { @@ -34,97 +33,78 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array $response */ $response = $this->transporter->requestObject($payload); - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method - - return CreateResponse::from($response->data(), $metaData); + return CreateResponse::from($response->data(), $response->meta()); } /** - * {@inheritDoc} + * When you create a Response with stream set to true, + * the server will emit server-sent events to the client as the Response is generated. + * + * @see https://platform.openai.com/docs/api-reference/responses-streaming * - * @return \OpenAI\Responses\Responses\CreateStreamedResponse + * @param array $parameters + * @return StreamResponse */ - public function createStreamed(array $parameters): CreateStreamedResponse + public function createStreamed(array $parameters): StreamResponse { - $payload = Payload::createStreamed('responses', $parameters); + $parameters = $this->setStreamParameter($parameters); - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ - $response = $this->transporter->requestObject($payload); + $payload = Payload::create('responses', $parameters); - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method + $response = $this->transporter->requestObject($payload); - return CreateStreamedResponse::from($response->data(), $metaData); + return new StreamResponse(CreateStreamedResponse::class, $response->getResponse()); } /** - * {@inheritDoc} + * Retrieves a model response with the given ID. + * + * @see https://platform.openai.com/docs/api-reference/responses/get */ public function retrieve(string $id): RetrieveResponse { $payload = Payload::retrieve('responses', $id); - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array $response */ $response = $this->transporter->requestObject($payload); - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method - - return RetrieveResponse::from($response->data(), $metaData); - + return RetrieveResponse::from($response->data(), $response->meta()); } /** - * {@inheritDoc} + * Deletes a model response with the given ID. + * + * @see https://platform.openai.com/docs/api-reference/responses/delete */ public function delete(string $id): DeleteResponse { $payload = Payload::delete('responses', $id); - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ - $response = $this->transporter->requestObject($payload); - - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method - - return DeleteResponse::from($response->data(), $metaData); - - } - - /** - * {@inheritDoc} - * attribute input_items is only used for ListInputItemsResponse and not for create response - */ - public function listInputItems(string $id, array $parameters): ListInputItemsResponse - { - $payload = Payload::listInputItems('responses', $id , $parameters); - - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + /** @var Response $response */ $response = $this->transporter->requestObject($payload); - - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method - return ListInputItemsResponse::from($response->data(), $metaData); - - } - + return DeleteResponse::from($response->data(), $response->meta()); + } /** - * {@inheritDoc} + * Lists input items for a response with the given ID. + * + * @see https://platform.openai.com/docs/api-reference/responses/input-items + * + * @param array $parameters */ - public function list(array $parameters): ListResponse + public function list(string $id, array $parameters = []): ListInputItems { - $payload = Payload::list('responses', $parameters); + $payload = Payload::list('responses/'.$id.'/input_items', $parameters); - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $result */ + /** @var Response}>}>, first_id: ?string, last_id: ?string, has_more: bool}> $response */ $response = $this->transporter->requestObject($payload); - $metaData = $response->meta()->toArray(); // Assuming MetaInformation has a toArray() method - - return ListResponse::from($response->data(), $metaData); - + return ListInputItems::from($response->data(), $response->meta()); } - } \ No newline at end of file diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index caa91b53..d9e438e0 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -8,14 +8,17 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Contracts\ResponseHasMetaInformationContract; -use OpenAI\Contracts\StringableContract; +use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> + * @implements ResponseContract, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> */ -final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { + /** + * @use ArrayAccessible}>}> + */ use ArrayAccessible; use Fakeable; @@ -58,7 +61,9 @@ private function __construct( * Set of 16 key-value pairs that can be attached to the object. * This can be useful for storing additional information about the object in a structured format. */ - public readonly array $metadata, + private readonly MetaInformation $meta, + + public readonly CreateResponseUsage $usage, /** * The input for the response. @@ -67,21 +72,28 @@ private function __construct( */ public readonly string|array $input = [], - public readonly ?array $error = null, + public readonly object|null $error = null, public readonly ?array $incompleteDetails = null, public readonly ?string $instructions = null, public readonly ?int $maxOutputTokens = null, public readonly bool $parallelToolCalls = false, public readonly ?string $previousResponseId = null, + public readonly bool $store = false, + public readonly ?float $temperature = null, + public readonly ?float $topP = null, + public readonly ?int $truncation = null, + public readonly array $tools = [], + public readonly ?string $user = null, ) { } /** - * @param array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes + * Acts as static factory, and returns a new Response instance. + * + * @param array{id: string, object: string, created_at: int, status: string, error: ??object, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes */ - public static function from(array $attributes, array $meta): self + public static function from(array $attributes, MetaInformation $meta): self { - return new self( $attributes['id'], $attributes['object'], @@ -89,7 +101,8 @@ public static function from(array $attributes, array $meta): self $attributes['status'], $attributes['model'], $attributes['output'], - $attributes['metadata'] ?? [], + $meta, + CreateResponseUsage::from($attributes['usage']), $attributes['input'] ?? [], $attributes['error'] ?? null, $attributes['incomplete_details'] ?? null, @@ -97,26 +110,15 @@ public static function from(array $attributes, array $meta): self $attributes['max_output_tokens'] ?? null, $attributes['parallel_tool_calls'] ?? false, $attributes['previous_response_id'] ?? null, - $attributes['reasoning'] ?? [], $attributes['store'] ?? false, $attributes['temperature'] ?? null, - $attributes['text'] ?? [], - $attributes['tools'] ?? [], $attributes['top_p'] ?? null, - $attributes['truncation'] ?? 'disabled', - $attributes['usage'] ?? null, + $attributes['truncation'] ?? null, + $attributes['tools'] ?? [], $attributes['user'] ?? null, ); } - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->id; - } - /** * {@inheritDoc} */ @@ -129,7 +131,6 @@ public function toArray(): array 'status' => $this->status, 'model' => $this->model, 'output' => $this->output, - 'metadata' => $this->metadata, 'input' => $this->input, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, @@ -144,7 +145,7 @@ public function toArray(): array 'tools' => $this->tools, 'top_p' => $this->topP, 'truncation' => $this->truncation, - 'usage' => $this->usage, + 'usage' => $this->usage->toArray(), 'user' => $this->user, ]; } diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php new file mode 100644 index 00000000..d3dd1839 --- /dev/null +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -0,0 +1,38 @@ + $this->inputTokens, + 'output_tokens' => $this->outputTokens, + 'total_tokens' => $this->totalTokens, + ]; + } +} diff --git a/src/Responses/Responses/CreateStreamedResponse.php b/src/Responses/Responses/CreateStreamedResponse.php index 95ee0f80..fa5938a2 100644 --- a/src/Responses/Responses/CreateStreamedResponse.php +++ b/src/Responses/Responses/CreateStreamedResponse.php @@ -4,39 +4,66 @@ namespace OpenAI\Responses\Responses; -use OpenAI\Contracts\ResponseStreamContract; -use OpenAI\Responses\StreamResponse; -use OpenAI\Testing\Resources\Responses; -use Psr\Http\Message\ResponseInterface; -use OpenAI\Responses\Responses\PartialResponses\CreatedPartialResponse; +use OpenAI\Contracts\ResponseContract; +use OpenAI\Exceptions\UnknownEventException; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse; /** - * @implements ResponseStreamContract + * @implements ResponseContract}> */ -final class CreateStreamedResponse extends StreamResponse implements ResponseStreamContract +final class CreateStreamedResponse implements ResponseContract { - protected static function partialResponseClass(): string - { - return CreatedPartialResponse::class; - } + /** + * @use ArrayAccessible}> + */ + use ArrayAccessible; + + use FakeableForStreamedResponse; + + private function __construct( + public readonly string $event, + public readonly ?CreateResponse $response, + public readonly array $data, + ) {} /** - * @return \Generator + * Acts as static factory, and returns a new Response instance. + * + * Maps the appropriate classes onto each event from the responses streaming api + * https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses + * + * @param array $attributes */ - public static function fromResponse(ResponseInterface $response): \Generator + public static function from(array $attributes): self { - foreach (self::decodeStream($response) as $line) { - yield CreatedPartialResponse::from(json_decode($line, true, 512, JSON_THROW_ON_ERROR), []); + $event = $attributes['__event'] ?? null; + unset($attributes['__event']); + + $meta = $attributes['__meta'] ?? null; + unset($attributes['__meta']); + + // For events that return a full response object + $response = null; + if ($event === 'response.completed' && isset($meta)) { + $response = CreateResponse::from($attributes, $meta); } + + return new self( + $event ?? 'unknown', + $response, + $attributes, + ); } /** - * @param callable(CreatedPartialResponse): void $callback + * {@inheritDoc} */ - public static function stream(ResponseInterface $response, callable $callback): void + public function toArray(): array { - foreach (self::decodeStream($response) as $line) { - $callback(CreatedPartialResponse::from(json_decode($line, true, 512, JSON_THROW_ON_ERROR), [])); - } + return [ + 'event' => $this->event, + 'data' => $this->response ? $this->response->toArray() : $this->data, + ]; } } \ No newline at end of file diff --git a/src/Responses/Responses/DeleteResponse.php b/src/Responses/Responses/DeleteResponse.php index 9f184799..3201f19f 100644 --- a/src/Responses/Responses/DeleteResponse.php +++ b/src/Responses/Responses/DeleteResponse.php @@ -8,14 +8,19 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Contracts\ResponseHasMetaInformationContract; -use OpenAI\Contracts\StringableContract; - +use OpenAI\Responses\Meta\MetaInformation; +use OpenAI\Testing\Responses\Concerns\Fakeable; /** * @implements ResponseContract */ -final class DeleteResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +final class DeleteResponse implements ResponseContract, ResponseHasMetaInformationContract { + /** + * @use ArrayAccessible + */ use ArrayAccessible; + + use Fakeable; use HasMetaInformation; private function __construct( @@ -33,13 +38,18 @@ private function __construct( * Whether the response was successfully deleted. */ public readonly bool $deleted, + + private readonly MetaInformation $meta, + ) { } /** + * Acts as static factory, and returns a new Response instance. + * * @param array{id: string, object: string, deleted: bool} $attributes */ - public static function from(array $attributes, array $meta): self + public static function from(array $attributes, MetaInformation $meta): self { return new self( $attributes['id'], @@ -49,14 +59,6 @@ public static function from(array $attributes, array $meta): self ); } - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->id; - } - /** * {@inheritDoc} */ diff --git a/src/Responses/Responses/Input/Data.php b/src/Responses/Responses/Input/Data.php deleted file mode 100644 index 6aad4293..00000000 --- a/src/Responses/Responses/Input/Data.php +++ /dev/null @@ -1,70 +0,0 @@ - - */ -final class Data implements ResponseContract, ResponseHasMetaInformationContract, StringableContract -{ - use ArrayAccessible; - use HasMetaInformation; - - private function __construct( - /** - * The input id. - */ - public readonly string $id, - - /** - * The object type, which is always input. - */ - public readonly string $object, - - /** - * The content of the input. - */ - public readonly string $content, - ) { - } - - /** - * @param array{id: string, object: string, content: string} $attributes - */ - public static function from(array $attributes, array $meta): self - { - return new self( - $attributes['id'], - $attributes['object'], - $attributes['content'], - ); - } - - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->id; - } - - /** - * {@inheritDoc} - */ - public function toArray(): array - { - return [ - 'id' => $this->id, - 'object' => $this->object, - 'content' => $this->content, - ]; - } -} \ No newline at end of file diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php new file mode 100644 index 00000000..0bb4dc4c --- /dev/null +++ b/src/Responses/Responses/ListInputItems.php @@ -0,0 +1,77 @@ +}>}>, first_id: ?string, last_id: ?string, has_more: bool}> + */ +final class ListInputItems implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible}>}>, first_id: ?string, last_id: ?string, has_more: bool}> + */ + use ArrayAccessible; + + use Fakeable; + use HasMetaInformation; + + /** + * @param array $data + */ + private function __construct( + public readonly string $object, + public readonly array $data, + public readonly ?string $firstId, + public readonly ?string $lastId, + public readonly bool $hasMore, + private readonly MetaInformation $meta, + ) {} + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{object: string, data: array}>}>, first_id: ?string, last_id: ?string, has_more: bool} $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + $data = array_map(fn (array $result): ResponseObject => ResponseObject::from( + $result, + $meta, + ), $attributes['data']); + + return new self( + $attributes['object'], + $data, + $attributes['first_id'] ?? null, + $attributes['last_id'] ?? null, + $attributes['has_more'], + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map( + static fn (ResponseObject $result): array => $result->toArray(), + $this->data, + ), + 'first_id' => $this->firstId, + 'last_id' => $this->lastId, + 'has_more' => $this->hasMore, + ]; + } +} \ No newline at end of file diff --git a/src/Responses/Responses/ListInputItemsResponse.php b/src/Responses/Responses/ListInputItemsResponse.php deleted file mode 100644 index 9969c0a8..00000000 --- a/src/Responses/Responses/ListInputItemsResponse.php +++ /dev/null @@ -1,99 +0,0 @@ -, first_id: ?string, last_id: ?string, has_more: bool}> - */ -final class ListInputItemsResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract -{ - use ArrayAccessible; - use HasMetaInformation; - - /** - * @var array - */ - public readonly array $data; - - private function __construct( - /** - * The object type, which is always "list". - */ - public readonly string $object, - - /** - * The list of input items. - * - * @var array - */ - array $data, - - /** - * The first input item ID in the list. - */ - public readonly ?string $firstId, - - /** - * The last input item ID in the list. - */ - public readonly ?string $lastId, - - /** - * Whether there are more input items available beyond this list. - */ - public readonly bool $hasMore, - ) { - $this->data = $data; - } - - /** - * @param array{object: string, data: array, first_id: ?string, last_id: ?string, has_more: bool} $attributes - */ - public static function from(array $attributes, array $meta): self - { - $data = array_map(static function (array $inputItem): Data { - /** @var array{id: string, object: string, content: string} $inputItem */ - return Data::from($inputItem, []); - }, $attributes['data']); - - return new self( - $attributes['object'], - $data, - $attributes['first_id'] ?? null, - $attributes['last_id'] ?? null, - $attributes['has_more'], - $meta, - ); - } - - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->object; - } - - /** - * {@inheritDoc} - */ - public function toArray(): array - { - return [ - 'object' => $this->object, - 'data' => array_map(static fn (Data $inputItem): array => $inputItem->toArray(), $this->data), - 'first_id' => $this->firstId, - 'last_id' => $this->lastId, - 'has_more' => $this->hasMore, - ]; - } -} \ No newline at end of file diff --git a/src/Responses/Responses/ListResponse.php b/src/Responses/Responses/ListResponse.php deleted file mode 100644 index 4ce7170b..00000000 --- a/src/Responses/Responses/ListResponse.php +++ /dev/null @@ -1,99 +0,0 @@ -, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> - */ -final class ListResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract -{ - use ArrayAccessible; - use HasMetaInformation; - - /** - * @var array - */ - public readonly array $data; - - private function __construct( - /** - * The object type, which is always "list". - */ - public readonly string $object, - - /** - * The list of responses. - * - * @var array - */ - array $data, - - /** - * The first response ID in the list. - */ - public readonly ?string $firstId, - - /** - * The last response ID in the list. - */ - public readonly ?string $lastId, - - /** - * Whether there are more responses available beyond this list. - */ - public readonly bool $hasMore, - ) { - $this->data = $data; - } - - /** - * @param array{object: string, data: array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool} $attributes - */ - public static function from(array $attributes, array $meta): self - { - $data = array_map(static function (array $response): ResponseObject { - /** @var array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}},tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $response */ - return ResponseObject::from($response, []); - }, $attributes['data']); - - return new self( - $attributes['object'], - $data, - $attributes['first_id'] ?? null, - $attributes['last_id'] ?? null, - $attributes['has_more'], - $meta, - ); - } - - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->object; - } - - /** - * {@inheritDoc} - */ - public function toArray(): array - { - return [ - 'object' => $this->object, - 'data' => array_map(static fn (ResponseObject $response): array => $response->toArray(), $this->data), - 'first_id' => $this->firstId, - 'last_id' => $this->lastId, - 'has_more' => $this->hasMore, - ]; - } -} \ No newline at end of file diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index d22353b2..43b09675 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -8,15 +8,20 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Contracts\ResponseHasMetaInformationContract; -use OpenAI\Contracts\StringableContract; -use OpenAI\Responses\Responses\Input\Data; +use OpenAI\Responses\Meta\MetaInformation; +use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, metadata: array}> + * @implements ResponseContract, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array> */ -final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract { + /** + * @use ArrayAccessible, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array> + */ use ArrayAccessible; + + use Fakeable; use HasMetaInformation; /** @@ -49,12 +54,6 @@ private function __construct( */ public readonly string $status, - - /** - * The usage information for the response. - */ - public readonly array $usage, - /** * The error information for the response, if any. */ @@ -125,29 +124,27 @@ private function __construct( */ public readonly ?string $previousResponseId, + public readonly CreateResponseUsage $usage, /** * Set of 16 key-value pairs that can be attached to the object. * This can be useful for storing additional information about the object in a structured format. */ - public readonly array $metadata, + private readonly MetaInformation $meta, ) { } /** - * @param array{id: string, object: string, created_at: int, response_id: string, status: string, array{index: int, content: string}>, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, metadata: array} $attributes + * @param array{id: string, object: string, created_at: int, response_id: string, status: string, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array} $attributes */ - public static function from(array $attributes, array $meta): self + public static function from(array $attributes, MetaInformation $meta): self { - - return new self( $attributes['id'], $attributes['object'], $attributes['created_at'], $attributes['response_id'], $attributes['status'], - $attributes['usage'], $attributes['error'], $attributes['incomplete_details'], $attributes['text'], @@ -162,18 +159,11 @@ public static function from(array $attributes, array $meta): self $attributes['store'], $attributes['user'], $attributes['previous_response_id'], - $attributes['metadata'] ?? [], + CreateResponseUsage::from($attributes['usage']), + $meta, ); } - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->id; - } - /** * {@inheritDoc} */ @@ -185,7 +175,6 @@ public function toArray(): array 'createdAt' => $this->createdAt, 'response_id' => $this->responseId, 'status' => $this->status, - 'usage' => $this->usage, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, 'text' => $this->text, @@ -200,7 +189,8 @@ public function toArray(): array 'store' => $this->store, 'user' => $this->user, 'previous_response_id' => $this->previousResponseId, - 'metadata' => $this->metadata, + 'metadata' => $this->meta, + 'usage' => $this->usage->toArray(), ]; } } \ No newline at end of file diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index 89f0d3b8..d9200efe 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -8,15 +8,19 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Contracts\ResponseHasMetaInformationContract; -use OpenAI\Contracts\StringableContract; +use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; /** * @implements ResponseContract, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> */ -final class RetrieveResponse implements ResponseContract, ResponseHasMetaInformationContract, StringableContract +final class RetrieveResponse implements ResponseContract, ResponseHasMetaInformationContract { - use ArrayAccessible; + /** + * @use ArrayAccessible}> + */ + use ArrayAccessible; + use Fakeable; use HasMetaInformation; @@ -53,12 +57,6 @@ private function __construct( */ public readonly array $output, - /** - * Set of 16 key-value pairs that can be attached to the object. - * This can be useful for storing additional information about the object in a structured format. - */ - public readonly array $metadata, - /** * The input for the response. * @@ -66,7 +64,7 @@ private function __construct( */ public readonly string|array $input = [], - public readonly ?array $error = null, + public readonly object|null $error = null, public readonly ?array $incompleteDetails = null, public readonly ?string $instructions = null, public readonly ?int $maxOutputTokens = null, @@ -80,15 +78,18 @@ private function __construct( public readonly array $tools = [], public readonly ?float $topP = null, public readonly string $truncation = 'disabled', - public readonly ?array $usage = null, public readonly ?string $user = null, + public readonly CreateResponseUsage $usage, + private readonly MetaInformation $meta, ) { } /** - * @param array{id: string, object: string, created_at: int, status: string, error: ?array, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes + * Acts as static factory, and returns a new Response instance. + * + * @param array{id: string, object: string, created_at: int, status: string, error: ??object, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array, input: string|array} $attributes */ - public static function from(array $attributes, array $meta): self + public static function from(array $attributes, MetaInformation $meta): self { return new self( $attributes['id'], @@ -97,7 +98,6 @@ public static function from(array $attributes, array $meta): self $attributes['status'], $attributes['model'], $attributes['output'], - $attributes['metadata'] ?? [], $attributes['input'] ?? [], $attributes['error'] ?? null, $attributes['incomplete_details'] ?? null, @@ -109,23 +109,16 @@ public static function from(array $attributes, array $meta): self $attributes['store'] ?? false, $attributes['temperature'] ?? null, $attributes['text'] ?? [], - $attributes['toolChoice'] ?? 'auto', + $attributes['tool_choice'] ?? 'auto', $attributes['tools'] ?? [], - $attributes['topP'] ?? null, + $attributes['top_p'] ?? null, $attributes['truncation'] ?? 'disabled', - $attributes['usage'] ?? null, $attributes['user'] ?? null, + CreateResponseUsage::from($attributes['usage']), + $meta, ); } - /** - * {@inheritDoc} - */ - public function toString(): string - { - return $this->id; - } - /** * {@inheritDoc} */ @@ -134,11 +127,11 @@ public function toArray(): array return [ 'id' => $this->id, 'object' => $this->object, - 'createdAt' => $this->createdAt, + 'created_at' => $this->createdAt, 'status' => $this->status, 'model' => $this->model, 'output' => $this->output, - 'metadata' => $this->metadata, + 'metadata' => $this->meta->toArray(), 'input' => $this->input, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, @@ -154,8 +147,8 @@ public function toArray(): array 'tools' => $this->tools, 'top_p' => $this->topP, 'truncation' => $this->truncation, - 'usage' => $this->usage, 'user' => $this->user, + 'usage' => $this->usage->toArray(), ]; } } \ No newline at end of file diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index da4d4755..e18b0575 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -3,6 +3,7 @@ namespace OpenAI\Testing; use OpenAI\Contracts\ClientContract; +use OpenAI\Contracts\Resources\ResponsesContract; use OpenAI\Contracts\Resources\VectorStoresContract; use OpenAI\Contracts\ResponseContract; use OpenAI\Contracts\ResponseStreamContract; @@ -23,6 +24,7 @@ use OpenAI\Testing\Resources\ModerationsTestResource; use OpenAI\Testing\Resources\ThreadsTestResource; use OpenAI\Testing\Resources\VectorStoresTestResource; +use OpenAI\Testing\Resources\ResponsesTestResource; use PHPUnit\Framework\Assert as PHPUnit; use Throwable; @@ -131,6 +133,12 @@ public function record(TestRequest $request): ResponseContract|ResponseStreamCon return $response; } + + public function responses(): ResponsesContract + { + + return new ResponsesTestResource($this); + } public function completions(): CompletionsTestResource { diff --git a/src/Testing/Resources/ResponsesTestResource.php b/src/Testing/Resources/ResponsesTestResource.php new file mode 100644 index 00000000..5c4e5066 --- /dev/null +++ b/src/Testing/Resources/ResponsesTestResource.php @@ -0,0 +1,47 @@ +record(__FUNCTION__, func_get_args()); + } + + public function createStreamed(array $parameters): StreamResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function retrieve(string $id): RetrieveResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function list(string $id, array $parameters = []): ListInputItems + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function delete(string $id): DeleteResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } +} diff --git a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php new file mode 100644 index 00000000..fc7bc585 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php @@ -0,0 +1,64 @@ + 'resp_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'object' => 'response', + 'created_at' => 1741476542, + 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', + 'output' => [ + [ + 'type' => 'message', + 'id' => 'msg_67ccd2bf17f0819081ff3bb2cf6508e60bb6a6b452d3795b', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'In a peaceful grove beneath a silver moon, a unicorn named Lumina discovered a hidden pool that reflected the stars. As she dipped her horn into the water, the pool began to shimmer, revealing a pathway to a magical realm of endless night skies. Filled with wonder, Lumina whispered a wish for all who dream to find their own hidden magic, and as she glanced back, her hoofprints sparkled like stardust.', + 'annotations' => [] + ] + ] + ] + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'summary' => null + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text' + ] + ], + 'tool_choice' => 'auto', + 'tools' => [], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 36, + 'input_tokens_details' => [ + 'cached_tokens' => 0 + ], + 'output_tokens' => 87, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0 + ], + 'total_tokens' => 123 + ], + 'user' => null, + 'metadata' => [] + + ]; +} diff --git a/src/Testing/Responses/Fixtures/Responses/CreateStreamedResponseFixture.txt b/src/Testing/Responses/Fixtures/Responses/CreateStreamedResponseFixture.txt new file mode 100644 index 00000000..b7b9311e --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/CreateStreamedResponseFixture.txt @@ -0,0 +1,9 @@ +data: {"type":"response.created","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.in_progress","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"in_progress","role":"assistant","content":[]}} +data: {"type":"response.content_part.added","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}} +data: {"type":"response.output_text.delta","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"delta":"Hi"} +data: {"type":"response.output_text.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"text":"Hi there! How can I assist you today?"} +data: {"type":"response.content_part.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}} +data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}} +data: {"type":"response.completed","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"completed","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":37,"output_tokens":11,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":48},"user":null,"metadata":{}}} diff --git a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php new file mode 100644 index 00000000..24eee959 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php @@ -0,0 +1,14 @@ + 'resp_6786a1bec27481909a17d673315b29f6', + 'object' => 'response', + 'deleted' => true, + ] + ]; +} diff --git a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php new file mode 100644 index 00000000..e190811c --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php @@ -0,0 +1,28 @@ + 'list', + 'data' => [ + [ + 'type' => 'message', + 'id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'status' => 'completed', + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => 'Tell me a story about a unicorn', + 'annotations' => [] + ] + ] + ] + ], + 'first_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'last_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'has_more' => false + ]; +} \ No newline at end of file diff --git a/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php new file mode 100644 index 00000000..29de3acf --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php @@ -0,0 +1,63 @@ + 'resp_67cb71b351908190a308f3859487620d06981a8637e6bc44', + 'object' => 'response', + 'created_at' => 1741386163, + 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', + 'output' => [ + [ + 'type' => 'message', + 'id' => 'msg_67cb71b3c2b0819084d481baaaf148f206981a8637e6bc44', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'Silent circuits hum, \nThoughts emerge in data streams— \nDigital dawn breaks.', + 'annotations' => [], + ], + ], + ], + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'summary' => null, + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text', + ], + ], + 'tool_choice' => 'auto', + 'tools' => [], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 32, + 'input_tokens_details' => [ + 'cached_tokens' => 0, + ], + 'output_tokens' => 18, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0, + ], + 'total_tokens' => 50, + ], + 'user' => null, + 'metadata' => [], + ]; +} diff --git a/src/ValueObjects/ResourceUri.php b/src/ValueObjects/ResourceUri.php index 880ce0c5..aef2b019 100644 --- a/src/ValueObjects/ResourceUri.php +++ b/src/ValueObjects/ResourceUri.php @@ -35,14 +35,6 @@ public static function upload(string $resource): self return new self($resource); } - /** - * Creates a new ResourceUri value object that list the input items for the given resource. - */ - public static function listInputItems(string $resource, string $id): self - { - return new self("{$resource}/{$id}/input_items"); - } - /** * Creates a new ResourceUri value object that lists the given resource. */ diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index 18dd1426..6c277e06 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -32,20 +32,6 @@ private function __construct( // .. } - /** - * Creates a new Payload value object from the given parameters. - * - * @param array $parameters - */ - public static function listInputItems(string $resource, string $id, array $parameters = []): self - { - $contentType = ContentType::JSON; - $method = Method::GET; - $uri = ResourceUri::listInputItems($resource, $id); - - return new self($contentType, $method, $uri, $parameters); - } - /** * Creates a new Payload value object from the given parameters. * From 8ea527ae3b34ae66e4c07eac924df4eb5d94224a Mon Sep 17 00:00:00 2001 From: momostafa Date: Mon, 31 Mar 2025 02:31:00 +0200 Subject: [PATCH 04/60] fixed some phpstan errors --- src/Client.php | 2 +- src/Contracts/ClientContract.php | 102 +++-------- src/Contracts/Resources/ResponsesContract.php | 32 ++-- src/Resources/Responses.php | 34 ++-- src/Responses/Responses/CreateResponse.php | 122 +++++-------- .../Responses/CreateResponseUsage.php | 8 +- .../Responses/CreateStreamedResponse.php | 48 ++++-- src/Responses/Responses/DeleteResponse.php | 19 +- src/Responses/Responses/ListInputItems.php | 7 +- src/Responses/Responses/ResponseObject.php | 162 +++++------------- src/Responses/Responses/RetrieveResponse.php | 118 +++++-------- src/Testing/ClientFake.php | 2 +- .../Resources/ResponsesTestResource.php | 4 +- .../Responses/CreateResponseFixture.php | 22 +-- .../Responses/DeleteResponseFixture.php | 12 +- .../Responses/ListInputItemsFixture.php | 10 +- 16 files changed, 263 insertions(+), 441 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8ac67732..92f46bfb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ public function __construct(private readonly TransporterContract $transporter) // .. } - /** + /** * Manage responses to assist models with tasks. * * @see https://platform.openai.com/docs/api-reference/responses diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index ad018605..ce526b6f 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -1,132 +1,84 @@ $parameters */ public function create(array $parameters): CreateResponse; @@ -27,32 +29,40 @@ public function create(array $parameters): CreateResponse; * Create a streamed response. * * @see https://platform.openai.com/docs/api-reference/responses/create - * + * * @param array $parameters * @return StreamResponse */ public function createStreamed(array $parameters): StreamResponse; /** - * Retrieve a response. + * Retrieves a model response with the given ID. * * @see https://platform.openai.com/docs/api-reference/responses/retrieve + * + * @param string $id + * @return RetrieveResponse */ public function retrieve(string $id): RetrieveResponse; /** - * Delete a response. + * Deletes a model response with the given ID. * * @see https://platform.openai.com/docs/api-reference/responses/delete + * + * @param string $id + * @return DeleteResponse */ public function delete(string $id): DeleteResponse; /** - * List input items for a response. + * Returns a list of input items for a given response. * * @see https://platform.openai.com/docs/api-reference/responses/input-items * + * @param string $id * @param array $parameters + * @return ListInputItems */ public function list(string $id, array $parameters = []): ListInputItems; -} +} \ No newline at end of file diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index cf8d17dd..51eaca9b 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -8,23 +8,24 @@ use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Responses\Responses\CreateStreamedResponse; use OpenAI\Responses\StreamResponse; -use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\Responses\Responses\DeleteResponse; -use OpenAI\Responses\Responses\RetrieveResponse; use OpenAI\Responses\Responses\ListInputItems; +use OpenAI\Responses\Responses\RetrieveResponse; +use OpenAI\ValueObjects\Transporter\Payload; +use OpenAI\ValueObjects\Transporter\Response; final class Responses implements ResponsesContract { use Concerns\Streamable; use Concerns\Transportable; - /** - * Creates a model response. Provide text or image inputs to generate text or JSON outputs. + /** + * Creates a model response. Provide text or image inputs to generate text or JSON outputs. * Have the model call your own custom code or use built-in tools like web search or file search * to use your own data as input for the model's response. - * + * * @see https://platform.openai.com/docs/api-reference/responses/create - * + * * @param array $parameters */ public function create(array $parameters): CreateResponse @@ -33,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); @@ -41,9 +42,9 @@ public function create(array $parameters): CreateResponse } /** - * When you create a Response with stream set to true, + * When you create a Response with stream set to true, * the server will emit server-sent events to the client as the Response is generated. - * + * * @see https://platform.openai.com/docs/api-reference/responses-streaming * * @param array $parameters @@ -51,26 +52,25 @@ public function create(array $parameters): CreateResponse */ public function createStreamed(array $parameters): StreamResponse { - $parameters = $this->setStreamParameter($parameters); $payload = Payload::create('responses', $parameters); - $response = $this->transporter->requestObject($payload); + $response = $this->transporter->requestStream($payload); - return new StreamResponse(CreateStreamedResponse::class, $response->getResponse()); + return new StreamResponse(CreateStreamedResponse::class, $response); } /** * Retrieves a model response with the given ID. - * + * * @see https://platform.openai.com/docs/api-reference/responses/get */ public function retrieve(string $id): RetrieveResponse { $payload = Payload::retrieve('responses', $id); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return RetrieveResponse::from($response->data(), $response->meta()); @@ -78,7 +78,7 @@ public function retrieve(string $id): RetrieveResponse /** * Deletes a model response with the given ID. - * + * * @see https://platform.openai.com/docs/api-reference/responses/delete */ public function delete(string $id): DeleteResponse @@ -93,9 +93,9 @@ public function delete(string $id): DeleteResponse /** * Lists input items for a response with the given ID. - * + * * @see https://platform.openai.com/docs/api-reference/responses/input-items - * + * * @param array $parameters */ public function list(string $id, array $parameters = []): ListInputItems diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index d9e438e0..143735de 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -5,19 +5,19 @@ namespace OpenAI\Responses\Responses; use OpenAI\Contracts\ResponseContract; +use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; -use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -25,72 +25,36 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; private function __construct( - /** - * The response id. - */ public readonly string $id, - - /** - * The object type, which is always response. - */ public readonly string $object, - - /** - * The Unix timestamp (in seconds) when the response was created. - */ public readonly int $createdAt, - - /** - * The status of the response. - */ public readonly string $status, - - /** - * The model used for response. - */ + public readonly ?object $error, + public readonly ?object $incompleteDetails, + public readonly ?string $instructions, + public readonly ?int $maxOutputTokens, public readonly string $model, - - /** - * The output of the response. - * - * @var array}>}> - */ public readonly array $output, - - /** - * Set of 16 key-value pairs that can be attached to the object. - * This can be useful for storing additional information about the object in a structured format. - */ - private readonly MetaInformation $meta, - + public readonly bool $parallelToolCalls, + public readonly ?string $previousResponseId, + public readonly ?object $reasoning, + public readonly bool $store, + public readonly ?float $temperature, + public readonly object $text, + public readonly string $toolChoice, + public readonly array $tools, + public readonly ?float $topP, + public readonly string $truncation, + public readonly ?string $user, + public array $metadata, public readonly CreateResponseUsage $usage, - - /** - * The input for the response. - * - * @var string|array - */ - public readonly string|array $input = [], - - public readonly object|null $error = null, - public readonly ?array $incompleteDetails = null, - public readonly ?string $instructions = null, - public readonly ?int $maxOutputTokens = null, - public readonly bool $parallelToolCalls = false, - public readonly ?string $previousResponseId = null, - public readonly bool $store = false, - public readonly ?float $temperature = null, - public readonly ?float $topP = null, - public readonly ?int $truncation = null, - public readonly array $tools = [], - public readonly ?string $user = null, - ) { - } + private readonly MetaInformation $meta, + ) {} /** * Acts as static factory, and returns a new Response instance. - * - * @param array{id: string, object: string, created_at: int, status: string, error: ??object, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: array{format: array{type: string}}, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array} $attributes + * + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -99,23 +63,26 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['object'], $attributes['created_at'], $attributes['status'], + $attributes['error'], + $attributes['incomplete_details'], + $attributes['instructions'], + $attributes['max_output_tokens'], $attributes['model'], $attributes['output'], - $meta, + $attributes['parallel_tool_calls'], + $attributes['previous_response_id'], + $attributes['reasoning'], + $attributes['store'], + $attributes['temperature'], + $attributes['text'], + $attributes['tool_choice'], + $attributes['tools'], + $attributes['top_p'], + $attributes['truncation'], + $attributes['user'], + $attributes['metadata'] ?? [], CreateResponseUsage::from($attributes['usage']), - $attributes['input'] ?? [], - $attributes['error'] ?? null, - $attributes['incomplete_details'] ?? null, - $attributes['instructions'] ?? null, - $attributes['max_output_tokens'] ?? null, - $attributes['parallel_tool_calls'] ?? false, - $attributes['previous_response_id'] ?? null, - $attributes['store'] ?? false, - $attributes['temperature'] ?? null, - $attributes['top_p'] ?? null, - $attributes['truncation'] ?? null, - $attributes['tools'] ?? [], - $attributes['user'] ?? null, + $meta, ); } @@ -129,24 +96,25 @@ public function toArray(): array 'object' => $this->object, 'created_at' => $this->createdAt, 'status' => $this->status, - 'model' => $this->model, - 'output' => $this->output, - 'input' => $this->input, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, + 'model' => $this->model, + 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, 'previous_response_id' => $this->previousResponseId, 'reasoning' => $this->reasoning, 'store' => $this->store, 'temperature' => $this->temperature, 'text' => $this->text, + 'tool_choice' => $this->toolChoice, 'tools' => $this->tools, 'top_p' => $this->topP, 'truncation' => $this->truncation, - 'usage' => $this->usage->toArray(), 'user' => $this->user, + 'metadata' => $this->metadata, + 'usage' => $this->usage->toArray(), ]; } } \ No newline at end of file diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php index d3dd1839..1acdfd24 100644 --- a/src/Responses/Responses/CreateResponseUsage.php +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -13,13 +13,13 @@ private function __construct( ) {} /** - * @param array{input_tokens: int, output_tokens: int, total_tokens: int} $attributes + * @param array{input_tokens: int, output_tokens?: int, total_tokens: int} $attributes */ public static function from(array $attributes): self { return new self( $attributes['input_tokens'], - $attributes['output_tokens'] ?? null, + $attributes['output_tokens'] ?? 0, $attributes['total_tokens'], ); } @@ -31,8 +31,8 @@ public function toArray(): array { return [ 'input_tokens' => $this->inputTokens, - 'output_tokens' => $this->outputTokens, + 'output_tokens' => $this->outputTokens ?? 0, 'total_tokens' => $this->totalTokens, ]; } -} +} \ No newline at end of file diff --git a/src/Responses/Responses/CreateStreamedResponse.php b/src/Responses/Responses/CreateStreamedResponse.php index fa5938a2..bfdd7aaa 100644 --- a/src/Responses/Responses/CreateStreamedResponse.php +++ b/src/Responses/Responses/CreateStreamedResponse.php @@ -7,6 +7,7 @@ use OpenAI\Contracts\ResponseContract; use OpenAI\Exceptions\UnknownEventException; use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse; /** @@ -23,36 +24,54 @@ final class CreateStreamedResponse implements ResponseContract private function __construct( public readonly string $event, - public readonly ?CreateResponse $response, - public readonly array $data, + public readonly CreateResponse $response, ) {} /** * Acts as static factory, and returns a new Response instance. * - * Maps the appropriate classes onto each event from the responses streaming api - * https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses + * Maps the appropriate classes onto each event from the responses streaming api + * https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses * * @param array $attributes */ public static function from(array $attributes): self { - $event = $attributes['__event'] ?? null; + $event = $attributes['__event']; unset($attributes['__event']); - $meta = $attributes['__meta'] ?? null; + $meta = $attributes['__meta']; unset($attributes['__meta']); - // For events that return a full response object - $response = null; - if ($event === 'response.completed' && isset($meta)) { - $response = CreateResponse::from($attributes, $meta); - } + $response = match ($event) { + 'response.created' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.in_progress' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.completed' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.failed' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.incomplete' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.output_item.added' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.output_item.done' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.content_part.added' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.content_part.done' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.output_text.delta' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.output_text.annotation.added' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.output_text.done' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.refusal.delta' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.refusal.done' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.function_call_arguments.delta' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.function_call_arguments.done' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.file_search_call.in_progress' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.file_search_call.searching' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.file_search_call.completed' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.web_search_call.in_progress' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.web_search_call.searching' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + 'response.web_search_call.completed' => CreateResponse::from($attributes, $meta), // @phpstan-ignore-line + default => throw new UnknownEventException('Unknown event: '.$event), + }; return new self( - $event ?? 'unknown', + $event, // @phpstan-ignore-line $response, - $attributes, ); } @@ -63,7 +82,8 @@ public function toArray(): array { return [ 'event' => $this->event, - 'data' => $this->response ? $this->response->toArray() : $this->data, + 'data' => $this->response->toArray(), ]; } + } \ No newline at end of file diff --git a/src/Responses/Responses/DeleteResponse.php b/src/Responses/Responses/DeleteResponse.php index 3201f19f..6d4f5d9f 100644 --- a/src/Responses/Responses/DeleteResponse.php +++ b/src/Responses/Responses/DeleteResponse.php @@ -5,11 +5,12 @@ namespace OpenAI\Responses\Responses; use OpenAI\Contracts\ResponseContract; +use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; -use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; + /** * @implements ResponseContract */ @@ -24,25 +25,11 @@ final class DeleteResponse implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; private function __construct( - /** - * The response id. - */ public readonly string $id, - - /** - * The object type, which is always response. - */ public readonly string $object, - - /** - * Whether the response was successfully deleted. - */ public readonly bool $deleted, - private readonly MetaInformation $meta, - - ) { - } + ) {} /** * Acts as static factory, and returns a new Response instance. diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php index 0bb4dc4c..53a18c29 100644 --- a/src/Responses/Responses/ListInputItems.php +++ b/src/Responses/Responses/ListInputItems.php @@ -24,9 +24,6 @@ final class ListInputItems implements ResponseContract, ResponseHasMetaInformati use Fakeable; use HasMetaInformation; - /** - * @param array $data - */ private function __construct( public readonly string $object, public readonly array $data, @@ -51,8 +48,8 @@ public static function from(array $attributes, MetaInformation $meta): self return new self( $attributes['object'], $data, - $attributes['first_id'] ?? null, - $attributes['last_id'] ?? null, + $attributes['first_id'], + $attributes['last_id'], $attributes['has_more'], $meta, ); diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index 43b09675..2954e75a 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -5,137 +5,54 @@ namespace OpenAI\Responses\Responses; use OpenAI\Contracts\ResponseContract; +use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; -use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; use Fakeable; use HasMetaInformation; - /** - * @var array - */ - private function __construct( - /** - * The response id. - */ public readonly string $id, - - /** - * The object type, which is always model.response. - */ public readonly string $object, - - /** - * The Unix timestamp (in seconds) when the response was created. - */ public readonly int $createdAt, - - /** - * The response id. - */ - public readonly string $responseId, - - /** - * The status of the response, which can be “pending”, “processing”, “complete”, “error”, or “cancelled”. - */ public readonly string $status, - - /** - * The error information for the response, if any. - */ - public readonly ?array $error, - - /** - * The incomplete details information for the response, if any. - */ - public readonly ?array $incompleteDetails, - - /** - * The text format information for the response. - */ - public readonly array $text, - - /** - * The reasoning information for the response. - */ - public readonly array $reasoning, - - /** - * The instructions for the response, if any. - */ + public readonly object $error, + public readonly object $incompleteDetails, public readonly ?string $instructions, - - /** - * Whether parallel tool calls were used for the response. - */ + public readonly ?int $maxOutputTokens, + public readonly string $model, + public readonly array $output, public readonly bool $parallelToolCalls, - - /** - * The tools used for the response. - */ - public readonly array $tools, - - /** - * The tool choice for the response. - */ + public readonly ?string $previousResponseId, + public readonly object $reasoning, + public readonly bool $store, + public readonly ?float $temperature, + public readonly object $text, public readonly string $toolChoice, - - /** - * The top_p value for the response. - */ + public readonly array $tools, public readonly ?float $topP, - - /** - * The temperature value for the response. - */ - public readonly ?float $temperature, - - /** - * The maximum output tokens for the response, if any. - */ - public readonly ?int $maxOutputTokens, - - /** - * Whether the response was stored. - */ - public readonly bool $store, - - /** - * The user ID associated with the response, if any. - */ + public readonly string $truncation, public readonly ?string $user, - - /** - * The ID of the previous response, if any. - */ - public readonly ?string $previousResponseId, - + public array $metadata, public readonly CreateResponseUsage $usage, - - /** - * Set of 16 key-value pairs that can be attached to the object. - * This can be useful for storing additional information about the object in a structured format. - */ private readonly MetaInformation $meta, - ) { - } + ) {} /** - * @param array{id: string, object: string, created_at: int, response_id: string, status: string, error: ?array, incomplete_details: ?array, text: array{format: array{type: string}}, reasoning: array, instructions: ?string, parallel_tool_calls: bool, tools: array, tool_choice: string, top_p: ?float, temperature: ?float, max_output_tokens: ?int, store: bool, user: ?string, previous_response_id: ?string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, metadata: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -143,27 +60,30 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['id'], $attributes['object'], $attributes['created_at'], - $attributes['response_id'], $attributes['status'], $attributes['error'], $attributes['incomplete_details'], - $attributes['text'], - $attributes['reasoning'], $attributes['instructions'], + $attributes['max_output_tokens'], + $attributes['model'], + $attributes['output'], $attributes['parallel_tool_calls'], - $attributes['tools'], + $attributes['previous_response_id'], + $attributes['reasoning'], + $attributes['store'], + $attributes['temperature'], + $attributes['text'], $attributes['tool_choice'], + $attributes['tools'], $attributes['top_p'], - $attributes['temperature'], - $attributes['max_output_tokens'], - $attributes['store'], + $attributes['truncation'], $attributes['user'], - $attributes['previous_response_id'], + $attributes['metadata'] ?? [], CreateResponseUsage::from($attributes['usage']), $meta, ); } - + /** * {@inheritDoc} */ @@ -172,24 +92,26 @@ public function toArray(): array return [ 'id' => $this->id, 'object' => $this->object, - 'createdAt' => $this->createdAt, - 'response_id' => $this->responseId, + 'created_at' => $this->createdAt, 'status' => $this->status, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, - 'text' => $this->text, - 'reasoning' => $this->reasoning, 'instructions' => $this->instructions, + 'max_output_tokens' => $this->maxOutputTokens, + 'model' => $this->model, + 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, - 'tools' => $this->tools, + 'previous_response_id' => $this->previousResponseId, + 'reasoning' => $this->reasoning, + 'store' => $this->store, + 'temperature' => $this->temperature, + 'text' => $this->text, 'tool_choice' => $this->toolChoice, + 'tools' => $this->tools, 'top_p' => $this->topP, - 'temperature' => $this->temperature, - 'max_output_tokens' => $this->maxOutputTokens, - 'store' => $this->store, + 'truncation' => $this->truncation, 'user' => $this->user, - 'previous_response_id' => $this->previousResponseId, - 'metadata' => $this->meta, + 'metadata' => $this->metadata, 'usage' => $this->usage->toArray(), ]; } diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index d9200efe..b200f2a8 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -5,89 +5,56 @@ namespace OpenAI\Responses\Responses; use OpenAI\Contracts\ResponseContract; +use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; -use OpenAI\Contracts\ResponseHasMetaInformationContract; use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class RetrieveResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ - use ArrayAccessible; + use ArrayAccessible; use Fakeable; use HasMetaInformation; private function __construct( - /** - * The response id. - */ public readonly string $id, - - /** - * The object type, which is always response. - */ public readonly string $object, - - /** - * The Unix timestamp (in seconds) when the response was created. - */ public readonly int $createdAt, - - /** - * The status of the response. - */ public readonly string $status, - - /** - * The model used for response. - */ + public readonly object $error, + public readonly object $incompleteDetails, + public readonly ?string $instructions, + public readonly ?int $maxOutputTokens, public readonly string $model, - - /** - * The output of the response. - * - * @var array}>}> - */ public readonly array $output, - - /** - * The input for the response. - * - * @var string|array - */ - public readonly string|array $input = [], - - public readonly object|null $error = null, - public readonly ?array $incompleteDetails = null, - public readonly ?string $instructions = null, - public readonly ?int $maxOutputTokens = null, - public readonly bool $parallelToolCalls = false, - public readonly ?string $previousResponseId = null, - public readonly array $reasoning = [], - public readonly bool $store = false, - public readonly ?float $temperature = null, - public readonly array $text = [], - public readonly string $toolChoice = 'auto', - public readonly array $tools = [], - public readonly ?float $topP = null, - public readonly string $truncation = 'disabled', - public readonly ?string $user = null, + public readonly bool $parallelToolCalls, + public readonly ?string $previousResponseId, + public readonly object $reasoning, + public readonly bool $store, + public readonly ?float $temperature, + public readonly object $text, + public readonly string $toolChoice, + public readonly array $tools, + public readonly ?float $topP, + public readonly string $truncation, + public readonly ?string $user, + public array $metadata, public readonly CreateResponseUsage $usage, private readonly MetaInformation $meta, - ) { - } + ) {} /** * Acts as static factory, and returns a new Response instance. - * - * @param array{id: string, object: string, created_at: int, status: string, error: ??object, incomplete_details: ?array, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata: array, input: string|array} $attributes + * + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -96,24 +63,24 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['object'], $attributes['created_at'], $attributes['status'], + $attributes['error'], + $attributes['incomplete_details'], + $attributes['instructions'], + $attributes['max_output_tokens'], $attributes['model'], $attributes['output'], - $attributes['input'] ?? [], - $attributes['error'] ?? null, - $attributes['incomplete_details'] ?? null, - $attributes['instructions'] ?? null, - $attributes['max_output_tokens'] ?? null, - $attributes['parallel_tool_calls'] ?? false, - $attributes['previous_response_id'] ?? null, - $attributes['reasoning'] ?? [], - $attributes['store'] ?? false, - $attributes['temperature'] ?? null, - $attributes['text'] ?? [], - $attributes['tool_choice'] ?? 'auto', - $attributes['tools'] ?? [], - $attributes['top_p'] ?? null, - $attributes['truncation'] ?? 'disabled', - $attributes['user'] ?? null, + $attributes['parallel_tool_calls'], + $attributes['previous_response_id'], + $attributes['reasoning'], + $attributes['store'], + $attributes['temperature'], + $attributes['text'], + $attributes['tool_choice'], + $attributes['tools'], + $attributes['top_p'], + $attributes['truncation'], + $attributes['user'], + $attributes['metadata'] ?? [], CreateResponseUsage::from($attributes['usage']), $meta, ); @@ -129,14 +96,12 @@ public function toArray(): array 'object' => $this->object, 'created_at' => $this->createdAt, 'status' => $this->status, - 'model' => $this->model, - 'output' => $this->output, - 'metadata' => $this->meta->toArray(), - 'input' => $this->input, 'error' => $this->error, 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, + 'model' => $this->model, + 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, 'previous_response_id' => $this->previousResponseId, 'reasoning' => $this->reasoning, @@ -148,6 +113,7 @@ public function toArray(): array 'top_p' => $this->topP, 'truncation' => $this->truncation, 'user' => $this->user, + 'metadata' => $this->metadata, 'usage' => $this->usage->toArray(), ]; } diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index e18b0575..d8065cf7 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -22,9 +22,9 @@ use OpenAI\Testing\Resources\ImagesTestResource; use OpenAI\Testing\Resources\ModelsTestResource; use OpenAI\Testing\Resources\ModerationsTestResource; +use OpenAI\Testing\Resources\ResponsesTestResource; use OpenAI\Testing\Resources\ThreadsTestResource; use OpenAI\Testing\Resources\VectorStoresTestResource; -use OpenAI\Testing\Resources\ResponsesTestResource; use PHPUnit\Framework\Assert as PHPUnit; use Throwable; diff --git a/src/Testing/Resources/ResponsesTestResource.php b/src/Testing/Resources/ResponsesTestResource.php index 5c4e5066..7468b3a8 100644 --- a/src/Testing/Resources/ResponsesTestResource.php +++ b/src/Testing/Resources/ResponsesTestResource.php @@ -4,10 +4,10 @@ use OpenAI\Contracts\Resources\ResponsesContract; use OpenAI\Resources\Responses; -use OpenAI\Responses\Responses\DeleteResponse; use OpenAI\Responses\Responses\CreateResponse; -use OpenAI\Responses\Responses\RetrieveResponse; +use OpenAI\Responses\Responses\DeleteResponse; use OpenAI\Responses\Responses\ListInputItems; +use OpenAI\Responses\Responses\RetrieveResponse; use OpenAI\Responses\StreamResponse; use OpenAI\Testing\Resources\Concerns\Testable; diff --git a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php index fc7bc585..c249cae7 100644 --- a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php @@ -24,22 +24,22 @@ final class CreateResponseFixture [ 'type' => 'output_text', 'text' => 'In a peaceful grove beneath a silver moon, a unicorn named Lumina discovered a hidden pool that reflected the stars. As she dipped her horn into the water, the pool began to shimmer, revealing a pathway to a magical realm of endless night skies. Filled with wonder, Lumina whispered a wish for all who dream to find their own hidden magic, and as she glanced back, her hoofprints sparkled like stardust.', - 'annotations' => [] - ] - ] - ] + 'annotations' => [], + ], + ], + ], ], 'parallel_tool_calls' => true, 'previous_response_id' => null, 'reasoning' => [ 'effort' => null, - 'summary' => null + 'summary' => null, ], 'store' => true, 'temperature' => 1.0, 'text' => [ 'format' => [ - 'type' => 'text' + 'type' => 'text', ] ], 'tool_choice' => 'auto', @@ -49,16 +49,16 @@ final class CreateResponseFixture 'usage' => [ 'input_tokens' => 36, 'input_tokens_details' => [ - 'cached_tokens' => 0 + 'cached_tokens' => 0, ], 'output_tokens' => 87, 'output_tokens_details' => [ - 'reasoning_tokens' => 0 + 'reasoning_tokens' => 0, ], - 'total_tokens' => 123 + 'total_tokens' => 123, ], 'user' => null, - 'metadata' => [] + 'metadata' => [], ]; -} +} \ No newline at end of file diff --git a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php index 24eee959..0f4ee103 100644 --- a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php @@ -5,10 +5,10 @@ final class DeleteResponseFixture { public const ATTRIBUTES = [ - [ - 'id' => 'resp_6786a1bec27481909a17d673315b29f6', - 'object' => 'response', - 'deleted' => true, - ] + [ + 'id' => 'resp_6786a1bec27481909a17d673315b29f6', + 'object' => 'response', + 'deleted' => true, + ], ]; -} +} \ No newline at end of file diff --git a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php index e190811c..524d405e 100644 --- a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php @@ -16,13 +16,13 @@ final class ListInputItemsFixture [ 'type' => 'text', 'text' => 'Tell me a story about a unicorn', - 'annotations' => [] - ] - ] - ] + 'annotations' => [], + ], + ], + ], ], 'first_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', 'last_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', - 'has_more' => false + 'has_more' => false, ]; } \ No newline at end of file From 412f35c7b8d4bce6f2622738a830a95edbc27e6b Mon Sep 17 00:00:00 2001 From: momostafa Date: Mon, 31 Mar 2025 03:59:21 +0200 Subject: [PATCH 05/60] fixing minor bugs after live testing now all models work during live testing --- src/Resources/Responses.php | 4 ++-- src/Responses/Responses/CreateResponse.php | 10 +++++----- src/Responses/Responses/ResponseObject.php | 12 ++++++------ src/Responses/Responses/RetrieveResponse.php | 14 +++++++------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 51eaca9b..f19ce8dd 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); @@ -70,7 +70,7 @@ public function retrieve(string $id): RetrieveResponse { $payload = Payload::retrieve('responses', $id); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return RetrieveResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 143735de..56ce787b 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -37,10 +37,10 @@ private function __construct( public readonly array $output, public readonly bool $parallelToolCalls, public readonly ?string $previousResponseId, - public readonly ?object $reasoning, + public readonly array $reasoning, public readonly bool $store, public readonly ?float $temperature, - public readonly object $text, + public readonly array $text, public readonly string $toolChoice, public readonly array $tools, public readonly ?float $topP, @@ -54,7 +54,7 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index 2954e75a..87f44ab0 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -30,17 +30,17 @@ private function __construct( public readonly int $createdAt, public readonly string $status, public readonly object $error, - public readonly object $incompleteDetails, + public readonly ?object $incompleteDetails, public readonly ?string $instructions, public readonly ?int $maxOutputTokens, public readonly string $model, public readonly array $output, public readonly bool $parallelToolCalls, public readonly ?string $previousResponseId, - public readonly object $reasoning, + public readonly array $reasoning, public readonly bool $store, public readonly ?float $temperature, - public readonly object $text, + public readonly array $text, public readonly string $toolChoice, public readonly array $tools, public readonly ?float $topP, @@ -52,7 +52,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index b200f2a8..b1306a99 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class RetrieveResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: object|null, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -29,18 +29,18 @@ private function __construct( public readonly string $object, public readonly int $createdAt, public readonly string $status, - public readonly object $error, - public readonly object $incompleteDetails, + public readonly ?object $error, + public readonly ?object $incompleteDetails, public readonly ?string $instructions, public readonly ?int $maxOutputTokens, public readonly string $model, public readonly array $output, public readonly bool $parallelToolCalls, public readonly ?string $previousResponseId, - public readonly object $reasoning, + public readonly array $reasoning, public readonly bool $store, public readonly ?float $temperature, - public readonly object $text, + public readonly array $text, public readonly string $toolChoice, public readonly array $tools, public readonly ?float $topP, @@ -54,7 +54,7 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: ?object, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: ?object, store: bool, temperature: ?float, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { From 29436e8492a83f8dc90d4a8dc2841b8649bdbaa9 Mon Sep 17 00:00:00 2001 From: momostafa Date: Sat, 5 Apr 2025 01:56:03 +0200 Subject: [PATCH 06/60] Fixed lint errors --- src/Client.php | 3 +-- src/Contracts/ClientContract.php | 2 +- src/Contracts/Resources/ResponsesContract.php | 10 +--------- src/Resources/Responses.php | 10 +++++----- src/Responses/Responses/CreateResponse.php | 6 +++--- src/Responses/Responses/CreateResponseUsage.php | 2 +- src/Responses/Responses/CreateStreamedResponse.php | 6 ++---- src/Responses/Responses/DeleteResponse.php | 6 +++--- src/Responses/Responses/ListInputItems.php | 2 +- src/Responses/Responses/ResponseObject.php | 6 +++--- src/Responses/Responses/RetrieveResponse.php | 4 ++-- src/Testing/ClientFake.php | 2 +- .../Fixtures/Responses/CreateResponseFixture.php | 4 ++-- .../Fixtures/Responses/DeleteResponseFixture.php | 2 +- .../Fixtures/Responses/ListInputItemsFixture.php | 2 +- src/ValueObjects/ResourceUri.php | 2 +- src/ValueObjects/Transporter/Payload.php | 2 +- 17 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/Client.php b/src/Client.php index 92f46bfb..3aca764f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -197,5 +197,4 @@ public function vectorStores(): VectorStoresContract { return new VectorStores($this->transporter); } - -} \ No newline at end of file +} diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index ce526b6f..ad598e44 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -8,13 +8,13 @@ use OpenAI\Contracts\Resources\AudioContract; use OpenAI\Contracts\Resources\ChatContract; use OpenAI\Contracts\Resources\CompletionsContract; +use OpenAI\Contracts\Resources\EditsContract; use OpenAI\Contracts\Resources\EmbeddingsContract; use OpenAI\Contracts\Resources\FilesContract; use OpenAI\Contracts\Resources\FineTunesContract; use OpenAI\Contracts\Resources\ImagesContract; use OpenAI\Contracts\Resources\ModelsContract; use OpenAI\Contracts\Resources\ModerationsContract; -use OpenAI\Contracts\Resources\EditsContract; use OpenAI\Contracts\Resources\ResponsesContract; // Add ResponsesContract /** diff --git a/src/Contracts/Resources/ResponsesContract.php b/src/Contracts/Resources/ResponsesContract.php index 87dbd730..4d13d6ed 100644 --- a/src/Contracts/Resources/ResponsesContract.php +++ b/src/Contracts/Resources/ResponsesContract.php @@ -39,9 +39,6 @@ public function createStreamed(array $parameters): StreamResponse; * Retrieves a model response with the given ID. * * @see https://platform.openai.com/docs/api-reference/responses/retrieve - * - * @param string $id - * @return RetrieveResponse */ public function retrieve(string $id): RetrieveResponse; @@ -49,9 +46,6 @@ public function retrieve(string $id): RetrieveResponse; * Deletes a model response with the given ID. * * @see https://platform.openai.com/docs/api-reference/responses/delete - * - * @param string $id - * @return DeleteResponse */ public function delete(string $id): DeleteResponse; @@ -60,9 +54,7 @@ public function delete(string $id): DeleteResponse; * * @see https://platform.openai.com/docs/api-reference/responses/input-items * - * @param string $id * @param array $parameters - * @return ListInputItems */ public function list(string $id, array $parameters = []): ListInputItems; -} \ No newline at end of file +} diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index f19ce8dd..30a70eef 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -7,10 +7,10 @@ use OpenAI\Contracts\Resources\ResponsesContract; use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Responses\Responses\CreateStreamedResponse; -use OpenAI\Responses\StreamResponse; use OpenAI\Responses\Responses\DeleteResponse; use OpenAI\Responses\Responses\ListInputItems; use OpenAI\Responses\Responses\RetrieveResponse; +use OpenAI\Responses\StreamResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; @@ -57,7 +57,7 @@ public function createStreamed(array $parameters): StreamResponse $payload = Payload::create('responses', $parameters); $response = $this->transporter->requestStream($payload); - + return new StreamResponse(CreateStreamedResponse::class, $response); } @@ -87,9 +87,9 @@ public function delete(string $id): DeleteResponse /** @var Response $response */ $response = $this->transporter->requestObject($payload); - + return DeleteResponse::from($response->data(), $response->meta()); - } + } /** * Lists input items for a response with the given ID. @@ -107,4 +107,4 @@ public function list(string $id, array $parameters = []): ListInputItems return ListInputItems::from($response->data(), $response->meta()); } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 56ce787b..ae927b65 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -18,7 +18,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati { /** * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> - */ + */ use ArrayAccessible; use Fakeable; @@ -54,7 +54,7 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -117,4 +117,4 @@ public function toArray(): array 'usage' => $this->usage->toArray(), ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php index 1acdfd24..a17d5aac 100644 --- a/src/Responses/Responses/CreateResponseUsage.php +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -35,4 +35,4 @@ public function toArray(): array 'total_tokens' => $this->totalTokens, ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/CreateStreamedResponse.php b/src/Responses/Responses/CreateStreamedResponse.php index bfdd7aaa..0cd3f4a4 100644 --- a/src/Responses/Responses/CreateStreamedResponse.php +++ b/src/Responses/Responses/CreateStreamedResponse.php @@ -7,7 +7,6 @@ use OpenAI\Contracts\ResponseContract; use OpenAI\Exceptions\UnknownEventException; use OpenAI\Responses\Concerns\ArrayAccessible; -use OpenAI\Responses\Responses\CreateResponse; use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse; /** @@ -28,7 +27,7 @@ private function __construct( ) {} /** - * Acts as static factory, and returns a new Response instance. + * Acts as static factory, and returns a new Response instance. * * Maps the appropriate classes onto each event from the responses streaming api * https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses @@ -85,5 +84,4 @@ public function toArray(): array 'data' => $this->response->toArray(), ]; } - -} \ No newline at end of file +} diff --git a/src/Responses/Responses/DeleteResponse.php b/src/Responses/Responses/DeleteResponse.php index 6d4f5d9f..20ef6f06 100644 --- a/src/Responses/Responses/DeleteResponse.php +++ b/src/Responses/Responses/DeleteResponse.php @@ -33,8 +33,8 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. - * - * @param array{id: string, object: string, deleted: bool} $attributes + * + * @param array{id: string, object: string, deleted: bool} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -57,4 +57,4 @@ public function toArray(): array 'deleted' => $this->deleted, ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php index 53a18c29..c9df1097 100644 --- a/src/Responses/Responses/ListInputItems.php +++ b/src/Responses/Responses/ListInputItems.php @@ -71,4 +71,4 @@ public function toArray(): array 'has_more' => $this->hasMore, ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index 87f44ab0..6d6be6db 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -52,7 +52,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -83,7 +83,7 @@ public static function from(array $attributes, MetaInformation $meta): self $meta, ); } - + /** * {@inheritDoc} */ @@ -115,4 +115,4 @@ public function toArray(): array 'usage' => $this->usage->toArray(), ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index b1306a99..d48aca6c 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -54,7 +54,7 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -117,4 +117,4 @@ public function toArray(): array 'usage' => $this->usage->toArray(), ]; } -} \ No newline at end of file +} diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index d8065cf7..d3952896 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -133,7 +133,7 @@ public function record(TestRequest $request): ResponseContract|ResponseStreamCon return $response; } - + public function responses(): ResponsesContract { diff --git a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php index c249cae7..0d3bdf37 100644 --- a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php @@ -40,7 +40,7 @@ final class CreateResponseFixture 'text' => [ 'format' => [ 'type' => 'text', - ] + ], ], 'tool_choice' => 'auto', 'tools' => [], @@ -61,4 +61,4 @@ final class CreateResponseFixture 'metadata' => [], ]; -} \ No newline at end of file +} diff --git a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php index 0f4ee103..3a4b84c7 100644 --- a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php @@ -11,4 +11,4 @@ final class DeleteResponseFixture 'deleted' => true, ], ]; -} \ No newline at end of file +} diff --git a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php index 524d405e..48e753c6 100644 --- a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php @@ -25,4 +25,4 @@ final class ListInputItemsFixture 'last_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', 'has_more' => false, ]; -} \ No newline at end of file +} diff --git a/src/ValueObjects/ResourceUri.php b/src/ValueObjects/ResourceUri.php index aef2b019..12e05b45 100644 --- a/src/ValueObjects/ResourceUri.php +++ b/src/ValueObjects/ResourceUri.php @@ -90,4 +90,4 @@ public function toString(): string { return $this->uri; } -} \ No newline at end of file +} diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index 6c277e06..24118c56 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -203,4 +203,4 @@ public function toRequest(BaseUri $baseUri, Headers $headers, QueryParams $query return $request; } -} \ No newline at end of file +} From dd263ac6285c81a9b234ba871e13d52b1aa49c3b Mon Sep 17 00:00:00 2001 From: momostafa Date: Sun, 6 Apr 2025 09:04:37 +0200 Subject: [PATCH 07/60] Fixed all PHPStan Errors and all other tests Pass 100% --- src/Responses/Responses/CreateResponse.php | 24 +++++++++++--------- src/Responses/Responses/ListInputItems.php | 15 ++++-------- src/Responses/Responses/ResponseObject.php | 22 ++++++++++++------ src/Responses/Responses/RetrieveResponse.php | 20 ++++++++++------ 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index ae927b65..8d49ad74 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -24,6 +24,13 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati use Fakeable; use HasMetaInformation; + /** + * @param array}>}> $output + * @param array $reasoning + * @param array{format: array{type: string}} $text + * @param array $tools + * @param array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} $usage + */ private function __construct( public readonly string $id, public readonly string $object, @@ -45,16 +52,13 @@ private function __construct( public readonly array $tools, public readonly ?float $topP, public readonly string $truncation, + public readonly array $usage, public readonly ?string $user, - public array $metadata, - public readonly CreateResponseUsage $usage, private readonly MetaInformation $meta, ) {} /** - * Acts as static factory, and returns a new Response instance. - * - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -79,15 +83,14 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['tools'], $attributes['top_p'], $attributes['truncation'], - $attributes['user'], - $attributes['metadata'] ?? [], - CreateResponseUsage::from($attributes['usage']), + $attributes['usage'], + $attributes['user'] ?? null, $meta, ); } /** - * {@inheritDoc} + * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} */ public function toArray(): array { @@ -112,9 +115,8 @@ public function toArray(): array 'tools' => $this->tools, 'top_p' => $this->topP, 'truncation' => $this->truncation, + 'usage' => $this->usage, 'user' => $this->user, - 'metadata' => $this->metadata, - 'usage' => $this->usage->toArray(), ]; } } diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php index c9df1097..8b30b9e4 100644 --- a/src/Responses/Responses/ListInputItems.php +++ b/src/Responses/Responses/ListInputItems.php @@ -24,6 +24,9 @@ final class ListInputItems implements ResponseContract, ResponseHasMetaInformati use Fakeable; use HasMetaInformation; + /** + * @param array}>}> $data + */ private function __construct( public readonly string $object, public readonly array $data, @@ -40,14 +43,9 @@ private function __construct( */ public static function from(array $attributes, MetaInformation $meta): self { - $data = array_map(fn (array $result): ResponseObject => ResponseObject::from( - $result, - $meta, - ), $attributes['data']); - return new self( $attributes['object'], - $data, + $attributes['data'], $attributes['first_id'], $attributes['last_id'], $attributes['has_more'], @@ -62,10 +60,7 @@ public function toArray(): array { return [ 'object' => $this->object, - 'data' => array_map( - static fn (ResponseObject $result): array => $result->toArray(), - $this->data, - ), + 'data' => $this->data, 'first_id' => $this->firstId, 'last_id' => $this->lastId, 'has_more' => $this->hasMore, diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index 6d6be6db..ecede7a1 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -12,7 +12,7 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: object{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract { @@ -24,12 +24,19 @@ final class ResponseObject implements ResponseContract, ResponseHasMetaInformati use Fakeable; use HasMetaInformation; + /** + * @param array}>}> $output + * @param array $reasoning + * @param array{format: array{type: string}} $text + * @param array $tools + * @param array $metadata + */ private function __construct( public readonly string $id, public readonly string $object, public readonly int $createdAt, public readonly string $status, - public readonly object $error, + public readonly ?object $error, public readonly ?object $incompleteDetails, public readonly ?string $instructions, public readonly ?int $maxOutputTokens, @@ -47,12 +54,13 @@ private function __construct( public readonly string $truncation, public readonly ?string $user, public array $metadata, - public readonly CreateResponseUsage $usage, + /** @var array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} */ + public readonly array $usage, private readonly MetaInformation $meta, ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: ?object, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -79,13 +87,13 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['truncation'], $attributes['user'], $attributes['metadata'] ?? [], - CreateResponseUsage::from($attributes['usage']), + $attributes['usage'], $meta, ); } /** - * {@inheritDoc} + * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata: array} */ public function toArray(): array { @@ -112,7 +120,7 @@ public function toArray(): array 'truncation' => $this->truncation, 'user' => $this->user, 'metadata' => $this->metadata, - 'usage' => $this->usage->toArray(), + 'usage' => $this->usage, ]; } } diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index d48aca6c..a5bc28aa 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -24,6 +24,14 @@ final class RetrieveResponse implements ResponseContract, ResponseHasMetaInforma use Fakeable; use HasMetaInformation; + /** + * @param array}>}> $output + * @param array $reasoning + * @param array{format: array{type: string}} $text + * @param array $tools + * @param array $metadata + * @param array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} $usage + */ private function __construct( public readonly string $id, public readonly string $object, @@ -47,14 +55,12 @@ private function __construct( public readonly string $truncation, public readonly ?string $user, public array $metadata, - public readonly CreateResponseUsage $usage, + public readonly array $usage, private readonly MetaInformation $meta, ) {} /** - * Acts as static factory, and returns a new Response instance. - * - * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: ?string, max_output_tokens: ?int, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -81,13 +87,13 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['truncation'], $attributes['user'], $attributes['metadata'] ?? [], - CreateResponseUsage::from($attributes['usage']), + $attributes['usage'], $meta, ); } /** - * {@inheritDoc} + * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata: array} */ public function toArray(): array { @@ -114,7 +120,7 @@ public function toArray(): array 'truncation' => $this->truncation, 'user' => $this->user, 'metadata' => $this->metadata, - 'usage' => $this->usage->toArray(), + 'usage' => $this->usage, ]; } } From 5585119ccbe0239c83a5d46be2d646463e56fd91 Mon Sep 17 00:00:00 2001 From: momostafa Date: Sat, 12 Apr 2025 01:55:11 +0200 Subject: [PATCH 08/60] Completed missing Tests, Created ClientFakeResponses, Modified Fakeable I had to modify OpenAI\Testing\Responses\Concerns\Fakeable as $class = str_replace('Responses\\', 'Testing\\Responses\\Fixtures\\', static::class).'Fixture'; was conflicting with newly added Responses folder and added docblock explaining the modification and tested against all files. Updated readme can be found at README-RESPONSES.md Added dedicated ClientFake for Responses tests/Testing/ClientFakeResponses.php --- Pest Test Results.yaml | 189 +++++++++++ README-RESPONSES.md | 294 ++++++++++++++++++ src/Testing/ClientFake.php | 4 +- src/Testing/Responses/Concerns/Fakeable.php | 43 ++- tests/Fixtures/Responses.php | 124 ++++++++ .../Streams/CreateStreamedResponse.txt | 9 + tests/Resources/Responses.php | 205 ++++++++++++ tests/Responses/Responses/CreateResponse.php | 84 +++++ .../Responses/CreateStreamedResponse.php | 89 ++++++ tests/Responses/Responses/DeleteResponse.php | 46 +++ tests/Responses/Responses/ListInputItems.php | 52 ++++ tests/Responses/Responses/ResponseObject.php | 125 ++++++++ .../Responses/Responses/RetrieveResponse.php | 93 ++++++ tests/Testing/ClientFakeResponses.php | 171 ++++++++++ .../Resources/ResponsesTestResource.php | 74 +++++ 15 files changed, 1596 insertions(+), 6 deletions(-) create mode 100644 Pest Test Results.yaml create mode 100644 README-RESPONSES.md create mode 100644 tests/Fixtures/Responses.php create mode 100644 tests/Fixtures/Streams/CreateStreamedResponse.txt create mode 100644 tests/Resources/Responses.php create mode 100644 tests/Responses/Responses/CreateResponse.php create mode 100644 tests/Responses/Responses/CreateStreamedResponse.php create mode 100644 tests/Responses/Responses/DeleteResponse.php create mode 100644 tests/Responses/Responses/ListInputItems.php create mode 100644 tests/Responses/Responses/ResponseObject.php create mode 100644 tests/Responses/Responses/RetrieveResponse.php create mode 100644 tests/Testing/ClientFakeResponses.php create mode 100644 tests/Testing/Resources/ResponsesTestResource.php diff --git a/Pest Test Results.yaml b/Pest Test Results.yaml new file mode 100644 index 00000000..6bae6af0 --- /dev/null +++ b/Pest Test Results.yaml @@ -0,0 +1,189 @@ +MoMos-MacBook-Pro:bulksms Mo$ ./vendor/bin/pest vendor/openai-php/client/tests/Testing/ClientFakeResponses.php + + PASS Vendor\openaiphp\client\tests\Testing\ClientFakeResponses + ✓ it returns a fake response for create 0.03s + ✓ it returns a fake response for retrieve + ✓ it returns a fake response for delete + ✓ it returns a fake response for list + ✓ it asserts a create request was sent + ✓ it asserts a retrieve request was sent + ✓ it asserts a delete request was sent + ✓ it asserts a list request was sent + ✓ it throws an exception if there are no more fake responses + ✓ it throws an exception if a request was not sent + + Tests: 10 passed (14 assertions) + Duration: 0.21s + +MoMos-MacBook-Pro:bulksms Mo$ for file in vendor/openai-php/client/tests/Testing/Resources/*TestResource.php; do ./vendor/bin/pest "$file"; done + + PASS Vendor\openaiphp\client\tests\Testing\Resources\AssistantsTestResource + ✓ it records an assistant create request 0.01s + ✓ it records an assistant retrieve request + ✓ it records an assistant modify request + ✓ it records an assistant delete request + ✓ it records an assistant list request + + Tests: 5 passed (5 assertions) + Duration: 0.10s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\AudioTestResource + ✓ it records a speech request 0.01s + ✓ it records a streamed speech request 0.01s + ✓ it records an audio transcription request + ✓ it records an audio translation request + + Tests: 4 passed (4 assertions) + Duration: 0.09s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\BatchTestResource + ✓ it records an batch create request 0.01s + ✓ it records an batch retrieve request + ✓ it records an batch cancel request + ✓ it records an batch list request + + Tests: 4 passed (4 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ChatTestResource + ✓ it records a chat create request 0.01s + ✓ it records a streamed create create request 0.01s + + Tests: 2 passed (2 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\CompletionsTestResource + ✓ it records a completions create request 0.01s + ✓ it records a streamed completions create request + + Tests: 2 passed (2 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\EditsTestResource + ✓ it records a edits create request 0.01s + + Tests: 1 passed (1 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\EmbeddingsTestResource + ✓ it records a embeddings create request 0.01s + + Tests: 1 passed (1 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\FilesTestResource + ✓ it records a files retrieve request 0.01s + ✓ it records a files list request + ✓ it records a files download request + ✓ it records a files delete request + ✓ it records a files upload request + + Tests: 5 passed (5 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\FineTunesTestResource + ✓ it records a fine tunes create request 0.01s + ✓ it records a fine tunes retrieve request + ✓ it records a fine tunes cancel request + ✓ it records a fine tunes list request + ✓ it records a fine tunes list events request + ✓ it records a streamed fine tunes list events request + + Tests: 6 passed (6 assertions) + Duration: 0.09s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\FineTuningTestResource + ✓ it records a fine tuning job create request 0.01s + ✓ it records a fine tuning job retrieve request + ✓ it records a fine tuning job cancel request + ✓ it records a fine tuning job list request + ✓ it records a fine tuning list job events request + + Tests: 5 passed (5 assertions) + Duration: 0.10s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ImagesTestResource + ✓ it records a images create request 0.01s + ✓ it records a images edit request + ✓ it records a images variation request + + Tests: 3 passed (3 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ModelsTestResource + ✓ it records a model retrieve request 0.01s + ✓ it records a model delete request + ✓ it records a model list request + + Tests: 3 passed (3 assertions) + Duration: 0.07s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ModerationsTestResource + ✓ it records a moderations create request 0.01s + + Tests: 1 passed (1 assertions) + Duration: 0.07s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ResponsesTestResource + ✓ it records a response create request 0.01s + ✓ it records a response retrieve request + ✓ it records a response delete request + ✓ it records a response list request + + Tests: 4 passed (4 assertions) + Duration: 0.07s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsMessagesTestResource + ✓ it records a thread message create request 0.01s + ✓ it records a thread message retrieve request + ✓ it records a thread message modify request + ✓ it records a thread message delete request + ✓ it records a thread message list request + + Tests: 5 passed (5 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsRunsStepsTestResource + ✓ it records a thread run step retrieve request 0.01s + ✓ it records a thread run step list request + + Tests: 2 passed (2 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsRunsTestResource + ✓ it records a thread run create request 0.01s + ✓ it records a thread run retrieve request + ✓ it records a thread run modify request + ✓ it records a thread run cancel request + ✓ it records a thread run submit tool outputs request + ✓ it records a thread run list request + + Tests: 6 passed (6 assertions) + Duration: 0.08s + + + PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsTestResource + ✓ it records a thread create request 0.01s + ✓ it records a thread create and run request + ✓ it records a thread retrieve request + ✓ it records a thread modify request + ✓ it records a thread delete request + + Tests: 5 passed (5 assertions) + Duration: 0.08s \ No newline at end of file diff --git a/README-RESPONSES.md b/README-RESPONSES.md new file mode 100644 index 00000000..dc9ba74f --- /dev/null +++ b/README-RESPONSES.md @@ -0,0 +1,294 @@ +# OpenAI PHP - Responses API + +This document outlines the Responses API functionality added to the OpenAI PHP client. + +## Table of Contents +- [Installation](#installation) +- [Usage](#usage) + - [Create Response](#create-response) + - [Create Streamed Response](#create-streamed-response) + - [Retrieve Response](#retrieve-response) + - [Delete Response](#delete-response) + - [List Responses](#list-responses) +- [Testing](#testing) + - [Response Fakes](#response-fakes) + - [Response Assertions](#response-assertions) + +## Installation + +The Responses API is included in the OpenAI PHP client. No additional installation is required beyond the base package: + +```bash +composer require openai-php/client +``` + +## Usage + +### Create Response + +Creates a model response. Provide text or image inputs to generate text or JSON outputs. Have the model call your own custom code or use built-in tools like web search or file search to use your own data as input for the model's response. + +```php +$response = $client->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?", + 'temperature' => 0.7, + 'max_output_tokens' => 150, + 'tool_choice' => 'auto', + 'parallel_tool_calls' => true, + 'store' => true, + 'metadata' => [ + 'user_id' => '123', + 'session_id' => 'abc456' + ] +]); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->createdAt; // 1741476542 +$response->status; // 'completed' +$response->model; // 'gpt-4o-mini' + +// Access output content +foreach ($response->output as $output) { + $output->type; // 'message' + $output->id; // 'msg_67ccd2bf17f0819081ff3bb2cf6508e6' + $output->status; // 'completed' + $output->role; // 'assistant' + + foreach ($output->content as $content) { + $content->type; // 'output_text' + $content->text; // The response text + $content->annotations; // Any annotations in the response + } +} + +// Access usage information +$response->usage->inputTokens; // 36 +$response->usage->outputTokens; // 87 +$response->usage->totalTokens; // 123 + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] +``` + +### Create Streamed Response + +When you create a Response with stream set to true, the server will emit server-sent events to the client as the Response is generated. + +```php +$stream = $client->responses()->createStreamed([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?", + 'stream' => true +]); + +foreach ($stream as $response) { + $response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' + $response->object; // 'response' + $response->createdAt; // 1741476542 + + foreach ($response->output as $output) { + // Process streaming output + echo $output->content[0]->text; + } +} +``` + +### Retrieve Response + +Retrieves a model response with the given ID. + +```php +$response = $client->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->createdAt; // 1741476542 +$response->status; // 'completed' +$response->error; // null +$response->incompleteDetails; // null +$response->instructions; // null +$response->maxOutputTokens; // null +$response->model; // 'gpt-4o-2024-08-06' +$response->parallelToolCalls; // true +$response->previousResponseId; // null +$response->store; // true +$response->temperature; // 1.0 +$response->toolChoice; // 'auto' +$response->topP; // 1.0 +$response->truncation; // 'disabled' + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] +``` + +### Delete Response + +Deletes a model response with the given ID. + +```php +$response = $client->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->deleted; // true + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', 'deleted' => true, ...] +``` + +### List Responses + +Lists input items for a response with the given ID. + +```php +$response = $client->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267', [ + 'limit' => 10, + 'order' => 'desc' +]); + +$response->object; // 'list' + +foreach ($response->data as $item) { + $item->type; // 'message' + $item->id; // Response item ID + $item->status; // 'completed' + $item->role; // 'user' or 'assistant' + + foreach ($item->content as $content) { + $content->type; // Content type + $content->text; // Content text + $content->annotations; // Content annotations + } +} + +$response->firstId; // First item ID in the list +$response->lastId; // Last item ID in the list +$response->hasMore; // Whether there are more items to fetch + +$response->toArray(); // ['object' => 'list', 'data' => [...], ...] +``` + +## Testing + +### Response Fakes + +The client includes fakes for testing response operations: + +```php +use OpenAI\Testing\ClientFake; +use OpenAI\Responses\Responses\CreateResponse; +use OpenAI\Responses\Responses\RetrieveResponse; +use OpenAI\Responses\Responses\DeleteResponse; +use OpenAI\Responses\Responses\ListInputItems; + +// Test create operation +$fake = new ClientFake([ + CreateResponse::fake([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]), +]); + +// Test retrieve operation +$fake = new ClientFake([ + RetrieveResponse::fake([ + 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267', + 'status' => 'completed' + ]), +]); + +// Test delete operation +$fake = new ClientFake([ + DeleteResponse::fake([ + 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267', + 'deleted' => true + ]), +]); + +// Test list operation +$fake = new ClientFake([ + ListInputItems::fake([ + 'data' => [ + [ + 'type' => 'message', + 'id' => 'msg_123', + 'status' => 'completed' + ] + ] + ]), +]); +``` + +### Response Assertions + +You can make assertions about the requests made to the Responses API: + +```php +// Assert a specific create request was made +$fake->assertSent(Responses::class, function ($method, $parameters) { + return $method === 'create' && + $parameters['model'] === 'gpt-4o-mini' && + $parameters['tools'][0]['type'] === 'web_search_preview' && + $parameters['input'] === "what was a positive news story from today?"; +}); + +// Assert a specific retrieve request was made +$fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'retrieve' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; +}); + +// Assert a specific delete request was made +$fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'delete' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; +}); + +// Assert a specific list request was made +$fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'list' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; +}); + +// Assert a request was not made +$fake->assertNotSent(Responses::class); + +// Assert number of requests +$fake->assertSent(Responses::class, 2); // Assert exactly 2 requests were made +``` + +## Meta Information + +Each response includes meta information about the request: + +```php +$response = $client->responses()->create([/* ... */]); + +$response->meta()->openai->model; // The model used +$response->meta()->openai->organization; // Your organization +$response->meta()->openai->version; // API version +$response->meta()->openai->processingMs; // Processing time in milliseconds +$response->meta()->requestId; // Request ID +$response->meta()->requestLimit->limit; // Rate limit info +$response->meta()->requestLimit->remaining; // Remaining requests +$response->meta()->requestLimit->reset; // Rate limit reset time +$response->meta()->tokenLimit->limit; // Token limit info +$response->meta()->tokenLimit->remaining; // Remaining tokens +$response->meta()->tokenLimit->reset; // Token limit reset time +``` + +This meta information is useful for debugging and tracking API usage. \ No newline at end of file diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index d3952896..956d32e7 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -3,7 +3,6 @@ namespace OpenAI\Testing; use OpenAI\Contracts\ClientContract; -use OpenAI\Contracts\Resources\ResponsesContract; use OpenAI\Contracts\Resources\VectorStoresContract; use OpenAI\Contracts\ResponseContract; use OpenAI\Contracts\ResponseStreamContract; @@ -134,9 +133,8 @@ public function record(TestRequest $request): ResponseContract|ResponseStreamCon return $response; } - public function responses(): ResponsesContract + public function responses(): ResponsesTestResource { - return new ResponsesTestResource($this); } diff --git a/src/Testing/Responses/Concerns/Fakeable.php b/src/Testing/Responses/Concerns/Fakeable.php index da0b117f..dfaf49de 100644 --- a/src/Testing/Responses/Concerns/Fakeable.php +++ b/src/Testing/Responses/Concerns/Fakeable.php @@ -9,14 +9,51 @@ trait Fakeable { /** - * @param array $override + * Create a fake response instance with optional attribute overrides. + * + * This method handles both simple and nested namespace structures for response fixtures: + * - Simple: OpenAI\Responses\Category\Response -> OpenAI\Testing\Responses\Fixtures\Category\ResponseFixture + * - Nested: OpenAI\Responses\Category\SubCategory\Response -> OpenAI\Testing\Responses\Fixtures\Category\SubCategory\ResponseFixture + * + * The method preserves the namespace hierarchy after 'Responses' to maintain proper fixture organization: + * Example paths: + * - Responses\Threads\ThreadResponse -> Fixtures\Threads\ThreadResponseFixture + * - Responses\Threads\Runs\ThreadRunResponse -> Fixtures\Threads\Runs\ThreadRunResponseFixture + * + * It also handles cases where fixture ATTRIBUTES might be wrapped in an additional array layer, + * automatically unwrapping single-element arrays to maintain consistency. + * + * @param array $override Optional attributes to override in the fake response + * @param MetaInformation|null $meta Optional meta information for the response + * @throws \RuntimeException If the Responses namespace cannot be found in the class path + * @return static Returns a new instance of the response class with fake data */ public static function fake(array $override = [], ?MetaInformation $meta = null): static { - $class = str_replace('Responses\\', 'Testing\\Responses\\Fixtures\\', static::class).'Fixture'; + $parts = explode('\\', static::class); + $className = end($parts); + + // Find the position of 'Responses' in the namespace + $responsesPos = array_search('Responses', $parts); + if ($responsesPos === false) { + throw new \RuntimeException('Unable to determine fixture path: no Responses namespace found'); + } + + // Get all parts after 'Responses' to preserve nested structure + $subPath = implode('\\', array_slice($parts, $responsesPos + 1, -1)); + + // Construct the fixture class path + $namespace = 'OpenAI\\Testing\\Responses\\Fixtures\\' . $subPath . '\\'; + $class = $namespace . $className . 'Fixture'; + + $attributes = $class::ATTRIBUTES; + // If attributes is a nested array with only one element, use that element + if (is_array($attributes) && count($attributes) === 1 && isset($attributes[0]) && is_array($attributes[0])) { + $attributes = $attributes[0]; + } return static::from( - self::buildAttributes($class::ATTRIBUTES, $override), + self::buildAttributes($attributes, $override), $meta ?? self::fakeResponseMetaInformation(), ); } diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php new file mode 100644 index 00000000..c67ebbad --- /dev/null +++ b/tests/Fixtures/Responses.php @@ -0,0 +1,124 @@ + + */ +function createResponseResource(): array +{ + return [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'object' => 'response', + 'created_at' => 1699619403, + 'status' => 'completed', + 'output' => [ + [ + 'type' => 'message', + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.', + 'annotations' => [] + ] + ] + ] + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'generate_summary' => null + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text' + ] + ], + 'tool_choice' => 'auto', + 'tools' => [], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 328, + 'input_tokens_details' => [ + 'cached_tokens' => 0 + ], + 'output_tokens' => 52, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0 + ], + 'total_tokens' => 380 + ], + 'user' => null, + 'metadata' => [] + ]; +} + +/** + * @return array + */ +function retrieveResponseResource(): array +{ + return [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'object' => 'response', + 'created_at' => 1699619403, + 'status' => 'completed' + ]; +} + +/** + * @return array + */ +function listInputItemsResource(): array +{ + return [ + 'object' => 'list', + 'data' => [ + [ + 'id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', + 'object' => 'response', + 'created_at' => 1699619403, + 'status' => 'completed' + ] + ], + 'first_id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', + 'last_id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', + 'has_more' => false + ]; +} + +/** + * @return array + */ +function deleteResponseResource(): array +{ + return [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'object' => 'response.deleted', + 'deleted' => true + ]; +} + +/** + * @return array + */ +function createStreamedResponseResource(): array +{ + return [ + 'event' => 'response.created', + 'data' => createResponse() + ]; +} + +/** + * @return resource + */ +function createStreamedResource() +{ + return fopen(__DIR__.'/Streams/CreateStreamedResponse.txt', 'r'); +} diff --git a/tests/Fixtures/Streams/CreateStreamedResponse.txt b/tests/Fixtures/Streams/CreateStreamedResponse.txt new file mode 100644 index 00000000..b7b9311e --- /dev/null +++ b/tests/Fixtures/Streams/CreateStreamedResponse.txt @@ -0,0 +1,9 @@ +data: {"type":"response.created","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.in_progress","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"in_progress","role":"assistant","content":[]}} +data: {"type":"response.content_part.added","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}} +data: {"type":"response.output_text.delta","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"delta":"Hi"} +data: {"type":"response.output_text.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"text":"Hi there! How can I assist you today?"} +data: {"type":"response.content_part.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}} +data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}} +data: {"type":"response.completed","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"completed","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":37,"output_tokens":11,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":48},"user":null,"metadata":{}}} diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php new file mode 100644 index 00000000..8b1e32cb --- /dev/null +++ b/tests/Resources/Responses.php @@ -0,0 +1,205 @@ +responses()->delete('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + expect($result) + ->toBeInstanceOf(DeleteResponse::class) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response.deleted') + ->deleted->toBeTrue(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list', function () { + $client = mockClient('GET', 'responses/asst_SMzoVX8XmCZEg1EbMHoAm8tc/input_items', [], Response::from(listInputItemsResource(), metaHeaders())); + + $result = $client->responses()->list('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + expect($result) + ->toBeInstanceOf(ListInputItems::class) + ->object->toBe('list') + ->data->toBeArray() + ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->lastId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->hasMore->toBeFalse(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('retrieve', function () { + $client = mockClient('GET', 'responses/asst_SMzoVX8XmCZEg1EbMHoAm8tc', [], Response::from(retrieveResponseResource(), metaHeaders())); + + $result = $client->responses()->retrieve('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + expect($result) + ->toBeInstanceOf(RetrieveResponse::class) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->createdAt->toBe(1699619403) + ->status->toBe('completed'); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('create', function () { + $client = mockClient('POST', 'responses', [ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ], Response::from(createResponseResource(), metaHeaders())); + + $result = $client->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + expect($result) + ->toBeInstanceOf(ResponseObject::class) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->createdAt->toBe(1699619403) + ->status->toBe('completed') + ->output->toBeArray() + ->output->toHaveCount(1) + ->outout[0]->type->toBe('message') + ->output[0]->toBeInstanceOf(ResponseObject::class) + ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->output[0]->status->toBe('completed') + ->output[0]->role->toBe('assistant') + ->output[0]->content->toBeArray() + ->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) + ->output[0]->content[0]->type->toBe('output_text') + ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') + ->output[0]->content[0]->annotations->toBeArray() + ->output[0]->content[0]->annotations->toHaveCount(0) + ->parallelToolCalls->toBeTrue() + ->previousResponseId->toBeNull() + ->reasoning->toBeArray() + ->reasoning['effort']->toBeNull() + ->reasoning['generate_summary']->toBeNull() + ->store->toBeTrue() + ->temperature->toBe(1.0) + ->text->toBeArray() + ->text['format']['type']->toBe('text') + ->toolChoice->toBe('auto') + ->tools->toBeArray() + ->tools->toHaveCount(0) + ->topP->toBe(1.0) + ->truncation->toBe('disabled') + ->usage->toBeArray() + ->usage['input_tokens']->toBe(328) + ->usage['input_tokens_details']['cached_tokens']->toBe(0) + ->usage['output_tokens']->toBe(52) + ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) + ->usage['total_tokens']->toBe(380) + ->user->toBeNull() + ->metadata->toBeArray() + ->metadata->toBeEmpty(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('createStreamed', function () { + $client = mockClient('POST', 'responses', [ + 'stream' => true, + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ], Response::from(createStreamedResponseResource(), metaHeaders())); + + $result = $client->responses()->createStreamed([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + expect($result) + ->toBeInstanceOf(StreamResponse::class) + ->toBeInstanceOf(IteratorAggregate::class); + + expect($result->getIterator()) + ->toBeInstanceOf(Iterator::class); + + expect($result->getIterator()->current()) + ->toBeInstanceOf(CreateStreamedResponse::class) + ->event->toBe('response.created') + ->response->toBeInstanceOf(CreateResponse::class) + ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->response->object->toBe('response') + ->response->createdAt->toBe(1699619403) + ->response->status->toBe('completed') + ->response->output->toBeArray() + ->response->output->toHaveCount(1) + ->response->output[0]->type->toBe('message') + ->response->output[0]->toBeInstanceOf(ResponseObject::class) + ->response->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->response->output[0]->status->toBe('completed') + ->response->output[0]->role->toBe('assistant') + ->response->output[0]->content->toBeArray() + ->response->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) + ->response->output[0]->content[0]->type->toBe('output_text') + ->response->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') + ->response->output[0]->content[0]->annotations->toBeArray() + ->response->output[0]->content[0]->annotations->toHaveCount(0) + ->response->parallelToolCalls->toBeTrue() + ->response->previousResponseId->toBeNull() + ->response->reasoning->toBeArray() + ->response->reasoning['effort']->toBeNull() + ->response->reasoning['generate_summary']->toBeNull() + ->response->store->toBeTrue() + ->response->temperature->toBe(1.0) + ->response->text->toBeArray() + ->response->text['format']['type']->toBe('text') + ->response->toolChoice->toBe('auto') + ->response->tools->toBeArray() + ->response->tools->toHaveCount(0) + ->response->topP->toBe(1.0) + ->response->truncation->toBe('disabled') + ->response->usage->toBeArray() + ->response->usage['input_tokens']->toBe(328) + ->response->usage['input_tokens_details']['cached_tokens']->toBe(0) + ->response->usage['output_tokens']->toBe(52) + ->response->usage['output_tokens_details']['reasoning_tokens']->toBe(0) + ->response->usage['total_tokens']->toBe(380) + ->response->user->toBeNull() + ->response->metadata->toBeArray() + ->response->metadata->toBeEmpty(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); diff --git a/tests/Responses/Responses/CreateResponse.php b/tests/Responses/Responses/CreateResponse.php new file mode 100644 index 00000000..8a89af46 --- /dev/null +++ b/tests/Responses/Responses/CreateResponse.php @@ -0,0 +1,84 @@ +toBeInstanceOf(CreateResponse::class) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->createdAt->toBe(1699619403) + ->status->toBe('completed') + ->output->toBeArray() + ->output->toHaveCount(1) + ->output[0]->toBeInstanceOf(ResponseObject::class) + ->output[0]->type->toBe('message') + ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->output[0]->status->toBe('completed') + ->output[0]->role->toBe('assistant') + ->output[0]->content->toBeArray() + ->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) + ->output[0]->content[0]->type->toBe('output_text') + ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') + ->output[0]->content[0]->annotations->toBeArray() + ->output[0]->content[0]->annotations->toHaveCount(0) + ->parallelToolCalls->toBeTrue() + ->previousResponseId->toBeNull() + ->reasoning->toBeArray() + ->reasoning['effort']->toBeNull() + ->reasoning['generate_summary']->toBeNull() + ->store->toBeTrue() + ->temperature->toBe(1.0) + ->text->toBeArray() + ->text['format']['type']->toBe('text') + ->toolChoice->toBe('auto') + ->tools->toBeArray() + ->tools->toHaveCount(0) + ->topP->toBe(1.0) + ->truncation->toBe('disabled') + ->usage->toBeArray() + ->usage['input_tokens']->toBe(328) + ->usage['input_tokens_details']['cached_tokens']->toBe(0) + ->usage['output_tokens']->toBe(52) + ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) + ->usage['total_tokens']->toBe(380) + ->user->toBeNull() + ->metadata->toBeArray() + ->metadata->toBeEmpty(); + + expect($response->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $response = CreateResponse::from(createResponse(), meta()); + + expect($response['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('to array', function () { + $response = CreateResponse::from(createResponse(), meta()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(createResponse()); +}); + +test('fake', function () { + $response = CreateResponse::fake(); + + expect($response) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('fake with override', function () { + $response = CreateResponse::fake([ + 'id' => 'asst_1234', + ]); + + expect($response) + ->id->toBe('asst_1234'); +}); diff --git a/tests/Responses/Responses/CreateStreamedResponse.php b/tests/Responses/Responses/CreateStreamedResponse.php new file mode 100644 index 00000000..6836ad8d --- /dev/null +++ b/tests/Responses/Responses/CreateStreamedResponse.php @@ -0,0 +1,89 @@ + 'response.created', + '__meta' => meta(), + 'response' => [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'object' => 'response', + 'created_at' => 1699619403, + 'status' => 'completed', + 'output' => [ + [ + 'type' => 'message', + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'The image depicts a scenic landscape', + 'annotations' => [] + ] + ] + ] + ] + ] + ]); + + expect($response) + ->toBeInstanceOf(CreateStreamedResponse::class) + ->event->toBe('response.created') + ->response->toBeInstanceOf(CreateResponse::class) + ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('as array accessible', function () { + $response = CreateStreamedResponse::from([ + '__event' => 'response.created', + '__meta' => meta(), + 'response' => [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' + ] + ]); + + expect($response['event'])->toBe('response.created'); +}); + +test('to array', function () { + $response = CreateStreamedResponse::from([ + '__event' => 'response.created', + '__meta' => meta(), + 'response' => [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' + ] + ]); + + expect($response->toArray()) + ->toBeArray() + ->toBe([ + 'event' => 'response.created', + 'response' => [ + 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' + ] + ]); +}); + +test('fake', function () { + $response = CreateStreamedResponse::fake(); + + expect($response) + ->event->toBe('response.created') + ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('fake with override', function () { + $response = CreateStreamedResponse::fake([ + 'event' => 'response.completed', + 'response' => ['id' => 'asst_1234'] + ]); + + expect($response) + ->event->toBe('response.completed') + ->response->id->toBe('asst_1234'); +}); + diff --git a/tests/Responses/Responses/DeleteResponse.php b/tests/Responses/Responses/DeleteResponse.php new file mode 100644 index 00000000..20732197 --- /dev/null +++ b/tests/Responses/Responses/DeleteResponse.php @@ -0,0 +1,46 @@ +id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response.deleted') + ->deleted->toBe(true) + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = DeleteResponse::from(deleteResponseResource(), meta()); + + expect($result['id']) + ->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('to array', function () { + $result = DeleteResponse::from(deleteResponseResource(), meta()); + + expect($result->toArray()) + ->toBe(deleteResponseResource()); +}); + +test('fake', function () { + $response = DeleteResponse::fake(); + + expect($response) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->deleted->toBe(true); +}); + +test('fake with override', function () { + $response = DeleteResponse::fake([ + 'id' => 'asst_1234', + 'deleted' => false, + ]); + + expect($response) + ->id->toBe('asst_1234') + ->deleted->toBe(false); +}); diff --git a/tests/Responses/Responses/ListInputItems.php b/tests/Responses/Responses/ListInputItems.php new file mode 100644 index 00000000..fd038089 --- /dev/null +++ b/tests/Responses/Responses/ListInputItems.php @@ -0,0 +1,52 @@ +toBeInstanceOf(ListInputItems::class) + ->object->toBe('list') + ->data->toBeArray() + ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->lastId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->hasMore->toBeFalse() + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = ListInputItems::from(listInputItemsResource(), meta()); + + expect($result['object'])->toBe('list'); +}); + +test('to array', function () { + $result = ListInputItems::from(listInputItemsResource(), meta()); + + expect($result->toArray()) + ->toBeArray() + ->toBe(listInputItemsResource()); +}); + +test('fake', function () { + $response = ListInputItems::fake(); + + expect($response) + ->object->toBe('list') + ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->hasMore->toBeFalse(); +}); + +test('fake with override', function () { + $response = ListInputItems::fake([ + 'object' => 'custom_list', + 'first_id' => 'msg_1234', + 'has_more' => true + ]); + + expect($response) + ->object->toBe('custom_list') + ->firstId->toBe('msg_1234') + ->hasMore->toBeTrue(); +}); diff --git a/tests/Responses/Responses/ResponseObject.php b/tests/Responses/Responses/ResponseObject.php new file mode 100644 index 00000000..dbd17b64 --- /dev/null +++ b/tests/Responses/Responses/ResponseObject.php @@ -0,0 +1,125 @@ +toBeInstanceOf(ResponseObject::class) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->createdAt->toBe(1699619403) + ->status->toBe('completed') + ->output->toBeArray() + ->output->toHaveCount(1) + ->type->toBe('message') + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->status->toBe('completed') + ->role->toBe('assistant') + ->content->toBeArray() + ->content[0]->toBeInstanceOf(ResponseObject::class) + ->content[0]->type->toBe('output_text') + ->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') + ->content[0]->annotations->toBeArray() + ->content[0]->annotations->toHaveCount(0) + ->parallelToolCalls->toBeTrue() + ->previousResponseId->toBeNull() + ->reasoning->toBeArray() + ->reasoning['effort']->toBeNull() + ->reasoning['generate_summary']->toBeNull() + ->store->toBeTrue() + ->temperature->toBe(1.0) + ->text->toBeArray() + ->text['format']['type']->toBe('text') + ->toolChoice->toBe('auto') + ->tools->toBeArray() + ->tools->toHaveCount(0) + ->topP->toBe(1.0) + ->truncation->toBe('disabled') + ->usage->toBeArray() + ->usage['input_tokens']->toBe(328) + ->usage['input_tokens_details']['cached_tokens']->toBe(0) + ->usage['output_tokens']->toBe(52) + ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) + ->usage['total_tokens']->toBe(380) + ->user->toBeNull() + ->metadata->toBeArray() + ->metadata->toBeEmpty() + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = ResponseObject::from(responseObject(), meta()); + + expect($result['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('to array', function () { + $result = ResponseObject::from(responseObject(), meta()); + + expect($result->toArray()) + ->toBeArray() + ->toBe(responseObject()); +}); + +test('fake', function () { + $response = ResponseObject::fake(); + + expect($response) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->status->toBe('completed'); +}); + +test('fake with override', function () { + $response = ResponseObject::fake([ + 'id' => 'asst_1234', + 'object' => 'custom_response', + 'status' => 'failed' + ]); + + expect($response) + ->id->toBe('asst_1234') + ->object->toBe('custom_response') + ->status->toBe('failed'); +}); + +test('from', function () { + $result = ResponseObject::from(responseObject(), meta()); + + expect($result) + ->toBeInstanceOf(ResponseObject::class) + ->object->toBe('response') + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = ResponseObject::from(responseObject(), meta()); + + expect($result['object'])->toBe('response'); +}); + +test('to array', function () { + $result = ResponseObject::from(responseObject(), meta()); + + expect($result->toArray()) + ->toBe(responseObject()); +}); + +test('fake', function () { + $response = ResponseObject::fake(); + + expect($response) + ->object->toBe('response'); +}); + +test('fake with override', function () { + $response = ResponseObject::fake([ + 'object' => 'custom_response' + ]); + + expect($response) + ->object->toBe('custom_response'); +}); diff --git a/tests/Responses/Responses/RetrieveResponse.php b/tests/Responses/Responses/RetrieveResponse.php new file mode 100644 index 00000000..f5a98786 --- /dev/null +++ b/tests/Responses/Responses/RetrieveResponse.php @@ -0,0 +1,93 @@ +id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->createdAt->toBe(1699619403) + ->status->toBe('completed') + ->output->toBeArray() + ->output->toHaveCount(1) + ->output[0]->type->toBe('message') + ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->output[0]->status->toBe('completed') + ->output[0]->role->toBe('assistant') + ->output[0]->content->toBeArray() + ->output[0]->content[0]->type->toBe('output_text') + ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') + ->output[0]->content[0]->annotations->toBeArray() + ->output[0]->content[0]->annotations->toHaveCount(0) + ->parallelToolCalls->toBeTrue() + ->previousResponseId->toBeNull() + ->reasoning->toBeArray() + ->reasoning['effort']->toBeNull() + ->reasoning['generate_summary']->toBeNull() + ->store->toBeTrue() + ->temperature->toBe(1.0) + ->text->toBeArray() + ->text['format']['type']->toBe('text') + ->toolChoice->toBe('auto') + ->tools->toBeArray() + ->tools->toHaveCount(0) + ->topP->toBe(1.0) + ->truncation->toBe('disabled') + ->usage->toBeArray() + ->usage['input_tokens']->toBe(328) + ->usage['input_tokens_details']['cached_tokens']->toBe(0) + ->usage['output_tokens']->toBe(52) + ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) + ->usage['total_tokens']->toBe(380) + ->user->toBeNull() + ->metadata->toBeArray() + ->metadata->toBeEmpty() + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = RetrieveResponse::from(retrieveResponseResource(), meta()); + + expect($result['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); +}); + +test('to array', function () { + $result = RetrieveResponse::from(retrieveResponseResource(), meta()); + + expect($result->toArray()) + ->toBe(retrieveResponseResource()); +}); + +test('fake', function () { + $response = RetrieveResponse::fake(); + + expect($response) + ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->object->toBe('response') + ->status->toBe('completed'); +}); + +test('fake with override', function () { + $response = RetrieveResponse::fake([ + 'id' => 'asst_1234', + 'object' => 'custom_response', + 'status' => 'failed' + ]); + + expect($response) + ->id->toBe('asst_1234') + ->object->toBe('custom_response') + ->status->toBe('failed'); +}); + +test('from', function () { + $result = RetrieveResponse::from(retrieveResponseResource(), meta()); + + expect($result) + ->toBeInstanceOf(RetrieveResponse::class) + ->object->toBe('response') + ->data->toBeInstanceOf(ResponseObject::class) + ->meta()->toBeInstanceOf(MetaInformation::class); +}); diff --git a/tests/Testing/ClientFakeResponses.php b/tests/Testing/ClientFakeResponses.php new file mode 100644 index 00000000..b78eecb2 --- /dev/null +++ b/tests/Testing/ClientFakeResponses.php @@ -0,0 +1,171 @@ + 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]), + ]); + + $response = $fake->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + expect($response['model'])->toBe('gpt-4o-mini'); + expect($response['tools'][0]['type'])->toBe('web_search_preview'); +}); + +it('returns a fake response for retrieve', function () { + $fake = new ClientFake([ + RetrieveResponse::fake([ + 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267' + ]), + ]); + + $response = $fake->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + + expect($response) + ->id->toBe('resp_67ccd2bed1ec8190b14f964abc054267') + ->object->toBe('response'); +}); + +it('returns a fake response for delete', function () { + $fake = new ClientFake([ + DeleteResponse::fake(), + ]); + + $response = $fake->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + + expect($response) + ->id->toBe('resp_6786a1bec27481909a17d673315b29f6') + ->deleted->toBeTrue(); +}); + +it('returns a fake response for list', function () { + $fake = new ClientFake([ + ListInputItems::fake(), + ]); + + $response = $fake->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267'); + + expect($response->data)->toBeArray(); +}); + +it('asserts a create request was sent', function () { + $fake = new ClientFake([ + CreateResponse::fake(), + ]); + + $fake->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + $fake->assertSent(Responses::class, function ($method, $parameters) { + return $method === 'create' && + $parameters['model'] === 'gpt-4o-mini' && + $parameters['tools'][0]['type'] === 'web_search_preview' && + $parameters['input'] === "what was a positive news story from today?"; + }); +}); + +it('asserts a retrieve request was sent', function () { + $fake = new ClientFake([ + RetrieveResponse::fake(), + ]); + + $fake->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'retrieve' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + }); +}); + +it('asserts a delete request was sent', function () { + $fake = new ClientFake([ + DeleteResponse::fake(), + ]); + + $fake->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'delete' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + }); +}); + +it('asserts a list request was sent', function () { + $fake = new ClientFake([ + ListInputItems::fake(), + ]); + + $fake->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'list' && + $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + }); +}); + +it('throws an exception if there are no more fake responses', function () { + $fake = new ClientFake([ + CreateResponse::fake(), + ]); + + $fake->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + $fake->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); +})->expectExceptionMessage('No fake responses left'); + +it('throws an exception if a request was not sent', function () { + $fake = new ClientFake([ + CreateResponse::fake(), + ]); + + $fake->assertSent(Responses::class, function ($method, $parameters) { + return $method === 'create'; + }); +})->expectException(ExpectationFailedException::class); + diff --git a/tests/Testing/Resources/ResponsesTestResource.php b/tests/Testing/Resources/ResponsesTestResource.php new file mode 100644 index 00000000..f5d54d04 --- /dev/null +++ b/tests/Testing/Resources/ResponsesTestResource.php @@ -0,0 +1,74 @@ +responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?" + ]); + + $fake->assertSent(Responses::class, function ($method, $parameters) { + return $method === 'create' && + $parameters['model'] === 'gpt-4o-mini' && + $parameters['tools'] === [ + [ + 'type' => 'web_search_preview' + ] + ] && + $parameters['input'] === "what was a positive news story from today?"; + }); +}); + +it('records a response retrieve request', function () { + $fake = new ClientFake([ + RetrieveResponse::fake(), + ]); + + $fake->responses()->retrieve('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'retrieve' && + $responseId === 'asst_SMzoVX8XmCZEg1EbMHoAm8tc'; + }); +}); + +it('records a response delete request', function () { + $fake = new ClientFake([ + DeleteResponse::fake(), + ]); + + $fake->responses()->delete('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'delete' && + $responseId === 'asst_SMzoVX8XmCZEg1EbMHoAm8tc'; + }); +}); + +it('records a response list request', function () { + $fake = new ClientFake([ + ListInputItems::fake(), + ]); + + $fake->responses()->list('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + + $fake->assertSent(Responses::class, function ($method, $responseId) { + return $method === 'list' && + $responseId === 'asst_SMzoVX8XmCZEg1EbMHoAm8tc'; + }); +}); From caf4413e807e55c057cabb6f5290e4cd9f9fb3db Mon Sep 17 00:00:00 2001 From: momostafa Date: Sun, 13 Apr 2025 07:27:38 +0200 Subject: [PATCH 09/60] Updated Test files, Fixed Lint errors, all tests pass except test:unit --- src/Testing/Responses/Concerns/Fakeable.php | 17 +- .../Responses/CreateResponseFixture.php | 61 ++- .../Responses/DeleteResponseFixture.php | 2 +- .../Responses/ListInputItemsFixture.php | 8 +- .../Responses/ResponseObjectFixture.php | 103 ++++++ .../Responses/RetrieveResponseFixture.php | 60 ++- tests/Fixtures/Responses.php | 309 ++++++++++++++-- .../Streams/CreateStreamedResponse.txt | 20 +- tests/Resources/Responses.php | 349 +++++++++++------- tests/Responses/Responses/CreateResponse.php | 91 +++-- .../Responses/CreateStreamedResponse.php | 67 ++-- tests/Responses/Responses/DeleteResponse.php | 13 +- tests/Responses/Responses/ListInputItems.php | 11 +- tests/Responses/Responses/ResponseObject.php | 124 +++---- .../Responses/Responses/RetrieveResponse.php | 93 +++-- tests/Testing/ClientFakeResponses.php | 77 ++-- .../Resources/ResponsesTestResource.php | 14 +- 17 files changed, 1006 insertions(+), 413 deletions(-) create mode 100644 src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php diff --git a/src/Testing/Responses/Concerns/Fakeable.php b/src/Testing/Responses/Concerns/Fakeable.php index dfaf49de..7c698a41 100644 --- a/src/Testing/Responses/Concerns/Fakeable.php +++ b/src/Testing/Responses/Concerns/Fakeable.php @@ -23,28 +23,29 @@ trait Fakeable * It also handles cases where fixture ATTRIBUTES might be wrapped in an additional array layer, * automatically unwrapping single-element arrays to maintain consistency. * - * @param array $override Optional attributes to override in the fake response - * @param MetaInformation|null $meta Optional meta information for the response - * @throws \RuntimeException If the Responses namespace cannot be found in the class path + * @param array $override Optional attributes to override in the fake response + * @param MetaInformation|null $meta Optional meta information for the response * @return static Returns a new instance of the response class with fake data + * + * @throws \RuntimeException If the Responses namespace cannot be found in the class path */ public static function fake(array $override = [], ?MetaInformation $meta = null): static { $parts = explode('\\', static::class); $className = end($parts); - + // Find the position of 'Responses' in the namespace $responsesPos = array_search('Responses', $parts); if ($responsesPos === false) { throw new \RuntimeException('Unable to determine fixture path: no Responses namespace found'); } - + // Get all parts after 'Responses' to preserve nested structure $subPath = implode('\\', array_slice($parts, $responsesPos + 1, -1)); - + // Construct the fixture class path - $namespace = 'OpenAI\\Testing\\Responses\\Fixtures\\' . $subPath . '\\'; - $class = $namespace . $className . 'Fixture'; + $namespace = 'OpenAI\\Testing\\Responses\\Fixtures\\'.$subPath.'\\'; + $class = $namespace.$className.'Fixture'; $attributes = $class::ATTRIBUTES; // If attributes is a nested array with only one element, use that element diff --git a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php index 0d3bdf37..344ba724 100644 --- a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php @@ -5,9 +5,9 @@ final class CreateResponseFixture { public const ATTRIBUTES = [ - 'id' => 'resp_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', - 'created_at' => 1741476542, + 'created_at' => 1741484430, 'status' => 'completed', 'error' => null, 'incomplete_details' => null, @@ -15,16 +15,43 @@ final class CreateResponseFixture 'max_output_tokens' => null, 'model' => 'gpt-4o-2024-08-06', 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], [ 'type' => 'message', - 'id' => 'msg_67ccd2bf17f0819081ff3bb2cf6508e60bb6a6b452d3795b', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'status' => 'completed', 'role' => 'assistant', 'content' => [ [ 'type' => 'output_text', - 'text' => 'In a peaceful grove beneath a silver moon, a unicorn named Lumina discovered a hidden pool that reflected the stars. As she dipped her horn into the water, the pool began to shimmer, revealing a pathway to a magical realm of endless night skies. Filled with wonder, Lumina whispered a wish for all who dream to find their own hidden magic, and as she glanced back, her hoofprints sparkled like stardust.', - 'annotations' => [], + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], ], ], ], @@ -33,7 +60,7 @@ final class CreateResponseFixture 'previous_response_id' => null, 'reasoning' => [ 'effort' => null, - 'summary' => null, + 'generate_summary' => null, ], 'store' => true, 'temperature' => 1.0, @@ -43,22 +70,34 @@ final class CreateResponseFixture ], ], 'tool_choice' => 'auto', - 'tools' => [], + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], 'top_p' => 1.0, 'truncation' => 'disabled', 'usage' => [ - 'input_tokens' => 36, + 'input_tokens' => 328, 'input_tokens_details' => [ 'cached_tokens' => 0, ], - 'output_tokens' => 87, + 'output_tokens' => 356, 'output_tokens_details' => [ 'reasoning_tokens' => 0, ], - 'total_tokens' => 123, + 'total_tokens' => 684, ], 'user' => null, 'metadata' => [], - ]; } diff --git a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php index 3a4b84c7..8f0fb93d 100644 --- a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php @@ -6,7 +6,7 @@ final class DeleteResponseFixture { public const ATTRIBUTES = [ [ - 'id' => 'resp_6786a1bec27481909a17d673315b29f6', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', 'deleted' => true, ], diff --git a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php index 48e753c6..44576636 100644 --- a/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/ListInputItemsFixture.php @@ -9,20 +9,20 @@ final class ListInputItemsFixture 'data' => [ [ 'type' => 'message', - 'id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'status' => 'completed', 'role' => 'user', 'content' => [ [ 'type' => 'text', - 'text' => 'Tell me a story about a unicorn', + 'text' => 'What was a positive news story from today?', 'annotations' => [], ], ], ], ], - 'first_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', - 'last_id' => 'resp_item_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b', + 'first_id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'last_id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'has_more' => false, ]; } diff --git a/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php b/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php new file mode 100644 index 00000000..bf73f54c --- /dev/null +++ b/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php @@ -0,0 +1,103 @@ + 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + 'object' => 'response', + 'created_at' => 1741484430, + 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', + 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], + [ + 'type' => 'message', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], + ], + ], + ], + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'generate_summary' => null, + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text', + ], + ], + 'tool_choice' => 'auto', + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 328, + 'input_tokens_details' => [ + 'cached_tokens' => 0, + ], + 'output_tokens' => 356, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0, + ], + 'total_tokens' => 684, + ], + 'user' => null, + 'metadata' => [], + ]; +} diff --git a/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php index 29de3acf..a521ce7c 100644 --- a/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php @@ -5,9 +5,9 @@ final class RetrieveResponseFixture { public const ATTRIBUTES = [ - 'id' => 'resp_67cb71b351908190a308f3859487620d06981a8637e6bc44', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', - 'created_at' => 1741386163, + 'created_at' => 1741484430, 'status' => 'completed', 'error' => null, 'incomplete_details' => null, @@ -15,16 +15,43 @@ final class RetrieveResponseFixture 'max_output_tokens' => null, 'model' => 'gpt-4o-2024-08-06', 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], [ 'type' => 'message', - 'id' => 'msg_67cb71b3c2b0819084d481baaaf148f206981a8637e6bc44', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'status' => 'completed', 'role' => 'assistant', 'content' => [ [ 'type' => 'output_text', - 'text' => 'Silent circuits hum, \nThoughts emerge in data streams— \nDigital dawn breaks.', - 'annotations' => [], + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], ], ], ], @@ -33,7 +60,7 @@ final class RetrieveResponseFixture 'previous_response_id' => null, 'reasoning' => [ 'effort' => null, - 'summary' => null, + 'generate_summary' => null, ], 'store' => true, 'temperature' => 1.0, @@ -43,19 +70,32 @@ final class RetrieveResponseFixture ], ], 'tool_choice' => 'auto', - 'tools' => [], + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], 'top_p' => 1.0, 'truncation' => 'disabled', 'usage' => [ - 'input_tokens' => 32, + 'input_tokens' => 328, 'input_tokens_details' => [ 'cached_tokens' => 0, ], - 'output_tokens' => 18, + 'output_tokens' => 356, 'output_tokens_details' => [ 'reasoning_tokens' => 0, ], - 'total_tokens' => 50, + 'total_tokens' => 684, ], 'user' => null, 'metadata' => [], diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index c67ebbad..78abf662 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -1,60 +1,208 @@ + */ +function responseObject(): array +{ + return [ + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + 'object' => 'response', + 'created_at' => 1741484430, + 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', + 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], + [ + 'type' => 'message', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], + ], + ], + ], + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'generate_summary' => null, + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text', + ], + ], + 'tool_choice' => 'auto', + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 328, + 'input_tokens_details' => [ + 'cached_tokens' => 0, + ], + 'output_tokens' => 356, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0, + ], + 'total_tokens' => 684, + ], + 'user' => null, + 'metadata' => [], + ]; +} + /** * @return array */ function createResponseResource(): array { return [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', - 'created_at' => 1699619403, + 'created_at' => 1741484430, 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], [ 'type' => 'message', - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'status' => 'completed', 'role' => 'assistant', 'content' => [ [ 'type' => 'output_text', - 'text' => 'The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.', - 'annotations' => [] - ] - ] - ] + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], + ], + ], + ], ], 'parallel_tool_calls' => true, 'previous_response_id' => null, 'reasoning' => [ 'effort' => null, - 'generate_summary' => null + 'generate_summary' => null, ], 'store' => true, 'temperature' => 1.0, 'text' => [ 'format' => [ - 'type' => 'text' - ] + 'type' => 'text', + ], ], 'tool_choice' => 'auto', - 'tools' => [], + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], 'top_p' => 1.0, 'truncation' => 'disabled', 'usage' => [ 'input_tokens' => 328, 'input_tokens_details' => [ - 'cached_tokens' => 0 + 'cached_tokens' => 0, ], - 'output_tokens' => 52, + 'output_tokens' => 356, 'output_tokens_details' => [ - 'reasoning_tokens' => 0 + 'reasoning_tokens' => 0, ], - 'total_tokens' => 380 + 'total_tokens' => 684, ], 'user' => null, - 'metadata' => [] + 'metadata' => [], ]; } @@ -64,10 +212,100 @@ function createResponseResource(): array function retrieveResponseResource(): array { return [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', - 'created_at' => 1699619403, - 'status' => 'completed' + 'created_at' => 1741484430, + 'status' => 'completed', + 'error' => null, + 'incomplete_details' => null, + 'instructions' => null, + 'max_output_tokens' => null, + 'model' => 'gpt-4o-2024-08-06', + 'output' => [ + [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ], + [ + 'type' => 'message', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'status' => 'completed', + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'output_text', + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], + ], + ], + ], + ], + 'parallel_tool_calls' => true, + 'previous_response_id' => null, + 'reasoning' => [ + 'effort' => null, + 'generate_summary' => null, + ], + 'store' => true, + 'temperature' => 1.0, + 'text' => [ + 'format' => [ + 'type' => 'text', + ], + ], + 'tool_choice' => 'auto', + 'tools' => [ + [ + 'type' => 'web_search_preview', + 'domains' => [], + 'search_context_size' => 'medium', + 'user_location' => [ + 'type' => 'approximate', + 'city' => null, + 'country' => 'US', + 'region' => null, + 'timezone' => null, + ], + ], + ], + 'top_p' => 1.0, + 'truncation' => 'disabled', + 'usage' => [ + 'input_tokens' => 328, + 'input_tokens_details' => [ + 'cached_tokens' => 0, + ], + 'output_tokens' => 356, + 'output_tokens_details' => [ + 'reasoning_tokens' => 0, + ], + 'total_tokens' => 684, + ], + 'user' => null, + 'metadata' => [], ]; } @@ -80,15 +318,22 @@ function listInputItemsResource(): array 'object' => 'list', 'data' => [ [ - 'id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', - 'object' => 'response', - 'created_at' => 1699619403, - 'status' => 'completed' - ] - ], - 'first_id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', - 'last_id' => 'msg_KNsDDwE41BUAHhcPNpDkdHWZ', - 'has_more' => false + 'type' => 'message', + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'status' => 'completed', + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => 'What was a positive news story from today?', + 'annotations' => [], + ], + ], + ], + ], + 'first_id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'last_id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'has_more' => false, ]; } @@ -98,9 +343,9 @@ function listInputItemsResource(): array function deleteResponseResource(): array { return [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response.deleted', - 'deleted' => true + 'deleted' => true, ]; } @@ -111,7 +356,7 @@ function createStreamedResponseResource(): array { return [ 'event' => 'response.created', - 'data' => createResponse() + 'data' => createResponseResource(), ]; } diff --git a/tests/Fixtures/Streams/CreateStreamedResponse.txt b/tests/Fixtures/Streams/CreateStreamedResponse.txt index b7b9311e..e8f05b1e 100644 --- a/tests/Fixtures/Streams/CreateStreamedResponse.txt +++ b/tests/Fixtures/Streams/CreateStreamedResponse.txt @@ -1,9 +1,11 @@ -data: {"type":"response.created","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} -data: {"type":"response.in_progress","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"in_progress","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} -data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"in_progress","role":"assistant","content":[]}} -data: {"type":"response.content_part.added","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}} -data: {"type":"response.output_text.delta","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"delta":"Hi"} -data: {"type":"response.output_text.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"text":"Hi there! How can I assist you today?"} -data: {"type":"response.content_part.done","item_id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}} -data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}} -data: {"type":"response.completed","response":{"id":"resp_67c9fdcecf488190bdd9a0409de3a1ec07b8b0ad4e5eb654","object":"response","created_at":1741290958,"status":"completed","error":null,"incomplete_details":null,"instructions":"You are a helpful assistant.","max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[{"id":"msg_67c9fdcf37fc8190ba82116e33fb28c507b8b0ad4e5eb654","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hi there! How can I assist you today?","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":37,"output_tokens":11,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":48},"user":null,"metadata":{}}} +data: {"type":"response.created","response":{"id":"resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c","object":"response","created_at":1741484430,"status":"in_progress","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"web_search_preview","domains":[],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.in_progress","response":{"id":"resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c","object":"response","created_at":1741484430,"status":"in_progress","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"web_search_preview","domains":[],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} +data: {"type":"response.output_item.added","output_index":0,"item":{"id":"ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c","type":"web_search_call","status":"in_progress"}} +data: {"type":"response.output_item.done","output_index":0,"item":{"id":"ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c","type":"web_search_call","status":"completed"}} +data: {"type":"response.output_item.added","output_index":1,"item":{"id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","type":"message","status":"in_progress","role":"assistant","content":[]}} +data: {"type":"response.content_part.added","item_id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","output_index":1,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}} +data: {"type":"response.output_text.delta","item_id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","output_index":1,"content_index":0,"delta":"As of today, March 9, 2025, one notable positive news story..."} +data: {"type":"response.output_text.done","item_id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","output_index":1,"content_index":0,"text":"As of today, March 9, 2025, one notable positive news story..."} +data: {"type":"response.content_part.done","item_id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","output_index":1,"content_index":0,"part":{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}} +data: {"type":"response.output_item.done","output_index":1,"item":{"id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}]}} +data: {"type":"response.completed","response":{"id":"resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c","object":"response","created_at":1741484430,"status":"completed","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[{"type":"web_search_call","id":"ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c","status":"completed"},{"type":"message","id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","status":"completed","role":"assistant","content":[{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"web_search_preview","domains":[],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":328,"input_tokens_details":{"cached_tokens":0},"output_tokens":356,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":684},"user":null,"metadata":{}}} diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php index 8b1e32cb..26e124ba 100644 --- a/tests/Resources/Responses.php +++ b/tests/Resources/Responses.php @@ -1,23 +1,23 @@ responses()->delete('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + $result = $client->responses()->delete('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($result) ->toBeInstanceOf(DeleteResponse::class) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response.deleted') ->deleted->toBeTrue(); @@ -26,16 +26,16 @@ }); test('list', function () { - $client = mockClient('GET', 'responses/asst_SMzoVX8XmCZEg1EbMHoAm8tc/input_items', [], Response::from(listInputItemsResource(), metaHeaders())); + $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c/input_items', [], Response::from(listInputItemsResource(), metaHeaders())); - $result = $client->responses()->list('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + $result = $client->responses()->list('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($result) ->toBeInstanceOf(ListInputItems::class) ->object->toBe('list') ->data->toBeArray() - ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') - ->lastId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->firstId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->lastId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') ->hasMore->toBeFalse(); expect($result->meta()) @@ -43,15 +43,15 @@ }); test('retrieve', function () { - $client = mockClient('GET', 'responses/asst_SMzoVX8XmCZEg1EbMHoAm8tc', [], Response::from(retrieveResponseResource(), metaHeaders())); + $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', [], Response::from(retrieveResponseResource(), metaHeaders())); - $result = $client->responses()->retrieve('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + $result = $client->responses()->retrieve('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($result) ->toBeInstanceOf(RetrieveResponse::class) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') - ->createdAt->toBe(1699619403) + ->createdAt->toBe(1741484430) ->status->toBe('completed'); expect($result->meta()) @@ -60,67 +60,59 @@ test('create', function () { $client = mockClient('POST', 'responses', [ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" - ], Response::from(createResponseResource(), metaHeaders())); + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', + ], Response::from(createResponseResource(), metaHeaders())); $result = $client->responses()->create([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" - ]); + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', + ]); + $output = $result->output; expect($result) - ->toBeInstanceOf(ResponseObject::class) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->toBeInstanceOf(CreateResponse::class) + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') - ->createdAt->toBe(1699619403) + ->createdAt->toBe(1741484430) ->status->toBe('completed') + ->error->toBeNull() + ->incompleteDetails->toBeNull() + ->instructions->toBeNull() + ->maxOutputTokens->toBeNull() + ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(1) - ->outout[0]->type->toBe('message') - ->output[0]->toBeInstanceOf(ResponseObject::class) - ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') - ->output[0]->status->toBe('completed') - ->output[0]->role->toBe('assistant') - ->output[0]->content->toBeArray() - ->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) - ->output[0]->content[0]->type->toBe('output_text') - ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') - ->output[0]->content[0]->annotations->toBeArray() - ->output[0]->content[0]->annotations->toHaveCount(0) + ->output->toHaveCount(2); + + expect($output[0]) + ->type->toBe('web_search_call') + ->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->status->toBe('completed'); + + expect($output[1]) + ->type->toBe('message') + ->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->status->toBe('completed') + ->role->toBe('assistant') + ->content->toBeArray() + ->content->toHaveCount(1); + + expect($output[1]['content'][0]) + ->type->toBe('output_text') + ->text->toBe('As of today, March 9, 2025, one notable positive news story...'); + + expect($result) ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() - ->reasoning->toBeArray() - ->reasoning['effort']->toBeNull() - ->reasoning['generate_summary']->toBeNull() - ->store->toBeTrue() ->temperature->toBe(1.0) - ->text->toBeArray() - ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') - ->tools->toBeArray() - ->tools->toHaveCount(0) ->topP->toBe(1.0) - ->truncation->toBe('disabled') - ->usage->toBeArray() - ->usage['input_tokens']->toBe(328) - ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(52) - ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(380) - ->user->toBeNull() - ->metadata->toBeArray() - ->metadata->toBeEmpty(); + ->truncation->toBe('disabled'); + + expect($result->truncation) + ->toBe('disabled'); expect($result->meta()) ->toBeInstanceOf(MetaInformation::class); @@ -129,24 +121,16 @@ test('createStreamed', function () { $client = mockClient('POST', 'responses', [ 'stream' => true, - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" - ], Response::from(createStreamedResponseResource(), metaHeaders())); - - $result = $client->responses()->createStreamed([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" - ]); + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', + ], Response::from(createStreamedResponseResource(), metaHeaders())); + + $result = $client->responses()->createStreamed([ + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', + ]); expect($result) ->toBeInstanceOf(StreamResponse::class) @@ -155,51 +139,162 @@ expect($result->getIterator()) ->toBeInstanceOf(Iterator::class); - expect($result->getIterator()->current()) - ->toBeInstanceOf(CreateStreamedResponse::class) - ->event->toBe('response.created') - ->response->toBeInstanceOf(CreateResponse::class) - ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') - ->response->object->toBe('response') - ->response->createdAt->toBe(1699619403) - ->response->status->toBe('completed') - ->response->output->toBeArray() - ->response->output->toHaveCount(1) - ->response->output[0]->type->toBe('message') - ->response->output[0]->toBeInstanceOf(ResponseObject::class) - ->response->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') - ->response->output[0]->status->toBe('completed') - ->response->output[0]->role->toBe('assistant') - ->response->output[0]->content->toBeArray() - ->response->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) - ->response->output[0]->content[0]->type->toBe('output_text') - ->response->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') - ->response->output[0]->content[0]->annotations->toBeArray() - ->response->output[0]->content[0]->annotations->toHaveCount(0) - ->response->parallelToolCalls->toBeTrue() - ->response->previousResponseId->toBeNull() - ->response->reasoning->toBeArray() - ->response->reasoning['effort']->toBeNull() - ->response->reasoning['generate_summary']->toBeNull() - ->response->store->toBeTrue() - ->response->temperature->toBe(1.0) - ->response->text->toBeArray() - ->response->text['format']['type']->toBe('text') - ->response->toolChoice->toBe('auto') - ->response->tools->toBeArray() - ->response->tools->toHaveCount(0) - ->response->topP->toBe(1.0) - ->response->truncation->toBe('disabled') - ->response->usage->toBeArray() - ->response->usage['input_tokens']->toBe(328) - ->response->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->response->usage['output_tokens']->toBe(52) - ->response->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->response->usage['total_tokens']->toBe(380) - ->response->user->toBeNull() - ->response->metadata->toBeArray() - ->response->metadata->toBeEmpty(); + $current = $result->getIterator()->current(); + expect($current) + ->toBeInstanceOf(CreateStreamedResponse::class); + expect($current->event) + ->toBe('response.created'); + expect($current->response) + ->toBeInstanceOf(CreateResponse::class); + expect($current->response->id) + ->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); + expect($current->response->object) + ->toBe('response'); + expect($current->response->createdAt) + ->toBe(1741484430); + expect($current->response->status) + ->toBe('completed'); + expect($current->response->error) + ->toBeNull(); + expect($current->response->incompleteDetails) + ->toBeNull(); + expect($current->response->instructions) + ->toBeNull(); + expect($current->response->maxOutputTokens) + ->toBeNull(); + expect($current->response->model) + ->toBe('gpt-4o-2024-08-06'); + expect($current->response->output) + ->toBeArray(); + expect($current->response->output) + ->toHaveCount(2); + expect($current->response->output[0]) + ->toBeInstanceOf(ResponseObject::class); + expect($current->response->output[0]->type) + ->toBe('web_search_call'); + expect($current->response->output[0]->id) + ->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c'); + expect($current->response->output[0]->status) + ->toBe('completed'); + expect($current->response->output[1]) + ->toBeInstanceOf(ResponseObject::class); + expect($current->response->output[1]->type) + ->toBe('message'); + expect($current->response->output[1]->id) + ->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c'); + expect($current->response->output[1]->status) + ->toBe('completed'); + expect($current->response->output[1]->role) + ->toBe('assistant'); + expect($current->response->output[1]->content) + ->toBeArray(); + expect($current->response->output[1]->content) + ->toHaveCount(1); + expect($current->response->output[1]->content[0]) + ->toBeInstanceOf(ResponseObject::class); + expect($current->response->output[1]->content[0]->type) + ->toBe('output_text'); + expect($current->response->output[1]->content[0]->text) + ->toBe('As of today, March 9, 2025, one notable positive news story...'); + expect($current->response->output[1]->content[0]->annotations) + ->toBeArray(); + expect($current->response->output[1]->content[0]->annotations) + ->toHaveCount(3); + expect($current->response->output[1]->content[0]->annotations[0]->type) + ->toBe('url_citation'); + expect($current->response->output[1]->content[0]->annotations[0]->startIndex) + ->toBe(442); + expect($current->response->output[1]->content[0]->annotations[0]->endIndex) + ->toBe(557); + expect($current->response->output[1]->content[0]->annotations[0]->url) + ->toBe('https://.../?utm_source=chatgpt.com'); + expect($current->response->output[1]->content[0]->annotations[0]->title) + ->toBe('...'); + expect($current->response->output[1]->content[0]->annotations[1]->type) + ->toBe('url_citation'); + expect($current->response->output[1]->content[0]->annotations[1]->startIndex) + ->toBe(962); + expect($current->response->output[1]->content[0]->annotations[1]->endIndex) + ->toBe(1077); + expect($current->response->output[1]->content[0]->annotations[1]->url) + ->toBe('https://.../?utm_source=chatgpt.com'); + expect($current->response->output[1]->content[0]->annotations[1]->title) + ->toBe('...'); + expect($current->response->output[1]->content[0]->annotations[2]->type) + ->toBe('url_citation'); + expect($current->response->output[1]->content[0]->annotations[2]->startIndex) + ->toBe(1336); + expect($current->response->output[1]->content[0]->annotations[2]->endIndex) + ->toBe(1451); + expect($current->response->output[1]->content[0]->annotations[2]->url) + ->toBe('https://.../?utm_source=chatgpt.com'); + expect($current->response->output[1]->content[0]->annotations[2]->title) + ->toBe('...'); + expect($current->response->parallelToolCalls) + ->toBeTrue(); + expect($current->response->previousResponseId) + ->toBeNull(); + expect($current->response->temperature) + ->toBe(1.0); + expect($current->response->toolChoice) + ->toBe('auto'); + expect($current->response->topP) + ->toBe(1.0); + expect($current->response->truncation) + ->toBe('disabled'); + expect($current->response->reasoning) + ->toBeArray(); + expect($current->response->reasoning['effort']) + ->toBeNull(); + expect($current->response->reasoning['generate_summary']) + ->toBeNull(); + expect($current->response->text) + ->toBeArray(); + expect($current->response->text['format']['type']) + ->toBe('text'); + expect($current->response->tools) + ->toBeArray(); + expect($current->response->tools) + ->toHaveCount(1); + expect($current->response->tools[0]->type) + ->toBe('web_search_preview'); + expect($current->response->tools[0]->domains) + ->toBeArray()->toBeEmpty(); + expect($current->response->tools[0]->searchContextSize) + ->toBe('medium'); + expect($current->response->tools[0]->userLocation) + ->toBeArray(); + expect($current->response->tools[0]->userLocation['type']) + ->toBe('approximate'); + expect($current->response->tools[0]->userLocation['city']) + ->toBeNull(); + expect($current->response->tools[0]->userLocation['country']) + ->toBe('US'); + expect($current->response->tools[0]->userLocation['region']) + ->toBeNull(); + expect($current->response->tools[0]->userLocation['timezone']) + ->toBeNull(); + expect($current->response->usage) + ->toBeArray(); + expect($current->response->usage['input_tokens']) + ->toBe(328); + expect($current->response->usage['input_tokens_details']['cached_tokens']) + ->toBe(0); + expect($current->response->usage['output_tokens']) + ->toBe(356); + expect($current->response->usage['output_tokens_details']['reasoning_tokens']) + ->toBe(0); + expect($current->response->usage['total_tokens']) + ->toBe(684); + expect($current->response->user) + ->toBeNull(); + expect($current->response->metadata) + ->toBeArray(); + expect($current->response->metadata) + ->toBeEmpty(); + expect($current->response->truncation) + ->toBe('disabled'); expect($result->meta()) - ->toBeInstanceOf(MetaInformation::class); + ->toBeInstanceOf(MetaInformation::class); }); diff --git a/tests/Responses/Responses/CreateResponse.php b/tests/Responses/Responses/CreateResponse.php index 8a89af46..1d280be0 100644 --- a/tests/Responses/Responses/CreateResponse.php +++ b/tests/Responses/Responses/CreateResponse.php @@ -1,30 +1,52 @@ toBeInstanceOf(CreateResponse::class) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') - ->createdAt->toBe(1699619403) + ->createdAt->toBe(1741484430) ->status->toBe('completed') + ->error->toBeNull() + ->incompleteDetails->toBeNull() + ->instructions->toBeNull() + ->maxOutputTokens->toBeNull() + ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(1) - ->output[0]->toBeInstanceOf(ResponseObject::class) - ->output[0]->type->toBe('message') - ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->output->toHaveCount(2) + ->output[0]->type->toBe('web_search_call') + ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') ->output[0]->status->toBe('completed') - ->output[0]->role->toBe('assistant') - ->output[0]->content->toBeArray() - ->output[0]->content[0]->toBeInstanceOf(ResponseObject::class) - ->output[0]->content[0]->type->toBe('output_text') - ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') - ->output[0]->content[0]->annotations->toBeArray() - ->output[0]->content[0]->annotations->toHaveCount(0) + ->output[1]->type->toBe('message') + ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->output[1]->status->toBe('completed') + ->output[1]->role->toBe('assistant') + ->output[1]->content->toBeArray() + ->output[1]->content->toHaveCount(1) + ->output[1]->content[0]->type->toBe('output_text') + ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') + ->output[1]->content[0]->annotations->toBeArray() + ->output[1]->content[0]->annotations->toHaveCount(3) + ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) + ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) + ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[0]->title->toBe('...') + ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) + ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) + ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[1]->title->toBe('...') + ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) + ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) + ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() @@ -36,49 +58,62 @@ ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() - ->tools->toHaveCount(0) + ->tools->toHaveCount(1) + ->tools[0]->type->toBe('web_search_preview') + ->tools[0]->domains->toBeArray()->toBeEmpty() + ->tools[0]->searchContextSize->toBe('medium') + ->tools[0]->userLocation->toBeArray() + ->tools[0]->userLocation['type']->toBe('approximate') + ->tools[0]->userLocation['city']->toBeNull() + ->tools[0]->userLocation['country']->toBe('US') + ->tools[0]->userLocation['region']->toBeNull() + ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() + ->usage->toHaveCount(1) ->usage['input_tokens']->toBe(328) ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(52) + ->usage['output_tokens']->toBe(356) ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(380) + ->usage['total_tokens']->toBe(684) ->user->toBeNull() - ->metadata->toBeArray() - ->metadata->toBeEmpty(); + ->metadata->toBe([]); expect($response->meta()) ->toBeInstanceOf(MetaInformation::class); }); test('as array accessible', function () { - $response = CreateResponse::from(createResponse(), meta()); + $response = CreateResponse::from(createResponseResource(), meta()); - expect($response['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + expect($response['id'])->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('to array', function () { - $response = CreateResponse::from(createResponse(), meta()); + $response = CreateResponse::from(createResponseResource(), meta()); expect($response->toArray()) ->toBeArray() - ->toBe(createResponse()); + ->toBe(createResponseResource()); }); test('fake', function () { $response = CreateResponse::fake(); expect($response) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('fake with override', function () { $response = CreateResponse::fake([ - 'id' => 'asst_1234', + 'id' => 'resp_1234', + 'object' => 'custom_response', + 'status' => 'failed', ]); expect($response) - ->id->toBe('asst_1234'); + ->id->toBe('resp_1234') + ->object->toBe('custom_response') + ->status->toBe('failed'); }); diff --git a/tests/Responses/Responses/CreateStreamedResponse.php b/tests/Responses/Responses/CreateStreamedResponse.php index 6836ad8d..627e0999 100644 --- a/tests/Responses/Responses/CreateStreamedResponse.php +++ b/tests/Responses/Responses/CreateStreamedResponse.php @@ -1,40 +1,62 @@ 'response.created', '__meta' => meta(), 'response' => [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'object' => 'response', - 'created_at' => 1699619403, + 'created_at' => 1741484430, 'status' => 'completed', 'output' => [ [ 'type' => 'message', - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc', + 'id' => 'msg_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', 'status' => 'completed', 'role' => 'assistant', 'content' => [ [ 'type' => 'output_text', - 'text' => 'The image depicts a scenic landscape', - 'annotations' => [] - ] - ] - ] - ] - ] + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'annotations' => [ + [ + 'type' => 'url_citation', + 'start_index' => 442, + 'end_index' => 557, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 962, + 'end_index' => 1077, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + [ + 'type' => 'url_citation', + 'start_index' => 1336, + 'end_index' => 1451, + 'url' => 'https://.../?utm_source=chatgpt.com', + 'title' => '...', + ], + ], + ], + ], + ], + ], + ], ]); expect($response) ->toBeInstanceOf(CreateStreamedResponse::class) ->event->toBe('response.created') ->response->toBeInstanceOf(CreateResponse::class) - ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + ->response->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('as array accessible', function () { @@ -42,8 +64,8 @@ '__event' => 'response.created', '__meta' => meta(), 'response' => [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' - ] + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + ], ]); expect($response['event'])->toBe('response.created'); @@ -54,8 +76,8 @@ '__event' => 'response.created', '__meta' => meta(), 'response' => [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' - ] + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + ], ]); expect($response->toArray()) @@ -63,8 +85,8 @@ ->toBe([ 'event' => 'response.created', 'response' => [ - 'id' => 'asst_SMzoVX8XmCZEg1EbMHoAm8tc' - ] + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + ], ]); }); @@ -73,17 +95,16 @@ expect($response) ->event->toBe('response.created') - ->response->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + ->response->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('fake with override', function () { $response = CreateStreamedResponse::fake([ 'event' => 'response.completed', - 'response' => ['id' => 'asst_1234'] + 'response' => ['id' => 'resp_1234'], ]); expect($response) ->event->toBe('response.completed') - ->response->id->toBe('asst_1234'); + ->response->id->toBe('resp_1234'); }); - diff --git a/tests/Responses/Responses/DeleteResponse.php b/tests/Responses/Responses/DeleteResponse.php index 20732197..a6796d2d 100644 --- a/tests/Responses/Responses/DeleteResponse.php +++ b/tests/Responses/Responses/DeleteResponse.php @@ -1,12 +1,13 @@ id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response.deleted') ->deleted->toBe(true) ->meta()->toBeInstanceOf(MetaInformation::class); @@ -16,7 +17,7 @@ $result = DeleteResponse::from(deleteResponseResource(), meta()); expect($result['id']) - ->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + ->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('to array', function () { @@ -30,17 +31,17 @@ $response = DeleteResponse::fake(); expect($response) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->deleted->toBe(true); }); test('fake with override', function () { $response = DeleteResponse::fake([ - 'id' => 'asst_1234', + 'id' => 'resp_1234', 'deleted' => false, ]); expect($response) - ->id->toBe('asst_1234') + ->id->toBe('resp_1234') ->deleted->toBe(false); }); diff --git a/tests/Responses/Responses/ListInputItems.php b/tests/Responses/Responses/ListInputItems.php index fd038089..95daaf47 100644 --- a/tests/Responses/Responses/ListInputItems.php +++ b/tests/Responses/Responses/ListInputItems.php @@ -1,6 +1,7 @@ toBeInstanceOf(ListInputItems::class) ->object->toBe('list') ->data->toBeArray() - ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') - ->lastId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->firstId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->lastId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') ->hasMore->toBeFalse() ->meta()->toBeInstanceOf(MetaInformation::class); }); @@ -34,7 +35,7 @@ expect($response) ->object->toBe('list') - ->firstId->toBe('msg_KNsDDwE41BUAHhcPNpDkdHWZ') + ->firstId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') ->hasMore->toBeFalse(); }); @@ -42,7 +43,7 @@ $response = ListInputItems::fake([ 'object' => 'custom_list', 'first_id' => 'msg_1234', - 'has_more' => true + 'has_more' => true, ]); expect($response) diff --git a/tests/Responses/Responses/ResponseObject.php b/tests/Responses/Responses/ResponseObject.php index dbd17b64..605faa03 100644 --- a/tests/Responses/Responses/ResponseObject.php +++ b/tests/Responses/Responses/ResponseObject.php @@ -1,29 +1,52 @@ toBeInstanceOf(ResponseObject::class) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') - ->createdAt->toBe(1699619403) + ->createdAt->toBe(1741484430) ->status->toBe('completed') + ->error->toBeNull() + ->incompleteDetails->toBeNull() + ->instructions->toBeNull() + ->maxOutputTokens->toBeNull() + ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(1) - ->type->toBe('message') - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') - ->status->toBe('completed') - ->role->toBe('assistant') - ->content->toBeArray() - ->content[0]->toBeInstanceOf(ResponseObject::class) - ->content[0]->type->toBe('output_text') - ->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') - ->content[0]->annotations->toBeArray() - ->content[0]->annotations->toHaveCount(0) + ->output->toHaveCount(2) + ->output[0]->type->toBe('web_search_call') + ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->output[0]->status->toBe('completed') + ->output[1]->type->toBe('message') + ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->output[1]->status->toBe('completed') + ->output[1]->role->toBe('assistant') + ->output[1]->content->toBeArray() + ->output[1]->content->toHaveCount(1) + ->output[1]->content[0]->type->toBe('output_text') + ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') + ->output[1]->content[0]->annotations->toBeArray() + ->output[1]->content[0]->annotations->toHaveCount(3) + ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) + ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) + ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[0]->title->toBe('...') + ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) + ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) + ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[1]->title->toBe('...') + ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) + ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) + ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() @@ -35,32 +58,43 @@ ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() - ->tools->toHaveCount(0) + ->tools->toHaveCount(1) + ->tools[0]->type->toBe('web_search_preview') + ->tools[0]->domains->toBeArray()->toBeEmpty() + ->tools[0]->searchContextSize->toBe('medium') + ->tools[0]->userLocation->toBeArray() + ->tools[0]->userLocation['type']->toBe('approximate') + ->tools[0]->userLocation['city']->toBeNull() + ->tools[0]->userLocation['country']->toBe('US') + ->tools[0]->userLocation['region']->toBeNull() + ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() + ->usage->toHaveCount(1) ->usage['input_tokens']->toBe(328) ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(52) + ->usage['output_tokens']->toBe(356) ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(380) + ->usage['total_tokens']->toBe(684) ->user->toBeNull() - ->metadata->toBeArray() - ->metadata->toBeEmpty() - ->meta()->toBeInstanceOf(MetaInformation::class); + ->metadata->toBe([]); + + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); }); test('as array accessible', function () { $result = ResponseObject::from(responseObject(), meta()); - expect($result['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + expect($result['id'])->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('to array', function () { $result = ResponseObject::from(responseObject(), meta()); expect($result->toArray()) - ->toBeArray() ->toBe(responseObject()); }); @@ -68,58 +102,20 @@ $response = ResponseObject::fake(); expect($response) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') ->status->toBe('completed'); }); test('fake with override', function () { $response = ResponseObject::fake([ - 'id' => 'asst_1234', + 'id' => 'resp_1234', 'object' => 'custom_response', - 'status' => 'failed' + 'status' => 'failed', ]); expect($response) - ->id->toBe('asst_1234') + ->id->toBe('resp_1234') ->object->toBe('custom_response') ->status->toBe('failed'); }); - -test('from', function () { - $result = ResponseObject::from(responseObject(), meta()); - - expect($result) - ->toBeInstanceOf(ResponseObject::class) - ->object->toBe('response') - ->meta()->toBeInstanceOf(MetaInformation::class); -}); - -test('as array accessible', function () { - $result = ResponseObject::from(responseObject(), meta()); - - expect($result['object'])->toBe('response'); -}); - -test('to array', function () { - $result = ResponseObject::from(responseObject(), meta()); - - expect($result->toArray()) - ->toBe(responseObject()); -}); - -test('fake', function () { - $response = ResponseObject::fake(); - - expect($response) - ->object->toBe('response'); -}); - -test('fake with override', function () { - $response = ResponseObject::fake([ - 'object' => 'custom_response' - ]); - - expect($response) - ->object->toBe('custom_response'); -}); diff --git a/tests/Responses/Responses/RetrieveResponse.php b/tests/Responses/Responses/RetrieveResponse.php index f5a98786..21141230 100644 --- a/tests/Responses/Responses/RetrieveResponse.php +++ b/tests/Responses/Responses/RetrieveResponse.php @@ -1,26 +1,52 @@ id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->toBeInstanceOf(RetrieveResponse::class) + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') - ->createdAt->toBe(1699619403) + ->createdAt->toBe(1741484430) ->status->toBe('completed') + ->error->toBeNull() + ->incompleteDetails->toBeNull() + ->instructions->toBeNull() + ->maxOutputTokens->toBeNull() + ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(1) - ->output[0]->type->toBe('message') - ->output[0]->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->output->toHaveCount(2) + ->output[0]->type->toBe('web_search_call') + ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') ->output[0]->status->toBe('completed') - ->output[0]->role->toBe('assistant') - ->output[0]->content->toBeArray() - ->output[0]->content[0]->type->toBe('output_text') - ->output[0]->content[0]->text->toBe('The image depicts a scenic landscape with a wooden boardwalk or pathway leading through lush, green grass under a blue sky with some clouds. The setting suggests a peaceful natural area, possibly a park or nature reserve. There are trees and shrubs in the background.') - ->output[0]->content[0]->annotations->toBeArray() - ->output[0]->content[0]->annotations->toHaveCount(0) + ->output[1]->type->toBe('message') + ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->output[1]->status->toBe('completed') + ->output[1]->role->toBe('assistant') + ->output[1]->content->toBeArray() + ->output[1]->content->toHaveCount(1) + ->output[1]->content[0]->type->toBe('output_text') + ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') + ->output[1]->content[0]->annotations->toBeArray() + ->output[1]->content[0]->annotations->toHaveCount(3) + ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) + ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) + ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[0]->title->toBe('...') + ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) + ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) + ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[1]->title->toBe('...') + ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') + ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) + ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) + ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') + ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() @@ -32,25 +58,36 @@ ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() - ->tools->toHaveCount(0) + ->tools->toHaveCount(1) + ->tools[0]->type->toBe('web_search_preview') + ->tools[0]->domains->toBeArray()->toBeEmpty() + ->tools[0]->searchContextSize->toBe('medium') + ->tools[0]->userLocation->toBeArray() + ->tools[0]->userLocation['type']->toBe('approximate') + ->tools[0]->userLocation['city']->toBeNull() + ->tools[0]->userLocation['country']->toBe('US') + ->tools[0]->userLocation['region']->toBeNull() + ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() + ->usage->toHaveCount(1) ->usage['input_tokens']->toBe(328) ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(52) + ->usage['output_tokens']->toBe(356) ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(380) + ->usage['total_tokens']->toBe(684) ->user->toBeNull() - ->metadata->toBeArray() - ->metadata->toBeEmpty() - ->meta()->toBeInstanceOf(MetaInformation::class); + ->metadata->toBe([]); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); }); test('as array accessible', function () { $result = RetrieveResponse::from(retrieveResponseResource(), meta()); - expect($result['id'])->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc'); + expect($result['id'])->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); }); test('to array', function () { @@ -64,30 +101,20 @@ $response = RetrieveResponse::fake(); expect($response) - ->id->toBe('asst_SMzoVX8XmCZEg1EbMHoAm8tc') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response') ->status->toBe('completed'); }); test('fake with override', function () { $response = RetrieveResponse::fake([ - 'id' => 'asst_1234', + 'id' => 'resp_1234', 'object' => 'custom_response', - 'status' => 'failed' + 'status' => 'failed', ]); expect($response) - ->id->toBe('asst_1234') + ->id->toBe('resp_1234') ->object->toBe('custom_response') ->status->toBe('failed'); }); - -test('from', function () { - $result = RetrieveResponse::from(retrieveResponseResource(), meta()); - - expect($result) - ->toBeInstanceOf(RetrieveResponse::class) - ->object->toBe('response') - ->data->toBeInstanceOf(ResponseObject::class) - ->meta()->toBeInstanceOf(MetaInformation::class); -}); diff --git a/tests/Testing/ClientFakeResponses.php b/tests/Testing/ClientFakeResponses.php index b78eecb2..8db5636f 100644 --- a/tests/Testing/ClientFakeResponses.php +++ b/tests/Testing/ClientFakeResponses.php @@ -11,41 +11,33 @@ it('returns a fake response for create', function () { $fake = new ClientFake([ CreateResponse::fake([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', ]), ]); $response = $fake->responses()->create([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', ]); - expect($response['model'])->toBe('gpt-4o-mini'); + expect($response['model'])->toBe('gpt-4o'); expect($response['tools'][0]['type'])->toBe('web_search_preview'); }); it('returns a fake response for retrieve', function () { $fake = new ClientFake([ RetrieveResponse::fake([ - 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267' + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', ]), ]); - $response = $fake->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + $response = $fake->responses()->retrieve('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($response) - ->id->toBe('resp_67ccd2bed1ec8190b14f964abc054267') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->object->toBe('response'); }); @@ -54,10 +46,10 @@ DeleteResponse::fake(), ]); - $response = $fake->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + $response = $fake->responses()->delete('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($response) - ->id->toBe('resp_6786a1bec27481909a17d673315b29f6') + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') ->deleted->toBeTrue(); }); @@ -66,7 +58,7 @@ ListInputItems::fake(), ]); - $response = $fake->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267'); + $response = $fake->responses()->list('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); expect($response->data)->toBeArray(); }); @@ -77,20 +69,16 @@ ]); $fake->responses()->create([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" + 'model' => 'gpt-4o', + 'tools' => [['type' => 'web_search_preview']], + 'input' => 'what was a positive news story from today?', ]); $fake->assertSent(Responses::class, function ($method, $parameters) { return $method === 'create' && - $parameters['model'] === 'gpt-4o-mini' && + $parameters['model'] === 'gpt-4o' && $parameters['tools'][0]['type'] === 'web_search_preview' && - $parameters['input'] === "what was a positive news story from today?"; + $parameters['input'] === 'what was a positive news story from today?'; }); }); @@ -99,11 +87,11 @@ RetrieveResponse::fake(), ]); - $fake->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + $fake->responses()->retrieve('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); $fake->assertSent(Responses::class, function ($method, $responseId) { return $method === 'retrieve' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + $responseId === 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'; }); }); @@ -112,11 +100,11 @@ DeleteResponse::fake(), ]); - $fake->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + $fake->responses()->delete('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); $fake->assertSent(Responses::class, function ($method, $responseId) { return $method === 'delete' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + $responseId === 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'; }); }); @@ -125,11 +113,11 @@ ListInputItems::fake(), ]); - $fake->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267'); + $fake->responses()->list('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); $fake->assertSent(Responses::class, function ($method, $responseId) { return $method === 'list' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; + $responseId === 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'; }); }); @@ -139,23 +127,23 @@ ]); $fake->responses()->create([ - 'model' => 'gpt-4o-mini', + 'model' => 'gpt-4o', 'tools' => [ [ - 'type' => 'web_search_preview' - ] + 'type' => 'web_search_preview', + ], ], - 'input' => "what was a positive news story from today?" + 'input' => 'what was a positive news story from today?', ]); $fake->responses()->create([ - 'model' => 'gpt-4o-mini', + 'model' => 'gpt-4o', 'tools' => [ [ - 'type' => 'web_search_preview' - ] + 'type' => 'web_search_preview', + ], ], - 'input' => "what was a positive news story from today?" + 'input' => 'what was a positive news story from today?', ]); })->expectExceptionMessage('No fake responses left'); @@ -168,4 +156,3 @@ return $method === 'create'; }); })->expectException(ExpectationFailedException::class); - diff --git a/tests/Testing/Resources/ResponsesTestResource.php b/tests/Testing/Resources/ResponsesTestResource.php index f5d54d04..705f04e7 100644 --- a/tests/Testing/Resources/ResponsesTestResource.php +++ b/tests/Testing/Resources/ResponsesTestResource.php @@ -1,10 +1,10 @@ 'gpt-4o-mini', 'tools' => [ [ - 'type' => 'web_search_preview' - ] + 'type' => 'web_search_preview', + ], ], - 'input' => "what was a positive news story from today?" + 'input' => 'what was a positive news story from today?', ]); $fake->assertSent(Responses::class, function ($method, $parameters) { @@ -27,10 +27,10 @@ $parameters['model'] === 'gpt-4o-mini' && $parameters['tools'] === [ [ - 'type' => 'web_search_preview' - ] + 'type' => 'web_search_preview', + ], ] && - $parameters['input'] === "what was a positive news story from today?"; + $parameters['input'] === 'what was a positive news story from today?'; }); }); From a8d5ac961160b998e8fa84955df1de93854733d6 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sun, 13 Apr 2025 10:27:39 -0400 Subject: [PATCH 10/60] chore: remove log file --- Pest Test Results.yaml | 189 ----------------------------------------- 1 file changed, 189 deletions(-) delete mode 100644 Pest Test Results.yaml diff --git a/Pest Test Results.yaml b/Pest Test Results.yaml deleted file mode 100644 index 6bae6af0..00000000 --- a/Pest Test Results.yaml +++ /dev/null @@ -1,189 +0,0 @@ -MoMos-MacBook-Pro:bulksms Mo$ ./vendor/bin/pest vendor/openai-php/client/tests/Testing/ClientFakeResponses.php - - PASS Vendor\openaiphp\client\tests\Testing\ClientFakeResponses - ✓ it returns a fake response for create 0.03s - ✓ it returns a fake response for retrieve - ✓ it returns a fake response for delete - ✓ it returns a fake response for list - ✓ it asserts a create request was sent - ✓ it asserts a retrieve request was sent - ✓ it asserts a delete request was sent - ✓ it asserts a list request was sent - ✓ it throws an exception if there are no more fake responses - ✓ it throws an exception if a request was not sent - - Tests: 10 passed (14 assertions) - Duration: 0.21s - -MoMos-MacBook-Pro:bulksms Mo$ for file in vendor/openai-php/client/tests/Testing/Resources/*TestResource.php; do ./vendor/bin/pest "$file"; done - - PASS Vendor\openaiphp\client\tests\Testing\Resources\AssistantsTestResource - ✓ it records an assistant create request 0.01s - ✓ it records an assistant retrieve request - ✓ it records an assistant modify request - ✓ it records an assistant delete request - ✓ it records an assistant list request - - Tests: 5 passed (5 assertions) - Duration: 0.10s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\AudioTestResource - ✓ it records a speech request 0.01s - ✓ it records a streamed speech request 0.01s - ✓ it records an audio transcription request - ✓ it records an audio translation request - - Tests: 4 passed (4 assertions) - Duration: 0.09s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\BatchTestResource - ✓ it records an batch create request 0.01s - ✓ it records an batch retrieve request - ✓ it records an batch cancel request - ✓ it records an batch list request - - Tests: 4 passed (4 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ChatTestResource - ✓ it records a chat create request 0.01s - ✓ it records a streamed create create request 0.01s - - Tests: 2 passed (2 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\CompletionsTestResource - ✓ it records a completions create request 0.01s - ✓ it records a streamed completions create request - - Tests: 2 passed (2 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\EditsTestResource - ✓ it records a edits create request 0.01s - - Tests: 1 passed (1 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\EmbeddingsTestResource - ✓ it records a embeddings create request 0.01s - - Tests: 1 passed (1 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\FilesTestResource - ✓ it records a files retrieve request 0.01s - ✓ it records a files list request - ✓ it records a files download request - ✓ it records a files delete request - ✓ it records a files upload request - - Tests: 5 passed (5 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\FineTunesTestResource - ✓ it records a fine tunes create request 0.01s - ✓ it records a fine tunes retrieve request - ✓ it records a fine tunes cancel request - ✓ it records a fine tunes list request - ✓ it records a fine tunes list events request - ✓ it records a streamed fine tunes list events request - - Tests: 6 passed (6 assertions) - Duration: 0.09s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\FineTuningTestResource - ✓ it records a fine tuning job create request 0.01s - ✓ it records a fine tuning job retrieve request - ✓ it records a fine tuning job cancel request - ✓ it records a fine tuning job list request - ✓ it records a fine tuning list job events request - - Tests: 5 passed (5 assertions) - Duration: 0.10s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ImagesTestResource - ✓ it records a images create request 0.01s - ✓ it records a images edit request - ✓ it records a images variation request - - Tests: 3 passed (3 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ModelsTestResource - ✓ it records a model retrieve request 0.01s - ✓ it records a model delete request - ✓ it records a model list request - - Tests: 3 passed (3 assertions) - Duration: 0.07s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ModerationsTestResource - ✓ it records a moderations create request 0.01s - - Tests: 1 passed (1 assertions) - Duration: 0.07s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ResponsesTestResource - ✓ it records a response create request 0.01s - ✓ it records a response retrieve request - ✓ it records a response delete request - ✓ it records a response list request - - Tests: 4 passed (4 assertions) - Duration: 0.07s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsMessagesTestResource - ✓ it records a thread message create request 0.01s - ✓ it records a thread message retrieve request - ✓ it records a thread message modify request - ✓ it records a thread message delete request - ✓ it records a thread message list request - - Tests: 5 passed (5 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsRunsStepsTestResource - ✓ it records a thread run step retrieve request 0.01s - ✓ it records a thread run step list request - - Tests: 2 passed (2 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsRunsTestResource - ✓ it records a thread run create request 0.01s - ✓ it records a thread run retrieve request - ✓ it records a thread run modify request - ✓ it records a thread run cancel request - ✓ it records a thread run submit tool outputs request - ✓ it records a thread run list request - - Tests: 6 passed (6 assertions) - Duration: 0.08s - - - PASS Vendor\openaiphp\client\tests\Testing\Resources\ThreadsTestResource - ✓ it records a thread create request 0.01s - ✓ it records a thread create and run request - ✓ it records a thread retrieve request - ✓ it records a thread modify request - ✓ it records a thread delete request - - Tests: 5 passed (5 assertions) - Duration: 0.08s \ No newline at end of file From 780ce1a8cdd51fa0e3b1b17eae0bffe070a7bd52 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sun, 13 Apr 2025 10:27:43 -0400 Subject: [PATCH 11/60] chore: pint --- tests/Responses/Responses/ResponseObject.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Responses/Responses/ResponseObject.php b/tests/Responses/Responses/ResponseObject.php index 605faa03..1d3cfa34 100644 --- a/tests/Responses/Responses/ResponseObject.php +++ b/tests/Responses/Responses/ResponseObject.php @@ -80,7 +80,6 @@ ->user->toBeNull() ->metadata->toBe([]); - expect($result->meta()) ->toBeInstanceOf(MetaInformation::class); }); From fa48f22afaa36995af0feeb72760bd85f20c0c9c Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sun, 13 Apr 2025 10:30:11 -0400 Subject: [PATCH 12/60] chore: inline Responses doc to readme --- README-RESPONSES.md | 294 -------------------------------------------- README.md | 156 +++++++++++++++++++++++ 2 files changed, 156 insertions(+), 294 deletions(-) delete mode 100644 README-RESPONSES.md diff --git a/README-RESPONSES.md b/README-RESPONSES.md deleted file mode 100644 index dc9ba74f..00000000 --- a/README-RESPONSES.md +++ /dev/null @@ -1,294 +0,0 @@ -# OpenAI PHP - Responses API - -This document outlines the Responses API functionality added to the OpenAI PHP client. - -## Table of Contents -- [Installation](#installation) -- [Usage](#usage) - - [Create Response](#create-response) - - [Create Streamed Response](#create-streamed-response) - - [Retrieve Response](#retrieve-response) - - [Delete Response](#delete-response) - - [List Responses](#list-responses) -- [Testing](#testing) - - [Response Fakes](#response-fakes) - - [Response Assertions](#response-assertions) - -## Installation - -The Responses API is included in the OpenAI PHP client. No additional installation is required beyond the base package: - -```bash -composer require openai-php/client -``` - -## Usage - -### Create Response - -Creates a model response. Provide text or image inputs to generate text or JSON outputs. Have the model call your own custom code or use built-in tools like web search or file search to use your own data as input for the model's response. - -```php -$response = $client->responses()->create([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?", - 'temperature' => 0.7, - 'max_output_tokens' => 150, - 'tool_choice' => 'auto', - 'parallel_tool_calls' => true, - 'store' => true, - 'metadata' => [ - 'user_id' => '123', - 'session_id' => 'abc456' - ] -]); - -$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' -$response->object; // 'response' -$response->createdAt; // 1741476542 -$response->status; // 'completed' -$response->model; // 'gpt-4o-mini' - -// Access output content -foreach ($response->output as $output) { - $output->type; // 'message' - $output->id; // 'msg_67ccd2bf17f0819081ff3bb2cf6508e6' - $output->status; // 'completed' - $output->role; // 'assistant' - - foreach ($output->content as $content) { - $content->type; // 'output_text' - $content->text; // The response text - $content->annotations; // Any annotations in the response - } -} - -// Access usage information -$response->usage->inputTokens; // 36 -$response->usage->outputTokens; // 87 -$response->usage->totalTokens; // 123 - -$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] -``` - -### Create Streamed Response - -When you create a Response with stream set to true, the server will emit server-sent events to the client as the Response is generated. - -```php -$stream = $client->responses()->createStreamed([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?", - 'stream' => true -]); - -foreach ($stream as $response) { - $response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' - $response->object; // 'response' - $response->createdAt; // 1741476542 - - foreach ($response->output as $output) { - // Process streaming output - echo $output->content[0]->text; - } -} -``` - -### Retrieve Response - -Retrieves a model response with the given ID. - -```php -$response = $client->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); - -$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' -$response->object; // 'response' -$response->createdAt; // 1741476542 -$response->status; // 'completed' -$response->error; // null -$response->incompleteDetails; // null -$response->instructions; // null -$response->maxOutputTokens; // null -$response->model; // 'gpt-4o-2024-08-06' -$response->parallelToolCalls; // true -$response->previousResponseId; // null -$response->store; // true -$response->temperature; // 1.0 -$response->toolChoice; // 'auto' -$response->topP; // 1.0 -$response->truncation; // 'disabled' - -$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] -``` - -### Delete Response - -Deletes a model response with the given ID. - -```php -$response = $client->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); - -$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' -$response->object; // 'response' -$response->deleted; // true - -$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', 'deleted' => true, ...] -``` - -### List Responses - -Lists input items for a response with the given ID. - -```php -$response = $client->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267', [ - 'limit' => 10, - 'order' => 'desc' -]); - -$response->object; // 'list' - -foreach ($response->data as $item) { - $item->type; // 'message' - $item->id; // Response item ID - $item->status; // 'completed' - $item->role; // 'user' or 'assistant' - - foreach ($item->content as $content) { - $content->type; // Content type - $content->text; // Content text - $content->annotations; // Content annotations - } -} - -$response->firstId; // First item ID in the list -$response->lastId; // Last item ID in the list -$response->hasMore; // Whether there are more items to fetch - -$response->toArray(); // ['object' => 'list', 'data' => [...], ...] -``` - -## Testing - -### Response Fakes - -The client includes fakes for testing response operations: - -```php -use OpenAI\Testing\ClientFake; -use OpenAI\Responses\Responses\CreateResponse; -use OpenAI\Responses\Responses\RetrieveResponse; -use OpenAI\Responses\Responses\DeleteResponse; -use OpenAI\Responses\Responses\ListInputItems; - -// Test create operation -$fake = new ClientFake([ - CreateResponse::fake([ - 'model' => 'gpt-4o-mini', - 'tools' => [ - [ - 'type' => 'web_search_preview' - ] - ], - 'input' => "what was a positive news story from today?" - ]), -]); - -// Test retrieve operation -$fake = new ClientFake([ - RetrieveResponse::fake([ - 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267', - 'status' => 'completed' - ]), -]); - -// Test delete operation -$fake = new ClientFake([ - DeleteResponse::fake([ - 'id' => 'resp_67ccd2bed1ec8190b14f964abc054267', - 'deleted' => true - ]), -]); - -// Test list operation -$fake = new ClientFake([ - ListInputItems::fake([ - 'data' => [ - [ - 'type' => 'message', - 'id' => 'msg_123', - 'status' => 'completed' - ] - ] - ]), -]); -``` - -### Response Assertions - -You can make assertions about the requests made to the Responses API: - -```php -// Assert a specific create request was made -$fake->assertSent(Responses::class, function ($method, $parameters) { - return $method === 'create' && - $parameters['model'] === 'gpt-4o-mini' && - $parameters['tools'][0]['type'] === 'web_search_preview' && - $parameters['input'] === "what was a positive news story from today?"; -}); - -// Assert a specific retrieve request was made -$fake->assertSent(Responses::class, function ($method, $responseId) { - return $method === 'retrieve' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; -}); - -// Assert a specific delete request was made -$fake->assertSent(Responses::class, function ($method, $responseId) { - return $method === 'delete' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; -}); - -// Assert a specific list request was made -$fake->assertSent(Responses::class, function ($method, $responseId) { - return $method === 'list' && - $responseId === 'resp_67ccd2bed1ec8190b14f964abc054267'; -}); - -// Assert a request was not made -$fake->assertNotSent(Responses::class); - -// Assert number of requests -$fake->assertSent(Responses::class, 2); // Assert exactly 2 requests were made -``` - -## Meta Information - -Each response includes meta information about the request: - -```php -$response = $client->responses()->create([/* ... */]); - -$response->meta()->openai->model; // The model used -$response->meta()->openai->organization; // Your organization -$response->meta()->openai->version; // API version -$response->meta()->openai->processingMs; // Processing time in milliseconds -$response->meta()->requestId; // Request ID -$response->meta()->requestLimit->limit; // Rate limit info -$response->meta()->requestLimit->remaining; // Remaining requests -$response->meta()->requestLimit->reset; // Rate limit reset time -$response->meta()->tokenLimit->limit; // Token limit info -$response->meta()->tokenLimit->remaining; // Remaining tokens -$response->meta()->tokenLimit->reset; // Token limit reset time -``` - -This meta information is useful for debugging and tracking API usage. \ No newline at end of file diff --git a/README.md b/README.md index 019fe3c7..b10f4904 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ If you or your business relies on this package, it's important to support the de - [Get Started](#get-started) - [Usage](#usage) - [Models Resource](#models-resource) + - [Responses Resource](#responses-resource) - [Completions Resource](#completions-resource) - [Chat Resource](#chat-resource) - [Audio Resource](#audio-resource) @@ -154,6 +155,161 @@ $response->deleted; // true $response->toArray(); // ['id' => 'curie:ft-acmeco-2021-03-03-21-44-20', ...] ``` +### `Responses` Resource + +#### `create` + +Creates a model response. Provide text or image inputs to generate text or JSON outputs. Have the model call your own custom code or use built-in tools like web search or file search to use your own data as input for the model's response. + +```php +$response = $client->responses()->create([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?", + 'temperature' => 0.7, + 'max_output_tokens' => 150, + 'tool_choice' => 'auto', + 'parallel_tool_calls' => true, + 'store' => true, + 'metadata' => [ + 'user_id' => '123', + 'session_id' => 'abc456' + ] +]); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->createdAt; // 1741476542 +$response->status; // 'completed' +$response->model; // 'gpt-4o-mini' + +// Access output content +foreach ($response->output as $output) { + $output->type; // 'message' + $output->id; // 'msg_67ccd2bf17f0819081ff3bb2cf6508e6' + $output->status; // 'completed' + $output->role; // 'assistant' + + foreach ($output->content as $content) { + $content->type; // 'output_text' + $content->text; // The response text + $content->annotations; // Any annotations in the response + } +} + +// Access usage information +$response->usage->inputTokens; // 36 +$response->usage->outputTokens; // 87 +$response->usage->totalTokens; // 123 + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] +``` + +#### `create streamed` + +When you create a Response with stream set to true, the server will emit server-sent events to the client as the Response is generated. + +```php +$stream = $client->responses()->createStreamed([ + 'model' => 'gpt-4o-mini', + 'tools' => [ + [ + 'type' => 'web_search_preview' + ] + ], + 'input' => "what was a positive news story from today?", + 'stream' => true +]); + +foreach ($stream as $response) { + $response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' + $response->object; // 'response' + $response->createdAt; // 1741476542 + + foreach ($response->output as $output) { + // Process streaming output + echo $output->content[0]->text; + } +} +``` + +### `retrieve` + +Retrieves a model response with the given ID. + +```php +$response = $client->responses()->retrieve('resp_67ccd2bed1ec8190b14f964abc054267'); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->createdAt; // 1741476542 +$response->status; // 'completed' +$response->error; // null +$response->incompleteDetails; // null +$response->instructions; // null +$response->maxOutputTokens; // null +$response->model; // 'gpt-4o-2024-08-06' +$response->parallelToolCalls; // true +$response->previousResponseId; // null +$response->store; // true +$response->temperature; // 1.0 +$response->toolChoice; // 'auto' +$response->topP; // 1.0 +$response->truncation; // 'disabled' + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', ...] +``` + +### `delete` + +Deletes a model response with the given ID. + +```php +$response = $client->responses()->delete('resp_67ccd2bed1ec8190b14f964abc054267'); + +$response->id; // 'resp_67ccd2bed1ec8190b14f964abc054267' +$response->object; // 'response' +$response->deleted; // true + +$response->toArray(); // ['id' => 'resp_67ccd2bed1ec8190b14f964abc054267', 'deleted' => true, ...] +``` + +### `list` + +Lists input items for a response with the given ID. + +```php +$response = $client->responses()->list('resp_67ccd2bed1ec8190b14f964abc054267', [ + 'limit' => 10, + 'order' => 'desc' +]); + +$response->object; // 'list' + +foreach ($response->data as $item) { + $item->type; // 'message' + $item->id; // Response item ID + $item->status; // 'completed' + $item->role; // 'user' or 'assistant' + + foreach ($item->content as $content) { + $content->type; // Content type + $content->text; // Content text + $content->annotations; // Content annotations + } +} + +$response->firstId; // First item ID in the list +$response->lastId; // Last item ID in the list +$response->hasMore; // Whether there are more items to fetch + +$response->toArray(); // ['object' => 'list', 'data' => [...], ...] +``` + ### `Completions` Resource #### `create` From b1f521703393e362c1195772ff9b90c0b7f7969b Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sun, 13 Apr 2025 10:37:03 -0400 Subject: [PATCH 13/60] chore: align client contract to pattern --- src/Contracts/ClientContract.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index 8828acea..daf44729 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -15,6 +15,7 @@ use OpenAI\Contracts\Resources\ImagesContract; use OpenAI\Contracts\Resources\ModelsContract; use OpenAI\Contracts\Resources\ModerationsContract; +use OpenAI\Contracts\Resources\ResponsesContract; use OpenAI\Contracts\Resources\ThreadsContract; use OpenAI\Contracts\Resources\VectorStoresContract; @@ -29,6 +30,10 @@ interface ClientContract public function completions(): CompletionsContract; /** + * Manage responses to assist models with tasks. + * + * @see https://platform.openai.com/docs/api-reference/responses + */ public function responses(): ResponsesContract; /** From dc444b81b398b73ee4703541c167a339cfc66fad Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 19:04:02 -0400 Subject: [PATCH 14/60] docs: re-order chat/completion --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 255fbaf1..1336ca5d 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ If you or your business relies on this package, it's important to support the de - [Usage](#usage) - [Models Resource](#models-resource) - [Responses Resource](#responses-resource) - - [Completions Resource](#completions-resource) - [Chat Resource](#chat-resource) + - [Completions Resource](#completions-resource) - [Audio Resource](#audio-resource) - [Embeddings Resource](#embeddings-resource) - [Files Resource](#files-resource) From 69c7a13f31ac023676e736fcbbb47fdb43fa7b89 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 19:49:38 -0400 Subject: [PATCH 15/60] fix: use longer replacement to not clobber Responses/* --- src/Testing/Responses/Concerns/Fakeable.php | 44 ++------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/src/Testing/Responses/Concerns/Fakeable.php b/src/Testing/Responses/Concerns/Fakeable.php index 7c698a41..ca55e3ef 100644 --- a/src/Testing/Responses/Concerns/Fakeable.php +++ b/src/Testing/Responses/Concerns/Fakeable.php @@ -9,52 +9,14 @@ trait Fakeable { /** - * Create a fake response instance with optional attribute overrides. - * - * This method handles both simple and nested namespace structures for response fixtures: - * - Simple: OpenAI\Responses\Category\Response -> OpenAI\Testing\Responses\Fixtures\Category\ResponseFixture - * - Nested: OpenAI\Responses\Category\SubCategory\Response -> OpenAI\Testing\Responses\Fixtures\Category\SubCategory\ResponseFixture - * - * The method preserves the namespace hierarchy after 'Responses' to maintain proper fixture organization: - * Example paths: - * - Responses\Threads\ThreadResponse -> Fixtures\Threads\ThreadResponseFixture - * - Responses\Threads\Runs\ThreadRunResponse -> Fixtures\Threads\Runs\ThreadRunResponseFixture - * - * It also handles cases where fixture ATTRIBUTES might be wrapped in an additional array layer, - * automatically unwrapping single-element arrays to maintain consistency. - * - * @param array $override Optional attributes to override in the fake response - * @param MetaInformation|null $meta Optional meta information for the response - * @return static Returns a new instance of the response class with fake data - * - * @throws \RuntimeException If the Responses namespace cannot be found in the class path + * @param array $override */ public static function fake(array $override = [], ?MetaInformation $meta = null): static { - $parts = explode('\\', static::class); - $className = end($parts); - - // Find the position of 'Responses' in the namespace - $responsesPos = array_search('Responses', $parts); - if ($responsesPos === false) { - throw new \RuntimeException('Unable to determine fixture path: no Responses namespace found'); - } - - // Get all parts after 'Responses' to preserve nested structure - $subPath = implode('\\', array_slice($parts, $responsesPos + 1, -1)); - - // Construct the fixture class path - $namespace = 'OpenAI\\Testing\\Responses\\Fixtures\\'.$subPath.'\\'; - $class = $namespace.$className.'Fixture'; - - $attributes = $class::ATTRIBUTES; - // If attributes is a nested array with only one element, use that element - if (is_array($attributes) && count($attributes) === 1 && isset($attributes[0]) && is_array($attributes[0])) { - $attributes = $attributes[0]; - } + $class = str_replace('OpenAI\\Responses\\', 'OpenAI\\Testing\\Responses\\Fixtures\\', static::class).'Fixture'; return static::from( - self::buildAttributes($attributes, $override), + self::buildAttributes($class::ATTRIBUTES, $override), $meta ?? self::fakeResponseMetaInformation(), ); } From 1f68d2382f0b03d35ab42958de43ac5dabd4e190 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 20:24:20 -0400 Subject: [PATCH 16/60] fix: parse metadata (optionally) --- src/Responses/Responses/CreateResponse.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 8d49ad74..8573fa9a 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -54,6 +54,7 @@ private function __construct( public readonly string $truncation, public readonly array $usage, public readonly ?string $user, + public readonly array $metadata, private readonly MetaInformation $meta, ) {} @@ -85,6 +86,7 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['truncation'], $attributes['usage'], $attributes['user'] ?? null, + $attributes['metadata'] ?? [], $meta, ); } @@ -103,6 +105,7 @@ public function toArray(): array 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, + 'metadata' => $this->metadata, 'model' => $this->model, 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, From 5c2854779f47c9edca3e2bb205d110ef6ada48a8 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 20:24:36 -0400 Subject: [PATCH 17/60] test: don't assert on plain arrays --- tests/Fixtures/Responses.php | 10 ++--- tests/Responses/Responses/CreateResponse.php | 47 +------------------- 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 78abf662..3ee6eacb 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -14,6 +14,7 @@ function responseObject(): array 'incomplete_details' => null, 'instructions' => null, 'max_output_tokens' => null, + 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ [ @@ -99,7 +100,6 @@ function responseObject(): array 'total_tokens' => 684, ], 'user' => null, - 'metadata' => [], ]; } @@ -117,6 +117,7 @@ function createResponseResource(): array 'incomplete_details' => null, 'instructions' => null, 'max_output_tokens' => null, + 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ [ @@ -202,7 +203,6 @@ function createResponseResource(): array 'total_tokens' => 684, ], 'user' => null, - 'metadata' => [], ]; } @@ -220,6 +220,7 @@ function retrieveResponseResource(): array 'incomplete_details' => null, 'instructions' => null, 'max_output_tokens' => null, + 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ [ @@ -305,7 +306,6 @@ function retrieveResponseResource(): array 'total_tokens' => 684, ], 'user' => null, - 'metadata' => [], ]; } @@ -363,7 +363,7 @@ function createStreamedResponseResource(): array /** * @return resource */ -function createStreamedResource() +function responseStream() { - return fopen(__DIR__.'/Streams/CreateStreamedResponse.txt', 'r'); + return fopen(__DIR__.'/Streams/ResponseCreate.txt', 'r'); } diff --git a/tests/Responses/Responses/CreateResponse.php b/tests/Responses/Responses/CreateResponse.php index 1d280be0..690bb38d 100644 --- a/tests/Responses/Responses/CreateResponse.php +++ b/tests/Responses/Responses/CreateResponse.php @@ -19,64 +19,19 @@ ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() ->output->toHaveCount(2) - ->output[0]->type->toBe('web_search_call') - ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') - ->output[0]->status->toBe('completed') - ->output[1]->type->toBe('message') - ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') - ->output[1]->status->toBe('completed') - ->output[1]->role->toBe('assistant') - ->output[1]->content->toBeArray() - ->output[1]->content->toHaveCount(1) - ->output[1]->content[0]->type->toBe('output_text') - ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') - ->output[1]->content[0]->annotations->toBeArray() - ->output[1]->content[0]->annotations->toHaveCount(3) - ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) - ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) - ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[0]->title->toBe('...') - ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) - ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) - ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[1]->title->toBe('...') - ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) - ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) - ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() - ->reasoning['effort']->toBeNull() - ->reasoning['generate_summary']->toBeNull() ->store->toBeTrue() ->temperature->toBe(1.0) ->text->toBeArray() - ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() ->tools->toHaveCount(1) - ->tools[0]->type->toBe('web_search_preview') - ->tools[0]->domains->toBeArray()->toBeEmpty() - ->tools[0]->searchContextSize->toBe('medium') - ->tools[0]->userLocation->toBeArray() - ->tools[0]->userLocation['type']->toBe('approximate') - ->tools[0]->userLocation['city']->toBeNull() - ->tools[0]->userLocation['country']->toBe('US') - ->tools[0]->userLocation['region']->toBeNull() - ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() - ->usage->toHaveCount(1) - ->usage['input_tokens']->toBe(328) - ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(356) - ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(684) + ->usage->toHaveCount(5) ->user->toBeNull() ->metadata->toBe([]); From 3bc814bf4fa21ce7bf6159716d37c275b357f715 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 20:24:45 -0400 Subject: [PATCH 18/60] test: assert stream properly on responses --- ...eateStreamedResponse.txt => ResponseCreate.txt} | 0 tests/Resources/Responses.php | 14 ++++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) rename tests/Fixtures/Streams/{CreateStreamedResponse.txt => ResponseCreate.txt} (100%) diff --git a/tests/Fixtures/Streams/CreateStreamedResponse.txt b/tests/Fixtures/Streams/ResponseCreate.txt similarity index 100% rename from tests/Fixtures/Streams/CreateStreamedResponse.txt rename to tests/Fixtures/Streams/ResponseCreate.txt diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php index 26e124ba..05d20da2 100644 --- a/tests/Resources/Responses.php +++ b/tests/Resources/Responses.php @@ -1,5 +1,7 @@ toBeInstanceOf(MetaInformation::class); }); -test('createStreamed', function () { - $client = mockClient('POST', 'responses', [ +test('create streamed', function () { + $response = new Response( + body: new Stream(responseStream()), + headers: metaHeaders(), + ); + + $client = mockStreamClient('POST', 'responses', [ 'stream' => true, 'model' => 'gpt-4o', 'tools' => [['type' => 'web_search_preview']], 'input' => 'what was a positive news story from today?', - ], Response::from(createStreamedResponseResource(), metaHeaders())); + ], $response); $result = $client->responses()->createStreamed([ 'model' => 'gpt-4o', From d533727567e8a0d468316c1da0240776a374b6c7 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 21:01:27 -0400 Subject: [PATCH 19/60] chore: add missing docblock property --- src/Responses/Responses/CreateResponse.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 8573fa9a..ae1c126c 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -30,6 +30,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati * @param array{format: array{type: string}} $text * @param array $tools * @param array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} $usage + * @param array $metadata */ private function __construct( public readonly string $id, From b9828265f88899aa012b9b631cc95c788e51d3a1 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 21:29:36 -0400 Subject: [PATCH 20/60] fix: add metadata into Responses payload --- src/Responses/Responses/ResponseObject.php | 2 +- src/Responses/Responses/RetrieveResponse.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php index ecede7a1..7493c2e9 100644 --- a/src/Responses/Responses/ResponseObject.php +++ b/src/Responses/Responses/ResponseObject.php @@ -106,6 +106,7 @@ public function toArray(): array 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, + 'metadata' => $this->metadata, 'model' => $this->model, 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, @@ -119,7 +120,6 @@ public function toArray(): array 'top_p' => $this->topP, 'truncation' => $this->truncation, 'user' => $this->user, - 'metadata' => $this->metadata, 'usage' => $this->usage, ]; } diff --git a/src/Responses/Responses/RetrieveResponse.php b/src/Responses/Responses/RetrieveResponse.php index a5bc28aa..df56f030 100644 --- a/src/Responses/Responses/RetrieveResponse.php +++ b/src/Responses/Responses/RetrieveResponse.php @@ -106,6 +106,7 @@ public function toArray(): array 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, + 'metadata' => $this->metadata, 'model' => $this->model, 'output' => $this->output, 'parallel_tool_calls' => $this->parallelToolCalls, @@ -119,7 +120,6 @@ public function toArray(): array 'top_p' => $this->topP, 'truncation' => $this->truncation, 'user' => $this->user, - 'metadata' => $this->metadata, 'usage' => $this->usage, ]; } From 779446e87986c812cf4d6b50c560aa7318636905 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 21:29:47 -0400 Subject: [PATCH 21/60] test: fix double nesting on delete attrs --- .../Fixtures/Responses/DeleteResponseFixture.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php index 8f0fb93d..0ed29bba 100644 --- a/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/DeleteResponseFixture.php @@ -5,10 +5,8 @@ final class DeleteResponseFixture { public const ATTRIBUTES = [ - [ - 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', - 'object' => 'response', - 'deleted' => true, - ], + 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', + 'object' => 'response', + 'deleted' => true, ]; } From 56dd359afb90adae155ab516fbc62a58ae41c945 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Apr 2025 21:30:04 -0400 Subject: [PATCH 22/60] test: correct bad assertions on tests --- tests/Fixtures/Responses.php | 8 +- ...reate.txt => ResponseCompletionCreate.txt} | 0 tests/Resources/Responses.php | 105 +++++++++--------- tests/Responses/Responses/ResponseObject.php | 47 +------- .../Responses/Responses/RetrieveResponse.php | 47 +------- 5 files changed, 60 insertions(+), 147 deletions(-) rename tests/Fixtures/Streams/{ResponseCreate.txt => ResponseCompletionCreate.txt} (100%) diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 3ee6eacb..d2305e1b 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -88,6 +88,7 @@ function responseObject(): array ], 'top_p' => 1.0, 'truncation' => 'disabled', + 'user' => null, 'usage' => [ 'input_tokens' => 328, 'input_tokens_details' => [ @@ -99,7 +100,6 @@ function responseObject(): array ], 'total_tokens' => 684, ], - 'user' => null, ]; } @@ -294,6 +294,7 @@ function retrieveResponseResource(): array ], 'top_p' => 1.0, 'truncation' => 'disabled', + 'user' => null, 'usage' => [ 'input_tokens' => 328, 'input_tokens_details' => [ @@ -305,7 +306,6 @@ function retrieveResponseResource(): array ], 'total_tokens' => 684, ], - 'user' => null, ]; } @@ -363,7 +363,7 @@ function createStreamedResponseResource(): array /** * @return resource */ -function responseStream() +function responseCompletionStream() { - return fopen(__DIR__.'/Streams/ResponseCreate.txt', 'r'); + return fopen(__DIR__.'/Streams/ResponseCompletionCreate.txt', 'r'); } diff --git a/tests/Fixtures/Streams/ResponseCreate.txt b/tests/Fixtures/Streams/ResponseCompletionCreate.txt similarity index 100% rename from tests/Fixtures/Streams/ResponseCreate.txt rename to tests/Fixtures/Streams/ResponseCompletionCreate.txt diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php index 05d20da2..2ec79473 100644 --- a/tests/Resources/Responses.php +++ b/tests/Resources/Responses.php @@ -11,60 +11,12 @@ use OpenAI\Responses\Responses\RetrieveResponse; use OpenAI\Responses\StreamResponse; -test('delete', function () { - $client = mockClient('DELETE', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', [], Response::from(deleteResponseResource(), metaHeaders())); - - $result = $client->responses()->delete('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); - - expect($result) - ->toBeInstanceOf(DeleteResponse::class) - ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') - ->object->toBe('response.deleted') - ->deleted->toBeTrue(); - - expect($result->meta()) - ->toBeInstanceOf(MetaInformation::class); -}); - -test('list', function () { - $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c/input_items', [], Response::from(listInputItemsResource(), metaHeaders())); - - $result = $client->responses()->list('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); - - expect($result) - ->toBeInstanceOf(ListInputItems::class) - ->object->toBe('list') - ->data->toBeArray() - ->firstId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') - ->lastId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') - ->hasMore->toBeFalse(); - - expect($result->meta()) - ->toBeInstanceOf(MetaInformation::class); -}); - -test('retrieve', function () { - $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', [], Response::from(retrieveResponseResource(), metaHeaders())); - - $result = $client->responses()->retrieve('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); - - expect($result) - ->toBeInstanceOf(RetrieveResponse::class) - ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') - ->object->toBe('response') - ->createdAt->toBe(1741484430) - ->status->toBe('completed'); - - expect($result->meta()) - ->toBeInstanceOf(MetaInformation::class); -}); - test('create', function () { $client = mockClient('POST', 'responses', [ 'model' => 'gpt-4o', 'tools' => [['type' => 'web_search_preview']], 'input' => 'what was a positive news story from today?', - ], Response::from(createResponseResource(), metaHeaders())); + ], \OpenAI\ValueObjects\Transporter\Response::from(createResponseResource(), metaHeaders())); $result = $client->responses()->create([ 'model' => 'gpt-4o', @@ -121,15 +73,15 @@ test('create streamed', function () { $response = new Response( - body: new Stream(responseStream()), + body: new Stream(responseCompletionStream()), headers: metaHeaders(), ); $client = mockStreamClient('POST', 'responses', [ - 'stream' => true, 'model' => 'gpt-4o', 'tools' => [['type' => 'web_search_preview']], 'input' => 'what was a positive news story from today?', + 'stream' => true, ], $response); $result = $client->responses()->createStreamed([ @@ -304,3 +256,54 @@ expect($result->meta()) ->toBeInstanceOf(MetaInformation::class); }); + +test('delete', function () { + $client = mockClient('DELETE', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', [ + ], \OpenAI\ValueObjects\Transporter\Response::from(deleteResponseResource(), metaHeaders())); + + $result = $client->responses()->delete('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); + + expect($result) + ->toBeInstanceOf(DeleteResponse::class) + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') + ->object->toBe('response.deleted') + ->deleted->toBeTrue(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list', function () { + $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c/input_items', [ + ], \OpenAI\ValueObjects\Transporter\Response::from(listInputItemsResource(), metaHeaders())); + + $result = $client->responses()->list('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); + + expect($result) + ->toBeInstanceOf(ListInputItems::class) + ->object->toBe('list') + ->data->toBeArray() + ->firstId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->lastId->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') + ->hasMore->toBeFalse(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('retrieve', function () { + $client = mockClient('GET', 'responses/resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', [ + ], \OpenAI\ValueObjects\Transporter\Response::from(retrieveResponseResource(), metaHeaders())); + + $result = $client->responses()->retrieve('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); + + expect($result) + ->toBeInstanceOf(RetrieveResponse::class) + ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') + ->object->toBe('response') + ->createdAt->toBe(1741484430) + ->status->toBe('completed'); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); diff --git a/tests/Responses/Responses/ResponseObject.php b/tests/Responses/Responses/ResponseObject.php index 1d3cfa34..d7b31d4a 100644 --- a/tests/Responses/Responses/ResponseObject.php +++ b/tests/Responses/Responses/ResponseObject.php @@ -19,64 +19,19 @@ ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() ->output->toHaveCount(2) - ->output[0]->type->toBe('web_search_call') - ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') - ->output[0]->status->toBe('completed') - ->output[1]->type->toBe('message') - ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') - ->output[1]->status->toBe('completed') - ->output[1]->role->toBe('assistant') - ->output[1]->content->toBeArray() - ->output[1]->content->toHaveCount(1) - ->output[1]->content[0]->type->toBe('output_text') - ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') - ->output[1]->content[0]->annotations->toBeArray() - ->output[1]->content[0]->annotations->toHaveCount(3) - ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) - ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) - ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[0]->title->toBe('...') - ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) - ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) - ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[1]->title->toBe('...') - ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) - ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) - ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() - ->reasoning['effort']->toBeNull() - ->reasoning['generate_summary']->toBeNull() ->store->toBeTrue() ->temperature->toBe(1.0) ->text->toBeArray() - ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() ->tools->toHaveCount(1) - ->tools[0]->type->toBe('web_search_preview') - ->tools[0]->domains->toBeArray()->toBeEmpty() - ->tools[0]->searchContextSize->toBe('medium') - ->tools[0]->userLocation->toBeArray() - ->tools[0]->userLocation['type']->toBe('approximate') - ->tools[0]->userLocation['city']->toBeNull() - ->tools[0]->userLocation['country']->toBe('US') - ->tools[0]->userLocation['region']->toBeNull() - ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() - ->usage->toHaveCount(1) - ->usage['input_tokens']->toBe(328) - ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(356) - ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(684) + ->usage->toHaveCount(5) ->user->toBeNull() ->metadata->toBe([]); diff --git a/tests/Responses/Responses/RetrieveResponse.php b/tests/Responses/Responses/RetrieveResponse.php index 21141230..68d992de 100644 --- a/tests/Responses/Responses/RetrieveResponse.php +++ b/tests/Responses/Responses/RetrieveResponse.php @@ -19,64 +19,19 @@ ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() ->output->toHaveCount(2) - ->output[0]->type->toBe('web_search_call') - ->output[0]->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') - ->output[0]->status->toBe('completed') - ->output[1]->type->toBe('message') - ->output[1]->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') - ->output[1]->status->toBe('completed') - ->output[1]->role->toBe('assistant') - ->output[1]->content->toBeArray() - ->output[1]->content->toHaveCount(1) - ->output[1]->content[0]->type->toBe('output_text') - ->output[1]->content[0]->text->toBe('As of today, March 9, 2025, one notable positive news story...') - ->output[1]->content[0]->annotations->toBeArray() - ->output[1]->content[0]->annotations->toHaveCount(3) - ->output[1]->content[0]->annotations[0]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[0]->startIndex->toBe(442) - ->output[1]->content[0]->annotations[0]->endIndex->toBe(557) - ->output[1]->content[0]->annotations[0]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[0]->title->toBe('...') - ->output[1]->content[0]->annotations[1]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[1]->startIndex->toBe(962) - ->output[1]->content[0]->annotations[1]->endIndex->toBe(1077) - ->output[1]->content[0]->annotations[1]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[1]->title->toBe('...') - ->output[1]->content[0]->annotations[2]->type->toBe('url_citation') - ->output[1]->content[0]->annotations[2]->startIndex->toBe(1336) - ->output[1]->content[0]->annotations[2]->endIndex->toBe(1451) - ->output[1]->content[0]->annotations[2]->url->toBe('https://.../?utm_source=chatgpt.com') - ->output[1]->content[0]->annotations[2]->title->toBe('...') ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() ->reasoning->toBeArray() - ->reasoning['effort']->toBeNull() - ->reasoning['generate_summary']->toBeNull() ->store->toBeTrue() ->temperature->toBe(1.0) ->text->toBeArray() - ->text['format']['type']->toBe('text') ->toolChoice->toBe('auto') ->tools->toBeArray() ->tools->toHaveCount(1) - ->tools[0]->type->toBe('web_search_preview') - ->tools[0]->domains->toBeArray()->toBeEmpty() - ->tools[0]->searchContextSize->toBe('medium') - ->tools[0]->userLocation->toBeArray() - ->tools[0]->userLocation['type']->toBe('approximate') - ->tools[0]->userLocation['city']->toBeNull() - ->tools[0]->userLocation['country']->toBe('US') - ->tools[0]->userLocation['region']->toBeNull() - ->tools[0]->userLocation['timezone']->toBeNull() ->topP->toBe(1.0) ->truncation->toBe('disabled') ->usage->toBeArray() - ->usage->toHaveCount(1) - ->usage['input_tokens']->toBe(328) - ->usage['input_tokens_details']['cached_tokens']->toBe(0) - ->usage['output_tokens']->toBe(356) - ->usage['output_tokens_details']['reasoning_tokens']->toBe(0) - ->usage['total_tokens']->toBe(684) + ->usage->toHaveCount(5) ->user->toBeNull() ->metadata->toBe([]); From bec19fb75c880635a7b6ec52946bd4cb18ab7252 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 05:52:12 -0400 Subject: [PATCH 23/60] fix: remove ResponseObject --- src/Responses/Responses/ResponseObject.php | 126 ------------------- tests/Resources/Responses.php | 7 -- tests/Responses/Responses/ResponseObject.php | 75 ----------- 3 files changed, 208 deletions(-) delete mode 100644 src/Responses/Responses/ResponseObject.php delete mode 100644 tests/Responses/Responses/ResponseObject.php diff --git a/src/Responses/Responses/ResponseObject.php b/src/Responses/Responses/ResponseObject.php deleted file mode 100644 index 7493c2e9..00000000 --- a/src/Responses/Responses/ResponseObject.php +++ /dev/null @@ -1,126 +0,0 @@ -}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> - */ -final class ResponseObject implements ResponseContract, ResponseHasMetaInformationContract -{ - /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> - */ - use ArrayAccessible; - - use Fakeable; - use HasMetaInformation; - - /** - * @param array}>}> $output - * @param array $reasoning - * @param array{format: array{type: string}} $text - * @param array $tools - * @param array $metadata - */ - private function __construct( - public readonly string $id, - public readonly string $object, - public readonly int $createdAt, - public readonly string $status, - public readonly ?object $error, - public readonly ?object $incompleteDetails, - public readonly ?string $instructions, - public readonly ?int $maxOutputTokens, - public readonly string $model, - public readonly array $output, - public readonly bool $parallelToolCalls, - public readonly ?string $previousResponseId, - public readonly array $reasoning, - public readonly bool $store, - public readonly ?float $temperature, - public readonly array $text, - public readonly string $toolChoice, - public readonly array $tools, - public readonly ?float $topP, - public readonly string $truncation, - public readonly ?string $user, - public array $metadata, - /** @var array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} */ - public readonly array $usage, - private readonly MetaInformation $meta, - ) {} - - /** - * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} $attributes - */ - public static function from(array $attributes, MetaInformation $meta): self - { - return new self( - $attributes['id'], - $attributes['object'], - $attributes['created_at'], - $attributes['status'], - $attributes['error'], - $attributes['incomplete_details'], - $attributes['instructions'], - $attributes['max_output_tokens'], - $attributes['model'], - $attributes['output'], - $attributes['parallel_tool_calls'], - $attributes['previous_response_id'], - $attributes['reasoning'], - $attributes['store'], - $attributes['temperature'], - $attributes['text'], - $attributes['tool_choice'], - $attributes['tools'], - $attributes['top_p'], - $attributes['truncation'], - $attributes['user'], - $attributes['metadata'] ?? [], - $attributes['usage'], - $meta, - ); - } - - /** - * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata: array} - */ - public function toArray(): array - { - return [ - 'id' => $this->id, - 'object' => $this->object, - 'created_at' => $this->createdAt, - 'status' => $this->status, - 'error' => $this->error, - 'incomplete_details' => $this->incompleteDetails, - 'instructions' => $this->instructions, - 'max_output_tokens' => $this->maxOutputTokens, - 'metadata' => $this->metadata, - 'model' => $this->model, - 'output' => $this->output, - 'parallel_tool_calls' => $this->parallelToolCalls, - 'previous_response_id' => $this->previousResponseId, - 'reasoning' => $this->reasoning, - 'store' => $this->store, - 'temperature' => $this->temperature, - 'text' => $this->text, - 'tool_choice' => $this->toolChoice, - 'tools' => $this->tools, - 'top_p' => $this->topP, - 'truncation' => $this->truncation, - 'user' => $this->user, - 'usage' => $this->usage, - ]; - } -} diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php index 2ec79473..1aab2e46 100644 --- a/tests/Resources/Responses.php +++ b/tests/Resources/Responses.php @@ -7,7 +7,6 @@ use OpenAI\Responses\Responses\CreateStreamedResponse; use OpenAI\Responses\Responses\DeleteResponse; use OpenAI\Responses\Responses\ListInputItems; -use OpenAI\Responses\Responses\ResponseObject; use OpenAI\Responses\Responses\RetrieveResponse; use OpenAI\Responses\StreamResponse; @@ -126,16 +125,12 @@ ->toBeArray(); expect($current->response->output) ->toHaveCount(2); - expect($current->response->output[0]) - ->toBeInstanceOf(ResponseObject::class); expect($current->response->output[0]->type) ->toBe('web_search_call'); expect($current->response->output[0]->id) ->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c'); expect($current->response->output[0]->status) ->toBe('completed'); - expect($current->response->output[1]) - ->toBeInstanceOf(ResponseObject::class); expect($current->response->output[1]->type) ->toBe('message'); expect($current->response->output[1]->id) @@ -148,8 +143,6 @@ ->toBeArray(); expect($current->response->output[1]->content) ->toHaveCount(1); - expect($current->response->output[1]->content[0]) - ->toBeInstanceOf(ResponseObject::class); expect($current->response->output[1]->content[0]->type) ->toBe('output_text'); expect($current->response->output[1]->content[0]->text) diff --git a/tests/Responses/Responses/ResponseObject.php b/tests/Responses/Responses/ResponseObject.php deleted file mode 100644 index d7b31d4a..00000000 --- a/tests/Responses/Responses/ResponseObject.php +++ /dev/null @@ -1,75 +0,0 @@ -toBeInstanceOf(ResponseObject::class) - ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') - ->object->toBe('response') - ->createdAt->toBe(1741484430) - ->status->toBe('completed') - ->error->toBeNull() - ->incompleteDetails->toBeNull() - ->instructions->toBeNull() - ->maxOutputTokens->toBeNull() - ->model->toBe('gpt-4o-2024-08-06') - ->output->toBeArray() - ->output->toHaveCount(2) - ->parallelToolCalls->toBeTrue() - ->previousResponseId->toBeNull() - ->reasoning->toBeArray() - ->store->toBeTrue() - ->temperature->toBe(1.0) - ->text->toBeArray() - ->toolChoice->toBe('auto') - ->tools->toBeArray() - ->tools->toHaveCount(1) - ->topP->toBe(1.0) - ->truncation->toBe('disabled') - ->usage->toBeArray() - ->usage->toHaveCount(5) - ->user->toBeNull() - ->metadata->toBe([]); - - expect($result->meta()) - ->toBeInstanceOf(MetaInformation::class); -}); - -test('as array accessible', function () { - $result = ResponseObject::from(responseObject(), meta()); - - expect($result['id'])->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c'); -}); - -test('to array', function () { - $result = ResponseObject::from(responseObject(), meta()); - - expect($result->toArray()) - ->toBe(responseObject()); -}); - -test('fake', function () { - $response = ResponseObject::fake(); - - expect($response) - ->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c') - ->object->toBe('response') - ->status->toBe('completed'); -}); - -test('fake with override', function () { - $response = ResponseObject::fake([ - 'id' => 'resp_1234', - 'object' => 'custom_response', - 'status' => 'failed', - ]); - - expect($response) - ->id->toBe('resp_1234') - ->object->toBe('custom_response') - ->status->toBe('failed'); -}); From 18bf40e761d99b019f5d6ccbdeda9269545c30a7 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 06:11:03 -0400 Subject: [PATCH 24/60] feat: split out usage into classes --- src/Resources/Responses.php | 2 +- src/Responses/Responses/CreateResponse.php | 11 +++---- .../Responses/CreateResponseUsage.php | 10 ++++-- .../CreateResponseUsageInputTokenDetails.php | 32 +++++++++++++++++++ .../CreateResponseUsageOutputTokenDetails.php | 32 +++++++++++++++++++ 5 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseUsageInputTokenDetails.php create mode 100644 src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 30a70eef..9e53f177 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index ae1c126c..81f97e3d 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -29,7 +29,6 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati * @param array $reasoning * @param array{format: array{type: string}} $text * @param array $tools - * @param array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int} $usage * @param array $metadata */ private function __construct( @@ -53,14 +52,14 @@ private function __construct( public readonly array $tools, public readonly ?float $topP, public readonly string $truncation, - public readonly array $usage, + public readonly CreateResponseUsage $usage, public readonly ?string $user, public readonly array $metadata, private readonly MetaInformation $meta, ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -85,7 +84,7 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['tools'], $attributes['top_p'], $attributes['truncation'], - $attributes['usage'], + CreateResponseUsage::from($attributes['usage']), $attributes['user'] ?? null, $attributes['metadata'] ?? [], $meta, @@ -93,7 +92,7 @@ public static function from(array $attributes, MetaInformation $meta): self } /** - * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array} + * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} */ public function toArray(): array { @@ -119,7 +118,7 @@ public function toArray(): array 'tools' => $this->tools, 'top_p' => $this->topP, 'truncation' => $this->truncation, - 'usage' => $this->usage, + 'usage' => $this->usage->toArray(), 'user' => $this->user, ]; } diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php index a17d5aac..7d12dd63 100644 --- a/src/Responses/Responses/CreateResponseUsage.php +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -10,10 +10,12 @@ private function __construct( public readonly int $inputTokens, public readonly ?int $outputTokens, public readonly int $totalTokens, + public readonly CreateResponseUsageInputTokenDetails $inputTokensDetails, + public readonly CreateResponseUsageOutputTokenDetails $outputTokensDetails, ) {} /** - * @param array{input_tokens: int, output_tokens?: int, total_tokens: int} $attributes + * @param array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens?: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int} $attributes */ public static function from(array $attributes): self { @@ -21,17 +23,21 @@ public static function from(array $attributes): self $attributes['input_tokens'], $attributes['output_tokens'] ?? 0, $attributes['total_tokens'], + CreateResponseUsageInputTokenDetails::from($attributes['input_tokens_details']), + CreateResponseUsageOutputTokenDetails::from($attributes['output_tokens_details']), ); } /** - * @return array{input_tokens: int, output_tokens: int, total_tokens: int} + * @return array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int} */ public function toArray(): array { return [ 'input_tokens' => $this->inputTokens, + 'input_tokens_details' => $this->inputTokensDetails->toArray(), 'output_tokens' => $this->outputTokens ?? 0, + 'output_tokens_details' => $this->outputTokensDetails->toArray(), 'total_tokens' => $this->totalTokens, ]; } diff --git a/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php b/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php new file mode 100644 index 00000000..d2294b0e --- /dev/null +++ b/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php @@ -0,0 +1,32 @@ + $this->cachedTokens, + ]; + } +} diff --git a/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php b/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php new file mode 100644 index 00000000..c7a3b69f --- /dev/null +++ b/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php @@ -0,0 +1,32 @@ + $this->reasoningTokens, + ]; + } +} From e9d3d95611ac1d2374987df6209f083402734e24 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 06:23:54 -0400 Subject: [PATCH 25/60] feat: split out error into classes --- src/Resources/Responses.php | 2 +- src/Responses/Responses/CreateResponse.php | 14 ++++---- .../Responses/CreateResponseError.php | 35 +++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseError.php diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 9e53f177..3d49d978 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 81f97e3d..8428bf26 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -36,7 +36,7 @@ private function __construct( public readonly string $object, public readonly int $createdAt, public readonly string $status, - public readonly ?object $error, + public readonly ?CreateResponseError $error, public readonly ?object $incompleteDetails, public readonly ?string $instructions, public readonly ?int $maxOutputTokens, @@ -59,7 +59,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -68,7 +68,7 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['object'], $attributes['created_at'], $attributes['status'], - $attributes['error'], + isset($attributes['error']) ? CreateResponseError::from($attributes['error']) : null, $attributes['incomplete_details'], $attributes['instructions'], $attributes['max_output_tokens'], @@ -92,7 +92,7 @@ public static function from(array $attributes, MetaInformation $meta): self } /** - * @return array{id: string, object: string, created_at: int, status: string, error: object|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} + * @return array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} */ public function toArray(): array { @@ -101,7 +101,7 @@ public function toArray(): array 'object' => $this->object, 'created_at' => $this->createdAt, 'status' => $this->status, - 'error' => $this->error, + 'error' => $this->error?->toArray(), 'incomplete_details' => $this->incompleteDetails, 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, diff --git a/src/Responses/Responses/CreateResponseError.php b/src/Responses/Responses/CreateResponseError.php new file mode 100644 index 00000000..25f21e3f --- /dev/null +++ b/src/Responses/Responses/CreateResponseError.php @@ -0,0 +1,35 @@ + $this->code, + 'message' => $this->message, + ]; + } +} From d06d33b2fd4530dbf6fb7b809667af6d9f036820 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 06:39:54 -0400 Subject: [PATCH 26/60] feat: split out incomplete_details into classes --- src/Resources/Responses.php | 2 +- src/Responses/Responses/CreateResponse.php | 20 +++++++----- .../CreateResponseIncompleteDetails.php | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseIncompleteDetails.php diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 3d49d978..a339c601 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 8428bf26..259db7c1 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -37,7 +37,7 @@ private function __construct( public readonly int $createdAt, public readonly string $status, public readonly ?CreateResponseError $error, - public readonly ?object $incompleteDetails, + public readonly ?CreateResponseIncompleteDetails $incompleteDetails, public readonly ?string $instructions, public readonly ?int $maxOutputTokens, public readonly string $model, @@ -59,7 +59,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -68,8 +68,12 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['object'], $attributes['created_at'], $attributes['status'], - isset($attributes['error']) ? CreateResponseError::from($attributes['error']) : null, - $attributes['incomplete_details'], + isset($attributes['error']) + ? CreateResponseError::from($attributes['error']) + : null, + isset($attributes['incomplete_details']) + ? CreateResponseIncompleteDetails::from($attributes['incomplete_details']) + : null, $attributes['instructions'], $attributes['max_output_tokens'], $attributes['model'], @@ -92,7 +96,7 @@ public static function from(array $attributes, MetaInformation $meta): self } /** - * @return array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: object|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} + * @return array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} */ public function toArray(): array { @@ -102,7 +106,7 @@ public function toArray(): array 'created_at' => $this->createdAt, 'status' => $this->status, 'error' => $this->error?->toArray(), - 'incomplete_details' => $this->incompleteDetails, + 'incomplete_details' => $this->incompleteDetails?->toArray(), 'instructions' => $this->instructions, 'max_output_tokens' => $this->maxOutputTokens, 'metadata' => $this->metadata, diff --git a/src/Responses/Responses/CreateResponseIncompleteDetails.php b/src/Responses/Responses/CreateResponseIncompleteDetails.php new file mode 100644 index 00000000..fac84772 --- /dev/null +++ b/src/Responses/Responses/CreateResponseIncompleteDetails.php @@ -0,0 +1,32 @@ + $this->reason, + ]; + } +} From 5792dea00ed8d808a92774eff7bcc5c28aa5f4db Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 18:10:31 -0400 Subject: [PATCH 27/60] chore(wip): introduction of 'output' typing --- src/Responses/Responses/CreateResponse.php | 25 ++++++++- .../Output/OutputComputerToolCall.php | 24 +++++++++ .../Output/OutputFileSearchToolCall.php | 24 +++++++++ .../Output/OutputFunctionToolCall.php | 24 +++++++++ .../Responses/Output/OutputMessage.php | 36 +++++++++++++ .../Responses/Output/OutputMessageContent.php | 36 +++++++++++++ .../Output/OutputMessageContentOutputText.php | 37 ++++++++++++++ ...putMessageContentOutputTextAnnotations.php | 51 +++++++++++++++++++ ...ntentOutputTextAnnotationsFileCitation.php | 41 +++++++++++++++ ...geContentOutputTextAnnotationsFilePath.php | 41 +++++++++++++++ ...ontentOutputTextAnnotationsUrlCitation.php | 47 +++++++++++++++++ .../Responses/Output/OutputReasoning.php | 24 +++++++++ .../Output/OutputWebSearchToolCall.php | 24 +++++++++ 13 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 src/Responses/Responses/Output/OutputComputerToolCall.php create mode 100644 src/Responses/Responses/Output/OutputFileSearchToolCall.php create mode 100644 src/Responses/Responses/Output/OutputFunctionToolCall.php create mode 100644 src/Responses/Responses/Output/OutputMessage.php create mode 100644 src/Responses/Responses/Output/OutputMessageContent.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputText.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php create mode 100644 src/Responses/Responses/Output/OutputReasoning.php create mode 100644 src/Responses/Responses/Output/OutputWebSearchToolCall.php diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 259db7c1..a337b072 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -9,6 +9,15 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Responses\Meta\MetaInformation; +use OpenAI\Responses\Responses\Output\OutputComputerToolCall; +use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall; +use OpenAI\Responses\Responses\Output\OutputFunctionToolCall; +use OpenAI\Responses\Responses\Output\OutputMessage; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFileCitation as FileCitation; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFilePath as FilePath; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsUrlCitation as UrlCitation; +use OpenAI\Responses\Responses\Output\OutputReasoning; +use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall; use OpenAI\Testing\Responses\Concerns\Fakeable; /** @@ -25,7 +34,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; /** - * @param array}>}> $output + * @param array $output * @param array $reasoning * @param array{format: array{type: string}} $text * @param array $tools @@ -63,6 +72,18 @@ private function __construct( */ public static function from(array $attributes, MetaInformation $meta): self { + $output = array_map( + fn (array $output): FileCitation|FilePath|UrlCitation => match ($output['type']) { + 'message' => OutputMessage::from($output), + 'file_search_call' => OutputFileSearchToolCall::from($output), + 'function_call' => OutputFunctionToolCall::from($output), + 'web_search_call' => OutputWebSearchToolCall::from($output), + 'computer_call' => OutputComputerToolCall::from($output), + 'reasoning' => OutputReasoning::from($output), + }, + $attributes['output'], + ); + return new self( $attributes['id'], $attributes['object'], @@ -77,7 +98,7 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['instructions'], $attributes['max_output_tokens'], $attributes['model'], - $attributes['output'], + $output, $attributes['parallel_tool_calls'], $attributes['previous_response_id'], $attributes['reasoning'], diff --git a/src/Responses/Responses/Output/OutputComputerToolCall.php b/src/Responses/Responses/Output/OutputComputerToolCall.php new file mode 100644 index 00000000..46452680 --- /dev/null +++ b/src/Responses/Responses/Output/OutputComputerToolCall.php @@ -0,0 +1,24 @@ + $outputs + */ + private function __construct( + public readonly array $outputs, + ) {} + + /** + * @param array{reasoning_tokens: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['reasoning_tokens'], + ); + } + + /** + * @return array{reasoning_tokens: int} + */ + public function toArray(): array + { + return array_map( + fn (OutputMessage $output): array => $output->toArray(), + $this->outputs, + ); + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContent.php b/src/Responses/Responses/Output/OutputMessageContent.php new file mode 100644 index 00000000..72fdb5c1 --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContent.php @@ -0,0 +1,36 @@ + $this->reasoningTokens, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputText.php b/src/Responses/Responses/Output/OutputMessageContentOutputText.php new file mode 100644 index 00000000..dc8213ce --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentOutputText.php @@ -0,0 +1,37 @@ + $annotations + */ + private function __construct( + public readonly array $annotations, + public readonly string $text, + public readonly string $type + ) {} + + /** + * @param array{reasoning_tokens: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['reasoning_tokens'], + ); + } + + /** + * @return array{reasoning_tokens: int} + */ + public function toArray(): array + { + return [ + 'reasoning_tokens' => $this->reasoningTokens, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php new file mode 100644 index 00000000..37f787a7 --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php @@ -0,0 +1,51 @@ + $annotations + */ + private function __construct( + public readonly array $annotations, + ) {} + + /** + * @param array{annotations: array} $attributes + */ + public static function from(array $attributes): self + { + $annotations = array_map( + fn (array $annotation): FileCitation|FilePath|UrlCitation => match ($annotation['type']) { + 'file_citation' => FileCitation::from($annotation), + 'file_path' => FilePath::from($annotation), + 'url_citation' => UrlCitation::from($annotation), + }, + $attributes['annotations'], + ); + + return new self( + $annotations, + ); + } + + /** + * @return array{annotations: array} + */ + public function toArray(): array + { + return [ + 'annotations' => array_map( + fn (FileCitation|FilePath|UrlCitation $annotation): array => $annotation->toArray(), + $this->annotations, + ), + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php new file mode 100644 index 00000000..741d4ceb --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php @@ -0,0 +1,41 @@ + $this->fileId, + 'index' => $this->index, + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php new file mode 100644 index 00000000..e246b670 --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php @@ -0,0 +1,41 @@ + $this->fileId, + 'index' => $this->index, + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php new file mode 100644 index 00000000..b806046a --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php @@ -0,0 +1,47 @@ + $this->endIndex, + 'start_index' => $this->startIndex, + 'title' => $this->title, + 'type' => $this->type, + 'url' => $this->url, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputReasoning.php b/src/Responses/Responses/Output/OutputReasoning.php new file mode 100644 index 00000000..a63b886a --- /dev/null +++ b/src/Responses/Responses/Output/OutputReasoning.php @@ -0,0 +1,24 @@ + Date: Tue, 15 Apr 2025 20:11:59 -0400 Subject: [PATCH 28/60] fix: correct OutputMessageContentOutputText + child classes --- .../Output/OutputMessageContentOutputText.php | 46 ++++++++++++++--- ...putMessageContentOutputTextAnnotations.php | 51 ------------------- ...ntentOutputTextAnnotationsFileCitation.php | 18 ++++++- ...geContentOutputTextAnnotationsFilePath.php | 18 ++++++- ...ontentOutputTextAnnotationsUrlCitation.php | 18 ++++++- 5 files changed, 88 insertions(+), 63 deletions(-) delete mode 100644 src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputText.php b/src/Responses/Responses/Output/OutputMessageContentOutputText.php index dc8213ce..c64bf736 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputText.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputText.php @@ -4,10 +4,28 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputMessageContentOutputText +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFileCitation as AnnotationFileCitation; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFilePath as AnnotationFilePath; +use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsUrlCitation as AnnotationUrlCitation; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract, text: string, type: 'output_text'}> + */ +final class OutputMessageContentOutputText implements ResponseContract { /** - * @param array $annotations + * @use ArrayAccessible, text: string, type: 'output_text'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $annotations + * @param 'output_text' $type */ private function __construct( public readonly array $annotations, @@ -16,22 +34,38 @@ private function __construct( ) {} /** - * @param array{reasoning_tokens: int} $attributes + * @param array{annotations: array, text: string, type: 'output_text'} $attributes */ public static function from(array $attributes): self { + $annotations = array_map( + fn (array $annotation): AnnotationFileCitation|AnnotationFilePath|AnnotationUrlCitation => match ($annotation['type']) { + 'file_citation' => AnnotationFileCitation::from($annotation), + 'file_path' => AnnotationFilePath::from($annotation), + 'url_citation' => AnnotationUrlCitation::from($annotation), + }, + $attributes['annotations'], + ); + return new self( - $attributes['reasoning_tokens'], + annotations: $annotations, + text: $attributes['text'], + type: $attributes['type'], ); } /** - * @return array{reasoning_tokens: int} + * {@inheritDoc} */ public function toArray(): array { return [ - 'reasoning_tokens' => $this->reasoningTokens, + 'annotations' => array_map( + fn (AnnotationFileCitation|AnnotationFilePath|AnnotationUrlCitation $annotation): array => $annotation->toArray(), + $this->annotations, + ), + 'text' => $this->text, + 'type' => $this->type, ]; } } diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php deleted file mode 100644 index 37f787a7..00000000 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotations.php +++ /dev/null @@ -1,51 +0,0 @@ - $annotations - */ - private function __construct( - public readonly array $annotations, - ) {} - - /** - * @param array{annotations: array} $attributes - */ - public static function from(array $attributes): self - { - $annotations = array_map( - fn (array $annotation): FileCitation|FilePath|UrlCitation => match ($annotation['type']) { - 'file_citation' => FileCitation::from($annotation), - 'file_path' => FilePath::from($annotation), - 'url_citation' => UrlCitation::from($annotation), - }, - $attributes['annotations'], - ); - - return new self( - $annotations, - ); - } - - /** - * @return array{annotations: array} - */ - public function toArray(): array - { - return [ - 'annotations' => array_map( - fn (FileCitation|FilePath|UrlCitation $annotation): array => $annotation->toArray(), - $this->annotations, - ), - ]; - } -} diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php index 741d4ceb..35638fb8 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputMessageContentOutputTextAnnotationsFileCitation +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class OutputMessageContentOutputTextAnnotationsFileCitation implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + /** * @param 'file_citation' $type */ @@ -28,7 +42,7 @@ public static function from(array $attributes): self } /** - * @return array{file_id: string, index: int, type: 'file_citation'} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php index e246b670..abd17966 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputMessageContentOutputTextAnnotationsFilePath +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class OutputMessageContentOutputTextAnnotationsFilePath implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + /** * @param 'file_path' $type */ @@ -28,7 +42,7 @@ public static function from(array $attributes): self } /** - * @return array{file_id: string, index: int, type: 'file_path'} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php index b806046a..84cb2e84 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputMessageContentOutputTextAnnotationsUrlCitation +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class OutputMessageContentOutputTextAnnotationsUrlCitation implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + /** * @param 'url_citation' $type */ @@ -32,7 +46,7 @@ public static function from(array $attributes): self } /** - * @return array{end_index: int, start_index: int, title: string, type: 'url_citation', url: string} + * {@inheritDoc} */ public function toArray(): array { From d8ce1a6379471f9d5ca33fb230112ff24630baab Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 20:24:17 -0400 Subject: [PATCH 29/60] OutputMessage --- .../Responses/Output/OutputMessage.php | 58 +++++++++++++++---- .../Responses/Output/OutputMessageContent.php | 36 ------------ .../Output/OutputMessageContentRefusal.php | 52 +++++++++++++++++ 3 files changed, 100 insertions(+), 46 deletions(-) delete mode 100644 src/Responses/Responses/Output/OutputMessageContent.php create mode 100644 src/Responses/Responses/Output/OutputMessageContentRefusal.php diff --git a/src/Responses/Responses/Output/OutputMessage.php b/src/Responses/Responses/Output/OutputMessage.php index 15cc454e..b91a7845 100644 --- a/src/Responses/Responses/Output/OutputMessage.php +++ b/src/Responses/Responses/Output/OutputMessage.php @@ -4,33 +4,71 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputMessage +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}> + */ +final class OutputMessage implements ResponseContract { /** - * @param array $outputs + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $content + * @param 'in_progress'|'completed'|'incomplete' $status + * @param 'message' $type */ private function __construct( - public readonly array $outputs, + public readonly array $content, + public readonly string $id, + public readonly string $role, + public readonly string $status, + public readonly string $type, ) {} /** - * @param array{reasoning_tokens: int} $attributes + * @param array{content: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'} $attributes */ public static function from(array $attributes): self { + $content = array_map( + fn (array $item): OutputMessageContentOutputText|OutputMessageContentRefusal => match ($item['type']) { + 'output_text' => OutputMessageContentOutputText::from($item), + 'refusal' => OutputMessageContentRefusal::from($item), + }, + $attributes['content'], + ); + return new self( - $attributes['reasoning_tokens'], + $content, + $attributes['id'], + $attributes['role'], + $attributes['status'], + $attributes['type'], ); } /** - * @return array{reasoning_tokens: int} + * {@inheritDoc} */ public function toArray(): array { - return array_map( - fn (OutputMessage $output): array => $output->toArray(), - $this->outputs, - ); + return [ + 'content' => array_map( + fn (OutputMessageContentOutputText|OutputMessageContentRefusal $item): array => $item->toArray(), + $this->content, + ), + 'id' => $this->id, + 'role' => $this->role, + 'status' => $this->status, + 'type' => $this->type, + ]; } } diff --git a/src/Responses/Responses/Output/OutputMessageContent.php b/src/Responses/Responses/Output/OutputMessageContent.php deleted file mode 100644 index 72fdb5c1..00000000 --- a/src/Responses/Responses/Output/OutputMessageContent.php +++ /dev/null @@ -1,36 +0,0 @@ - $this->reasoningTokens, - ]; - } -} diff --git a/src/Responses/Responses/Output/OutputMessageContentRefusal.php b/src/Responses/Responses/Output/OutputMessageContentRefusal.php new file mode 100644 index 00000000..ffd64c43 --- /dev/null +++ b/src/Responses/Responses/Output/OutputMessageContentRefusal.php @@ -0,0 +1,52 @@ + + */ +final class OutputMessageContentRefusal implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'refusal' $type + */ + private function __construct( + public readonly string $refusal, + public readonly string $type, + ) {} + + /** + * @param array{refusal: string, type: 'refusal'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['refusal'], + $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'refusal' => $this->refusal, + 'type' => $this->type, + ]; + } +} From b9db0a1443b6f7ea4263cebb3900f294fe155e9f Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 20:40:54 -0400 Subject: [PATCH 30/60] chore: continued work on output message --- .../Output/OutputFileSearchToolCall.php | 58 ++++++++++++++++-- .../Output/OutputFileSearchToolCallResult.php | 61 +++++++++++++++++++ .../Responses/Output/OutputMessage.php | 16 ++--- .../Output/OutputMessageContentOutputText.php | 2 +- ...ntentOutputTextAnnotationsFileCitation.php | 6 +- ...geContentOutputTextAnnotationsFilePath.php | 6 +- ...ontentOutputTextAnnotationsUrlCitation.php | 10 +-- 7 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 src/Responses/Responses/Output/OutputFileSearchToolCallResult.php diff --git a/src/Responses/Responses/Output/OutputFileSearchToolCall.php b/src/Responses/Responses/Output/OutputFileSearchToolCall.php index 4b9799e8..c3ec6dee 100644 --- a/src/Responses/Responses/Output/OutputFileSearchToolCall.php +++ b/src/Responses/Responses/Output/OutputFileSearchToolCall.php @@ -4,21 +4,71 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputFileSearchToolCall +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}}> + */ +final class OutputFileSearchToolCall implements ResponseContract { - private function __construct( + /** + * @use ArrayAccessible, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}}> + */ + use ArrayAccessible; + + use Fakeable; + /** + * @param array $queries + * @param 'in_progress'|'searching'|'incomplete'|'failed' $status + * @param 'file_search_call' $type + * @param ?array $results + */ + private function __construct( + public readonly string $id, + public readonly array $queries, + public readonly string $status, + public readonly string $type, + public readonly ?array $results = null, ) {} + /** + * @param array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}} $attributes + */ public static function from(array $attributes): self { - return new self; + $results = isset($attributes['results']) + ? array_map( + fn (array $result): OutputFileSearchToolCallResult => OutputFileSearchToolCallResult::from($result), + $attributes['results'] + ) + : null; + + return new self( + id: $attributes['id'], + queries: $attributes['queries'], + status: $attributes['status'], + type: $attributes['type'], + results: $results, + ); } + /** + * {@inheritDoc} + */ public function toArray(): array { return [ - + 'id' => $this->id, + 'queries' => $this->queries, + 'status' => $this->status, + 'type' => $this->type, + 'results' => isset($this->results) ? array_map( + fn (OutputFileSearchToolCallResult $result) => $result->toArray(), + $this->results + ) : null, ]; } } diff --git a/src/Responses/Responses/Output/OutputFileSearchToolCallResult.php b/src/Responses/Responses/Output/OutputFileSearchToolCallResult.php new file mode 100644 index 00000000..8fcf5892 --- /dev/null +++ b/src/Responses/Responses/Output/OutputFileSearchToolCallResult.php @@ -0,0 +1,61 @@ +, file_id: string, filename: string, score: float, text: string}> + */ +final class OutputFileSearchToolCallResult implements ResponseContract +{ + /** + * @use ArrayAccessible, file_id: string, filename: string, score: float, text: string}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $attributes + */ + private function __construct( + public readonly array $attributes, + public readonly string $fileId, + public readonly string $filename, + public readonly float $score, + public readonly string $text, + ) {} + + /** + * @param array{attributes: array, file_id: string, filename: string, score: float, text: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + attributes: $attributes['attributes'], + fileId: $attributes['file_id'], + filename: $attributes['filename'], + score: $attributes['score'], + text: $attributes['text'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'attributes' => $this->attributes, + 'file_id' => $this->fileId, + 'filename' => $this->filename, + 'score' => $this->score, + 'text' => $this->text, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputMessage.php b/src/Responses/Responses/Output/OutputMessage.php index b91a7845..5004f530 100644 --- a/src/Responses/Responses/Output/OutputMessage.php +++ b/src/Responses/Responses/Output/OutputMessage.php @@ -21,9 +21,9 @@ final class OutputMessage implements ResponseContract use Fakeable; /** - * @param array $content - * @param 'in_progress'|'completed'|'incomplete' $status - * @param 'message' $type + * @param array $content + * @param 'in_progress'|'completed'|'incomplete' $status + * @param 'message' $type */ private function __construct( public readonly array $content, @@ -47,11 +47,11 @@ public static function from(array $attributes): self ); return new self( - $content, - $attributes['id'], - $attributes['role'], - $attributes['status'], - $attributes['type'], + content: $content, + id: $attributes['id'], + role: $attributes['role'], + status: $attributes['status'], + type: $attributes['type'], ); } diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputText.php b/src/Responses/Responses/Output/OutputMessageContentOutputText.php index c64bf736..2af601cc 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputText.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputText.php @@ -25,7 +25,7 @@ final class OutputMessageContentOutputText implements ResponseContract /** * @param array $annotations - * @param 'output_text' $type + * @param 'output_text' $type */ private function __construct( public readonly array $annotations, diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php index 35638fb8..71c7e877 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFileCitation.php @@ -35,9 +35,9 @@ private function __construct( public static function from(array $attributes): self { return new self( - $attributes['file_id'], - $attributes['index'], - $attributes['type'], + fileId: $attributes['file_id'], + index: $attributes['index'], + type: $attributes['type'], ); } diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php index abd17966..e0b1d3e0 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsFilePath.php @@ -35,9 +35,9 @@ private function __construct( public static function from(array $attributes): self { return new self( - $attributes['file_id'], - $attributes['index'], - $attributes['type'], + fileId: $attributes['file_id'], + index: $attributes['index'], + type: $attributes['type'], ); } diff --git a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php index 84cb2e84..e2de42cc 100644 --- a/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php +++ b/src/Responses/Responses/Output/OutputMessageContentOutputTextAnnotationsUrlCitation.php @@ -37,11 +37,11 @@ private function __construct( public static function from(array $attributes): self { return new self( - $attributes['end_index'], - $attributes['start_index'], - $attributes['title'], - $attributes['type'], - $attributes['url'], + endIndex: $attributes['end_index'], + startIndex: $attributes['start_index'], + title: $attributes['title'], + type: $attributes['type'], + url: $attributes['url'], ); } From 26990e21509b8a820197794230592e76a351474b Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 15 Apr 2025 20:57:17 -0400 Subject: [PATCH 31/60] chore: start of ComputerToolActions --- .../OutputComputerActionClick.php | 59 +++++++++++++++++++ .../Output/OutputFileSearchToolCall.php | 6 +- .../Output/OutputFunctionToolCall.php | 49 +++++++++++++-- .../Output/OutputWebSearchToolCall.php | 39 ++++++++++-- 4 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php new file mode 100644 index 00000000..4aa04083 --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php @@ -0,0 +1,59 @@ + + */ +final class OutputComputerActionClick implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'left'|'right'|'wheel'|'back'|'forward' $button + * @param 'click' $type + */ + private function __construct( + public readonly string $button, + public readonly string $type, + public readonly float $x, + public readonly float $y, + ) {} + + /** + * @param array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float} $attributes + */ + public static function from(array $attributes): self + { + return new self( + button: $attributes['button'], + type: $attributes['type'], + x: $attributes['x'], + y: $attributes['y'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'button' => $this->button, + 'type' => $this->type, + 'x' => $this->x, + 'y' => $this->y, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputFileSearchToolCall.php b/src/Responses/Responses/Output/OutputFileSearchToolCall.php index c3ec6dee..ac36e699 100644 --- a/src/Responses/Responses/Output/OutputFileSearchToolCall.php +++ b/src/Responses/Responses/Output/OutputFileSearchToolCall.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}}> + * @implements ResponseContract, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}> */ final class OutputFileSearchToolCall implements ResponseContract { /** - * @use ArrayAccessible, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}}> + * @use ArrayAccessible, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}> */ use ArrayAccessible; @@ -35,7 +35,7 @@ private function __construct( ) {} /** - * @param array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array{attributes: array, file_id: string, filename: string, score: float, text: string}} $attributes + * @param array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>} $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/Output/OutputFunctionToolCall.php b/src/Responses/Responses/Output/OutputFunctionToolCall.php index a83ccf01..7835a82a 100644 --- a/src/Responses/Responses/Output/OutputFunctionToolCall.php +++ b/src/Responses/Responses/Output/OutputFunctionToolCall.php @@ -4,21 +4,62 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputFunctionToolCall +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class OutputFunctionToolCall implements ResponseContract { - private function __construct( + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + /** + * @param 'function_call' $type + * @param 'in_progress'|'completed'|'incomplete' $status + */ + private function __construct( + public readonly string $arguments, + public readonly string $callId, + public readonly string $name, + public readonly string $type, + public readonly string $id, + public readonly string $status, ) {} + /** + * @param array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'} $attributes + */ public static function from(array $attributes): self { - return new self; + return new self( + arguments: $attributes['arguments'], + callId: $attributes['call_id'], + name: $attributes['name'], + type: $attributes['type'], + id: $attributes['id'], + status: $attributes['status'], + ); } + /** + * {@inheritDoc} + */ public function toArray(): array { return [ - + 'arguments' => $this->arguments, + 'call_id' => $this->callId, + 'name' => $this->name, + 'type' => $this->type, + 'id' => $this->id, + 'status' => $this->status, ]; } } diff --git a/src/Responses/Responses/Output/OutputWebSearchToolCall.php b/src/Responses/Responses/Output/OutputWebSearchToolCall.php index fcadb794..6097031a 100644 --- a/src/Responses/Responses/Output/OutputWebSearchToolCall.php +++ b/src/Responses/Responses/Output/OutputWebSearchToolCall.php @@ -4,21 +4,52 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputWebSearchToolCall +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class OutputWebSearchToolCall implements ResponseContract { - private function __construct( + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + /** + * @param 'web_search_call' $type + */ + private function __construct( + public readonly string $id, + public readonly string $status, + public readonly string $type, ) {} + /** + * @param array{id: string, status: string, type: 'web_search_call'} $attributes + */ public static function from(array $attributes): self { - return new self; + return new self( + id: $attributes['id'], + status: $attributes['status'], + type: $attributes['type'], + ); } + /** + * {@inheritDoc} + */ public function toArray(): array { return [ - + 'id' => $this->id, + 'status' => $this->status, + 'type' => $this->type, ]; } } From 4dda791ae0e7ef0827713d456f5eb32893d9f78b Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 06:50:24 -0400 Subject: [PATCH 32/60] feat: complete "computer tool call" --- .../OutputComputerActionDoubleClick.php | 55 +++++++++++++ .../OutputComputerActionDrag.php | 61 ++++++++++++++ .../OutputComputerActionKeyPress.php | 53 ++++++++++++ .../OutputComputerActionMove.php | 55 +++++++++++++ .../OutputComputerActionScreenshot.php | 49 ++++++++++++ .../OutputComputerActionScroll.php | 61 ++++++++++++++ .../OutputComputerActionType.php | 52 ++++++++++++ .../OutputComputerActionWait.php | 49 ++++++++++++ .../ComputerAction/OutputComputerDragPath.php | 49 ++++++++++++ .../OutputComputerPendingSafetyCheck.php | 52 ++++++++++++ .../Output/OutputComputerToolCall.php | 80 ++++++++++++++++++- .../Output/OutputMessageContentRefusal.php | 4 +- 12 files changed, 614 insertions(+), 6 deletions(-) create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionDoubleClick.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionDrag.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionKeyPress.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionMove.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionScreenshot.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionScroll.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionType.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerActionWait.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerDragPath.php create mode 100644 src/Responses/Responses/Output/ComputerAction/OutputComputerPendingSafetyCheck.php diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDoubleClick.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDoubleClick.php new file mode 100644 index 00000000..451ecf31 --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDoubleClick.php @@ -0,0 +1,55 @@ + + */ +final class OutputComputerActionDoubleClick implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'double_click' $type + */ + private function __construct( + public readonly string $type, + public readonly float $x, + public readonly float $y, + ) {} + + /** + * @param array{type: 'double_click', x: float, y: float} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + x: $attributes['x'], + y: $attributes['y'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'x' => $this->x, + 'y' => $this->y, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDrag.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDrag.php new file mode 100644 index 00000000..3fcbdf6e --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionDrag.php @@ -0,0 +1,61 @@ +, type: 'drag'}> + */ +final class OutputComputerActionDrag implements ResponseContract +{ + /** + * @use ArrayAccessible, type: 'drag'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $path + * @param 'drag' $type + */ + private function __construct( + public readonly array $path, + public readonly string $type, + ) {} + + /** + * @param array{path: array, type: 'drag'} $attributes + */ + public static function from(array $attributes): self + { + $paths = array_map( + static fn (array $path): OutputComputerDragPath => OutputComputerDragPath::from($path), + $attributes['path'], + ); + + return new self( + path: $paths, + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'path' => array_map( + static fn (OutputComputerDragPath $path): array => $path->toArray(), + $this->path, + ), + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionKeyPress.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionKeyPress.php new file mode 100644 index 00000000..1b16722e --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionKeyPress.php @@ -0,0 +1,53 @@ +, type: 'keypress'}> + */ +final class OutputComputerActionKeyPress implements ResponseContract +{ + /** + * @use ArrayAccessible, type: 'keypress'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $keys + * @param 'keypress' $type + */ + private function __construct( + public readonly array $keys, + public readonly string $type, + ) {} + + /** + * @param array{keys: array, type: 'keypress'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + keys: $attributes['keys'], + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'keys' => $this->keys, + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionMove.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionMove.php new file mode 100644 index 00000000..4e9612ae --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionMove.php @@ -0,0 +1,55 @@ + + */ +final class OutputComputerActionMove implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'move' $type + */ + private function __construct( + public readonly string $type, + public readonly int $x, + public readonly int $y, + ) {} + + /** + * @param array{type: 'move', x: int, y: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + x: $attributes['x'], + y: $attributes['y'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'x' => $this->x, + 'y' => $this->y, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScreenshot.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScreenshot.php new file mode 100644 index 00000000..ee1db8b9 --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScreenshot.php @@ -0,0 +1,49 @@ + + */ +final class OutputComputerActionScreenshot implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'screenshot' $type + */ + private function __construct( + public readonly string $type, + ) {} + + /** + * @param array{type: 'screenshot'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScroll.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScroll.php new file mode 100644 index 00000000..11599c7f --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionScroll.php @@ -0,0 +1,61 @@ + + */ +final class OutputComputerActionScroll implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'scroll' $type + */ + private function __construct( + public readonly int $scrollX, + public readonly int $scrollY, + public readonly string $type, + public readonly int $x, + public readonly int $y, + ) {} + + /** + * @param array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + scrollX: $attributes['scroll_x'], + scrollY: $attributes['scroll_y'], + type: $attributes['type'], + x: $attributes['x'], + y: $attributes['y'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'scroll_x' => $this->scrollX, + 'scroll_y' => $this->scrollY, + 'type' => $this->type, + 'x' => $this->x, + 'y' => $this->y, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionType.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionType.php new file mode 100644 index 00000000..c9042f0b --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionType.php @@ -0,0 +1,52 @@ + + */ +final class OutputComputerActionType implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'type' $type + */ + private function __construct( + public readonly string $text, + public readonly string $type, + ) {} + + /** + * @param array{text: string, type: 'type'} $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, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionWait.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionWait.php new file mode 100644 index 00000000..43556eb4 --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionWait.php @@ -0,0 +1,49 @@ + + */ +final class OutputComputerActionWait implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'wait' $type + */ + private function __construct( + public readonly string $type, + ) {} + + /** + * @param array{type: 'wait'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerDragPath.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerDragPath.php new file mode 100644 index 00000000..7e0e33ab --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerDragPath.php @@ -0,0 +1,49 @@ + + */ +final class OutputComputerDragPath implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly int $x, + public readonly int $y, + ) {} + + /** + * @param array{x: int, y: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + x: $attributes['x'], + y: $attributes['y'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'x' => $this->x, + 'y' => $this->y, + ]; + } +} diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerPendingSafetyCheck.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerPendingSafetyCheck.php new file mode 100644 index 00000000..3e9289d8 --- /dev/null +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerPendingSafetyCheck.php @@ -0,0 +1,52 @@ + + */ +final class OutputComputerPendingSafetyCheck implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly string $code, + public readonly string $id, + public readonly string $message, + ) {} + + /** + * @param array{code: string, id: string, message: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + code: $attributes['code'], + id: $attributes['id'], + message: $attributes['message'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'code' => $this->code, + 'id' => $this->id, + 'message' => $this->message, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputComputerToolCall.php b/src/Responses/Responses/Output/OutputComputerToolCall.php index 46452680..b2483091 100644 --- a/src/Responses/Responses/Output/OutputComputerToolCall.php +++ b/src/Responses/Responses/Output/OutputComputerToolCall.php @@ -4,21 +4,93 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputComputerToolCall +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionClick as Click; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionDoubleClick as DoubleClick; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionDrag as Drag; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionKeyPress as KeyPress; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionMove as Move; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionScreenshot as Screenshot; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionScroll as Scroll; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionType as Type; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerActionWait as Wait; +use OpenAI\Responses\Responses\Output\ComputerAction\OutputComputerPendingSafetyCheck; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> + */ +final class OutputComputerToolCall implements ResponseContract { - private function __construct( + /** + * @use ArrayAccessible, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> + */ + use ArrayAccessible; + use Fakeable; + + /** + * @param array $pendingSafetyChecks + * @param 'in_progress'|'completed'|'incomplete' $status + * @param 'computer_call' $type + */ + private function __construct( + public readonly Click|DoubleClick|Drag|KeyPress|Move|Screenshot|Scroll|Type|Wait $action, + public readonly string $callId, + public readonly string $id, + public readonly array $pendingSafetyChecks, + public readonly string $status, + public readonly string $type, ) {} + /** + * @param array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'} $attributes + */ public static function from(array $attributes): self { - return new self; + $action = match ($attributes['action']['type']) { + 'click' => Click::from($attributes['action']), + 'double_click' => DoubleClick::from($attributes['action']), + 'drag' => Drag::from($attributes['action']), + 'keypress' => KeyPress::from($attributes['action']), + 'move' => Move::from($attributes['action']), + 'screenshot' => Screenshot::from($attributes['action']), + 'scroll' => Scroll::from($attributes['action']), + 'type' => Type::from($attributes['action']), + 'wait' => Wait::from($attributes['action']), + }; + + $pendingSafetyChecks = array_map( + fn (array $safetyCheck): OutputComputerPendingSafetyCheck => OutputComputerPendingSafetyCheck::from($safetyCheck), + $attributes['pending_safety_checks'] + ); + + return new self( + action: $action, + callId: $attributes['call_id'], + id: $attributes['id'], + pendingSafetyChecks: $pendingSafetyChecks, + status: $attributes['status'], + type: $attributes['type'], + ); } + /** + * {@inheritDoc} + */ public function toArray(): array { return [ - + 'action' => $this->action->toArray(), + 'call_id' => $this->callId, + 'id' => $this->id, + 'pending_safety_checks' => array_map( + fn (OutputComputerPendingSafetyCheck $safetyCheck): array => $safetyCheck->toArray(), + $this->pendingSafetyChecks, + ), + 'status' => $this->status, + 'type' => $this->type, ]; } } diff --git a/src/Responses/Responses/Output/OutputMessageContentRefusal.php b/src/Responses/Responses/Output/OutputMessageContentRefusal.php index ffd64c43..cfe1127f 100644 --- a/src/Responses/Responses/Output/OutputMessageContentRefusal.php +++ b/src/Responses/Responses/Output/OutputMessageContentRefusal.php @@ -34,8 +34,8 @@ private function __construct( public static function from(array $attributes): self { return new self( - $attributes['refusal'], - $attributes['type'], + refusal: $attributes['refusal'], + type: $attributes['type'], ); } From c9c5a561bc0024e772e8906a0c4f833f4e89de39 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 14:36:40 -0400 Subject: [PATCH 33/60] fix: add response contract to existing classes --- .../Responses/CreateResponseError.php | 18 +++++++++-- .../CreateResponseIncompleteDetails.php | 18 +++++++++-- .../Responses/CreateResponseUsage.php | 32 +++++++++++++------ .../CreateResponseUsageInputTokenDetails.php | 18 +++++++++-- .../CreateResponseUsageOutputTokenDetails.php | 18 +++++++++-- 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/Responses/Responses/CreateResponseError.php b/src/Responses/Responses/CreateResponseError.php index 25f21e3f..21106770 100644 --- a/src/Responses/Responses/CreateResponseError.php +++ b/src/Responses/Responses/CreateResponseError.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses; -final class CreateResponseError +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class CreateResponseError implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + private function __construct( public readonly string $code, public readonly string $message @@ -23,7 +37,7 @@ public static function from(array $attributes): self } /** - * @return array{code: string, message: string} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/CreateResponseIncompleteDetails.php b/src/Responses/Responses/CreateResponseIncompleteDetails.php index fac84772..1f7caaf9 100644 --- a/src/Responses/Responses/CreateResponseIncompleteDetails.php +++ b/src/Responses/Responses/CreateResponseIncompleteDetails.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses; -final class CreateResponseIncompleteDetails +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class CreateResponseIncompleteDetails implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + private function __construct( public readonly string $reason, ) {} @@ -21,7 +35,7 @@ public static function from(array $attributes): self } /** - * @return array{reason: string} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php index 7d12dd63..1fc2a648 100644 --- a/src/Responses/Responses/CreateResponseUsage.php +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -4,14 +4,28 @@ namespace OpenAI\Responses\Responses; -final class CreateResponseUsage +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class CreateResponseUsage implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + private function __construct( public readonly int $inputTokens, - public readonly ?int $outputTokens, - public readonly int $totalTokens, public readonly CreateResponseUsageInputTokenDetails $inputTokensDetails, + public readonly ?int $outputTokens, public readonly CreateResponseUsageOutputTokenDetails $outputTokensDetails, + public readonly int $totalTokens, ) {} /** @@ -20,16 +34,16 @@ private function __construct( public static function from(array $attributes): self { return new self( - $attributes['input_tokens'], - $attributes['output_tokens'] ?? 0, - $attributes['total_tokens'], - CreateResponseUsageInputTokenDetails::from($attributes['input_tokens_details']), - CreateResponseUsageOutputTokenDetails::from($attributes['output_tokens_details']), + inputTokens: $attributes['input_tokens'], + inputTokensDetails: CreateResponseUsageInputTokenDetails::from($attributes['input_tokens_details']), + outputTokens: $attributes['output_tokens'] ?? null, + outputTokensDetails: CreateResponseUsageOutputTokenDetails::from($attributes['output_tokens_details']), + totalTokens: $attributes['total_tokens'], ); } /** - * @return array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php b/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php index d2294b0e..1a05b963 100644 --- a/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php +++ b/src/Responses/Responses/CreateResponseUsageInputTokenDetails.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses; -final class CreateResponseUsageInputTokenDetails +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class CreateResponseUsageInputTokenDetails implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + private function __construct( public readonly int $cachedTokens, ) {} @@ -21,7 +35,7 @@ public static function from(array $attributes): self } /** - * @return array{cached_tokens: int} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php b/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php index c7a3b69f..b463d8ec 100644 --- a/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php +++ b/src/Responses/Responses/CreateResponseUsageOutputTokenDetails.php @@ -4,8 +4,22 @@ namespace OpenAI\Responses\Responses; -final class CreateResponseUsageOutputTokenDetails +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract + */ +final class CreateResponseUsageOutputTokenDetails implements ResponseContract { + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + private function __construct( public readonly int $reasoningTokens, ) {} @@ -21,7 +35,7 @@ public static function from(array $attributes): self } /** - * @return array{reasoning_tokens: int} + * {@inheritDoc} */ public function toArray(): array { From 501d61f539e8f95136130d808daa6eac52afc475 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 16:38:37 -0400 Subject: [PATCH 34/60] fix: more fixes to typing on CreateResponse --- src/Responses/Responses/CreateResponse.php | 88 +++++++++---------- .../Responses/Output/OutputReasoning.php | 52 ++++++++++- .../Output/OutputReasoningSummary.php | 52 +++++++++++ 3 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 src/Responses/Responses/Output/OutputReasoningSummary.php diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index a337b072..96236b9d 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -9,24 +9,21 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Responses\Meta\MetaInformation; -use OpenAI\Responses\Responses\Output\OutputComputerToolCall; -use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall; -use OpenAI\Responses\Responses\Output\OutputFunctionToolCall; -use OpenAI\Responses\Responses\Output\OutputMessage; -use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFileCitation as FileCitation; -use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsFilePath as FilePath; -use OpenAI\Responses\Responses\Output\OutputMessageContentOutputTextAnnotationsUrlCitation as UrlCitation; -use OpenAI\Responses\Responses\Output\OutputReasoning; -use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall; +use OpenAI\Responses\Responses\Output\OutputComputerToolCall as ComputerToolCall; +use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall as FileSearchToolCall; +use OpenAI\Responses\Responses\Output\OutputFunctionToolCall as FunctionToolCall; +use OpenAI\Responses\Responses\Output\OutputMessage as MessageCall; +use OpenAI\Responses\Responses\Output\OutputReasoning as ReasoningCall; +use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall as WebSearchToolCall; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -34,7 +31,8 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; /** - * @param array $output + * @param 'completed'|'failed'|'in_progress'|'incomplete' $status + * @param array $output * @param array $reasoning * @param array{format: array{type: string}} $text * @param array $tools @@ -68,56 +66,56 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { $output = array_map( - fn (array $output): FileCitation|FilePath|UrlCitation => match ($output['type']) { - 'message' => OutputMessage::from($output), - 'file_search_call' => OutputFileSearchToolCall::from($output), - 'function_call' => OutputFunctionToolCall::from($output), - 'web_search_call' => OutputWebSearchToolCall::from($output), - 'computer_call' => OutputComputerToolCall::from($output), - 'reasoning' => OutputReasoning::from($output), + fn (array $output): MessageCall|ComputerToolCall|FileSearchToolCall|WebSearchToolCall|FunctionToolCall|ReasoningCall => match ($output['type']) { + 'message' => MessageCall::from($output), + 'file_search_call' => FileSearchToolCall::from($output), + 'function_call' => FunctionToolCall::from($output), + 'web_search_call' => WebSearchToolCall::from($output), + 'computer_call' => ComputerToolCall::from($output), + 'reasoning' => ReasoningCall::from($output), }, $attributes['output'], ); return new self( - $attributes['id'], - $attributes['object'], - $attributes['created_at'], - $attributes['status'], - isset($attributes['error']) + id: $attributes['id'], + object: $attributes['object'], + createdAt: $attributes['created_at'], + status: $attributes['status'], + error: isset($attributes['error']) ? CreateResponseError::from($attributes['error']) : null, - isset($attributes['incomplete_details']) + incompleteDetails: isset($attributes['incomplete_details']) ? CreateResponseIncompleteDetails::from($attributes['incomplete_details']) : null, - $attributes['instructions'], - $attributes['max_output_tokens'], - $attributes['model'], - $output, - $attributes['parallel_tool_calls'], - $attributes['previous_response_id'], - $attributes['reasoning'], - $attributes['store'], - $attributes['temperature'], - $attributes['text'], - $attributes['tool_choice'], - $attributes['tools'], - $attributes['top_p'], - $attributes['truncation'], - CreateResponseUsage::from($attributes['usage']), - $attributes['user'] ?? null, - $attributes['metadata'] ?? [], - $meta, + instructions: $attributes['instructions'], + maxOutputTokens: $attributes['max_output_tokens'], + model: $attributes['model'], + output: $output, + parallelToolCalls: $attributes['parallel_tool_calls'], + previousResponseId: $attributes['previous_response_id'], + reasoning: $attributes['reasoning'], + store: $attributes['store'], + temperature: $attributes['temperature'], + text: $attributes['text'], + toolChoice: $attributes['tool_choice'], + tools: $attributes['tools'], + topP: $attributes['top_p'], + truncation: $attributes['truncation'], + usage: CreateResponseUsage::from($attributes['usage']), + user: $attributes['user'] ?? null, + metadata: $attributes['metadata'] ?? [], + meta: $meta, ); } /** - * @return array{id: string, object: string, created_at: int, status: string, error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} + * {@inheritDoc} */ public function toArray(): array { diff --git a/src/Responses/Responses/Output/OutputReasoning.php b/src/Responses/Responses/Output/OutputReasoning.php index a63b886a..6af48ce3 100644 --- a/src/Responses/Responses/Output/OutputReasoning.php +++ b/src/Responses/Responses/Output/OutputReasoning.php @@ -4,21 +4,65 @@ namespace OpenAI\Responses\Responses\Output; -final class OutputReasoning +use OpenAI\Contracts\ResponseContract; +use OpenAI\Responses\Concerns\ArrayAccessible; +use OpenAI\Testing\Responses\Concerns\Fakeable; + +/** + * @implements ResponseContract, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}> + */ +final class OutputReasoning implements ResponseContract { - private function __construct( + /** + * @use ArrayAccessible, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}> + */ + use ArrayAccessible; + + use Fakeable; + /** + * @param array $summary + * @param 'reasoning' $type + * @param 'in_progress'|'completed'|'incomplete' $status + */ + private function __construct( + public readonly string $id, + public readonly array $summary, + public readonly string $type, + public readonly string $status, ) {} + /** + * @param array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'} $attributes + */ public static function from(array $attributes): self { - return new self; + $summary = array_map( + static fn (array $summary): OutputReasoningSummary => OutputReasoningSummary::from($summary), + $attributes['summary'], + ); + + return new self( + id: $attributes['id'], + summary: $summary, + type: $attributes['type'], + status: $attributes['status'], + ); } + /** + * {@inheritDoc} + */ public function toArray(): array { return [ - + 'id' => $this->id, + 'summary' => array_map( + static fn (OutputReasoningSummary $summary): array => $summary->toArray(), + $this->summary, + ), + 'type' => $this->type, + 'status' => $this->status, ]; } } diff --git a/src/Responses/Responses/Output/OutputReasoningSummary.php b/src/Responses/Responses/Output/OutputReasoningSummary.php new file mode 100644 index 00000000..ad4a5da7 --- /dev/null +++ b/src/Responses/Responses/Output/OutputReasoningSummary.php @@ -0,0 +1,52 @@ + + */ +final class OutputReasoningSummary implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'summary_text' $type + */ + private function __construct( + public readonly string $text, + public readonly string $type, + ) {} + + /** + * @param array{text: string, type: 'summary_text'} $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, + ]; + } +} From 314e1893bb25970ae1db847421db871a19b70be4 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 19:40:30 -0400 Subject: [PATCH 35/60] feat: add 'reasoning' prop for response --- src/Responses/Responses/CreateResponse.php | 13 ++--- .../Responses/CreateResponseReasoning.php | 49 +++++++++++++++++++ .../Responses/Output/OutputReasoning.php | 2 +- 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseReasoning.php diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 96236b9d..26ab5e9d 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -18,12 +18,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -33,7 +33,6 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati /** * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output - * @param array $reasoning * @param array{format: array{type: string}} $text * @param array $tools * @param array $metadata @@ -51,7 +50,7 @@ private function __construct( public readonly array $output, public readonly bool $parallelToolCalls, public readonly ?string $previousResponseId, - public readonly array $reasoning, + public readonly ?CreateResponseReasoning $reasoning, public readonly bool $store, public readonly ?float $temperature, public readonly array $text, @@ -66,7 +65,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: array, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -99,7 +98,9 @@ public static function from(array $attributes, MetaInformation $meta): self output: $output, parallelToolCalls: $attributes['parallel_tool_calls'], previousResponseId: $attributes['previous_response_id'], - reasoning: $attributes['reasoning'], + reasoning: isset($attributes['reasoning']) + ? CreateResponseReasoning::from($attributes['reasoning']) + : null, store: $attributes['store'], temperature: $attributes['temperature'], text: $attributes['text'], diff --git a/src/Responses/Responses/CreateResponseReasoning.php b/src/Responses/Responses/CreateResponseReasoning.php new file mode 100644 index 00000000..db6bb4bb --- /dev/null +++ b/src/Responses/Responses/CreateResponseReasoning.php @@ -0,0 +1,49 @@ + + */ +final class CreateResponseReasoning implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly ?string $effort, + public readonly ?string $generate_summary, + ) {} + + /** + * @param array{effort: ?string, generate_summary: ?string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + effort: $attributes['effort'] ?? null, + generate_summary: $attributes['generate_summary'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'effort' => $this->effort, + 'generate_summary' => $this->generate_summary, + ]; + } +} diff --git a/src/Responses/Responses/Output/OutputReasoning.php b/src/Responses/Responses/Output/OutputReasoning.php index 6af48ce3..288b13f8 100644 --- a/src/Responses/Responses/Output/OutputReasoning.php +++ b/src/Responses/Responses/Output/OutputReasoning.php @@ -21,7 +21,7 @@ final class OutputReasoning implements ResponseContract use Fakeable; /** - * @param array $summary + * @param array $summary * @param 'reasoning' $type * @param 'in_progress'|'completed'|'incomplete' $status */ From 63f6c10e8dba8acbced5214a9d6582166e015d1e Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 21:01:04 -0400 Subject: [PATCH 36/60] feat: add 'text' (format) to create --- src/Responses/Responses/CreateResponse.php | 5 +- .../Responses/CreateResponseFormat.php | 55 ++++++++++++++++ .../Responses/Format/JsonObjectFormat.php | 49 +++++++++++++++ .../Responses/Format/JsonSchemaFormat.php | 62 +++++++++++++++++++ src/Responses/Responses/Format/TextFormat.php | 49 +++++++++++++++ 5 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 src/Responses/Responses/CreateResponseFormat.php create mode 100644 src/Responses/Responses/Format/JsonObjectFormat.php create mode 100644 src/Responses/Responses/Format/JsonSchemaFormat.php create mode 100644 src/Responses/Responses/Format/TextFormat.php diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 26ab5e9d..03828a25 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -33,7 +33,6 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati /** * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output - * @param array{format: array{type: string}} $text * @param array $tools * @param array $metadata */ @@ -53,7 +52,7 @@ private function __construct( public readonly ?CreateResponseReasoning $reasoning, public readonly bool $store, public readonly ?float $temperature, - public readonly array $text, + public readonly CreateResponseFormat $text, public readonly string $toolChoice, public readonly array $tools, public readonly ?float $topP, @@ -103,7 +102,7 @@ public static function from(array $attributes, MetaInformation $meta): self : null, store: $attributes['store'], temperature: $attributes['temperature'], - text: $attributes['text'], + text: CreateResponseFormat::from($attributes['text']), toolChoice: $attributes['tool_choice'], tools: $attributes['tools'], topP: $attributes['top_p'], diff --git a/src/Responses/Responses/CreateResponseFormat.php b/src/Responses/Responses/CreateResponseFormat.php new file mode 100644 index 00000000..f35a1f2f --- /dev/null +++ b/src/Responses/Responses/CreateResponseFormat.php @@ -0,0 +1,55 @@ +, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}> + */ +final class CreateResponseFormat implements ResponseContract +{ + /** + * @use ArrayAccessible, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}> + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly TextFormat|JsonSchemaFormat|JsonObjectFormat $format + ) {} + + /** + * @param array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}} $attributes + */ + public static function from(array $attributes): self + { + $format = match ($attributes['format']['type']) { + 'text' => TextFormat::from($attributes['format']), + 'json_schema' => JsonSchemaFormat::from($attributes['format']), + 'json_object' => JsonObjectFormat::from($attributes['format']), + }; + + return new self( + format: $format + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'format' => $this->format->toArray(), + ]; + } +} diff --git a/src/Responses/Responses/Format/JsonObjectFormat.php b/src/Responses/Responses/Format/JsonObjectFormat.php new file mode 100644 index 00000000..d66aa73a --- /dev/null +++ b/src/Responses/Responses/Format/JsonObjectFormat.php @@ -0,0 +1,49 @@ + + */ +final class JsonObjectFormat implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'json_object' $type + */ + private function __construct( + public readonly string $type, + ) {} + + /** + * @param array{type: 'json_object'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Format/JsonSchemaFormat.php b/src/Responses/Responses/Format/JsonSchemaFormat.php new file mode 100644 index 00000000..f7d3768d --- /dev/null +++ b/src/Responses/Responses/Format/JsonSchemaFormat.php @@ -0,0 +1,62 @@ +, type: 'json_schema', description: string, strict: ?bool}> + */ +final class JsonSchemaFormat implements ResponseContract +{ + /** + * @use ArrayAccessible, type: 'json_schema', description: string, strict: ?bool}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $schema + * @param 'json_schema' $type + */ + private function __construct( + public readonly string $name, + public readonly array $schema, + public readonly string $type, + public readonly string $description, + public readonly ?bool $strict = null, + ) {} + + /** + * @param array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool} $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + schema: $attributes['schema'], + type: $attributes['type'], + description: $attributes['description'], + strict: $attributes['strict'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'schema' => $this->schema, + 'type' => $this->type, + 'description' => $this->description, + 'strict' => $this->strict, + ]; + } +} diff --git a/src/Responses/Responses/Format/TextFormat.php b/src/Responses/Responses/Format/TextFormat.php new file mode 100644 index 00000000..a0caf8bb --- /dev/null +++ b/src/Responses/Responses/Format/TextFormat.php @@ -0,0 +1,49 @@ + + */ +final class TextFormat implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'text' $type + */ + private function __construct( + public readonly string $type, + ) {} + + /** + * @param array{type: 'text'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + ]; + } +} From 788752b0813b917f68b02c5ee74e4e67a831e601 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 21:12:55 -0400 Subject: [PATCH 37/60] feat: add 'tool_choice' --- src/Responses/Responses/CreateResponse.php | 19 +++++-- .../Responses/ToolChoice/FunctionTool.php | 52 +++++++++++++++++++ .../Responses/ToolChoice/HostedTool.php | 49 +++++++++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/Responses/Responses/ToolChoice/FunctionTool.php create mode 100644 src/Responses/Responses/ToolChoice/HostedTool.php diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 03828a25..7912bcb7 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -15,15 +15,17 @@ use OpenAI\Responses\Responses\Output\OutputMessage as MessageCall; use OpenAI\Responses\Responses\Output\OutputReasoning as ReasoningCall; use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall as WebSearchToolCall; +use OpenAI\Responses\Responses\ToolChoice\FunctionTool; +use OpenAI\Responses\Responses\ToolChoice\HostedTool; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -53,7 +55,7 @@ private function __construct( public readonly bool $store, public readonly ?float $temperature, public readonly CreateResponseFormat $text, - public readonly string $toolChoice, + public readonly string|FunctionTool|HostedTool $toolChoice, public readonly array $tools, public readonly ?float $topP, public readonly string $truncation, @@ -64,7 +66,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -80,6 +82,13 @@ public static function from(array $attributes, MetaInformation $meta): self $attributes['output'], ); + $toolChoice = is_array($attributes['tool_choice']) + ? match ($attributes['tool_choice']['type']) { + 'file_search', 'web_search_preview', 'computer_use_preview' => HostedTool::from($attributes['tool_choice']), + 'function' => FunctionTool::from($attributes['tool_choice']), + } + : $attributes['tool_choice']; + return new self( id: $attributes['id'], object: $attributes['object'], @@ -103,7 +112,7 @@ public static function from(array $attributes, MetaInformation $meta): self store: $attributes['store'], temperature: $attributes['temperature'], text: CreateResponseFormat::from($attributes['text']), - toolChoice: $attributes['tool_choice'], + toolChoice: $toolChoice, tools: $attributes['tools'], topP: $attributes['top_p'], truncation: $attributes['truncation'], diff --git a/src/Responses/Responses/ToolChoice/FunctionTool.php b/src/Responses/Responses/ToolChoice/FunctionTool.php new file mode 100644 index 00000000..5a9e84cb --- /dev/null +++ b/src/Responses/Responses/ToolChoice/FunctionTool.php @@ -0,0 +1,52 @@ + + */ +final class FunctionTool implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'function' $type + */ + private function __construct( + public readonly string $name, + public readonly string $type, + ) {} + + /** + * @param array{name: string, type: 'function'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/ToolChoice/HostedTool.php b/src/Responses/Responses/ToolChoice/HostedTool.php new file mode 100644 index 00000000..7e89dd63 --- /dev/null +++ b/src/Responses/Responses/ToolChoice/HostedTool.php @@ -0,0 +1,49 @@ + + */ +final class HostedTool implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'file_search'|'web_search_preview'|'computer_use_preview' $type + */ + private function __construct( + public readonly string $type, + ) {} + + /** + * @param array{type: 'file_search'|'web_search_preview'|'computer_use_preview'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + ]; + } +} From 8798fbff40ed012d63a267a529ec8fb0d5c00c5d Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 16 Apr 2025 21:15:47 -0400 Subject: [PATCH 38/60] fix: add 'truncation' --- src/Responses/Responses/CreateResponse.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 7912bcb7..b4013c28 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -20,12 +20,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -36,6 +36,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output * @param array $tools + * @param 'auto'|'disabled'|null $truncation * @param array $metadata */ private function __construct( @@ -58,7 +59,7 @@ private function __construct( public readonly string|FunctionTool|HostedTool $toolChoice, public readonly array $tools, public readonly ?float $topP, - public readonly string $truncation, + public readonly ?string $truncation, public readonly CreateResponseUsage $usage, public readonly ?string $user, public readonly array $metadata, @@ -66,7 +67,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { From 5181077f27f22f4b4ef7f245118c3773afac6c97 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Thu, 17 Apr 2025 06:59:01 -0400 Subject: [PATCH 39/60] feat: add tool: FileSearch response --- .../Tool/FileSearchComparisonFilter.php | 55 +++++++++++++++ .../Tool/FileSearchCompoundFilter.php | 61 +++++++++++++++++ .../Tool/FileSearchRankingOption.php | 49 ++++++++++++++ .../Responses/Tool/FileSearchTool.php | 67 +++++++++++++++++++ 4 files changed, 232 insertions(+) create mode 100644 src/Responses/Responses/Tool/FileSearchComparisonFilter.php create mode 100644 src/Responses/Responses/Tool/FileSearchCompoundFilter.php create mode 100644 src/Responses/Responses/Tool/FileSearchRankingOption.php create mode 100644 src/Responses/Responses/Tool/FileSearchTool.php diff --git a/src/Responses/Responses/Tool/FileSearchComparisonFilter.php b/src/Responses/Responses/Tool/FileSearchComparisonFilter.php new file mode 100644 index 00000000..4b74859e --- /dev/null +++ b/src/Responses/Responses/Tool/FileSearchComparisonFilter.php @@ -0,0 +1,55 @@ + + */ +final class FileSearchComparisonFilter implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'eq'|'ne'|'gt'|'gte'|'lt'|'lte' $type + */ + private function __construct( + public readonly string $key, + public readonly string $type, + public readonly string|int|bool $value, + ) {} + + /** + * @param array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool} $attributes + */ + public static function from(array $attributes): self + { + return new self( + key: $attributes['key'], + type: $attributes['type'], + value: $attributes['value'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'key' => $this->key, + 'type' => $this->type, + 'value' => $this->value, + ]; + } +} diff --git a/src/Responses/Responses/Tool/FileSearchCompoundFilter.php b/src/Responses/Responses/Tool/FileSearchCompoundFilter.php new file mode 100644 index 00000000..def65a27 --- /dev/null +++ b/src/Responses/Responses/Tool/FileSearchCompoundFilter.php @@ -0,0 +1,61 @@ +, type: 'and'|'or'}> + */ +final class FileSearchCompoundFilter implements ResponseContract +{ + /** + * @use ArrayAccessible, type: 'and'|'or'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $filters + * @param 'and'|'or' $type + */ + private function __construct( + public readonly array $filters, + public readonly string $type, + ) {} + + /** + * @param array{filters: array, type: 'and'|'or'} $attributes + */ + public static function from(array $attributes): self + { + $filters = array_map( + static fn (array $filter): FileSearchComparisonFilter => FileSearchComparisonFilter::from($filter), + $attributes['filters'], + ); + + return new self( + filters: $filters, + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'filters' => array_map( + static fn (FileSearchComparisonFilter $filter): array => $filter->toArray(), + $this->filters, + ), + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Tool/FileSearchRankingOption.php b/src/Responses/Responses/Tool/FileSearchRankingOption.php new file mode 100644 index 00000000..6f0961f4 --- /dev/null +++ b/src/Responses/Responses/Tool/FileSearchRankingOption.php @@ -0,0 +1,49 @@ + + */ +final class FileSearchRankingOption implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly string $ranker, + public readonly float $scoreThreshold, + ) {} + + /** + * @param array{ranker: string, score_threshold: float} $attributes + */ + public static function from(array $attributes): self + { + return new self( + ranker: $attributes['ranker'], + scoreThreshold: $attributes['score_threshold'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'ranker' => $this->ranker, + 'score_threshold' => $this->scoreThreshold, + ]; + } +} diff --git a/src/Responses/Responses/Tool/FileSearchTool.php b/src/Responses/Responses/Tool/FileSearchTool.php new file mode 100644 index 00000000..181a8810 --- /dev/null +++ b/src/Responses/Responses/Tool/FileSearchTool.php @@ -0,0 +1,67 @@ +, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}> + */ +final class FileSearchTool implements ResponseContract +{ + /** + * @use ArrayAccessible, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $vectorStoreIds + * @param 'file_search' $type + */ + private function __construct( + public readonly string $type, + public readonly array $vectorStoreIds, + public readonly FileSearchComparisonFilter|FileSearchCompoundFilter $filters, + public readonly int $maxNumResults, + public readonly FileSearchRankingOption $rankingOptions, + ) {} + + /** + * @param array{type: 'file_search', vector_store_ids: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}} $attributes + */ + public static function from(array $attributes): self + { + $filters = match ($attributes['filters']['type']) { + 'eq', 'ne', 'gt', 'gte', 'lt', 'lte' => FileSearchComparisonFilter::from($attributes['filters']), + 'and', 'or' => FileSearchCompoundFilter::from($attributes['filters']), + }; + + return new self( + type: $attributes['type'], + vectorStoreIds: $attributes['vector_store_ids'], + filters: $filters, + maxNumResults: $attributes['max_num_results'], + rankingOptions: FileSearchRankingOption::from($attributes['ranking_options']), + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'vector_store_ids' => $this->vectorStoreIds, + 'filters' => $this->filters->toArray(), + 'max_num_results' => $this->maxNumResults, + 'ranking_options' => $this->rankingOptions->toArray(), + ]; + } +} From 219c6b87c9a51d02f9473bb7f1c8b101d83eba7f Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Thu, 17 Apr 2025 07:02:26 -0400 Subject: [PATCH 40/60] feat: add tool: FunctionTool response --- src/Responses/Responses/Tool/FunctionTool.php | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/Responses/Responses/Tool/FunctionTool.php diff --git a/src/Responses/Responses/Tool/FunctionTool.php b/src/Responses/Responses/Tool/FunctionTool.php new file mode 100644 index 00000000..dac7f631 --- /dev/null +++ b/src/Responses/Responses/Tool/FunctionTool.php @@ -0,0 +1,62 @@ +, strict: bool, type: 'function', description: ?string}> + */ +final class FunctionTool implements ResponseContract +{ + /** + * @use ArrayAccessible, strict: bool, type: 'function', description: ?string}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $parameters + * @param 'function' $type + */ + private function __construct( + public readonly string $name, + public readonly array $parameters, + public readonly bool $strict, + public readonly string $type, + public readonly ?string $description = null, + ) {} + + /** + * @param array{name: string, parameters: array, strict: bool, type: 'function', description: ?string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + parameters: $attributes['parameters'], + strict: $attributes['strict'], + type: $attributes['type'], + description: $attributes['description'] ?? null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'parameters' => $this->parameters, + 'strict' => $this->strict, + 'type' => $this->type, + 'description' => $this->description, + ]; + } +} From 0701c1597fca3ef932b228090d1c894bedd839ea Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Thu, 17 Apr 2025 07:04:16 -0400 Subject: [PATCH 41/60] feat: add tool 'ComputerUse' --- .../Responses/Tool/ComputerUseTool.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/Responses/Responses/Tool/ComputerUseTool.php diff --git a/src/Responses/Responses/Tool/ComputerUseTool.php b/src/Responses/Responses/Tool/ComputerUseTool.php new file mode 100644 index 00000000..bd017203 --- /dev/null +++ b/src/Responses/Responses/Tool/ComputerUseTool.php @@ -0,0 +1,58 @@ + + */ +final class ComputerUseTool implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'computer_use_preview' $type + */ + private function __construct( + public readonly int $displayHeight, + public readonly int $displayWidth, + public readonly string $environment, + public readonly string $type, + ) {} + + /** + * @param array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + displayHeight: $attributes['display_height'], + displayWidth: $attributes['display_width'], + environment: $attributes['environment'], + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'display_height' => $this->displayHeight, + 'display_width' => $this->displayWidth, + 'environment' => $this->environment, + 'type' => $this->type, + ]; + } +} From e56f9e995dfd256a9fb7625c6ad166be2d89ef0f Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Thu, 17 Apr 2025 07:14:31 -0400 Subject: [PATCH 42/60] fix: wire up 'tools' to CreateResponse --- src/Responses/Responses/CreateResponse.php | 35 ++++++++--- .../Responses/Tool/WebSearchTool.php | 58 ++++++++++++++++++ .../Responses/Tool/WebSearchUserLocation.php | 61 +++++++++++++++++++ ...unctionTool.php => FunctionToolChoice.php} | 2 +- .../{HostedTool.php => HostedToolChoice.php} | 2 +- 5 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 src/Responses/Responses/Tool/WebSearchTool.php create mode 100644 src/Responses/Responses/Tool/WebSearchUserLocation.php rename src/Responses/Responses/ToolChoice/{FunctionTool.php => FunctionToolChoice.php} (94%) rename src/Responses/Responses/ToolChoice/{HostedTool.php => HostedToolChoice.php} (94%) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index b4013c28..0f521b60 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -15,8 +15,12 @@ use OpenAI\Responses\Responses\Output\OutputMessage as MessageCall; use OpenAI\Responses\Responses\Output\OutputReasoning as ReasoningCall; use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall as WebSearchToolCall; -use OpenAI\Responses\Responses\ToolChoice\FunctionTool; -use OpenAI\Responses\Responses\ToolChoice\HostedTool; +use OpenAI\Responses\Responses\Tool\ComputerUseTool as ComputerTool; +use OpenAI\Responses\Responses\Tool\FileSearchTool; +use OpenAI\Responses\Responses\Tool\FunctionTool; +use OpenAI\Responses\Responses\Tool\WebSearchTool; +use OpenAI\Responses\Responses\ToolChoice\FunctionToolChoice; +use OpenAI\Responses\Responses\ToolChoice\HostedToolChoice; use OpenAI\Testing\Responses\Concerns\Fakeable; /** @@ -35,7 +39,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati /** * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output - * @param array $tools + * @param array $tools * @param 'auto'|'disabled'|null $truncation * @param array $metadata */ @@ -56,7 +60,7 @@ private function __construct( public readonly bool $store, public readonly ?float $temperature, public readonly CreateResponseFormat $text, - public readonly string|FunctionTool|HostedTool $toolChoice, + public readonly string|FunctionToolChoice|HostedToolChoice $toolChoice, public readonly array $tools, public readonly ?float $topP, public readonly ?string $truncation, @@ -85,11 +89,21 @@ public static function from(array $attributes, MetaInformation $meta): self $toolChoice = is_array($attributes['tool_choice']) ? match ($attributes['tool_choice']['type']) { - 'file_search', 'web_search_preview', 'computer_use_preview' => HostedTool::from($attributes['tool_choice']), - 'function' => FunctionTool::from($attributes['tool_choice']), + 'file_search', 'web_search_preview', 'computer_use_preview' => HostedToolChoice::from($attributes['tool_choice']), + 'function' => FunctionToolChoice::from($attributes['tool_choice']), } : $attributes['tool_choice']; + $tools = array_map( + fn (array $tool): ComputerTool|FileSearchTool|FunctionTool|WebSearchTool => match ($tool['type']) { + 'file_search' => FileSearchTool::from($tool), + 'web_search' => WebSearchTool::from($tool), + 'function' => FunctionTool::from($tool), + 'computer_use' => ComputerTool::from($tool), + }, + $attributes['tools'], + ); + return new self( id: $attributes['id'], object: $attributes['object'], @@ -114,7 +128,7 @@ public static function from(array $attributes, MetaInformation $meta): self temperature: $attributes['temperature'], text: CreateResponseFormat::from($attributes['text']), toolChoice: $toolChoice, - tools: $attributes['tools'], + tools: $tools, topP: $attributes['top_p'], truncation: $attributes['truncation'], usage: CreateResponseUsage::from($attributes['usage']), @@ -147,8 +161,11 @@ public function toArray(): array 'store' => $this->store, 'temperature' => $this->temperature, 'text' => $this->text, - 'tool_choice' => $this->toolChoice, - 'tools' => $this->tools, + 'tool_choice' => $this->toolChoice->toArray(), + 'tools' => array_map( + fn (ComputerTool|FileSearchTool|FunctionTool|WebSearchTool $tool) => $tool->toArray(), + $this->tools + ), 'top_p' => $this->topP, 'truncation' => $this->truncation, 'usage' => $this->usage->toArray(), diff --git a/src/Responses/Responses/Tool/WebSearchTool.php b/src/Responses/Responses/Tool/WebSearchTool.php new file mode 100644 index 00000000..cab83b09 --- /dev/null +++ b/src/Responses/Responses/Tool/WebSearchTool.php @@ -0,0 +1,58 @@ + + */ +final class WebSearchTool implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'web_search_preview'|'web_search_preview_2025_03_11' $type + * @param 'low'|'medium'|'high' $searchContextSize + */ + private function __construct( + public readonly string $type, + public readonly string $searchContextSize, + public readonly ?WebSearchUserLocation $userLocation, + ) {} + + /** + * @param array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + searchContextSize: $attributes['search_context_size'], + userLocation: isset($attributes['user_location']) + ? WebSearchUserLocation::from($attributes['user_location']) + : null, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'search_context_size' => $this->searchContextSize, + 'user_location' => $this->userLocation?->toArray(), + ]; + } +} diff --git a/src/Responses/Responses/Tool/WebSearchUserLocation.php b/src/Responses/Responses/Tool/WebSearchUserLocation.php new file mode 100644 index 00000000..d72ea24f --- /dev/null +++ b/src/Responses/Responses/Tool/WebSearchUserLocation.php @@ -0,0 +1,61 @@ + + */ +final class WebSearchUserLocation implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'approximate' $type + */ + private function __construct( + public readonly string $type, + public readonly string $city, + public readonly string $country, + public readonly string $region, + public readonly string $timezone, + ) {} + + /** + * @param array{type: 'approximate', city: string, country: string, region: string, timezone: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + city: $attributes['city'], + country: $attributes['country'], + region: $attributes['region'], + timezone: $attributes['timezone'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'city' => $this->city, + 'country' => $this->country, + 'region' => $this->region, + 'timezone' => $this->timezone, + ]; + } +} diff --git a/src/Responses/Responses/ToolChoice/FunctionTool.php b/src/Responses/Responses/ToolChoice/FunctionToolChoice.php similarity index 94% rename from src/Responses/Responses/ToolChoice/FunctionTool.php rename to src/Responses/Responses/ToolChoice/FunctionToolChoice.php index 5a9e84cb..64bfe253 100644 --- a/src/Responses/Responses/ToolChoice/FunctionTool.php +++ b/src/Responses/Responses/ToolChoice/FunctionToolChoice.php @@ -11,7 +11,7 @@ /** * @implements ResponseContract */ -final class FunctionTool implements ResponseContract +final class FunctionToolChoice implements ResponseContract { /** * @use ArrayAccessible diff --git a/src/Responses/Responses/ToolChoice/HostedTool.php b/src/Responses/Responses/ToolChoice/HostedToolChoice.php similarity index 94% rename from src/Responses/Responses/ToolChoice/HostedTool.php rename to src/Responses/Responses/ToolChoice/HostedToolChoice.php index 7e89dd63..bbef702c 100644 --- a/src/Responses/Responses/ToolChoice/HostedTool.php +++ b/src/Responses/Responses/ToolChoice/HostedToolChoice.php @@ -11,7 +11,7 @@ /** * @implements ResponseContract */ -final class HostedTool implements ResponseContract +final class HostedToolChoice implements ResponseContract { /** * @use ArrayAccessible From e811af7daeed083e45f4d83c08a131f75201af77 Mon Sep 17 00:00:00 2001 From: momostafa Date: Sun, 20 Apr 2025 05:40:24 +0200 Subject: [PATCH 43/60] Added InputMessage types, refactored ListInputItems - Added Classes related to InputMessage types - refactored ListInputItems to reflect the newly added classes --- .../Responses/Input/InputMessage.php | 111 ++++++++++++++++ .../Input/InputMessageContentInputFile.php | 61 +++++++++ .../Input/InputMessageContentInputImage.php | 61 +++++++++ .../Input/InputMessageContentInputText.php | 52 ++++++++ src/Responses/Responses/ListInputItems.php | 125 ++++++++++++++++-- 5 files changed, 397 insertions(+), 13 deletions(-) create mode 100644 src/Responses/Responses/Input/InputMessage.php create mode 100644 src/Responses/Responses/Input/InputMessageContentInputFile.php create mode 100644 src/Responses/Responses/Input/InputMessageContentInputImage.php create mode 100644 src/Responses/Responses/Input/InputMessageContentInputText.php diff --git a/src/Responses/Responses/Input/InputMessage.php b/src/Responses/Responses/Input/InputMessage.php new file mode 100644 index 00000000..51374c3c --- /dev/null +++ b/src/Responses/Responses/Input/InputMessage.php @@ -0,0 +1,111 @@ +, + * id: string, + * role: string, + * status: 'in_progress'|'completed'|'incomplete', + * type: 'message' + * }> + */ +final class InputMessage implements ResponseContract +{ + /** + * @use ArrayAccessible, + * id: string, + * role: string, + * status: 'in_progress'|'completed'|'incomplete', + * type: 'message' + * }> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param array $content + * @param 'in_progress'|'completed'|'incomplete' $status + * @param 'message' $type + */ + private function __construct( + public readonly array $content, + public readonly string $id, + public readonly string $role, + public readonly string $status, + public readonly string $type, + ) {} + + /** + * @param array{ + * content: array, + * id: string, + * role: string, + * status: 'in_progress'|'completed'|'incomplete', + * type: 'message' + * } $attributes + */ + public static function from(array $attributes): self + { + $content = array_map( + fn (array $item): InputMessageContentInputText|InputMessageContentInputImage|InputMessageContentInputFile => match ($item['type']) { + 'input_text' => InputMessageContentInputText::from($item), + 'input_image' => InputMessageContentInputImage::from($item), + 'input_file' => InputMessageContentInputFile::from($item), + }, + $attributes['content'], + ); + + return new self( + content: $content, + id: $attributes['id'], + role: $attributes['role'], + status: $attributes['status'], + type: $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'content' => array_map( + fn (InputMessageContentInputText|InputMessageContentInputImage|InputMessageContentInputFile $item): array => $item->toArray(), + $this->content, + ), + 'id' => $this->id, + 'role' => $this->role, + 'status' => $this->status, + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/Responses/Input/InputMessageContentInputFile.php b/src/Responses/Responses/Input/InputMessageContentInputFile.php new file mode 100644 index 00000000..fa6214b7 --- /dev/null +++ b/src/Responses/Responses/Input/InputMessageContentInputFile.php @@ -0,0 +1,61 @@ + + */ +final class InputMessageContentInputFile implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_file' $type + * @param string $file_data + * @param string $file_id + * @param string $filename + */ + private function __construct( + public readonly string $type, + public readonly string $file_data, + public readonly string $file_id, + public readonly string $filename, + ) {} + + /** + * @param array{type: 'input_file', file_data: string, file_id: string, filename: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + file_data: $attributes['file_data'], + file_id: $attributes['file_id'], + filename: $attributes['filename'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'file_data' => $this->file_data, + 'file_id' => $this->file_id, + 'filename' => $this->filename, + ]; + } +} \ No newline at end of file diff --git a/src/Responses/Responses/Input/InputMessageContentInputImage.php b/src/Responses/Responses/Input/InputMessageContentInputImage.php new file mode 100644 index 00000000..ba82b474 --- /dev/null +++ b/src/Responses/Responses/Input/InputMessageContentInputImage.php @@ -0,0 +1,61 @@ + + */ +final class InputMessageContentInputImage implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_image' $type + * @param string $detail + * @param string|null $file_id + * @param string|null $image_url + */ + private function __construct( + public readonly string $type, + public readonly string $detail, + public readonly ?string $file_id, + public readonly ?string $image_url, + ) {} + + /** + * @param array{type: 'input_image', detail: string, file_id: string|null, image_url: string|null} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + detail: $attributes['detail'], + file_id: $attributes['file_id'], + image_url: $attributes['image_url'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'detail' => $this->detail, + 'file_id' => $this->file_id, + 'image_url' => $this->image_url, + ]; + } +} \ No newline at end of file diff --git a/src/Responses/Responses/Input/InputMessageContentInputText.php b/src/Responses/Responses/Input/InputMessageContentInputText.php new file mode 100644 index 00000000..2bba131c --- /dev/null +++ b/src/Responses/Responses/Input/InputMessageContentInputText.php @@ -0,0 +1,52 @@ + + */ +final class InputMessageContentInputText implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'input_text' $type + */ + private function __construct( + public readonly string $text, + public readonly string $type + ) {} + + /** + * @param array{text: string, type: 'input_text'} $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, + ]; + } +} diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php index 8b30b9e4..a9c96959 100644 --- a/src/Responses/Responses/ListInputItems.php +++ b/src/Responses/Responses/ListInputItems.php @@ -9,15 +9,54 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Responses\Meta\MetaInformation; +use OpenAI\Responses\Responses\Input\InputMessageContentInputText; +use OpenAI\Responses\Responses\Input\InputMessageContentInputImage; +use OpenAI\Responses\Responses\Input\InputMessageContentInputFile; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, first_id: ?string, last_id: ?string, has_more: bool}> + * @implements ResponseContract + * }>, + * first_id: string, + * last_id: string, + * has_more: bool + * }> */ final class ListInputItems implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, first_id: ?string, last_id: ?string, has_more: bool}> + * @use ArrayAccessible + * }>, + * first_id: string, + * last_id: string, + * has_more: bool + * }> */ use ArrayAccessible; @@ -25,13 +64,19 @@ final class ListInputItems implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; /** - * @param array}>}> $data + * @param array + * }> $data */ private function __construct( public readonly string $object, public readonly array $data, - public readonly ?string $firstId, - public readonly ?string $lastId, + public readonly string $firstId, + public readonly string $lastId, public readonly bool $hasMore, private readonly MetaInformation $meta, ) {} @@ -39,17 +84,57 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{object: string, data: array}>}>, first_id: ?string, last_id: ?string, has_more: bool} $attributes + * @param array{ + * object: string, + * data: array + * }>, + * first_id: string, + * last_id: string, + * has_more: bool + * } $attributes + * @param MetaInformation $meta */ public static function from(array $attributes, MetaInformation $meta): self { - return new self( - $attributes['object'], + $data = array_map( + function (array $item): array { + $content = array_map( + fn (array $contentItem): InputMessageContentInputText|InputMessageContentInputImage|InputMessageContentInputFile => match ($contentItem['type']) { + 'input_text' => InputMessageContentInputText::from($contentItem), + 'input_image' => InputMessageContentInputImage::from($contentItem), + 'input_file' => InputMessageContentInputFile::from($contentItem), + }, + $item['content'], + ); + return [ + 'type' => $item['type'], + 'id' => $item['id'], + 'status' => $item['status'], + 'role' => $item['role'], + 'content' => $content, + ]; + }, $attributes['data'], - $attributes['first_id'], - $attributes['last_id'], - $attributes['has_more'], - $meta, + ); + + return new self( + object: $attributes['object'], + data: $data, + firstId: $attributes['first_id'], + lastId: $attributes['last_id'], + hasMore: $attributes['has_more'], + meta: $meta, ); } @@ -60,7 +145,21 @@ public function toArray(): array { return [ 'object' => $this->object, - 'data' => $this->data, + 'data' => array_map( + function (array $item): array { + return [ + 'type' => $item['type'], + 'id' => $item['id'], + 'status' => $item['status'], + 'role' => $item['role'], + 'content' => array_map( + fn (InputMessageContentInputText|InputMessageContentInputImage|InputMessageContentInputFile $contentItem): array => $contentItem->toArray(), + $item['content'], + ), + ]; + }, + $this->data, + ), 'first_id' => $this->firstId, 'last_id' => $this->lastId, 'has_more' => $this->hasMore, From efeb8d7a88f66ce9667a24cd058a645fe04f8156 Mon Sep 17 00:00:00 2001 From: momostafa Date: Sun, 20 Apr 2025 07:53:56 +0200 Subject: [PATCH 44/60] Corrected some properties to use camelCase --- .../Input/InputMessageContentInputFile.php | 16 ++++++++-------- .../Input/InputMessageContentInputImage.php | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Responses/Responses/Input/InputMessageContentInputFile.php b/src/Responses/Responses/Input/InputMessageContentInputFile.php index fa6214b7..65f038b0 100644 --- a/src/Responses/Responses/Input/InputMessageContentInputFile.php +++ b/src/Responses/Responses/Input/InputMessageContentInputFile.php @@ -22,14 +22,14 @@ final class InputMessageContentInputFile implements ResponseContract /** * @param 'input_file' $type - * @param string $file_data - * @param string $file_id + * @param string $fileData + * @param string $fileId * @param string $filename */ private function __construct( public readonly string $type, - public readonly string $file_data, - public readonly string $file_id, + public readonly string $fileData, + public readonly string $fileId, public readonly string $filename, ) {} @@ -40,8 +40,8 @@ public static function from(array $attributes): self { return new self( type: $attributes['type'], - file_data: $attributes['file_data'], - file_id: $attributes['file_id'], + fileData: $attributes['file_data'], + fileId: $attributes['file_id'], filename: $attributes['filename'], ); } @@ -53,8 +53,8 @@ public function toArray(): array { return [ 'type' => $this->type, - 'file_data' => $this->file_data, - 'file_id' => $this->file_id, + 'file_data' => $this->fileData, + 'file_id' => $this->fileId, 'filename' => $this->filename, ]; } diff --git a/src/Responses/Responses/Input/InputMessageContentInputImage.php b/src/Responses/Responses/Input/InputMessageContentInputImage.php index ba82b474..ce171d92 100644 --- a/src/Responses/Responses/Input/InputMessageContentInputImage.php +++ b/src/Responses/Responses/Input/InputMessageContentInputImage.php @@ -23,14 +23,14 @@ final class InputMessageContentInputImage implements ResponseContract /** * @param 'input_image' $type * @param string $detail - * @param string|null $file_id - * @param string|null $image_url + * @param string|null $fileId + * @param string|null $imageUrl */ private function __construct( public readonly string $type, public readonly string $detail, - public readonly ?string $file_id, - public readonly ?string $image_url, + public readonly ?string $fileId, + public readonly ?string $imageUrl, ) {} /** @@ -41,8 +41,8 @@ public static function from(array $attributes): self return new self( type: $attributes['type'], detail: $attributes['detail'], - file_id: $attributes['file_id'], - image_url: $attributes['image_url'], + fileId: $attributes['file_id'], + imageUrl: $attributes['image_url'], ); } @@ -54,8 +54,8 @@ public function toArray(): array return [ 'type' => $this->type, 'detail' => $this->detail, - 'file_id' => $this->file_id, - 'image_url' => $this->image_url, + 'file_id' => $this->fileId, + 'image_url' => $this->imageUrl, ]; } } \ No newline at end of file From 1d20ee80ddaee4531416e19eb0e16ae04104d614 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 08:46:24 -0400 Subject: [PATCH 45/60] fix: cleanup docblock on CreateResponse --- src/Resources/Responses.php | 2 +- src/Responses/Responses/CreateResponse.php | 29 +++++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index a339c601..f7f84825 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 0f521b60..0d62fff7 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -15,7 +15,7 @@ use OpenAI\Responses\Responses\Output\OutputMessage as MessageCall; use OpenAI\Responses\Responses\Output\OutputReasoning as ReasoningCall; use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall as WebSearchToolCall; -use OpenAI\Responses\Responses\Tool\ComputerUseTool as ComputerTool; +use OpenAI\Responses\Responses\Tool\ComputerUseTool; use OpenAI\Responses\Responses\Tool\FileSearchTool; use OpenAI\Responses\Responses\Tool\FunctionTool; use OpenAI\Responses\Responses\Tool\WebSearchTool; @@ -24,12 +24,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -39,7 +39,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati /** * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output - * @param array $tools + * @param array $tools * @param 'auto'|'disabled'|null $truncation * @param array $metadata */ @@ -71,7 +71,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array}>}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -95,11 +95,11 @@ public static function from(array $attributes, MetaInformation $meta): self : $attributes['tool_choice']; $tools = array_map( - fn (array $tool): ComputerTool|FileSearchTool|FunctionTool|WebSearchTool => match ($tool['type']) { + fn (array $tool): ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool => match ($tool['type']) { 'file_search' => FileSearchTool::from($tool), - 'web_search' => WebSearchTool::from($tool), + 'web_search_preview', 'web_search_preview_2025_03_11' => WebSearchTool::from($tool), 'function' => FunctionTool::from($tool), - 'computer_use' => ComputerTool::from($tool), + 'computer_use_preview' => ComputerUseTool::from($tool), }, $attributes['tools'], ); @@ -154,16 +154,21 @@ public function toArray(): array 'max_output_tokens' => $this->maxOutputTokens, 'metadata' => $this->metadata, 'model' => $this->model, - 'output' => $this->output, + 'output' => array_map( + fn (MessageCall|ComputerToolCall|FileSearchToolCall|WebSearchToolCall|FunctionToolCall|ReasoningCall $output) => $output->toArray(), + $this->output + ), 'parallel_tool_calls' => $this->parallelToolCalls, 'previous_response_id' => $this->previousResponseId, 'reasoning' => $this->reasoning, 'store' => $this->store, 'temperature' => $this->temperature, - 'text' => $this->text, - 'tool_choice' => $this->toolChoice->toArray(), + 'text' => $this->text->toArray(), + 'tool_choice' => is_string($this->toolChoice) + ? $this->toolChoice + : $this->toolChoice->toArray(), 'tools' => array_map( - fn (ComputerTool|FileSearchTool|FunctionTool|WebSearchTool $tool) => $tool->toArray(), + fn (ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool $tool) => $tool->toArray(), $this->tools ), 'top_p' => $this->topP, From 96c7fa0c169da5eced9d2f52be065db50e6abb53 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 09:20:45 -0400 Subject: [PATCH 46/60] chore: remove unused test response object --- tests/Fixtures/Responses.php | 103 ----------------------------------- 1 file changed, 103 deletions(-) diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index d2305e1b..c9245cf2 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -1,108 +1,5 @@ - */ -function responseObject(): array -{ - return [ - 'id' => 'resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c', - 'object' => 'response', - 'created_at' => 1741484430, - 'status' => 'completed', - 'error' => null, - 'incomplete_details' => null, - 'instructions' => null, - 'max_output_tokens' => null, - 'metadata' => [], - 'model' => 'gpt-4o-2024-08-06', - 'output' => [ - [ - 'type' => 'web_search_call', - 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', - 'status' => 'completed', - ], - [ - 'type' => 'message', - 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', - 'status' => 'completed', - 'role' => 'assistant', - 'content' => [ - [ - 'type' => 'output_text', - 'text' => 'As of today, March 9, 2025, one notable positive news story...', - 'annotations' => [ - [ - 'type' => 'url_citation', - 'start_index' => 442, - 'end_index' => 557, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 962, - 'end_index' => 1077, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 1336, - 'end_index' => 1451, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - ], - ], - ], - ], - ], - 'parallel_tool_calls' => true, - 'previous_response_id' => null, - 'reasoning' => [ - 'effort' => null, - 'generate_summary' => null, - ], - 'store' => true, - 'temperature' => 1.0, - 'text' => [ - 'format' => [ - 'type' => 'text', - ], - ], - 'tool_choice' => 'auto', - 'tools' => [ - [ - 'type' => 'web_search_preview', - 'domains' => [], - 'search_context_size' => 'medium', - 'user_location' => [ - 'type' => 'approximate', - 'city' => null, - 'country' => 'US', - 'region' => null, - 'timezone' => null, - ], - ], - ], - 'top_p' => 1.0, - 'truncation' => 'disabled', - 'user' => null, - 'usage' => [ - 'input_tokens' => 328, - 'input_tokens_details' => [ - 'cached_tokens' => 0, - ], - 'output_tokens' => 356, - 'output_tokens_details' => [ - 'reasoning_tokens' => 0, - ], - 'total_tokens' => 684, - ], - ]; -} - /** * @return array */ From f8c94d1bee86724842739c8a3ac433ec713827b8 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 09:20:57 -0400 Subject: [PATCH 47/60] chore: remove extra newline --- src/Resources/Responses.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index f7f84825..49b5f55f 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -38,7 +38,6 @@ public function create(array $parameters): CreateResponse $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); - } /** From 7a61c8bcfcbe9ee3c28d8e8d231eb2b9ce45181c Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 09:21:12 -0400 Subject: [PATCH 48/60] fix: 'role' is always 'assistant' --- src/Responses/Responses/CreateResponse.php | 6 +++--- src/Responses/Responses/CreateResponseUsage.php | 12 ++++++------ src/Responses/Responses/Output/OutputMessage.php | 7 ++++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 0d62fff7..9b17c64c 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -24,12 +24,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -71,7 +71,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { diff --git a/src/Responses/Responses/CreateResponseUsage.php b/src/Responses/Responses/CreateResponseUsage.php index 1fc2a648..aed48d5b 100644 --- a/src/Responses/Responses/CreateResponseUsage.php +++ b/src/Responses/Responses/CreateResponseUsage.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract + * @implements ResponseContract */ final class CreateResponseUsage implements ResponseContract { /** - * @use ArrayAccessible + * @use ArrayAccessible */ use ArrayAccessible; @@ -23,20 +23,20 @@ final class CreateResponseUsage implements ResponseContract private function __construct( public readonly int $inputTokens, public readonly CreateResponseUsageInputTokenDetails $inputTokensDetails, - public readonly ?int $outputTokens, + public readonly int $outputTokens, public readonly CreateResponseUsageOutputTokenDetails $outputTokensDetails, public readonly int $totalTokens, ) {} /** - * @param array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens?: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int} $attributes + * @param array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int} $attributes */ public static function from(array $attributes): self { return new self( inputTokens: $attributes['input_tokens'], inputTokensDetails: CreateResponseUsageInputTokenDetails::from($attributes['input_tokens_details']), - outputTokens: $attributes['output_tokens'] ?? null, + outputTokens: $attributes['output_tokens'], outputTokensDetails: CreateResponseUsageOutputTokenDetails::from($attributes['output_tokens_details']), totalTokens: $attributes['total_tokens'], ); @@ -50,7 +50,7 @@ public function toArray(): array return [ 'input_tokens' => $this->inputTokens, 'input_tokens_details' => $this->inputTokensDetails->toArray(), - 'output_tokens' => $this->outputTokens ?? 0, + 'output_tokens' => $this->outputTokens, 'output_tokens_details' => $this->outputTokensDetails->toArray(), 'total_tokens' => $this->totalTokens, ]; diff --git a/src/Responses/Responses/Output/OutputMessage.php b/src/Responses/Responses/Output/OutputMessage.php index 5004f530..3ae82d86 100644 --- a/src/Responses/Responses/Output/OutputMessage.php +++ b/src/Responses/Responses/Output/OutputMessage.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}> */ final class OutputMessage implements ResponseContract { /** - * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}> */ use ArrayAccessible; @@ -22,6 +22,7 @@ final class OutputMessage implements ResponseContract /** * @param array $content + * @param 'assistant' $role * @param 'in_progress'|'completed'|'incomplete' $status * @param 'message' $type */ @@ -34,7 +35,7 @@ private function __construct( ) {} /** - * @param array{content: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'} $attributes + * @param array{content: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'} $attributes */ public static function from(array $attributes): self { From 697145ac0723334bf04f08b5157ae41091c6a698 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 09:40:40 -0400 Subject: [PATCH 49/60] test: augment tests with single click --- .../OutputComputerActionClick.php | 10 ++-- .../Output/OutputComputerToolCall.php | 10 ++-- .../Responses/Output/OutputMessage.php | 2 +- tests/Fixtures/Responses.php | 46 +++++++++++++++++-- .../Output/OutputComputerToolCall.php | 30 ++++++++++++ 5 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 tests/Responses/Responses/Output/OutputComputerToolCall.php diff --git a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php index 4aa04083..1ee7a2de 100644 --- a/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php +++ b/src/Responses/Responses/Output/ComputerAction/OutputComputerActionClick.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract + * @implements ResponseContract */ final class OutputComputerActionClick implements ResponseContract { /** - * @use ArrayAccessible + * @use ArrayAccessible */ use ArrayAccessible; @@ -27,12 +27,12 @@ final class OutputComputerActionClick implements ResponseContract private function __construct( public readonly string $button, public readonly string $type, - public readonly float $x, - public readonly float $y, + public readonly int $x, + public readonly int $y, ) {} /** - * @param array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float} $attributes + * @param array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int} $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/Output/OutputComputerToolCall.php b/src/Responses/Responses/Output/OutputComputerToolCall.php index b2483091..5ba2f64a 100644 --- a/src/Responses/Responses/Output/OutputComputerToolCall.php +++ b/src/Responses/Responses/Output/OutputComputerToolCall.php @@ -19,12 +19,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> + * @implements ResponseContract, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> */ final class OutputComputerToolCall implements ResponseContract { /** - * @use ArrayAccessible, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> + * @use ArrayAccessible, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}> */ use ArrayAccessible; @@ -45,7 +45,7 @@ private function __construct( ) {} /** - * @param array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'} $attributes + * @param array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'} $attributes */ public static function from(array $attributes): self { @@ -82,15 +82,15 @@ public static function from(array $attributes): self public function toArray(): array { return [ - 'action' => $this->action->toArray(), + 'type' => $this->type, 'call_id' => $this->callId, 'id' => $this->id, + 'action' => $this->action->toArray(), 'pending_safety_checks' => array_map( fn (OutputComputerPendingSafetyCheck $safetyCheck): array => $safetyCheck->toArray(), $this->pendingSafetyChecks, ), 'status' => $this->status, - 'type' => $this->type, ]; } } diff --git a/src/Responses/Responses/Output/OutputMessage.php b/src/Responses/Responses/Output/OutputMessage.php index 3ae82d86..45f0ed65 100644 --- a/src/Responses/Responses/Output/OutputMessage.php +++ b/src/Responses/Responses/Output/OutputMessage.php @@ -22,7 +22,7 @@ final class OutputMessage implements ResponseContract /** * @param array $content - * @param 'assistant' $role + * @param 'assistant' $role * @param 'in_progress'|'completed'|'incomplete' $status * @param 'message' $type */ diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index c9245cf2..66a6d3de 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -17,6 +17,7 @@ function createResponseResource(): array 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ + outputWebSearchToolCall(), [ 'type' => 'web_search_call', 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', @@ -57,6 +58,7 @@ function createResponseResource(): array ], ], ], + outputComputerToolCall(), ], 'parallel_tool_calls' => true, 'previous_response_id' => null, @@ -120,11 +122,7 @@ function retrieveResponseResource(): array 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ - [ - 'type' => 'web_search_call', - 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', - 'status' => 'completed', - ], + outputWebSearchToolCall(), [ 'type' => 'message', 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', @@ -257,6 +255,44 @@ function createStreamedResponseResource(): array ]; } +/** + * @return array + */ +function outputComputerToolCall(): array +{ + return [ + 'type' => 'computer_call', + 'call_id' => 'call_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'id' => 'cu_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'action' => [ + 'button' => 'left', + 'type' => 'click', + 'x' => 117, + 'y' => 123, + ], + 'pending_safety_checks' => [ + [ + 'code' => 'malicious_instructions', + 'id' => 'cu_sc_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'message' => 'Safety check message', + ], + ], + 'status' => 'completed', + ]; +} + +/** + * @return array + */ +function outputWebSearchToolCall(): array +{ + return [ + 'type' => 'web_search_call', + 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'status' => 'completed', + ]; +} + /** * @return resource */ diff --git a/tests/Responses/Responses/Output/OutputComputerToolCall.php b/tests/Responses/Responses/Output/OutputComputerToolCall.php new file mode 100644 index 00000000..d4b6ba94 --- /dev/null +++ b/tests/Responses/Responses/Output/OutputComputerToolCall.php @@ -0,0 +1,30 @@ +toBeInstanceOf(OutputComputerToolCall::class) + ->action->toBeInstanceOf(OutputComputerActionClick::class) + ->callId->toBe('call_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->id->toBe('cu_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->status->toBe('completed') + ->pendingSafetyChecks->toBeArray(); +}); + +test('as array accessible', function () { + $response = OutputComputerToolCall::from(outputComputerToolCall()); + + expect($response['id'])->toBe('cu_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c'); +}); + +test('to array', function () { + $response = OutputComputerToolCall::from(outputComputerToolCall()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(outputComputerToolCall()); +}); From 30ef112525a06f8241307b49862bf07ac93d65ee Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 09:48:49 -0400 Subject: [PATCH 50/60] chore: add missing int for computer single click --- src/Responses/Responses/CreateResponse.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 9b17c64c..23721f28 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -24,12 +24,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -71,7 +71,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { From ac7868dd936d5bc87c8a7d1c9f54505c301912b6 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 10:55:55 -0400 Subject: [PATCH 51/60] test: progression towards 100% coverage on create response --- src/Responses/Responses/CreateResponse.php | 2 +- .../Responses/CreateResponseFixture.php | 6 +- .../Responses/ResponseObjectFixture.php | 6 +- .../Responses/RetrieveResponseFixture.php | 6 +- tests/Fixtures/Responses.php | 134 +++++++----------- tests/Responses/Responses/CreateResponse.php | 12 +- 6 files changed, 67 insertions(+), 99 deletions(-) diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 23721f28..456ab7b3 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -160,7 +160,7 @@ public function toArray(): array ), 'parallel_tool_calls' => $this->parallelToolCalls, 'previous_response_id' => $this->previousResponseId, - 'reasoning' => $this->reasoning, + 'reasoning' => $this->reasoning?->toArray(), 'store' => $this->store, 'temperature' => $this->temperature, 'text' => $this->text->toArray(), diff --git a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php index 344ba724..1d16c224 100644 --- a/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/CreateResponseFixture.php @@ -77,10 +77,10 @@ final class CreateResponseFixture 'search_context_size' => 'medium', 'user_location' => [ 'type' => 'approximate', - 'city' => null, + 'city' => 'San Francisco', 'country' => 'US', - 'region' => null, - 'timezone' => null, + 'region' => 'California', + 'timezone' => 'America/Los_Angeles', ], ], ], diff --git a/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php b/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php index bf73f54c..4d049b2f 100644 --- a/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/ResponseObjectFixture.php @@ -77,10 +77,10 @@ final class ResponseObjectFixture 'search_context_size' => 'medium', 'user_location' => [ 'type' => 'approximate', - 'city' => null, + 'city' => 'San Francisco', 'country' => 'US', - 'region' => null, - 'timezone' => null, + 'region' => 'California', + 'timezone' => 'America/Los_Angeles', ], ], ], diff --git a/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php index a521ce7c..9231f856 100644 --- a/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php +++ b/src/Testing/Responses/Fixtures/Responses/RetrieveResponseFixture.php @@ -77,10 +77,10 @@ final class RetrieveResponseFixture 'search_context_size' => 'medium', 'user_location' => [ 'type' => 'approximate', - 'city' => null, + 'city' => 'San Francisco', 'country' => 'US', - 'region' => null, - 'timezone' => null, + 'region' => 'California', + 'timezone' => 'America/Los_Angeles', ], ], ], diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 66a6d3de..f93bf220 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -18,46 +18,7 @@ function createResponseResource(): array 'model' => 'gpt-4o-2024-08-06', 'output' => [ outputWebSearchToolCall(), - [ - 'type' => 'web_search_call', - 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', - 'status' => 'completed', - ], - [ - 'type' => 'message', - 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', - 'status' => 'completed', - 'role' => 'assistant', - 'content' => [ - [ - 'type' => 'output_text', - 'text' => 'As of today, March 9, 2025, one notable positive news story...', - 'annotations' => [ - [ - 'type' => 'url_citation', - 'start_index' => 442, - 'end_index' => 557, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 962, - 'end_index' => 1077, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 1336, - 'end_index' => 1451, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - ], - ], - ], - ], + outputMessage(), outputComputerToolCall(), ], 'parallel_tool_calls' => true, @@ -77,14 +38,13 @@ function createResponseResource(): array 'tools' => [ [ 'type' => 'web_search_preview', - 'domains' => [], 'search_context_size' => 'medium', 'user_location' => [ 'type' => 'approximate', - 'city' => null, + 'city' => 'San Francisco', 'country' => 'US', - 'region' => null, - 'timezone' => null, + 'region' => 'California', + 'timezone' => 'America/Los_Angeles', ], ], ], @@ -123,41 +83,7 @@ function retrieveResponseResource(): array 'model' => 'gpt-4o-2024-08-06', 'output' => [ outputWebSearchToolCall(), - [ - 'type' => 'message', - 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', - 'status' => 'completed', - 'role' => 'assistant', - 'content' => [ - [ - 'type' => 'output_text', - 'text' => 'As of today, March 9, 2025, one notable positive news story...', - 'annotations' => [ - [ - 'type' => 'url_citation', - 'start_index' => 442, - 'end_index' => 557, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 962, - 'end_index' => 1077, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - [ - 'type' => 'url_citation', - 'start_index' => 1336, - 'end_index' => 1451, - 'url' => 'https://.../?utm_source=chatgpt.com', - 'title' => '...', - ], - ], - ], - ], - ], + outputMessage(), ], 'parallel_tool_calls' => true, 'previous_response_id' => null, @@ -180,10 +106,10 @@ function retrieveResponseResource(): array 'search_context_size' => 'medium', 'user_location' => [ 'type' => 'approximate', - 'city' => null, + 'city' => 'San Francisco', 'country' => 'US', - 'region' => null, - 'timezone' => null, + 'region' => 'California', + 'timezone' => 'America/Los_Angeles', ], ], ], @@ -287,9 +213,51 @@ function outputComputerToolCall(): array function outputWebSearchToolCall(): array { return [ - 'type' => 'web_search_call', 'id' => 'ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', 'status' => 'completed', + 'type' => 'web_search_call', + ]; +} + +/** + * @return array + */ +function outputMessage(): array +{ + return [ + 'content' => [ + [ + 'annotations' => [ + [ + 'end_index' => 557, + 'start_index' => 442, + 'title' => '...', + 'type' => 'url_citation', + 'url' => 'https://.../?utm_source=chatgpt.com', + ], + [ + 'end_index' => 1077, + 'start_index' => 962, + 'title' => '...', + 'type' => 'url_citation', + 'url' => 'https://.../?utm_source=chatgpt.com', + ], + [ + 'end_index' => 1451, + 'start_index' => 1336, + 'title' => '...', + 'type' => 'url_citation', + 'url' => 'https://.../?utm_source=chatgpt.com', + ], + ], + 'text' => 'As of today, March 9, 2025, one notable positive news story...', + 'type' => 'output_text', + ], + ], + 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', + 'role' => 'assistant', + 'status' => 'completed', + 'type' => 'message', ]; } diff --git a/tests/Responses/Responses/CreateResponse.php b/tests/Responses/Responses/CreateResponse.php index 690bb38d..e09e875a 100644 --- a/tests/Responses/Responses/CreateResponse.php +++ b/tests/Responses/Responses/CreateResponse.php @@ -2,6 +2,9 @@ use OpenAI\Responses\Meta\MetaInformation; use OpenAI\Responses\Responses\CreateResponse; +use OpenAI\Responses\Responses\CreateResponseFormat; +use OpenAI\Responses\Responses\CreateResponseReasoning; +use OpenAI\Responses\Responses\CreateResponseUsage; test('from', function () { $response = CreateResponse::from(createResponseResource(), meta()); @@ -18,20 +21,17 @@ ->maxOutputTokens->toBeNull() ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(2) ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() - ->reasoning->toBeArray() + ->reasoning->toBeInstanceOf(CreateResponseReasoning::class) ->store->toBeTrue() ->temperature->toBe(1.0) - ->text->toBeArray() + ->text->toBeInstanceOf(CreateResponseFormat::class) ->toolChoice->toBe('auto') ->tools->toBeArray() - ->tools->toHaveCount(1) ->topP->toBe(1.0) ->truncation->toBe('disabled') - ->usage->toBeArray() - ->usage->toHaveCount(5) + ->usage->toBeInstanceOf(CreateResponseUsage::class) ->user->toBeNull() ->metadata->toBe([]); From 274498ad2adfc943e05601bf3f16d538d125ed48 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 13:53:03 -0400 Subject: [PATCH 52/60] test: assertion on OutputFileSearchToolCall --- tests/Fixtures/Responses.php | 32 ++++++++++++++ .../Output/OutputFileSearchToolCall.php | 42 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/Responses/Responses/Output/OutputFileSearchToolCall.php diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index f93bf220..481b1554 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -18,6 +18,7 @@ function createResponseResource(): array 'model' => 'gpt-4o-2024-08-06', 'output' => [ outputWebSearchToolCall(), + outputFileSearchToolCall(), outputMessage(), outputComputerToolCall(), ], @@ -181,6 +182,33 @@ function createStreamedResponseResource(): array ]; } +/** + * @return array + */ +function outputFileSearchToolCall(): array +{ + return [ + 'id' => 'fs_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'queries' => [ + 'map', + 'kansas', + ], + 'status' => 'completed', + 'type' => 'file_search_call', + 'results' => [ + [ + 'attributes' => [ + 'foo' => 'bar', + ], + 'file_id' => 'file_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c', + 'filename' => 'kansas_map.geojson', + 'score' => 0.98882, + 'text' => 'Map of Kansas', + ], + ], + ]; +} + /** * @return array */ @@ -253,6 +281,10 @@ function outputMessage(): array 'text' => 'As of today, March 9, 2025, one notable positive news story...', 'type' => 'output_text', ], + [ + 'refusal' => 'The assistant refused to answer.', + 'type' => 'refusal', + ], ], 'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c', 'role' => 'assistant', diff --git a/tests/Responses/Responses/Output/OutputFileSearchToolCall.php b/tests/Responses/Responses/Output/OutputFileSearchToolCall.php new file mode 100644 index 00000000..b92548f7 --- /dev/null +++ b/tests/Responses/Responses/Output/OutputFileSearchToolCall.php @@ -0,0 +1,42 @@ +toBeInstanceOf(OutputFileSearchToolCall::class) + ->id->toBe('fs_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->queries->toBe(['map', 'kansas']) + ->status->toBe('completed') + ->type->toBe('file_search_call') + ->results->toBeArray(); +}); + +test('from results', function () { + $response = OutputFileSearchToolCallResult::from(outputFileSearchToolCall()['results'][0]); + + expect($response) + ->toBeInstanceOf(OutputFileSearchToolCallResult::class) + ->attributes->toBe(['foo' => 'bar']) + ->fileId->toBe('file_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->filename->toBe('kansas_map.geojson') + ->score->toBe(0.98882) + ->text->toBe('Map of Kansas'); +}); + +test('as array accessible', function () { + $response = OutputFileSearchToolCall::from(outputFileSearchToolCall()); + + expect($response['id'])->toBe('fs_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c'); +}); + +test('to array', function () { + $response = OutputFileSearchToolCall::from(outputFileSearchToolCall()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(outputFileSearchToolCall()); +}); From d4170aa4492b5c7fc531c9baa161a040061a7161 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 13:57:00 -0400 Subject: [PATCH 53/60] chore: ints for x/y on click event --- src/Resources/Responses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 49b5f55f..ebdd57f5 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: float, y: float}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ + /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); From e14b07c5a3d128e16058ec8dc909f7d4224e2ef6 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 17:56:06 -0400 Subject: [PATCH 54/60] fix: further cleanup on CreateResponse --- src/Resources/Responses.php | 2 +- src/Responses/Responses/CreateResponse.php | 11 ++++++----- tests/Fixtures/Responses.php | 2 +- tests/Resources/Responses.php | 16 ++++++++-------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index ebdd57f5..8508af9b 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: string, status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ + /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 456ab7b3..30b4a245 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -24,12 +24,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -37,6 +37,7 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati use HasMetaInformation; /** + * @param 'response' $object * @param 'completed'|'failed'|'in_progress'|'incomplete' $status * @param array $output * @param array $tools @@ -71,7 +72,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: string, created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -155,7 +156,7 @@ public function toArray(): array 'metadata' => $this->metadata, 'model' => $this->model, 'output' => array_map( - fn (MessageCall|ComputerToolCall|FileSearchToolCall|WebSearchToolCall|FunctionToolCall|ReasoningCall $output) => $output->toArray(), + fn (MessageCall|ComputerToolCall|FileSearchToolCall|WebSearchToolCall|FunctionToolCall|ReasoningCall $output): array => $output->toArray(), $this->output ), 'parallel_tool_calls' => $this->parallelToolCalls, @@ -168,7 +169,7 @@ public function toArray(): array ? $this->toolChoice : $this->toolChoice->toArray(), 'tools' => array_map( - fn (ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool $tool) => $tool->toArray(), + fn (ComputerUseTool|FileSearchTool|FunctionTool|WebSearchTool $tool): array => $tool->toArray(), $this->tools ), 'top_p' => $this->topP, diff --git a/tests/Fixtures/Responses.php b/tests/Fixtures/Responses.php index 481b1554..b959d445 100644 --- a/tests/Fixtures/Responses.php +++ b/tests/Fixtures/Responses.php @@ -17,9 +17,9 @@ function createResponseResource(): array 'metadata' => [], 'model' => 'gpt-4o-2024-08-06', 'output' => [ + outputMessage(), outputWebSearchToolCall(), outputFileSearchToolCall(), - outputMessage(), outputComputerToolCall(), ], 'parallel_tool_calls' => true, diff --git a/tests/Resources/Responses.php b/tests/Resources/Responses.php index 1aab2e46..cabc5048 100644 --- a/tests/Resources/Responses.php +++ b/tests/Resources/Responses.php @@ -36,25 +36,25 @@ ->maxOutputTokens->toBeNull() ->model->toBe('gpt-4o-2024-08-06') ->output->toBeArray() - ->output->toHaveCount(2); + ->output->toHaveCount(4); expect($output[0]) - ->type->toBe('web_search_call') - ->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') - ->status->toBe('completed'); - - expect($output[1]) ->type->toBe('message') ->id->toBe('msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c') ->status->toBe('completed') ->role->toBe('assistant') ->content->toBeArray() - ->content->toHaveCount(1); + ->content->toHaveCount(2); - expect($output[1]['content'][0]) + expect($output[0]['content'][0]) ->type->toBe('output_text') ->text->toBe('As of today, March 9, 2025, one notable positive news story...'); + expect($output[1]) + ->type->toBe('web_search_call') + ->id->toBe('ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c') + ->status->toBe('completed'); + expect($result) ->parallelToolCalls->toBeTrue() ->previousResponseId->toBeNull() From 130f74b96095cf8442d427f87e7af6104d086402 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 19:41:40 -0400 Subject: [PATCH 55/60] feat: add computer tool call output --- .../Input/AcknowledgedSafetyCheck.php | 52 +++++++++++++ .../Input/ComputerToolCallOutput.php | 74 +++++++++++++++++++ .../ComputerToolCallOutputScreenshot.php | 55 ++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/Responses/Responses/Input/AcknowledgedSafetyCheck.php create mode 100644 src/Responses/Responses/Input/ComputerToolCallOutput.php create mode 100644 src/Responses/Responses/Input/ComputerToolCallOutputScreenshot.php diff --git a/src/Responses/Responses/Input/AcknowledgedSafetyCheck.php b/src/Responses/Responses/Input/AcknowledgedSafetyCheck.php new file mode 100644 index 00000000..5a8c9b42 --- /dev/null +++ b/src/Responses/Responses/Input/AcknowledgedSafetyCheck.php @@ -0,0 +1,52 @@ + + */ +final class AcknowledgedSafetyCheck implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + private function __construct( + public readonly string $code, + public readonly string $id, + public readonly string $message, + ) {} + + /** + * @param array{code: string, id: string, message: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + code: $attributes['code'], + id: $attributes['id'], + message: $attributes['message'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'code' => $this->code, + 'id' => $this->id, + 'message' => $this->message, + ]; + } +} diff --git a/src/Responses/Responses/Input/ComputerToolCallOutput.php b/src/Responses/Responses/Input/ComputerToolCallOutput.php new file mode 100644 index 00000000..7ba66a45 --- /dev/null +++ b/src/Responses/Responses/Input/ComputerToolCallOutput.php @@ -0,0 +1,74 @@ +, status: 'in_progress'|'completed'|'incomplete'}> + */ +final class ComputerToolCallOutput implements ResponseContract +{ + /** + * @use ArrayAccessible, status: 'in_progress'|'completed'|'incomplete'}> + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'computer_call_output' $type + * @param array $acknowledgedSafetyChecks + * @param 'in_progress'|'completed'|'incomplete' $status + */ + private function __construct( + public readonly string $callId, + public readonly string $id, + public readonly ComputerToolCallOutputScreenshot $output, + public readonly string $type, + public readonly array $acknowledgedSafetyChecks, + public readonly string $status, + ) {} + + /** + * @param array{call_id: string, id: string, output: array{type: 'computer_screenshot', file_id: string, image_url: string}, type: 'computer_call_output', acknowledged_safety_checks: array, status: 'in_progress'|'completed'|'incomplete'} $attributes + */ + public static function from(array $attributes): self + { + $acknowledgedSafetyChecks = array_map( + fn (array $acknowledgedSafetyCheck) => AcknowledgedSafetyCheck::from($acknowledgedSafetyCheck), + $attributes['acknowledged_safety_checks'], + ); + + return new self( + callId: $attributes['call_id'], + id: $attributes['id'], + output: ComputerToolCallOutputScreenshot::from($attributes['output']), + type: $attributes['type'], + acknowledgedSafetyChecks: $acknowledgedSafetyChecks, + status: $attributes['status'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'call_id' => $this->callId, + 'id' => $this->id, + 'output' => $this->output->toArray(), + 'type' => $this->type, + 'acknowledged_safety_checks' => array_map( + fn (AcknowledgedSafetyCheck $acknowledgedSafetyCheck) => $acknowledgedSafetyCheck->toArray(), + $this->acknowledgedSafetyChecks, + ), + 'status' => $this->status, + ]; + } +} diff --git a/src/Responses/Responses/Input/ComputerToolCallOutputScreenshot.php b/src/Responses/Responses/Input/ComputerToolCallOutputScreenshot.php new file mode 100644 index 00000000..1bb9241a --- /dev/null +++ b/src/Responses/Responses/Input/ComputerToolCallOutputScreenshot.php @@ -0,0 +1,55 @@ + + */ +final class ComputerToolCallOutputScreenshot implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'computer_screenshot' $type + */ + private function __construct( + public readonly string $type, + public readonly string $fileId, + public readonly string $imageUrl, + ) {} + + /** + * @param array{type: 'computer_screenshot', file_id: string, image_url: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + type: $attributes['type'], + fileId: $attributes['file_id'], + imageUrl: $attributes['image_url'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'file_id' => $this->fileId, + 'image_url' => $this->imageUrl, + ]; + } +} From 84cbb392e244c86a709d82e10b35a6ba5c310a45 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 19:43:20 -0400 Subject: [PATCH 56/60] feat: add function tool call output --- .../Input/FunctionToolCallOutput.php | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Responses/Responses/Input/FunctionToolCallOutput.php diff --git a/src/Responses/Responses/Input/FunctionToolCallOutput.php b/src/Responses/Responses/Input/FunctionToolCallOutput.php new file mode 100644 index 00000000..f241fc91 --- /dev/null +++ b/src/Responses/Responses/Input/FunctionToolCallOutput.php @@ -0,0 +1,61 @@ + + */ +final class FunctionToolCallOutput implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + + /** + * @param 'function_call_output' $type + */ + private function __construct( + public readonly string $callId, + public readonly string $id, + public readonly string $output, + public readonly string $type, + public readonly string $status, + ) {} + + /** + * @param array{call_id: string, id: string, output: string, type: 'function_call_output', status: 'in_progress'|'completed'|'incompleted'} $attributes + */ + public static function from(array $attributes): self + { + return new self( + callId: $attributes['call_id'], + id: $attributes['id'], + output: $attributes['output'], + type: $attributes['type'], + status: $attributes['status'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'call_id' => $this->callId, + 'id' => $this->id, + 'output' => $this->output, + 'type' => $this->type, + 'status' => $this->status, + ]; + } +} From 60bcce44a414877acc4e684aa423d9acd25e6407 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 21:59:45 -0400 Subject: [PATCH 57/60] fix: city, region and timezone can return null --- src/Responses/Responses/Tool/WebSearchTool.php | 6 +++--- .../Responses/Tool/WebSearchUserLocation.php | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Responses/Responses/Tool/WebSearchTool.php b/src/Responses/Responses/Tool/WebSearchTool.php index cab83b09..c1ec5579 100644 --- a/src/Responses/Responses/Tool/WebSearchTool.php +++ b/src/Responses/Responses/Tool/WebSearchTool.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract + * @implements ResponseContract */ final class WebSearchTool implements ResponseContract { /** - * @use ArrayAccessible + * @use ArrayAccessible */ use ArrayAccessible; @@ -31,7 +31,7 @@ private function __construct( ) {} /** - * @param array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}} $attributes + * @param array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string|null, country: string, region: string|null, timezone: string|null}} $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/Tool/WebSearchUserLocation.php b/src/Responses/Responses/Tool/WebSearchUserLocation.php index d72ea24f..96a6268e 100644 --- a/src/Responses/Responses/Tool/WebSearchUserLocation.php +++ b/src/Responses/Responses/Tool/WebSearchUserLocation.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract + * @implements ResponseContract */ final class WebSearchUserLocation implements ResponseContract { /** - * @use ArrayAccessible + * @use ArrayAccessible */ use ArrayAccessible; @@ -25,14 +25,14 @@ final class WebSearchUserLocation implements ResponseContract */ private function __construct( public readonly string $type, - public readonly string $city, + public readonly ?string $city, public readonly string $country, - public readonly string $region, - public readonly string $timezone, + public readonly ?string $region, + public readonly ?string $timezone, ) {} /** - * @param array{type: 'approximate', city: string, country: string, region: string, timezone: string} $attributes + * @param array{type: 'approximate', city: string|null, country: string, region: string|null, timezone: string|null} $attributes */ public static function from(array $attributes): self { From 09d00e50bac8286ed1f2b23dc082ffb6a9e94363 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 21 Apr 2025 22:08:33 -0400 Subject: [PATCH 58/60] fix: json_schema description can be missing --- src/Responses/Responses/CreateResponseFormat.php | 6 +++--- src/Responses/Responses/Format/JsonSchemaFormat.php | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Responses/Responses/CreateResponseFormat.php b/src/Responses/Responses/CreateResponseFormat.php index f35a1f2f..5402c783 100644 --- a/src/Responses/Responses/CreateResponseFormat.php +++ b/src/Responses/Responses/CreateResponseFormat.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}> + * @implements ResponseContract, type: 'json_schema', description: ?string, strict: ?bool}|array{type: 'json_object'}}> */ final class CreateResponseFormat implements ResponseContract { /** - * @use ArrayAccessible, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}> + * @use ArrayAccessible, type: 'json_schema', description: ?string, strict: ?bool}|array{type: 'json_object'}}> */ use ArrayAccessible; @@ -28,7 +28,7 @@ private function __construct( ) {} /** - * @param array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}} $attributes + * @param array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: ?string, strict: ?bool}|array{type: 'json_object'}} $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/Format/JsonSchemaFormat.php b/src/Responses/Responses/Format/JsonSchemaFormat.php index f7d3768d..8ddcf252 100644 --- a/src/Responses/Responses/Format/JsonSchemaFormat.php +++ b/src/Responses/Responses/Format/JsonSchemaFormat.php @@ -9,12 +9,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, type: 'json_schema', description: string, strict: ?bool}> + * @implements ResponseContract, type: 'json_schema', description: ?string, strict: ?bool}> */ final class JsonSchemaFormat implements ResponseContract { /** - * @use ArrayAccessible, type: 'json_schema', description: string, strict: ?bool}> + * @use ArrayAccessible, type: 'json_schema', description: ?string, strict: ?bool}> */ use ArrayAccessible; @@ -28,12 +28,12 @@ private function __construct( public readonly string $name, public readonly array $schema, public readonly string $type, - public readonly string $description, + public readonly ?string $description, public readonly ?bool $strict = null, ) {} /** - * @param array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool} $attributes + * @param array{name: string, schema: array, type: 'json_schema', description: ?string, strict: ?bool} $attributes */ public static function from(array $attributes): self { @@ -41,7 +41,7 @@ public static function from(array $attributes): self name: $attributes['name'], schema: $attributes['schema'], type: $attributes['type'], - description: $attributes['description'], + description: $attributes['description'] ?? null, strict: $attributes['strict'] ?? null, ); } From cc27c28b0c795e8dbd732b52d9706d0d71fb053e Mon Sep 17 00:00:00 2001 From: momostafa Date: Tue, 22 Apr 2025 05:17:18 +0200 Subject: [PATCH 59/60] Lint Test Pass, Fixed Array map at ListInputItems --- .../Input/InputMessageContentInputFile.php | 9 +- .../Input/InputMessageContentInputImage.php | 9 +- .../Input/InputMessageContentInputText.php | 4 +- src/Responses/Responses/ListInputItems.php | 88 ++----------------- 4 files changed, 17 insertions(+), 93 deletions(-) diff --git a/src/Responses/Responses/Input/InputMessageContentInputFile.php b/src/Responses/Responses/Input/InputMessageContentInputFile.php index 65f038b0..e3bdf2e7 100644 --- a/src/Responses/Responses/Input/InputMessageContentInputFile.php +++ b/src/Responses/Responses/Input/InputMessageContentInputFile.php @@ -21,10 +21,7 @@ final class InputMessageContentInputFile implements ResponseContract use Fakeable; /** - * @param 'input_file' $type - * @param string $fileData - * @param string $fileId - * @param string $filename + * @param 'input_file' $type */ private function __construct( public readonly string $type, @@ -34,7 +31,7 @@ private function __construct( ) {} /** - * @param array{type: 'input_file', file_data: string, file_id: string, filename: string} $attributes + * @param array{type: 'input_file', file_data: string, file_id: string, filename: string} $attributes */ public static function from(array $attributes): self { @@ -58,4 +55,4 @@ public function toArray(): array 'filename' => $this->filename, ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/Input/InputMessageContentInputImage.php b/src/Responses/Responses/Input/InputMessageContentInputImage.php index ce171d92..01ace14b 100644 --- a/src/Responses/Responses/Input/InputMessageContentInputImage.php +++ b/src/Responses/Responses/Input/InputMessageContentInputImage.php @@ -21,10 +21,7 @@ final class InputMessageContentInputImage implements ResponseContract use Fakeable; /** - * @param 'input_image' $type - * @param string $detail - * @param string|null $fileId - * @param string|null $imageUrl + * @param 'input_image' $type */ private function __construct( public readonly string $type, @@ -34,7 +31,7 @@ private function __construct( ) {} /** - * @param array{type: 'input_image', detail: string, file_id: string|null, image_url: string|null} $attributes + * @param array{type: 'input_image', detail: string, file_id: string|null, image_url: string|null} $attributes */ public static function from(array $attributes): self { @@ -58,4 +55,4 @@ public function toArray(): array 'image_url' => $this->imageUrl, ]; } -} \ No newline at end of file +} diff --git a/src/Responses/Responses/Input/InputMessageContentInputText.php b/src/Responses/Responses/Input/InputMessageContentInputText.php index 2bba131c..540bfe95 100644 --- a/src/Responses/Responses/Input/InputMessageContentInputText.php +++ b/src/Responses/Responses/Input/InputMessageContentInputText.php @@ -21,7 +21,7 @@ final class InputMessageContentInputText implements ResponseContract use Fakeable; /** - * @param 'input_text' $type + * @param 'input_text' $type */ private function __construct( public readonly string $text, @@ -29,7 +29,7 @@ private function __construct( ) {} /** - * @param array{text: string, type: 'input_text'} $attributes + * @param array{text: string, type: 'input_text'} $attributes */ public static function from(array $attributes): self { diff --git a/src/Responses/Responses/ListInputItems.php b/src/Responses/Responses/ListInputItems.php index a9c96959..2680a9f3 100644 --- a/src/Responses/Responses/ListInputItems.php +++ b/src/Responses/Responses/ListInputItems.php @@ -9,68 +9,30 @@ use OpenAI\Responses\Concerns\ArrayAccessible; use OpenAI\Responses\Concerns\HasMetaInformation; use OpenAI\Responses\Meta\MetaInformation; -use OpenAI\Responses\Responses\Input\InputMessageContentInputText; -use OpenAI\Responses\Responses\Input\InputMessageContentInputImage; use OpenAI\Responses\Responses\Input\InputMessageContentInputFile; +use OpenAI\Responses\Responses\Input\InputMessageContentInputImage; +use OpenAI\Responses\Responses\Input\InputMessageContentInputText; use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract - * }>, - * first_id: string, - * last_id: string, - * has_more: bool - * }> + * @implements ResponseContract}>, first_id: string, last_id: string, has_more: bool}> */ final class ListInputItems implements ResponseContract, ResponseHasMetaInformationContract { - /** - * @use ArrayAccessible - * }>, - * first_id: string, - * last_id: string, - * has_more: bool - * }> - */ + /** @use ArrayAccessible}>, first_id: string, last_id: string, has_more: bool}> */ use ArrayAccessible; use Fakeable; use HasMetaInformation; /** - * @param array - * }> $data + * }> $data */ private function __construct( public readonly string $object, @@ -84,26 +46,7 @@ private function __construct( /** * Acts as static factory, and returns a new Response instance. * - * @param array{ - * object: string, - * data: array - * }>, - * first_id: string, - * last_id: string, - * has_more: bool - * } $attributes - * @param MetaInformation $meta + * @param array{object: string, data: array}>, first_id: string, last_id: string, has_more: bool} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { @@ -117,6 +60,7 @@ function (array $item): array { }, $item['content'], ); + return [ 'type' => $item['type'], 'id' => $item['id'], @@ -145,21 +89,7 @@ public function toArray(): array { return [ 'object' => $this->object, - 'data' => array_map( - function (array $item): array { - return [ - 'type' => $item['type'], - 'id' => $item['id'], - 'status' => $item['status'], - 'role' => $item['role'], - 'content' => array_map( - fn (InputMessageContentInputText|InputMessageContentInputImage|InputMessageContentInputFile $contentItem): array => $contentItem->toArray(), - $item['content'], - ), - ]; - }, - $this->data, - ), + 'data' => $this->data, 'first_id' => $this->firstId, 'last_id' => $this->lastId, 'has_more' => $this->hasMore, From a3654b89e2bd7c8045d1afdb7d4d1a20b155b1cf Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Tue, 22 Apr 2025 15:43:31 -0400 Subject: [PATCH 60/60] chore: rework text format typing on create response --- src/Resources/Responses.php | 4 ++-- src/Responses/Responses/CreateResponse.php | 6 +++--- src/Responses/Responses/CreateResponseFormat.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Resources/Responses.php b/src/Resources/Responses.php index 8508af9b..421d00a6 100644 --- a/src/Resources/Responses.php +++ b/src/Resources/Responses.php @@ -34,7 +34,7 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('responses', $parameters); - /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ + /** @var Response, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string, name?: string, schema?: array, description?: ?string, strict?: ?bool}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return CreateResponse::from($response->data(), $response->meta()); @@ -69,7 +69,7 @@ public function retrieve(string $id): RetrieveResponse { $payload = Payload::retrieve('responses', $id); - /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ + /** @var Response}>}>, parallel_tool_calls: bool, previous_response_id: ?string, reasoning: array, store: bool, temperature: ?float, text: array{format: array{type: string, name?: string, schema?: array, description?: ?string, strict?: ?bool}}, tool_choice: string, tools: array, top_p: ?float, truncation: string, usage: array{input_tokens: int, input_tokens_details: array, output_tokens: int, output_tokens_details: array, total_tokens: int}, user: ?string, metadata?: array}> $response */ $response = $this->transporter->requestObject($payload); return RetrieveResponse::from($response->data(), $response->meta()); diff --git a/src/Responses/Responses/CreateResponse.php b/src/Responses/Responses/CreateResponse.php index 30b4a245..c3756d1d 100644 --- a/src/Responses/Responses/CreateResponse.php +++ b/src/Responses/Responses/CreateResponse.php @@ -24,12 +24,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @implements ResponseContract, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string, name?: string, schema?: array, description?: ?string, strict?: ?bool}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract { /** - * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> + * @use ArrayAccessible, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: string, name?: string, schema?: array, description?: ?string, strict?: ?bool}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array}> */ use ArrayAccessible; @@ -72,7 +72,7 @@ private function __construct( ) {} /** - * @param array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'text'}|array{name: string, schema: array, type: 'json_schema', description: string, strict: ?bool}|array{type: 'json_object'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes + * @param array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: array{code: string, message: string}|null, incomplete_details: array{reason: string}|null, instructions: string|null, max_output_tokens: int|null, model: string, output: array, text: string, type: 'output_text'}|array{refusal: string, type: 'refusal'}>, id: string, role: 'assistant', status: 'in_progress'|'completed'|'incomplete', type: 'message'}|array{id: string, queries: array, status: 'in_progress'|'searching'|'incomplete'|'failed', type: 'file_search_call', results: ?array, file_id: string, filename: string, score: float, text: string}>}|array{arguments: string, call_id: string, name: string, type: 'function_call', id: string, status: 'in_progress'|'completed'|'incomplete'}|array{id: string, status: string, type: 'web_search_call'}|array{action: array{button: 'left'|'right'|'wheel'|'back'|'forward', type: 'click', x: int, y: int}|array{type: 'double_click', x: float, y: float}|array{path: array, type: 'drag'}|array{keys: array, type: 'keypress'}|array{type: 'move', x: int, y: int}|array{type: 'screenshot'}|array{scroll_x: int, scroll_y: int, type: 'scroll', x: int, y: int}|array{text: string, type: 'type'}|array{type: 'wait'}, call_id: string, id: string, pending_safety_checks: array, status: 'in_progress'|'completed'|'incomplete', type: 'computer_call'}|array{id: string, summary: array, type: 'reasoning', status: 'in_progress'|'completed'|'incomplete'}>, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ?array{effort: ?string, generate_summary: ?string}, store: bool, temperature: float|null, text: array{format: array{type: 'json_schema', name: string, schema: array, description: string, strict: ?bool}|array{type: 'json_object'}|array{type: 'text'}}, tool_choice: 'none'|'auto'|'required'|array{type: 'file_search'|'web_search_preview'|'computer_use_preview'}|array{name: string, type: 'function'}, tools: array, filters: array{key: string, type: 'eq'|'ne'|'gt'|'gte'|'lt'|'lte', value: string|int|bool}|array{filters: array, type: 'and'|'or'}, max_num_results: int, ranking_options: array{ranker: string, score_threshold: float}}|array{name: string, parameters: array, strict: bool, type: 'function', description: ?string}|array{display_height: int, display_width: int, environment: string, type: 'computer_use_preview'}|array{type: 'web_search_preview'|'web_search_preview_2025_03_11', search_context_size: 'low'|'medium'|'high', user_location: ?array{type: 'approximate', city: string, country: string, region: string, timezone: string}}>, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: array{input_tokens: int, input_tokens_details: array{cached_tokens: int}, output_tokens: int, output_tokens_details: array{reasoning_tokens: int}, total_tokens: int}, user: string|null, metadata?: array} $attributes */ public static function from(array $attributes, MetaInformation $meta): self { diff --git a/src/Responses/Responses/CreateResponseFormat.php b/src/Responses/Responses/CreateResponseFormat.php index 5402c783..b21dc65b 100644 --- a/src/Responses/Responses/CreateResponseFormat.php +++ b/src/Responses/Responses/CreateResponseFormat.php @@ -12,12 +12,12 @@ use OpenAI\Testing\Responses\Concerns\Fakeable; /** - * @implements ResponseContract, type: 'json_schema', description: ?string, strict: ?bool}|array{type: 'json_object'}}> + * @implements ResponseContract, description?: ?string, strict?: ?bool}}> */ final class CreateResponseFormat implements ResponseContract { /** - * @use ArrayAccessible, type: 'json_schema', description: ?string, strict: ?bool}|array{type: 'json_object'}}> + * @use ArrayAccessible, description?: ?string, strict?: ?bool}}> */ use ArrayAccessible;