diff --git a/README.md b/README.md index 01729f86..269bb185 100644 --- a/README.md +++ b/README.md @@ -29,6 +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) + - [Containers Resource](#containers-resource) + - [Containers Files Resource](#container-files-resource) - [Chat Resource](#chat-resource) - [Audio Resource](#audio-resource) - [Embeddings Resource](#embeddings-resource) @@ -76,14 +78,12 @@ Then, interact with OpenAI's API: $yourApiKey = getenv('YOUR_API_KEY'); $client = OpenAI::client($yourApiKey); -$result = $client->chat()->create([ +$response = $client->responses()->create([ 'model' => 'gpt-4o', - 'messages' => [ - ['role' => 'user', 'content' => 'Hello!'], - ], + 'input' => 'Hello!', ]); -echo $result->choices[0]->message->content; // Hello! How can I assist you today? +echo $response->outputText; // Hello! How can I assist you today? ``` If necessary, it is possible to configure and create a separate client. @@ -187,6 +187,7 @@ $response->object; // 'response' $response->createdAt; // 1741476542 $response->status; // 'completed' $response->model; // 'gpt-4o-mini' +$response->outputText; // 'The combined response text of any `output_text` content.' foreach ($response->output as $output) { $output->type; // 'message' @@ -308,58 +309,192 @@ $response->hasMore; // false $response->toArray(); // ['object' => 'list', 'data' => [...], ...] ``` -### `Completions` Resource +### `Containers` Resource #### `create` -Creates a completion for the provided prompt and parameters. +Creates a container for use with the Code Interpreter tool. ```php -$response = $client->completions()->create([ - 'model' => 'gpt-3.5-turbo-instruct', - 'prompt' => 'Say this is a test', - 'max_tokens' => 6, - 'temperature' => 0 +$response = $client->containers()->create([ + 'name' => 'My Container', + 'expires_after' => [ + 'anchor' => 'last_active_at', + 'minutes' => 60, + ], ]); -$response->id; // 'cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7' -$response->object; // 'text_completion' -$response->created; // 1589478378 -$response->model; // 'gpt-3.5-turbo-instruct' +$response->id; // 'container_abc123' +$response->object; // 'container' +$response->createdAt; // 1690000000 +$response->status; // 'active' +$response->expiresAfter->anchor; // 'last_active_at' +$response->expiresAfter->minutes; // 60 +$response->lastActiveAt; // 1690001000 +$response->name; // 'My Container' -foreach ($response->choices as $choice) { - $choice->text; // '\n\nThis is a test' - $choice->index; // 0 - $choice->logprobs; // null - $choice->finishReason; // 'length' or null +$response->toArray(); // ['id' => 'container_abc123', 'object' => 'container', ...] +``` + +#### `list` + +Returns a list of containers. + +```php +$response = $client->containers()->list([ + 'limit' => 10, + 'order' => 'desc', +]); + +$response->object; // 'list' + +foreach ($response->data as $container) { + $container->id; // 'container_abc123' + $container->object; // 'container' + $container->createdAt; // 1690000000 + $container->status; // 'active' + $container->expiresAfter->anchor; // 'last_active_at' + $container->expiresAfter->minutes; // 60 + $container->lastActiveAt; // 1690001000 + $container->name; // 'Test Container' } -$response->usage->promptTokens; // 5, -$response->usage->completionTokens; // 6, -$response->usage->totalTokens; // 11 +$response->firstId; // 'container_abc123' +$response->lastId; // 'container_def456' +$response->hasMore; // false -$response->toArray(); // ['id' => 'cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7', ...] +$response->toArray(); // ['object' => 'list', 'data' => [...], ...] ``` -#### `create streamed` +#### `retrieve` -Creates a streamed completion for the provided prompt and parameters. +Retrieves a container with the given ID. ```php -$stream = $client->completions()->createStreamed([ - 'model' => 'gpt-3.5-turbo-instruct', - 'prompt' => 'Hi', - 'max_tokens' => 10, - ]); +$response = $client->containers()->retrieve('container_abc123'); -foreach($stream as $response){ - $response->choices[0]->text; +$response->id; // 'container_abc123' +$response->object; // 'container' +$response->createdAt; // 1690000000 +$response->status; // 'active' +$response->expiresAfter->anchor; // 'last_active_at' +$response->expiresAfter->minutes; // 60 +$response->lastActiveAt; // 1690001000 +$response->name; // 'Test Container' + +$response->toArray(); // ['id' => 'container_abc123', 'object' => 'container', ...] +``` + +#### `delete` + +Delete a container with the given ID. + +```php +$response = $client->containers()->delete('container_abc123'); + +$response->id; // 'container_abc123' +$response->object; // 'container' +$response->deleted; // true + +$response->toArray(); // ['id' => 'container_abc123', 'object' => 'container', 'deleted' => true] +``` + +### `Containers Files` Resource + +#### `create` + +Create or upload a file into a container. + +```php +$response = $client->containers()->files()->create('container_abc123', [ + 'file' => fopen('path/to/local-file.txt', 'r'), +]); +$response = $client->containers()->files()->create('container_abc123', [ + 'file_id' => 'file_XjGxS3KTG0uNmNOK362iJua3', +]); + +$response->id; // 'cfile_682e0e8a43c88191a7978f477a09bdf5' +$response->object; // 'container.file' +$response->createdAt; // 1747848842 +$response->bytes; // 880 +$response->containerId; // 'container_abc123' +$response->path; // '/mnt/data/local-file.txt' +$response->source; // 'user' + +$response->toArray(); // ['id' => 'cfile_...', 'object' => 'container.file', ...] +``` + +> [!NOTE] +> You must provide either `file` or `file_id`, but not both. + +#### `list` + +Returns a list of files in the container. + +```php +$response = $client->containers()->files()->list('container_abc123', [ + 'limit' => 10, + 'order' => 'desc', +]); + +$response->object; // 'list' + +foreach ($response->data as $file) { + $file->id; // 'cfile_682e0e8a43c88191a7978f477a09bdf5' + $file->object; // 'container.file' + $file->createdAt; // 1747848842 + $file->bytes; // 880 + $file->containerId; // 'container_abc123' + $file->path; // '/mnt/data/...' + $file->source; // 'user' } -// 1. iteration => 'I' -// 2. iteration => ' am' -// 3. iteration => ' very' -// 4. iteration => ' excited' -// ... + +$response->firstId; // 'cfile_...' +$response->lastId; // 'cfile_...' +$response->hasMore; // false + +$response->toArray(); // ['object' => 'list', 'data' => [...], ...] +``` + +#### `retrieve` + +Retrieve information about a container file. + +```php +$response = $client->containers()->files()->retrieve('container_abc123', 'cfile_682e0e8a43c88191a7978f477a09bdf5'); + +$response->id; // 'cfile_682e0e8a43c88191a7978f477a09bdf5' +$response->object; // 'container.file' +$response->createdAt; // 1747848842 +$response->bytes; // 880 +$response->containerId; // 'container_abc123' +$response->path; // '/mnt/data/...' +$response->source; // 'user' + +$response->toArray(); // ['id' => 'cfile_...', 'object' => 'container.file', ...] +``` + +#### `retrieve content` + +Returns the raw content of the specified container file. + +```php +$content = $client->containers()->files()->content('container_abc123', 'cfile_682e0e8a43c88191a7978f477a09bdf5'); +// $content => string +``` + +#### `delete` + +Delete a container file. + +```php +$response = $client->containers()->files()->delete('container_abc123', 'cfile_682e0e8a43c88191a7978f477a09bdf5'); + +$response->id; // 'cfile_682e0e8a43c88191a7978f477a09bdf5' +$response->object; // 'container.file.deleted' +$response->deleted; // true + +$response->toArray(); // ['id' => 'cfile_...', 'object' => 'container.file.deleted', 'deleted' => true] ``` ### `Chat` Resource diff --git a/src/Contracts/Resources/ContainerFileContract.php b/src/Contracts/Resources/ContainerFileContract.php new file mode 100644 index 00000000..010acbaa --- /dev/null +++ b/src/Contracts/Resources/ContainerFileContract.php @@ -0,0 +1,49 @@ + $parameters + */ + public function create(string $containerId, array $parameters = []): ContainerFileResponse; + + /** + * List container files + * + * @see https://platform.openai.com/docs/api-reference/container_files/listContainerFiles + * + * @param array $parameters + */ + public function list(string $containerId, array $parameters = []): ContainerFileListResponse; + + /** + * Retrieve a container file + * + * @see https://platform.openai.com/docs/api-reference/container_files/retrieveContainerFile + */ + public function retrieve(string $containerId, string $fileId): ContainerFileResponse; + + /** + * Retrieve container file content + * + * @see https://platform.openai.com/docs/api-reference/container_files/retrieveContainerFileContent + */ + public function content(string $containerId, string $fileId): string; + + /** + * Delete a container file + * + * @see https://platform.openai.com/docs/api-reference/container_files/deleteContainerFile + */ + public function delete(string $containerId, string $fileId): ContainerFileDeleteResponse; +} diff --git a/src/Contracts/Resources/ContainersContract.php b/src/Contracts/Resources/ContainersContract.php index 3066a58d..ea4a86ea 100644 --- a/src/Contracts/Resources/ContainersContract.php +++ b/src/Contracts/Resources/ContainersContract.php @@ -42,4 +42,11 @@ public function delete(string $id): DeleteContainer; * @param array $parameters */ public function list(array $parameters = []): ListContainers; + + /** + * Manage the files related to the container + * + * @see https://platform.openai.com/docs/api-reference/container-files + */ + public function files(): ContainerFileContract; } diff --git a/src/Resources/ContainerFile.php b/src/Resources/ContainerFile.php new file mode 100644 index 00000000..7fc16764 --- /dev/null +++ b/src/Resources/ContainerFile.php @@ -0,0 +1,105 @@ + $parameters + */ + public function create(string $containerId, array $parameters = []): ContainerFileResponse + { + if (isset($parameters['file_id']) && isset($parameters['file'])) { + throw new \InvalidArgumentException('You cannot set both "file_id" and "file" parameters.'); + } + + $url = "containers/$containerId/files"; + $payload = isset($parameters['file']) + ? Payload::upload($url, $parameters) + : Payload::create($url, $parameters); + + /** @var Response $response */ + $response = $this->transporter->requestObject($payload); + + return ContainerFileResponse::from($response->data(), $response->meta()); + } + + /** + * List container files + * + * @see https://platform.openai.com/docs/api-reference/containers/list-container-files + * + * @param array $parameters + */ + public function list(string $containerId, array $parameters = []): ContainerFileListResponse + { + $payload = Payload::list("containers/$containerId/files", $parameters); + + /** @var Response $response */ + $response = $this->transporter->requestObject($payload); + + return ContainerFileListResponse::from($response->data(), $response->meta()); + } + + /** + * Retrieve a container file + * + * @see https://platform.openai.com/docs/api-reference/containers/retrieve-container-file + */ + public function retrieve(string $containerId, string $fileId): ContainerFileResponse + { + $payload = Payload::retrieve("containers/$containerId/files", $fileId); + + /** @var Response $response */ + $response = $this->transporter->requestObject($payload); + + return ContainerFileResponse::from($response->data(), $response->meta()); + } + + /** + * Retrieve container file content + * + * @see https://platform.openai.com/docs/api-reference/containers/retrieve-container-file-content + */ + public function content(string $containerId, string $fileId): string + { + $payload = Payload::retrieveContent("containers/$containerId/files", $fileId); + + return $this->transporter->requestContent($payload); + } + + /** + * Delete a container file + * + * @see https://platform.openai.com/docs/api-reference/containers/delete-container-file + */ + public function delete(string $containerId, string $fileId): ContainerFileDeleteResponse + { + $payload = Payload::delete("containers/$containerId/files", $fileId); + + /** @var Response $response */ + $response = $this->transporter->requestObject($payload); + + return ContainerFileDeleteResponse::from($response->data(), $response->meta()); + } +} diff --git a/src/Resources/Containers.php b/src/Resources/Containers.php index 65470c3b..12a8bb49 100644 --- a/src/Resources/Containers.php +++ b/src/Resources/Containers.php @@ -4,6 +4,7 @@ namespace OpenAI\Resources; +use OpenAI\Contracts\Resources\ContainerFileContract; use OpenAI\Contracts\Resources\ContainersContract; use OpenAI\Responses\Containers\CreateContainer; use OpenAI\Responses\Containers\DeleteContainer; @@ -85,4 +86,14 @@ public function list(array $parameters = []): ListContainers return ListContainers::from($response->data(), $response->meta()); } + + /** + * Manage the files related to the container. + * + * @see https://platform.openai.com/docs/api-reference/container-files + */ + public function files(): ContainerFileContract + { + return new ContainerFile($this->transporter); + } } diff --git a/src/Responses/Containers/Files/ContainerFileDeleteResponse.php b/src/Responses/Containers/Files/ContainerFileDeleteResponse.php new file mode 100644 index 00000000..1f094322 --- /dev/null +++ b/src/Responses/Containers/Files/ContainerFileDeleteResponse.php @@ -0,0 +1,62 @@ + + */ +final class ContainerFileDeleteResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + use HasMetaInformation; + + private function __construct( + public readonly string $id, + public readonly string $object, + public readonly bool $deleted, + private readonly MetaInformation $meta, + ) {} + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param ContainerFileDeleteType $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + return new self( + id: $attributes['id'], + object: $attributes['object'], + deleted: $attributes['deleted'], + meta: $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'deleted' => $this->deleted, + ]; + } +} diff --git a/src/Responses/Containers/Files/ContainerFileListResponse.php b/src/Responses/Containers/Files/ContainerFileListResponse.php new file mode 100644 index 00000000..92aed589 --- /dev/null +++ b/src/Responses/Containers/Files/ContainerFileListResponse.php @@ -0,0 +1,82 @@ +, first_id: ?string, last_id: ?string, has_more: bool} + * + * @implements ResponseContract + */ +final class ContainerFileListResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + use HasMetaInformation; + + /** + * @param 'list' $object + * @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 ContainerFileListType $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + $data = array_map(fn (array $result): ContainerFileResponse => ContainerFileResponse::from( + $result, + $meta, + ), $attributes['data']); + + return new self( + object: $attributes['object'], + data: $data, + firstId: $attributes['first_id'] ?? null, + lastId: $attributes['last_id'] ?? null, + hasMore: $attributes['has_more'], + meta: $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map( + static fn (ContainerFileResponse $response): array => $response->toArray(), + $this->data, + ), + 'first_id' => $this->firstId, + 'last_id' => $this->lastId, + 'has_more' => $this->hasMore, + ]; + } +} diff --git a/src/Responses/Containers/Files/ContainerFileResponse.php b/src/Responses/Containers/Files/ContainerFileResponse.php new file mode 100644 index 00000000..45480a1f --- /dev/null +++ b/src/Responses/Containers/Files/ContainerFileResponse.php @@ -0,0 +1,78 @@ + + */ +final class ContainerFileResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + use Fakeable; + use HasMetaInformation; + + /** + * @param 'container.file' $object + * @param 'user'|'assistant' $source + */ + private function __construct( + public readonly string $id, + public readonly string $object, + public readonly int $createdAt, + public readonly int $bytes, + public readonly string $containerId, + public readonly string $path, + public readonly string $source, + private readonly MetaInformation $meta, + ) {} + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param ContainerFileType $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + return new self( + id: $attributes['id'], + object: $attributes['object'], + createdAt: $attributes['created_at'], + bytes: $attributes['bytes'], + containerId: $attributes['container_id'], + path: $attributes['path'], + source: $attributes['source'], + meta: $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'created_at' => $this->createdAt, + 'bytes' => $this->bytes, + 'container_id' => $this->containerId, + 'path' => $this->path, + 'source' => $this->source, + ]; + } +} diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index 4fcf826c..16fc5426 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -13,6 +13,7 @@ use OpenAI\Testing\Resources\BatchesTestResource; use OpenAI\Testing\Resources\ChatTestResource; use OpenAI\Testing\Resources\CompletionsTestResource; +use OpenAI\Testing\Resources\ContainersTestResource; use OpenAI\Testing\Resources\EditsTestResource; use OpenAI\Testing\Resources\EmbeddingsTestResource; use OpenAI\Testing\Resources\FilesTestResource; @@ -154,6 +155,11 @@ public function chat(): ChatTestResource return new ChatTestResource($this); } + public function containers(): ContainersTestResource + { + return new ContainersTestResource($this); + } + public function embeddings(): EmbeddingsTestResource { return new EmbeddingsTestResource($this); diff --git a/src/Testing/Resources/ContainerFileTestResource.php b/src/Testing/Resources/ContainerFileTestResource.php new file mode 100644 index 00000000..930f512d --- /dev/null +++ b/src/Testing/Resources/ContainerFileTestResource.php @@ -0,0 +1,45 @@ +record(__FUNCTION__, func_get_args()); + } + + public function list(string $containerId, array $parameters = []): ContainerFileListResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function retrieve(string $containerId, string $fileId): ContainerFileResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function content(string $containerId, string $fileId): string + { + return $this->record(__FUNCTION__, func_get_args()); + } + + public function delete(string $containerId, string $fileId): ContainerFileDeleteResponse + { + return $this->record(__FUNCTION__, func_get_args()); + } +} diff --git a/src/Testing/Resources/ContainersTestResource.php b/src/Testing/Resources/ContainersTestResource.php index 2c4d2ebd..aac1d548 100644 --- a/src/Testing/Resources/ContainersTestResource.php +++ b/src/Testing/Resources/ContainersTestResource.php @@ -2,6 +2,7 @@ namespace OpenAI\Testing\Resources; +use OpenAI\Contracts\Resources\ContainerFileContract; use OpenAI\Contracts\Resources\ContainersContract; use OpenAI\Resources\Containers; use OpenAI\Responses\Containers\CreateContainer; @@ -38,4 +39,9 @@ public function delete(string $id): DeleteContainer { return $this->record(__FUNCTION__, func_get_args()); } + + public function files(): ContainerFileContract + { + return new ContainerFileTestResource($this->fake); + } } diff --git a/src/Testing/Responses/Fixtures/Containers/CreateContainerFixture.php b/src/Testing/Responses/Fixtures/Containers/CreateContainerFixture.php new file mode 100644 index 00000000..60cac016 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/CreateContainerFixture.php @@ -0,0 +1,19 @@ + 'container_abc123', + 'object' => 'container', + 'created_at' => 1690000000, + 'status' => 'active', + 'expires_after' => [ + 'anchor' => 'last_active_at', + 'minutes' => 60, + ], + 'last_active_at' => 1690001000, + 'name' => 'Test Container', + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/DeleteContainerFixture.php b/src/Testing/Responses/Fixtures/Containers/DeleteContainerFixture.php new file mode 100644 index 00000000..0b985436 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/DeleteContainerFixture.php @@ -0,0 +1,12 @@ + 'container_abc123', + 'object' => 'container', + 'deleted' => true, + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileDeleteResponseFixture.php b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileDeleteResponseFixture.php new file mode 100644 index 00000000..2f85bc2d --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileDeleteResponseFixture.php @@ -0,0 +1,12 @@ + 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'object' => 'container.file.deleted', + 'deleted' => true, + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileListResponseFixture.php b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileListResponseFixture.php new file mode 100644 index 00000000..19d50377 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileListResponseFixture.php @@ -0,0 +1,24 @@ + 'list', + 'data' => [ + [ + 'id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'object' => 'container.file', + 'created_at' => 1747848842, + 'bytes' => 880, + 'container_id' => 'cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04', + 'path' => '/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json', + 'source' => 'user', + ], + ], + 'first_id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'last_id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'has_more' => false, + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileResponseFixture.php b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileResponseFixture.php new file mode 100644 index 00000000..8446a6cc --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/Files/ContainerFileResponseFixture.php @@ -0,0 +1,16 @@ + 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'object' => 'container.file', + 'created_at' => 1747848842, + 'bytes' => 880, + 'container_id' => 'cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04', + 'path' => '/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json', + 'source' => 'user', + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/ListContainersFixture.php b/src/Testing/Responses/Fixtures/Containers/ListContainersFixture.php new file mode 100644 index 00000000..53329553 --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/ListContainersFixture.php @@ -0,0 +1,39 @@ + 'list', + 'data' => [ + [ + 'id' => 'container_abc123', + 'object' => 'container', + 'created_at' => 1690000000, + 'status' => 'active', + 'expires_after' => [ + 'anchor' => 'last_active_at', + 'minutes' => 60, + ], + 'last_active_at' => 1690001000, + 'name' => 'Test Container', + ], + [ + 'id' => 'container_def456', + 'object' => 'container', + 'created_at' => 1690010000, + 'status' => 'active', + 'expires_after' => [ + 'anchor' => 'last_active_at', + 'minutes' => 120, + ], + 'last_active_at' => 1690011000, + 'name' => 'Another Test Container', + ], + ], + 'first_id' => 'container_abc123', + 'last_id' => 'container_def456', + 'has_more' => false, + ]; +} diff --git a/src/Testing/Responses/Fixtures/Containers/RetrieveContainerFixture.php b/src/Testing/Responses/Fixtures/Containers/RetrieveContainerFixture.php new file mode 100644 index 00000000..3ecfbabb --- /dev/null +++ b/src/Testing/Responses/Fixtures/Containers/RetrieveContainerFixture.php @@ -0,0 +1,19 @@ + 'container_abc123', + 'object' => 'container', + 'created_at' => 1690000000, + 'status' => 'active', + 'expires_after' => [ + 'anchor' => 'last_active_at', + 'minutes' => 60, + ], + 'last_active_at' => 1690001000, + 'name' => 'Test Container', + ]; +} diff --git a/tests/Fixtures/ContainerFile.php b/tests/Fixtures/ContainerFile.php new file mode 100644 index 00000000..b74e61bf --- /dev/null +++ b/tests/Fixtures/ContainerFile.php @@ -0,0 +1,45 @@ + + */ +function containerFileResource(): array +{ + return [ + 'id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'object' => 'container.file', + 'created_at' => 1747848842, + 'bytes' => 880, + 'container_id' => 'cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04', + 'path' => '/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json', + 'source' => 'user', + ]; +} + +/** + * @return array + */ +function containerFileListResource(): array +{ + return [ + 'object' => 'list', + 'data' => [ + containerFileResource(), + ], + 'first_id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'last_id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'has_more' => false, + ]; +} + +/** + * @return array + */ +function containerFileDeleteResource(): array +{ + return [ + 'id' => 'cfile_682e0e8a43c88191a7978f477a09bdf5', + 'object' => 'container.file.deleted', + 'deleted' => true, + ]; +} diff --git a/tests/Resources/ContainerFiles.php b/tests/Resources/ContainerFiles.php new file mode 100644 index 00000000..0110cdf2 --- /dev/null +++ b/tests/Resources/ContainerFiles.php @@ -0,0 +1,142 @@ + 'file_abc123', + ], Response::from(containerFileResource(), metaHeaders())); + + $result = $client->containers()->files()->create($containerId, [ + 'file_id' => 'file_abc123', + ]); + + expect($result) + ->toBeInstanceOf(ContainerFileResponse::class) + ->id->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->object->toBe('container.file') + ->createdAt->toBe(1747848842) + ->bytes->toBe(880) + ->containerId->toBe('cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04') + ->path->toBe('/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json') + ->source->toBe('user'); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('create with file upload', function () { + $containerId = 'container_upload123'; + + $client = mockClient('POST', "containers/$containerId/files", [ + 'file' => fileResourceResource(), + ], Response::from(containerFileResource(), metaHeaders()), validateParams: false); + + $result = $client->containers()->files()->create($containerId, [ + 'file' => fileResourceResource(), + ]); + + expect($result) + ->toBeInstanceOf(ContainerFileResponse::class); +}); + +test('create with both file_id and file throws', function () { + $containerId = 'container_both123'; + + OpenAI::client('foo')->containers()->files()->create($containerId, [ + 'file_id' => 'file_abc', + 'file' => fileResourceResource(), + ]); +})->throws(InvalidArgumentException::class, 'You cannot set both "file_id" and "file" parameters.'); + +test('list', function () { + $containerId = 'container_list123'; + + $client = mockClient('GET', "containers/$containerId/files", [], Response::from(containerFileListResource(), metaHeaders())); + + $result = $client->containers()->files()->list($containerId); + + expect($result) + ->toBeInstanceOf(ContainerFileListResponse::class) + ->object->toBe('list') + ->data->toBeArray()->toHaveCount(1) + ->data->{0}->toBeInstanceOf(ContainerFileResponse::class) + ->firstId->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->lastId->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->hasMore->toBe(false); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list with parameters', function () { + $containerId = 'container_list_params'; + + $client = mockClient('GET', "containers/$containerId/files", ['limit' => 1], Response::from(containerFileListResource(), metaHeaders())); + + $result = $client->containers()->files()->list($containerId, [ + 'limit' => 1, + ]); + + expect($result) + ->toBeInstanceOf(ContainerFileListResponse::class) + ->object->toBe('list') + ->data->toBeArray()->toHaveCount(1); +}); + +test('retrieve', function () { + $containerId = 'container_retrieve123'; + $fileId = 'cfile_682e0e8a43c88191a7978f477a09bdf5'; + + $client = mockClient('GET', "containers/$containerId/files/$fileId", [], Response::from(containerFileResource(), metaHeaders())); + + $result = $client->containers()->files()->retrieve($containerId, $fileId); + + expect($result) + ->toBeInstanceOf(ContainerFileResponse::class) + ->id->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->object->toBe('container.file') + ->createdAt->toBe(1747848842) + ->bytes->toBe(880) + ->containerId->toBe('cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04') + ->path->toBe('/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json') + ->source->toBe('user'); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('content', function () { + $containerId = 'container_content123'; + $fileId = 'cfile_682e0e8a43c88191a7978f477a09bdf5'; + + $client = mockContentClient('GET', "containers/{$containerId}/files/{$fileId}/content", [], 'file-content'); + + $result = $client->containers()->files()->content($containerId, $fileId); + + expect($result)->toBe('file-content'); +}); + +test('delete', function () { + $containerId = 'container_delete123'; + $fileId = 'cfile_682e0e8a43c88191a7978f477a09bdf5'; + + $client = mockClient('DELETE', "containers/$containerId/files/$fileId", [], Response::from(containerFileDeleteResource(), metaHeaders())); + + $result = $client->containers()->files()->delete($containerId, $fileId); + + expect($result) + ->toBeInstanceOf(ContainerFileDeleteResponse::class) + ->id->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->object->toBe('container.file.deleted') + ->deleted->toBe(true); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); diff --git a/tests/Responses/Containers/Files/ContainerFileDeleteResponse.php b/tests/Responses/Containers/Files/ContainerFileDeleteResponse.php new file mode 100644 index 00000000..4c2b7f89 --- /dev/null +++ b/tests/Responses/Containers/Files/ContainerFileDeleteResponse.php @@ -0,0 +1,28 @@ +id->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->object->toBe('container.file.deleted') + ->deleted->toBeTrue() + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = ContainerFileDeleteResponse::from(containerFileDeleteResource(), meta()); + + expect($result['id']) + ->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5'); +}); + +test('to array', function () { + $result = ContainerFileDeleteResponse::from(containerFileDeleteResource(), meta()); + + expect($result->toArray()) + ->toBe(containerFileDeleteResource()); +}); diff --git a/tests/Responses/Containers/Files/ContainerFileListResponse.php b/tests/Responses/Containers/Files/ContainerFileListResponse.php new file mode 100644 index 00000000..a4bcfcf3 --- /dev/null +++ b/tests/Responses/Containers/Files/ContainerFileListResponse.php @@ -0,0 +1,30 @@ +object->toBe('list') + ->data->toBeArray()->toHaveCount(1) + ->data->{0}->toBeInstanceOf(ContainerFileResponse::class) + ->firstId->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->lastId->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->hasMore->toBeFalse(); +}); + +test('as array accessible', function () { + $result = ContainerFileListResponse::from(containerFileListResource(), meta()); + + expect($result['first_id']) + ->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5'); +}); + +test('to array', function () { + $result = ContainerFileListResponse::from(containerFileListResource(), meta()); + + expect($result->toArray()) + ->toBe(containerFileListResource()); +}); diff --git a/tests/Responses/Containers/Files/ContainerFileResponse.php b/tests/Responses/Containers/Files/ContainerFileResponse.php new file mode 100644 index 00000000..37a52043 --- /dev/null +++ b/tests/Responses/Containers/Files/ContainerFileResponse.php @@ -0,0 +1,30 @@ +id->toBe('cfile_682e0e8a43c88191a7978f477a09bdf5') + ->object->toBe('container.file') + ->createdAt->toBe(1747848842) + ->bytes->toBe(880) + ->containerId->toBe('cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04') + ->path->toBe('/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json') + ->source->toBe('user'); +}); + +test('as array accessible', function () { + $result = ContainerFileResponse::from(containerFileResource(), meta()); + + expect($result['container_id']) + ->toBe('cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04'); +}); + +test('to array', function () { + $result = ContainerFileResponse::from(containerFileResource(), meta()); + + expect($result->toArray()) + ->toBe(containerFileResource()); +}); diff --git a/tests/Testing/Resources/ContainerFilesTestResource.php b/tests/Testing/Resources/ContainerFilesTestResource.php new file mode 100644 index 00000000..e4d3ada6 --- /dev/null +++ b/tests/Testing/Resources/ContainerFilesTestResource.php @@ -0,0 +1,94 @@ +containers()->files()->create('container_abc123', [ + 'file_id' => 'file_abc123', + ]); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId, $parameters) { + return $method === 'create' && + $containerId === 'container_abc123' && + $parameters['file_id'] === 'file_abc123'; + }); +}); + +it('records a container files list request', function () { + $fake = new ClientFake([ + ContainerFileListResponse::from(containerFileListResource(), meta()), + ]); + + $fake->containers()->files()->list('container_list123'); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId) { + return $method === 'list' && + $containerId === 'container_list123'; + }); +}); + +it('records a container files list request with parameters', function () { + $fake = new ClientFake([ + ContainerFileListResponse::from(containerFileListResource(), meta()), + ]); + + $fake->containers()->files()->list('container_list123', [ + 'limit' => 1, + ]); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId, $parameters) { + return $method === 'list' && + $containerId === 'container_list123' && + $parameters['limit'] === 1; + }); +}); + +it('records a container files retrieve request', function () { + $fake = new ClientFake([ + ContainerFileResponse::from(containerFileResource(), meta()), + ]); + + $fake->containers()->files()->retrieve('container_retrieve123', 'cfile_123'); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId, $fileId) { + return $method === 'retrieve' && + $containerId === 'container_retrieve123' && + $fileId === 'cfile_123'; + }); +}); + +it('records a container files content request', function () { + $fake = new ClientFake([ + 'file-content', + ]); + + $fake->containers()->files()->content('container_content123', 'cfile_456'); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId, $fileId) { + return $method === 'content' && + $containerId === 'container_content123' && + $fileId === 'cfile_456'; + }); +}); + +it('records a container files delete request', function () { + $fake = new ClientFake([ + ContainerFileDeleteResponse::from(containerFileDeleteResource(), meta()), + ]); + + $fake->containers()->files()->delete('container_delete123', 'cfile_789'); + + $fake->assertSent(ContainerFile::class, function ($method, $containerId, $fileId) { + return $method === 'delete' && + $containerId === 'container_delete123' && + $fileId === 'cfile_789'; + }); +}); diff --git a/tests/Testing/Resources/ContainersTestResource.php b/tests/Testing/Resources/ContainersTestResource.php new file mode 100644 index 00000000..94602815 --- /dev/null +++ b/tests/Testing/Resources/ContainersTestResource.php @@ -0,0 +1,76 @@ +containers()->create([ + 'name' => 'Test Container', + ]); + + $fake->assertSent(Containers::class, function ($method, $parameters) { + return $method === 'create' && + $parameters['name'] === 'Test Container'; + }); +}); + +it('records a containers retrieve request', function () { + $fake = new ClientFake([ + RetrieveContainer::from(retrieveContainerResource(), meta()), + ]); + + $fake->containers()->retrieve('container_abc123'); + + $fake->assertSent(Containers::class, function ($method, $id) { + return $method === 'retrieve' && + $id === 'container_abc123'; + }); +}); + +it('records a containers delete request', function () { + $fake = new ClientFake([ + DeleteContainer::from(deleteContainerResource(), meta()), + ]); + + $fake->containers()->delete('container_abc123'); + + $fake->assertSent(Containers::class, function ($method, $id) { + return $method === 'delete' && + $id === 'container_abc123'; + }); +}); + +it('records a containers list request', function () { + $fake = new ClientFake([ + ListContainers::from(listContainersResource(), meta()), + ]); + + $fake->containers()->list(); + + $fake->assertSent(Containers::class, function ($method) { + return $method === 'list'; + }); +}); + +it('records a containers list request with parameters', function () { + $fake = new ClientFake([ + ListContainers::from(listContainersResource(), meta()), + ]); + + $fake->containers()->list([ + 'limit' => 2, + ]); + + $fake->assertSent(Containers::class, function ($method, $parameters) { + return $method === 'list' && + $parameters['limit'] === 2; + }); +});