2727use Psr \Http \Message \StreamFactoryInterface ;
2828use RuntimeException ;
2929
30+ use function array_map ;
3031use function curl_close ;
3132use function curl_exec ;
3233use function curl_getinfo ;
3536use function extension_loaded ;
3637use function json_decode ;
3738use function sprintf ;
39+ use function strtolower ;
3840
3941class Client implements GeminiClientInterface
4042{
4143 private string $ baseUrl = 'https://generativelanguage.googleapis.com ' ;
44+
45+ /**
46+ * @var array<string, string>
47+ */
48+ private array $ requestHeaders = [];
49+
4250 public function __construct (
4351 private readonly string $ apiKey ,
4452 private ?HttpClientInterface $ client = null ,
@@ -88,13 +96,16 @@ public function generateContent(GenerateContentRequest $request): GenerateConten
8896 }
8997
9098 /**
99+ * @param GenerateContentStreamRequest $request
91100 * @param callable(GenerateContentResponse): void $callback
101+ * @param CurlHandle|null $curl
92102 * @throws BadMethodCallException
93103 * @throws RuntimeException
94104 */
95105 public function generateContentStream (
96106 GenerateContentStreamRequest $ request ,
97107 callable $ callback ,
108+ ?CurlHandle $ curl = null ,
98109 ): void {
99110 if (!extension_loaded ('curl ' )) {
100111 throw new BadMethodCallException ('Gemini API requires `curl` extension for streaming responses ' );
@@ -120,18 +131,25 @@ public function generateContentStream(
120131 );
121132 };
122133
123- $ ch = curl_init ("{ $ this -> baseUrl } /v1/ { $ request -> getOperation ()}" );
134+ $ ch = $ curl ?? curl_init ();
124135
125136 if ($ ch === false ) {
126137 throw new RuntimeException ('Gemini API cannot initialize streaming content request ' );
127138 }
128139
140+ $ headers = $ this ->requestHeaders + [
141+ 'content-type ' => 'application/json ' ,
142+ self ::API_KEY_HEADER_NAME => $ this ->apiKey ,
143+ ];
144+ $ headerLines = [];
145+ foreach ($ headers as $ name => $ value ) {
146+ $ headerLines [] = "{$ name }: {$ value }" ;
147+ }
148+
149+ curl_setopt ($ ch , CURLOPT_URL , "{$ this ->baseUrl }/v1/ {$ request ->getOperation ()}" );
129150 curl_setopt ($ ch , CURLOPT_POST , true );
130151 curl_setopt ($ ch , CURLOPT_POSTFIELDS , json_encode ($ request ));
131- curl_setopt ($ ch , CURLOPT_HTTPHEADER , [
132- 'Content-type: application/json ' ,
133- self ::API_KEY_HEADER_NAME . ": {$ this ->apiKey }" ,
134- ]);
152+ curl_setopt ($ ch , CURLOPT_HTTPHEADER , $ headerLines );
135153 curl_setopt ($ ch , CURLOPT_WRITEFUNCTION , $ writeFunction );
136154 curl_exec ($ ch );
137155 curl_close ($ ch );
@@ -179,6 +197,18 @@ public function withBaseUrl(string $baseUrl): self
179197 return $ clone ;
180198 }
181199
200+ /**
201+ * @param array<string, string> $headers
202+ * @return self
203+ */
204+ public function withRequestHeaders (array $ headers ): self
205+ {
206+ $ clone = clone $ this ;
207+ $ clone ->requestHeaders = array_map (strtolower (...), $ headers );
208+
209+ return $ clone ;
210+ }
211+
182212 /**
183213 * @throws ClientExceptionInterface
184214 */
@@ -193,6 +223,10 @@ private function doRequest(RequestInterface $request): string
193223 ->createRequest ($ request ->getHttpMethod (), $ uri )
194224 ->withAddedHeader (self ::API_KEY_HEADER_NAME , $ this ->apiKey );
195225
226+ foreach ($ this ->requestHeaders as $ name => $ value ) {
227+ $ httpRequest = $ httpRequest ->withAddedHeader ($ name , $ value );
228+ }
229+
196230 $ payload = $ request ->getHttpPayload ();
197231 if (!empty ($ payload )) {
198232 $ stream = $ this ->streamFactory ->createStream ($ payload );
0 commit comments