diff --git a/composer.json b/composer.json index 733092a0..f8d9b0c8 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "psr/http-client": "^1.0.3", "psr/http-client-implementation": "^1.0.1", "psr/http-factory-implementation": "*", - "psr/http-message": "^1.1.0|^2.0.0" + "psr/http-message": "^1.1.0|^2.0.0", + "psr/event-dispatcher": "^1.0.0" }, "require-dev": { "guzzlehttp/guzzle": "^7.8.1", diff --git a/rector.php b/rector.php index da8a14ea..9dbddc56 100644 --- a/rector.php +++ b/rector.php @@ -7,6 +7,7 @@ use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; +use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ @@ -15,6 +16,7 @@ $rectorConfig->skip([ __DIR__.'/src/Testing/ClientFake.php' => FinalizeClassesWithoutChildrenRector::class, + __DIR__.'/src/Events/NullEventDispatcher.php' => AddVoidReturnTypeWhereNoReturnRector::class, ]); $rectorConfig->rules([ diff --git a/src/Client.php b/src/Client.php index d876c55f..6a292d39 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,6 +5,7 @@ namespace OpenAI; use OpenAI\Contracts\ClientContract; +use OpenAI\Contracts\DispatcherContract; use OpenAI\Contracts\Resources\ThreadsContract; use OpenAI\Contracts\TransporterContract; use OpenAI\Resources\Assistants; @@ -26,8 +27,10 @@ final class Client implements ClientContract /** * Creates a Client instance with the given API token. */ - public function __construct(private readonly TransporterContract $transporter) - { + public function __construct( + private readonly TransporterContract $transporter, + private readonly DispatcherContract $events, + ) { // .. } @@ -39,7 +42,7 @@ public function __construct(private readonly TransporterContract $transporter) */ public function completions(): Completions { - return new Completions($this->transporter); + return new Completions($this->transporter, $this->events); } /** @@ -49,7 +52,7 @@ public function completions(): Completions */ public function chat(): Chat { - return new Chat($this->transporter); + return new Chat($this->transporter, $this->events); } /** @@ -59,7 +62,7 @@ public function chat(): Chat */ public function embeddings(): Embeddings { - return new Embeddings($this->transporter); + return new Embeddings($this->transporter, $this->events); } /** @@ -69,7 +72,7 @@ public function embeddings(): Embeddings */ public function audio(): Audio { - return new Audio($this->transporter); + return new Audio($this->transporter, $this->events); } /** @@ -79,7 +82,7 @@ public function audio(): Audio */ public function edits(): Edits { - return new Edits($this->transporter); + return new Edits($this->transporter, $this->events); } /** @@ -89,7 +92,7 @@ public function edits(): Edits */ public function files(): Files { - return new Files($this->transporter); + return new Files($this->transporter, $this->events); } /** @@ -99,7 +102,7 @@ public function files(): Files */ public function models(): Models { - return new Models($this->transporter); + return new Models($this->transporter, $this->events); } /** @@ -109,7 +112,7 @@ public function models(): Models */ public function fineTuning(): FineTuning { - return new FineTuning($this->transporter); + return new FineTuning($this->transporter, $this->events); } /** @@ -121,7 +124,7 @@ public function fineTuning(): FineTuning */ public function fineTunes(): FineTunes { - return new FineTunes($this->transporter); + return new FineTunes($this->transporter, $this->events); } /** @@ -131,7 +134,7 @@ public function fineTunes(): FineTunes */ public function moderations(): Moderations { - return new Moderations($this->transporter); + return new Moderations($this->transporter, $this->events); } /** @@ -141,7 +144,7 @@ public function moderations(): Moderations */ public function images(): Images { - return new Images($this->transporter); + return new Images($this->transporter, $this->events); } /** @@ -151,7 +154,7 @@ public function images(): Images */ public function assistants(): Assistants { - return new Assistants($this->transporter); + return new Assistants($this->transporter, $this->events); } /** @@ -161,6 +164,6 @@ public function assistants(): Assistants */ public function threads(): ThreadsContract { - return new Threads($this->transporter); + return new Threads($this->transporter, $this->events); } } diff --git a/src/Contracts/DispatcherContract.php b/src/Contracts/DispatcherContract.php new file mode 100644 index 00000000..57ea23a3 --- /dev/null +++ b/src/Contracts/DispatcherContract.php @@ -0,0 +1,16 @@ +events->dispatch($event); + } +} diff --git a/src/Events/NullEventDispatcher.php b/src/Events/NullEventDispatcher.php new file mode 100644 index 00000000..5d7a5487 --- /dev/null +++ b/src/Events/NullEventDispatcher.php @@ -0,0 +1,13 @@ +events = $events; + + return $this; + } + /** * Creates a new Open AI Client. */ @@ -158,7 +173,9 @@ public function make(): Client $transporter = new HttpTransporter($client, $baseUri, $headers, $queryParams, $sendAsync); - return new Client($transporter); + $dispatcher = new Dispatcher($this->events ?? new NullEventDispatcher); + + return new Client($transporter, $dispatcher); } /** diff --git a/src/Resources/Assistants.php b/src/Resources/Assistants.php index 2e785b06..722b507a 100644 --- a/src/Resources/Assistants.php +++ b/src/Resources/Assistants.php @@ -6,16 +6,15 @@ use OpenAI\Contracts\Resources\AssistantsContract; use OpenAI\Contracts\Resources\AssistantsFilesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Assistants\AssistantDeleteResponse; use OpenAI\Responses\Assistants\AssistantListResponse; use OpenAI\Responses\Assistants\AssistantResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Assistants implements AssistantsContract +final class Assistants extends Resource implements AssistantsContract { - use Concerns\Transportable; - /** * Create an assistant with a model and instructions. * @@ -27,10 +26,14 @@ public function create(array $parameters): AssistantResponse { $payload = Payload::create('assistants', $parameters); - /** @var Response}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -42,10 +45,14 @@ public function retrieve(string $id): AssistantResponse { $payload = Payload::retrieve('assistants', $id); - /** @var Response}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return AssistantResponse::from($response->data(), $response->meta()); + $response = AssistantResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -59,10 +66,14 @@ public function modify(string $id, array $parameters): AssistantResponse { $payload = Payload::modify('assistants', $id, $parameters); - /** @var Response}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantResponse::from($responseRaw->data(), $responseRaw->meta()); - return AssistantResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -74,10 +85,14 @@ public function delete(string $id): AssistantDeleteResponse { $payload = Payload::delete('assistants', $id); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantDeleteResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantDeleteResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -91,10 +106,14 @@ public function list(array $parameters = []): AssistantListResponse { $payload = Payload::list('assistants', $parameters); - /** @var Response}}>, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantListResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -104,6 +123,6 @@ public function list(array $parameters = []): AssistantListResponse */ public function files(): AssistantsFilesContract { - return new AssistantsFiles($this->transporter); + return new AssistantsFiles($this->transporter, $this->events); } } diff --git a/src/Resources/AssistantsFiles.php b/src/Resources/AssistantsFiles.php index aab3a447..d8a4ba48 100644 --- a/src/Resources/AssistantsFiles.php +++ b/src/Resources/AssistantsFiles.php @@ -5,16 +5,15 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\AssistantsFilesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Assistants\Files\AssistantFileDeleteResponse; use OpenAI\Responses\Assistants\Files\AssistantFileListResponse; use OpenAI\Responses\Assistants\Files\AssistantFileResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class AssistantsFiles implements AssistantsFilesContract +final class AssistantsFiles extends Resource implements AssistantsFilesContract { - use Concerns\Transportable; - /** * Create an assistant file by attaching a File to an assistant. * @@ -26,10 +25,14 @@ public function create(string $assistantId, array $parameters): AssistantFileRes { $payload = Payload::create("assistants/$assistantId/files", $parameters); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantFileResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantFileResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -41,10 +44,14 @@ public function retrieve(string $assistantId, string $fileId): AssistantFileResp { $payload = Payload::retrieve("assistants/$assistantId/files", $fileId); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantFileResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantFileResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -56,10 +63,14 @@ public function delete(string $assistantId, string $fileId): AssistantFileDelete { $payload = Payload::delete("assistants/$assistantId/files", $fileId); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return AssistantFileDeleteResponse::from($response->data(), $response->meta()); + $response = AssistantFileDeleteResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -73,9 +84,13 @@ public function list(string $assistantId, array $parameters = []): AssistantFile { $payload = Payload::list("assistants/$assistantId/files", $parameters); - /** @var Response, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = AssistantFileListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return AssistantFileListResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Audio.php b/src/Resources/Audio.php index 6dfef09d..16b5d072 100644 --- a/src/Resources/Audio.php +++ b/src/Resources/Audio.php @@ -5,16 +5,15 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\AudioContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Audio\SpeechStreamResponse; use OpenAI\Responses\Audio\TranscriptionResponse; use OpenAI\Responses\Audio\TranslationResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Audio implements AudioContract +final class Audio extends Resource implements AudioContract { - use Concerns\Transportable; - /** * Generates audio from the input text. * @@ -26,7 +25,11 @@ public function speech(array $parameters): string { $payload = Payload::create('audio/speech', $parameters); - return $this->transporter->requestContent($payload); + $response = $this->transporter->requestContent($payload); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -40,9 +43,13 @@ public function speechStreamed(array $parameters): SpeechStreamResponse { $payload = Payload::create('audio/speech', $parameters); - $response = $this->transporter->requestStream($payload); + $responseRaw = $this->transporter->requestStream($payload); + + $response = new SpeechStreamResponse($responseRaw); + + $this->event(new RequestHandled($payload, $response)); - return new SpeechStreamResponse($response); + return $response; } /** @@ -56,10 +63,14 @@ public function transcribe(array $parameters): TranscriptionResponse { $payload = Payload::upload('audio/transcriptions', $parameters); - /** @var Response, temperature: float, avg_logprob: float, compression_ratio: float, no_speech_prob: float, transient?: bool}>, text: string}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, temperature: float, avg_logprob: float, compression_ratio: float, no_speech_prob: float, transient?: bool}>, text: string}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return TranscriptionResponse::from($response->data(), $response->meta()); + $response = TranscriptionResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -73,9 +84,13 @@ public function translate(array $parameters): TranslationResponse { $payload = Payload::upload('audio/translations', $parameters); - /** @var Response, temperature: float, avg_logprob: float, compression_ratio: float, no_speech_prob: float, transient?: bool}>, text: string}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, temperature: float, avg_logprob: float, compression_ratio: float, no_speech_prob: float, transient?: bool}>, text: string}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = TranslationResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return TranslationResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Chat.php b/src/Resources/Chat.php index 4d4999f3..d60a9b4b 100644 --- a/src/Resources/Chat.php +++ b/src/Resources/Chat.php @@ -5,16 +5,16 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ChatContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Chat\CreateResponse; use OpenAI\Responses\Chat\CreateStreamedResponse; use OpenAI\Responses\StreamResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Chat implements ChatContract +final class Chat extends Resource implements ChatContract { use Concerns\Streamable; - use Concerns\Transportable; /** * Creates a completion for the chat message @@ -29,10 +29,14 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('chat/completions', $parameters); - /** @var Response}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return CreateResponse::from($response->data(), $response->meta()); + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -49,8 +53,12 @@ public function createStreamed(array $parameters): StreamResponse $payload = Payload::create('chat/completions', $parameters); - $response = $this->transporter->requestStream($payload); + $responseRaw = $this->transporter->requestStream($payload); + + $response = new StreamResponse(CreateStreamedResponse::class, $responseRaw); + + $this->event(new RequestHandled($payload, $response)); - return new StreamResponse(CreateStreamedResponse::class, $response); + return $response; } } diff --git a/src/Resources/Completions.php b/src/Resources/Completions.php index 60edcae9..ffec9703 100644 --- a/src/Resources/Completions.php +++ b/src/Resources/Completions.php @@ -5,16 +5,16 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\CompletionsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Completions\CreateResponse; use OpenAI\Responses\Completions\CreateStreamedResponse; use OpenAI\Responses\StreamResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Completions implements CompletionsContract +final class Completions extends Resource implements CompletionsContract { use Concerns\Streamable; - use Concerns\Transportable; /** * Creates a completion for the provided prompt and parameters @@ -29,10 +29,14 @@ public function create(array $parameters): CreateResponse $payload = Payload::create('completions', $parameters); - /** @var Response, token_logprobs: array, top_logprobs: array|null, text_offset: array}|null, finish_reason: string}>, usage: array{prompt_tokens: int, completion_tokens: int, total_tokens: int}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, token_logprobs: array, top_logprobs: array|null, text_offset: array}|null, finish_reason: string}>, usage: array{prompt_tokens: int, completion_tokens: int, total_tokens: int}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return CreateResponse::from($response->data(), $response->meta()); + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -49,8 +53,12 @@ public function createStreamed(array $parameters): StreamResponse $payload = Payload::create('completions', $parameters); - $response = $this->transporter->requestStream($payload); + $responseRaw = $this->transporter->requestStream($payload); + + $response = new StreamResponse(CreateStreamedResponse::class, $responseRaw); + + $this->event(new RequestHandled($payload, $response)); - return new StreamResponse(CreateStreamedResponse::class, $response); + return $response; } } diff --git a/src/Resources/Concerns/Transportable.php b/src/Resources/Concerns/Transportable.php deleted file mode 100644 index 091511f9..00000000 --- a/src/Resources/Concerns/Transportable.php +++ /dev/null @@ -1,18 +0,0 @@ -, usage: array{prompt_tokens: int, completion_tokens: int, total_tokens: int}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, usage: array{prompt_tokens: int, completion_tokens: int, total_tokens: int}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return CreateResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Embeddings.php b/src/Resources/Embeddings.php index fd0b9467..fbe70f7f 100644 --- a/src/Resources/Embeddings.php +++ b/src/Resources/Embeddings.php @@ -5,14 +5,13 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\EmbeddingsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Embeddings\CreateResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Embeddings implements EmbeddingsContract +final class Embeddings extends Resource implements EmbeddingsContract { - use Concerns\Transportable; - /** * Creates an embedding vector representing the input text. * @@ -24,9 +23,13 @@ public function create(array $parameters): CreateResponse { $payload = Payload::create('embeddings', $parameters); - /** @var Response, index: int}>, usage: array{prompt_tokens: int, total_tokens: int}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, index: int}>, usage: array{prompt_tokens: int, total_tokens: int}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return CreateResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Files.php b/src/Resources/Files.php index b5c53cce..b18afd7e 100644 --- a/src/Resources/Files.php +++ b/src/Resources/Files.php @@ -5,6 +5,7 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\FilesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Files\CreateResponse; use OpenAI\Responses\Files\DeleteResponse; use OpenAI\Responses\Files\ListResponse; @@ -12,10 +13,8 @@ use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Files implements FilesContract +final class Files extends Resource implements FilesContract { - use Concerns\Transportable; - /** * Returns a list of files that belong to the user's organization. * @@ -25,10 +24,14 @@ public function list(): ListResponse { $payload = Payload::list('files'); - /** @var Response|string|null}>}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response|string|null}>}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ListResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -40,10 +43,14 @@ public function retrieve(string $file): RetrieveResponse { $payload = Payload::retrieve('files', $file); - /** @var Response|string|null}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response|string|null}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return RetrieveResponse::from($response->data(), $response->meta()); + $response = RetrieveResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -55,7 +62,11 @@ public function download(string $file): string { $payload = Payload::retrieveContent('files', $file); - return $this->transporter->requestContent($payload); + $response = $this->transporter->requestContent($payload); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -69,10 +80,14 @@ public function upload(array $parameters): CreateResponse { $payload = Payload::upload('files', $parameters); - /** @var Response|string|null}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response|string|null}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return CreateResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -84,9 +99,13 @@ public function delete(string $file): DeleteResponse { $payload = Payload::delete('files', $file); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = DeleteResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return DeleteResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/FineTunes.php b/src/Resources/FineTunes.php index 3d9331df..54f0d380 100644 --- a/src/Resources/FineTunes.php +++ b/src/Resources/FineTunes.php @@ -5,6 +5,7 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\FineTunesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\FineTunes\ListEventsResponse; use OpenAI\Responses\FineTunes\ListResponse; use OpenAI\Responses\FineTunes\RetrieveResponse; @@ -13,10 +14,8 @@ use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class FineTunes implements FineTunesContract +final class FineTunes extends Resource implements FineTunesContract { - use Concerns\Transportable; - /** * Creates a job that fine-tunes a specified model from a given dataset. * @@ -30,10 +29,14 @@ public function create(array $parameters): RetrieveResponse { $payload = Payload::create('fine-tunes', $parameters); - /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveResponse::from($responseRaw->data(), $responseRaw->meta()); - return RetrieveResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -45,10 +48,14 @@ public function list(): ListResponse { $payload = Payload::list('fine-tunes'); - /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}>}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}>}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ListResponse::from($responseRaw->data(), $responseRaw->meta()); - return ListResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -60,10 +67,14 @@ public function retrieve(string $fineTuneId): RetrieveResponse { $payload = Payload::retrieve('fine-tunes', $fineTuneId); - /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return RetrieveResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -75,10 +86,14 @@ public function cancel(string $fineTuneId): RetrieveResponse { $payload = Payload::cancel('fine-tunes', $fineTuneId); - /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, fine_tuned_model: ?string, hyperparams: array{batch_size: ?int, learning_rate_multiplier: ?float, n_epochs: int, prompt_loss_weight: float}, organization_id: string, result_files: array|string|null}>, status: string, validation_files: array|string|null}>, training_files: array|string|null}>, updated_at: int}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return RetrieveResponse::from($response->data(), $response->meta()); + $response = RetrieveResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -90,10 +105,14 @@ public function listEvents(string $fineTuneId): ListEventsResponse { $payload = Payload::retrieve('fine-tunes', $fineTuneId, '/events'); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ListEventsResponse::from($responseRaw->data(), $responseRaw->meta()); - return ListEventsResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -107,8 +126,12 @@ public function listEventsStreamed(string $fineTuneId): StreamResponse { $payload = Payload::retrieve('fine-tunes', $fineTuneId, '/events?stream=true'); - $response = $this->transporter->requestStream($payload); + $responseRaw = $this->transporter->requestStream($payload); + + $response = new StreamResponse(RetrieveStreamedResponseEvent::class, $responseRaw); + + $this->event(new RequestHandled($payload, $response)); - return new StreamResponse(RetrieveStreamedResponseEvent::class, $response); + return $response; } } diff --git a/src/Resources/FineTuning.php b/src/Resources/FineTuning.php index ba9fc751..2ea6ad95 100644 --- a/src/Resources/FineTuning.php +++ b/src/Resources/FineTuning.php @@ -5,16 +5,15 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\FineTuningContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\FineTuning\ListJobEventsResponse; use OpenAI\Responses\FineTuning\ListJobsResponse; use OpenAI\Responses\FineTuning\RetrieveJobResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class FineTuning implements FineTuningContract +final class FineTuning extends Resource implements FineTuningContract { - use Concerns\Transportable; - /** * Creates a job that fine-tunes a specified model from a given dataset. * @@ -28,10 +27,14 @@ public function createJob(array $parameters): RetrieveJobResponse { $payload = Payload::create('fine_tuning/jobs', $parameters); - /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveJobResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return RetrieveJobResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -45,10 +48,14 @@ public function listJobs(array $parameters = []): ListJobsResponse { $payload = Payload::list('fine_tuning/jobs', $parameters); - /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}>, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}>, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return ListJobsResponse::from($response->data(), $response->meta()); + $response = ListJobsResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -60,10 +67,14 @@ public function retrieveJob(string $jobId): RetrieveJobResponse { $payload = Payload::retrieve('fine_tuning/jobs', $jobId); - /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveJobResponse::from($responseRaw->data(), $responseRaw->meta()); - return RetrieveJobResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -75,10 +86,14 @@ public function cancelJob(string $jobId): RetrieveJobResponse { $payload = Payload::cancel('fine_tuning/jobs', $jobId); - /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int, error: ?array{code: string, param: ?string, message: string}}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveJobResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return RetrieveJobResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -92,9 +107,13 @@ public function listJobEvents(string $jobId, array $parameters = []): ListJobEve { $payload = Payload::retrieve('fine_tuning/jobs', $jobId, '/events', $parameters); - /** @var Response, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ListJobEventsResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ListJobEventsResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Images.php b/src/Resources/Images.php index 465678c4..a5c8253f 100644 --- a/src/Resources/Images.php +++ b/src/Resources/Images.php @@ -5,16 +5,15 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ImagesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Images\CreateResponse; use OpenAI\Responses\Images\EditResponse; use OpenAI\Responses\Images\VariationResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Images implements ImagesContract +final class Images extends Resource implements ImagesContract { - use Concerns\Transportable; - /** * Creates an image given a prompt. * @@ -26,10 +25,14 @@ public function create(array $parameters): CreateResponse { $payload = Payload::create('images/generations', $parameters); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); - return CreateResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -43,10 +46,14 @@ public function edit(array $parameters): EditResponse { $payload = Payload::upload('images/edits', $parameters); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = EditResponse::from($responseRaw->data(), $responseRaw->meta()); - return EditResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -60,9 +67,13 @@ public function variation(array $parameters): VariationResponse { $payload = Payload::upload('images/variations', $parameters); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = VariationResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return VariationResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Models.php b/src/Resources/Models.php index f88817f4..3280ec7b 100644 --- a/src/Resources/Models.php +++ b/src/Resources/Models.php @@ -5,16 +5,15 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ModelsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Models\DeleteResponse; use OpenAI\Responses\Models\ListResponse; use OpenAI\Responses\Models\RetrieveResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Models implements ModelsContract +final class Models extends Resource implements ModelsContract { - use Concerns\Transportable; - /** * Lists the currently available models, and provides basic information about each one such as the owner and availability. * @@ -24,10 +23,14 @@ public function list(): ListResponse { $payload = Payload::list('models'); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ListResponse::from($responseRaw->data(), $responseRaw->meta()); - return ListResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -39,10 +42,14 @@ public function retrieve(string $model): RetrieveResponse { $payload = Payload::retrieve('models', $model); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = RetrieveResponse::from($responseRaw->data(), $responseRaw->meta()); - return RetrieveResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -54,9 +61,13 @@ public function delete(string $model): DeleteResponse { $payload = Payload::delete('models', $model); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = DeleteResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return DeleteResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Moderations.php b/src/Resources/Moderations.php index a979e23c..4308517b 100644 --- a/src/Resources/Moderations.php +++ b/src/Resources/Moderations.php @@ -5,14 +5,13 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ModerationsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Moderations\CreateResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Moderations implements ModerationsContract +final class Moderations extends Resource implements ModerationsContract { - use Concerns\Transportable; - /** * Classifies if text violates OpenAI's Content Policy. * @@ -24,9 +23,13 @@ public function create(array $parameters): CreateResponse { $payload = Payload::create('moderations', $parameters); - /** @var Response, category_scores: array, flagged: bool}>}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, category_scores: array, flagged: bool}>}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = CreateResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return CreateResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/Resource.php b/src/Resources/Resource.php new file mode 100644 index 00000000..74f0bfe0 --- /dev/null +++ b/src/Resources/Resource.php @@ -0,0 +1,21 @@ +events->dispatch($event); + } +} diff --git a/src/Resources/Threads.php b/src/Resources/Threads.php index 8966bc70..21cd426b 100644 --- a/src/Resources/Threads.php +++ b/src/Resources/Threads.php @@ -7,16 +7,15 @@ use OpenAI\Contracts\Resources\ThreadsContract; use OpenAI\Contracts\Resources\ThreadsMessagesContract; use OpenAI\Contracts\Resources\ThreadsRunsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Threads\Runs\ThreadRunResponse; use OpenAI\Responses\Threads\ThreadDeleteResponse; use OpenAI\Responses\Threads\ThreadResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class Threads implements ThreadsContract +final class Threads extends Resource implements ThreadsContract { - use Concerns\Transportable; - /** * Create a thread. * @@ -28,10 +27,14 @@ public function create(array $parameters): ThreadResponse { $payload = Payload::create('threads', $parameters); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -45,10 +48,14 @@ public function createAndRun(array $parameters): ThreadRunResponse { $payload = Payload::create('threads/runs', $parameters); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return ThreadRunResponse::from($response->data(), $response->meta()); + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -60,10 +67,14 @@ public function retrieve(string $id): ThreadResponse { $payload = Payload::retrieve('threads', $id); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadResponse::from($responseRaw->data(), $responseRaw->meta()); - return ThreadResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -77,10 +88,14 @@ public function modify(string $id, array $parameters): ThreadResponse { $payload = Payload::modify('threads', $id, $parameters); - /** @var Response}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -92,10 +107,14 @@ public function delete(string $id): ThreadDeleteResponse { $payload = Payload::delete('threads', $id); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadDeleteResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadDeleteResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -105,7 +124,7 @@ public function delete(string $id): ThreadDeleteResponse */ public function messages(): ThreadsMessagesContract { - return new ThreadsMessages($this->transporter); + return new ThreadsMessages($this->transporter, $this->events); } /** @@ -115,6 +134,6 @@ public function messages(): ThreadsMessagesContract */ public function runs(): ThreadsRunsContract { - return new ThreadsRuns($this->transporter); + return new ThreadsRuns($this->transporter, $this->events); } } diff --git a/src/Resources/ThreadsMessages.php b/src/Resources/ThreadsMessages.php index 9388a311..4d316f43 100644 --- a/src/Resources/ThreadsMessages.php +++ b/src/Resources/ThreadsMessages.php @@ -6,15 +6,14 @@ use OpenAI\Contracts\Resources\ThreadsMessagesContract; use OpenAI\Contracts\Resources\ThreadsMessagesFilesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Threads\Messages\ThreadMessageListResponse; use OpenAI\Responses\Threads\Messages\ThreadMessageResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class ThreadsMessages implements ThreadsMessagesContract +final class ThreadsMessages extends Resource implements ThreadsMessagesContract { - use Concerns\Transportable; - /** * Create a message. * @@ -26,10 +25,14 @@ public function create(string $threadId, array $parameters): ThreadMessageRespon { $payload = Payload::create("threads/$threadId/messages", $parameters); - /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadMessageResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadMessageResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -41,10 +44,14 @@ public function retrieve(string $threadId, string $messageId): ThreadMessageResp { $payload = Payload::retrieve("threads/$threadId/messages", $messageId); - /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadMessageResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadMessageResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -58,10 +65,14 @@ public function modify(string $threadId, string $messageId, array $parameters): { $payload = Payload::modify("threads/$threadId/messages", $messageId, $parameters); - /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return ThreadMessageResponse::from($response->data(), $response->meta()); + $response = ThreadMessageResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -75,10 +86,14 @@ public function list(string $threadId, array $parameters = []): ThreadMessageLis { $payload = Payload::list("threads/$threadId/messages", $parameters); - /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}>, assistant_id: ?string, run_id: ?string, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadMessageListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadMessageListResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -88,6 +103,6 @@ public function list(string $threadId, array $parameters = []): ThreadMessageLis */ public function files(): ThreadsMessagesFilesContract { - return new ThreadsMessagesFiles($this->transporter); + return new ThreadsMessagesFiles($this->transporter, $this->events); } } diff --git a/src/Resources/ThreadsMessagesFiles.php b/src/Resources/ThreadsMessagesFiles.php index d3086dde..068944a1 100644 --- a/src/Resources/ThreadsMessagesFiles.php +++ b/src/Resources/ThreadsMessagesFiles.php @@ -5,15 +5,14 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ThreadsMessagesFilesContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Threads\Messages\Files\ThreadMessageFileListResponse; use OpenAI\Responses\Threads\Messages\Files\ThreadMessageFileResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class ThreadsMessagesFiles implements ThreadsMessagesFilesContract +final class ThreadsMessagesFiles extends Resource implements ThreadsMessagesFilesContract { - use Concerns\Transportable; - /** * Retrieves a message file. * @@ -23,10 +22,14 @@ public function retrieve(string $threadId, string $messageId, string $fileId): T { $payload = Payload::retrieve("threads/$threadId/messages/$messageId/files", $fileId); - /** @var Response $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadMessageFileResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadMessageFileResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -40,9 +43,13 @@ public function list(string $threadId, string $messageId, array $parameters = [] { $payload = Payload::list("threads/$threadId/messages/$messageId/files", $parameters); - /** @var Response, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadMessageFileListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadMessageFileListResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/Resources/ThreadsRuns.php b/src/Resources/ThreadsRuns.php index 64e8dc00..eeae8047 100644 --- a/src/Resources/ThreadsRuns.php +++ b/src/Resources/ThreadsRuns.php @@ -6,15 +6,14 @@ use OpenAI\Contracts\Resources\ThreadsRunsContract; use OpenAI\Contracts\Resources\ThreadsRunsStepsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Threads\Runs\ThreadRunListResponse; use OpenAI\Responses\Threads\Runs\ThreadRunResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class ThreadsRuns implements ThreadsRunsContract +final class ThreadsRuns extends Resource implements ThreadsRunsContract { - use Concerns\Transportable; - /** * Create a run. * @@ -26,10 +25,14 @@ public function create(string $threadId, array $parameters): ThreadRunResponse { $payload = Payload::create('threads/'.$threadId.'/runs', $parameters); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); - return ThreadRunResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -41,10 +44,14 @@ public function retrieve(string $threadId, string $runId): ThreadRunResponse { $payload = Payload::retrieve('threads/'.$threadId.'/runs', $runId); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); - return ThreadRunResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -58,10 +65,14 @@ public function modify(string $threadId, string $runId, array $parameters): Thre { $payload = Payload::modify('threads/'.$threadId.'/runs', $runId, $parameters); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadRunResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -75,10 +86,14 @@ public function submitToolOutputs(string $threadId, string $runId, array $parame { $payload = Payload::create('threads/'.$threadId.'/runs/'.$runId.'/submit_tool_outputs', $parameters); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); - return ThreadRunResponse::from($response->data(), $response->meta()); + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -90,10 +105,14 @@ public function cancel(string $threadId, string $runId): ThreadRunResponse { $payload = Payload::cancel('threads/'.$threadId.'/runs', $runId); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunResponse::from($responseRaw->data(), $responseRaw->meta()); - return ThreadRunResponse::from($response->data(), $response->meta()); + $this->event(new RequestHandled($payload, $response)); + + return $response; } /** @@ -107,10 +126,14 @@ public function list(string $threadId, array $parameters = []): ThreadRunListRes { $payload = Payload::list('threads/'.$threadId.'/runs', $parameters); - /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}, last_error: ?array{code: string, message: string}, expires_at: ?int, started_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, model: string, instructions: ?string, tools: array}}>, file_ids: array, metadata: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadRunListResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -120,6 +143,6 @@ public function list(string $threadId, array $parameters = []): ThreadRunListRes */ public function steps(): ThreadsRunsStepsContract { - return new ThreadsRunsSteps($this->transporter); + return new ThreadsRunsSteps($this->transporter, $this->events); } } diff --git a/src/Resources/ThreadsRunsSteps.php b/src/Resources/ThreadsRunsSteps.php index a914956f..c85d6167 100644 --- a/src/Resources/ThreadsRunsSteps.php +++ b/src/Resources/ThreadsRunsSteps.php @@ -5,15 +5,14 @@ namespace OpenAI\Resources; use OpenAI\Contracts\Resources\ThreadsRunsStepsContract; +use OpenAI\Events\RequestHandled; use OpenAI\Responses\Threads\Runs\Steps\ThreadRunStepListResponse; use OpenAI\Responses\Threads\Runs\Steps\ThreadRunStepResponse; use OpenAI\ValueObjects\Transporter\Payload; use OpenAI\ValueObjects\Transporter\Response; -final class ThreadsRunsSteps implements ThreadsRunsStepsContract +final class ThreadsRunsSteps extends Resource implements ThreadsRunsStepsContract { - use Concerns\Transportable; - /** * Retrieves a run step. * @@ -23,10 +22,14 @@ public function retrieve(string $threadId, string $runId, string $stepId): Threa { $payload = Payload::retrieve('threads/'.$threadId.'/runs/'.$runId.'/steps', $stepId); - /** @var Response}}|array{id: string, type: 'retrieval', retrieval: array}|array{id: string, type: 'function', function: array{name: string, arguments: string, output: ?string}}>}|array{type: 'message_creation', message_creation: array{message_id: string}}, last_error: ?array{code: string, message: string}, expires_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, metadata?: array}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}|array{id: string, type: 'retrieval', retrieval: array}|array{id: string, type: 'function', function: array{name: string, arguments: string, output: ?string}}>}|array{type: 'message_creation', message_creation: array{message_id: string}}, last_error: ?array{code: string, message: string}, expires_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, metadata?: array}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunStepResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadRunStepResponse::from($response->data(), $response->meta()); + return $response; } /** @@ -40,9 +43,13 @@ public function list(string $threadId, string $runId, array $parameters = []): T { $payload = Payload::list('threads/'.$threadId.'/runs/'.$runId.'/steps', $parameters); - /** @var Response}}|array{id: string, type: 'retrieval', retrieval: array}|array{id: string, type: 'function', function: array{name: string, arguments: string, output: ?string}}>}|array{type: 'message_creation', message_creation: array{message_id: string}}, last_error: ?array{code: string, message: string}, expires_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, metadata?: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $response */ - $response = $this->transporter->requestObject($payload); + /** @var Response}}|array{id: string, type: 'retrieval', retrieval: array}|array{id: string, type: 'function', function: array{name: string, arguments: string, output: ?string}}>}|array{type: 'message_creation', message_creation: array{message_id: string}}, last_error: ?array{code: string, message: string}, expires_at: ?int, cancelled_at: ?int, failed_at: ?int, completed_at: ?int, metadata?: array}>, first_id: ?string, last_id: ?string, has_more: bool}> $responseRaw */ + $responseRaw = $this->transporter->requestObject($payload); + + $response = ThreadRunStepListResponse::from($responseRaw->data(), $responseRaw->meta()); + + $this->event(new RequestHandled($payload, $response)); - return ThreadRunStepListResponse::from($response->data(), $response->meta()); + return $response; } } diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index 4fcb4c5a..b1ba4f74 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -24,10 +24,10 @@ final class Payload * @param array $parameters */ private function __construct( - private readonly ContentType $contentType, - private readonly Method $method, - private readonly ResourceUri $uri, - private readonly array $parameters = [], + public readonly ContentType $contentType, + public readonly Method $method, + public readonly ResourceUri $uri, + public readonly array $parameters = [], ) { // .. } diff --git a/tests/Arch.php b/tests/Arch.php index 69aa3d56..3ad213c3 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -23,6 +23,7 @@ test('resources')->expect('OpenAI\Resources')->toOnlyUse([ 'OpenAI\Contracts', + 'OpenAI\Events', 'OpenAI\ValueObjects', 'OpenAI\Exceptions', 'OpenAI\Responses', @@ -31,6 +32,7 @@ test('responses')->expect('OpenAI\Responses')->toOnlyUse([ 'Http\Discovery\Psr17Factory', 'OpenAI\Enums', + 'OpenAI\Events', 'OpenAI\Exceptions\ErrorException', 'OpenAI\Contracts', 'OpenAI\Testing\Responses\Concerns', @@ -49,6 +51,7 @@ ]); test('client')->expect('OpenAI\Client')->toOnlyUse([ + 'OpenAI\Events', 'OpenAI\Resources', 'OpenAI\Contracts', ]); @@ -61,6 +64,7 @@ 'Http\Message\MultipartStream\MultipartStreamBuilder', 'OpenAI\Contracts', 'OpenAI\Resources', + 'Psr\EventDispatcher\EventDispatcherInterface', 'Psr\Http\Client', 'Psr\Http\Message\RequestInterface', 'Psr\Http\Message\ResponseInterface', diff --git a/tests/OpenAI.php b/tests/OpenAI.php index 44fdf987..58d3263b 100644 --- a/tests/OpenAI.php +++ b/tests/OpenAI.php @@ -65,6 +65,29 @@ expect($openAI)->toBeInstanceOf(Client::class); }); +it('sets a custom event dispatcher via factory', function () { + $dispatcher = new class() implements \Psr\EventDispatcher\EventDispatcherInterface + { + public $event; + + public function dispatch(object $event) + { + $this->event = $event; + } + }; + + $openAI = OpenAI::factory() + ->withEventDispatcher($dispatcher) + ->make(); + + expect($openAI)->toBeInstanceOf(Client::class); + + $openAI->chat()->event((object) ['foo' => 'bar']); + + expect($dispatcher->event) + ->foo->toBe('bar'); +}); + it('sets a custom stream handler via factory', function () { $openAI = OpenAI::factory() ->withHttpClient($client = new GuzzleClient()) diff --git a/tests/Pest.php b/tests/Pest.php index e8d8de39..ce7f8e85 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,6 +1,7 @@ getUri()->getPath() === "/v1/$resource"; })->andReturn($response); - return new Client($transporter); + $dispatcher = Mockery::mock(DispatcherContract::class); + + $dispatcher + ->shouldReceive('dispatch') + ->once(); + + return new Client($transporter, $dispatcher); } function mockContentClient(string $method, string $resource, array $params, string $response, bool $validateParams = true)