Skip to content

Commit 4562366

Browse files
authoredMay 3, 2024··
Add a request option for custom headers (#118)
* Add a request option for custom headers * Allow non-Header object as customHeaders and try to convert to Headers * Reject customHeaders for reserved header names with request input error * Use distinct error messages for customHeaders with api-key and api-client * Add changeset
1 parent d785a79 commit 4562366

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed
 

‎.changeset/strange-papayas-accept.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@google/generative-ai": minor
3+
---
4+
5+
Add a request option for custom headers

‎packages/main/src/errors.ts

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export class GoogleGenerativeAIFetchError extends GoogleGenerativeAIError {
5656
}
5757
}
5858

59+
/**
60+
*/
61+
export class GoogleGenerativeAIRequestInputError extends GoogleGenerativeAIError {}
62+
5963
/**
6064
* Details object that may be included in an error response.
6165
* @public

‎packages/main/src/requests/request.test.ts

+56-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import {
2727
_makeRequestInternal,
2828
constructRequest,
2929
} from "./request";
30-
import { GoogleGenerativeAIFetchError } from "../errors";
30+
import {
31+
GoogleGenerativeAIFetchError,
32+
GoogleGenerativeAIRequestInputError,
33+
} from "../errors";
3134

3235
use(sinonChai);
3336
use(chaiAsPromised);
@@ -155,6 +158,40 @@ describe("request methods", () => {
155158
);
156159
expect(request.fetchOptions.signal).to.be.instanceOf(AbortSignal);
157160
});
161+
it("passes custom headers", async () => {
162+
const request = await constructRequest(
163+
"model-name",
164+
Task.GENERATE_CONTENT,
165+
"key",
166+
true,
167+
"",
168+
{
169+
customHeaders: new Headers({ customerHeader: "customerHeaderValue" }),
170+
},
171+
);
172+
expect(
173+
(request.fetchOptions.headers as Headers).get("customerHeader"),
174+
).to.equal("customerHeaderValue");
175+
});
176+
it("passes custom x-goog-api-client header", async () => {
177+
await expect(
178+
constructRequest("model-name", Task.GENERATE_CONTENT, "key", true, "", {
179+
customHeaders: new Headers({
180+
"x-goog-api-client": "client/version",
181+
}),
182+
}),
183+
).to.be.rejectedWith(GoogleGenerativeAIRequestInputError);
184+
});
185+
it("passes apiClient and custom x-goog-api-client header", async () => {
186+
await expect(
187+
constructRequest("model-name", Task.GENERATE_CONTENT, "key", true, "", {
188+
apiClient: "client/version",
189+
customHeaders: new Headers({
190+
"x-goog-api-client": "client/version2",
191+
}),
192+
}),
193+
).to.be.rejectedWith(GoogleGenerativeAIRequestInputError);
194+
});
158195
});
159196
describe("_makeRequestInternal", () => {
160197
it("no error", async () => {
@@ -309,5 +346,23 @@ describe("request methods", () => {
309346
}
310347
expect(fetchStub).to.be.calledOnce;
311348
});
349+
it("has invalid custom header", async () => {
350+
const fetchStub = stub();
351+
await expect(
352+
_makeRequestInternal(
353+
"model-name",
354+
Task.GENERATE_CONTENT,
355+
"key",
356+
true,
357+
"",
358+
{
359+
customHeaders: new Headers({
360+
"x-goog-api-client": "client/version",
361+
}),
362+
},
363+
fetchStub as typeof fetch,
364+
),
365+
).to.be.rejectedWith(GoogleGenerativeAIRequestInputError);
366+
});
312367
});
313368
});

‎packages/main/src/requests/request.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RequestOptions } from "../../types";
1919
import {
2020
GoogleGenerativeAIError,
2121
GoogleGenerativeAIFetchError,
22+
GoogleGenerativeAIRequestInputError,
2223
} from "../errors";
2324

2425
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com";
@@ -76,6 +77,36 @@ export async function getHeaders(url: RequestUrl): Promise<Headers> {
7677
headers.append("Content-Type", "application/json");
7778
headers.append("x-goog-api-client", getClientHeaders(url.requestOptions));
7879
headers.append("x-goog-api-key", url.apiKey);
80+
81+
let customHeaders = url.requestOptions.customHeaders;
82+
if (customHeaders) {
83+
if (!(customHeaders instanceof Headers)) {
84+
try {
85+
customHeaders = new Headers(customHeaders);
86+
} catch (e) {
87+
throw new GoogleGenerativeAIRequestInputError(
88+
`unable to convert customHeaders value ${JSON.stringify(
89+
customHeaders,
90+
)} to Headers: ${e.message}`,
91+
);
92+
}
93+
}
94+
95+
for (const [headerName, headerValue] of customHeaders.entries()) {
96+
if (headerName === "x-goog-api-key") {
97+
throw new GoogleGenerativeAIRequestInputError(
98+
`Cannot set reserved header name ${headerName}`,
99+
);
100+
} else if (headerName === "x-goog-api-client") {
101+
throw new GoogleGenerativeAIRequestInputError(
102+
`Header name ${headerName} can only be set using the apiClient field`,
103+
);
104+
}
105+
106+
headers.append(headerName, headerValue);
107+
}
108+
}
109+
79110
return headers;
80111
}
81112

@@ -168,7 +199,12 @@ export async function _makeRequestInternal(
168199
}
169200
} catch (e) {
170201
let err = e;
171-
if (!(e instanceof GoogleGenerativeAIFetchError)) {
202+
if (
203+
!(
204+
e instanceof GoogleGenerativeAIFetchError ||
205+
e instanceof GoogleGenerativeAIRequestInputError
206+
)
207+
) {
172208
err = new GoogleGenerativeAIError(
173209
`Error fetching from ${url.toString()}: ${e.message}`,
174210
);

‎packages/main/types/requests.ts

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ export interface RequestOptions {
136136
* Base endpoint url. Defaults to "https://generativelanguage.googleapis.com"
137137
*/
138138
baseUrl?: string;
139+
/**
140+
* Custom HTTP request headers.
141+
*/
142+
customHeaders?: Headers | Record<string, string>;
139143
}
140144

141145
/**

0 commit comments

Comments
 (0)
Please sign in to comment.