|
4 | 4 |
|
5 | 5 | namespace GeminiAPI; |
6 | 6 |
|
| 7 | +use BadMethodCallException; |
| 8 | +use CurlHandle; |
7 | 9 | use GeminiAPI\ClientInterface as GeminiClientInterface; |
8 | 10 | use GeminiAPI\Enums\ModelName; |
| 11 | +use GeminiAPI\Json\ObjectListParser; |
9 | 12 | use GeminiAPI\Requests\CountTokensRequest; |
10 | 13 | use GeminiAPI\Requests\EmbedContentRequest; |
11 | 14 | use GeminiAPI\Requests\GenerateContentRequest; |
| 15 | +use GeminiAPI\Requests\GenerateContentStreamRequest; |
12 | 16 | use GeminiAPI\Requests\ListModelsRequest; |
13 | 17 | use GeminiAPI\Requests\RequestInterface; |
14 | 18 | use GeminiAPI\Responses\CountTokensResponse; |
|
23 | 27 | use Psr\Http\Message\StreamFactoryInterface; |
24 | 28 | use RuntimeException; |
25 | 29 |
|
| 30 | +use function curl_close; |
| 31 | +use function curl_exec; |
| 32 | +use function curl_init; |
| 33 | +use function curl_setopt; |
| 34 | +use function extension_loaded; |
26 | 35 | use function json_decode; |
27 | 36 |
|
28 | 37 | class Client implements GeminiClientInterface |
@@ -76,6 +85,45 @@ public function generateContent(GenerateContentRequest $request): GenerateConten |
76 | 85 | return GenerateContentResponse::fromArray($json); |
77 | 86 | } |
78 | 87 |
|
| 88 | + /** |
| 89 | + * @param callable(GenerateContentResponse): void $callback |
| 90 | + * @throws BadMethodCallException |
| 91 | + * @throws RuntimeException |
| 92 | + */ |
| 93 | + public function generateContentStream( |
| 94 | + GenerateContentStreamRequest $request, |
| 95 | + callable $callback, |
| 96 | + ): void { |
| 97 | + if (!extension_loaded('curl')) { |
| 98 | + throw new BadMethodCallException('Gemini API requires `curl` extension for streaming responses'); |
| 99 | + } |
| 100 | + |
| 101 | + $parser = new ObjectListParser( |
| 102 | + /* @phpstan-ignore-next-line */ |
| 103 | + static fn (array $arr) => $callback(GenerateContentResponse::fromArray($arr)), |
| 104 | + ); |
| 105 | + |
| 106 | + $ch = curl_init("{$this->baseUrl}/v1/{$request->getOperation()}"); |
| 107 | + |
| 108 | + if ($ch === false) { |
| 109 | + throw new RuntimeException('Gemini API cannot initialize streaming content request'); |
| 110 | + } |
| 111 | + |
| 112 | + curl_setopt($ch, CURLOPT_POST, true); |
| 113 | + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request)); |
| 114 | + curl_setopt($ch, CURLOPT_HTTPHEADER, [ |
| 115 | + 'Content-type: application/json', |
| 116 | + self::API_KEY_HEADER_NAME . ": {$this->apiKey}", |
| 117 | + ]); |
| 118 | + curl_setopt( |
| 119 | + $ch, |
| 120 | + CURLOPT_WRITEFUNCTION, |
| 121 | + static fn (CurlHandle $ch, string $str): int => $parser->consume($str), |
| 122 | + ); |
| 123 | + curl_exec($ch); |
| 124 | + curl_close($ch); |
| 125 | + } |
| 126 | + |
79 | 127 | /** |
80 | 128 | * @throws ClientExceptionInterface |
81 | 129 | */ |
|
0 commit comments