Skip to content

Commit c497a04

Browse files
authored
fix(js/plugins/google-genai): do not enforce API key when apiKey is set to false (#3933)
1 parent cc38483 commit c497a04

File tree

12 files changed

+65
-62
lines changed

12 files changed

+65
-62
lines changed

‎js/plugins/google-genai/src/googleai/client.ts‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
* @returns A promise that resolves to an array of Model objects.
4747
*/
4848
export async function listModels(
49-
apiKey: string,
49+
apiKey: string | undefined,
5050
clientOptions?: ClientOptions
5151
): Promise<Model[]> {
5252
const url = getGoogleAIUrl({
@@ -75,7 +75,7 @@ export async function listModels(
7575
* @throws {Error} If the API request fails or the response cannot be parsed.
7676
*/
7777
export async function generateContent(
78-
apiKey: string,
78+
apiKey: string | undefined,
7979
model: string,
8080
generateContentRequest: GenerateContentRequest,
8181
clientOptions?: ClientOptions
@@ -108,7 +108,7 @@ export async function generateContent(
108108
* @throws {Error} If the API request fails.
109109
*/
110110
export async function generateContentStream(
111-
apiKey: string,
111+
apiKey: string | undefined,
112112
model: string,
113113
generateContentRequest: GenerateContentRequest,
114114
clientOptions?: ClientOptions
@@ -140,7 +140,7 @@ export async function generateContentStream(
140140
* @throws {Error} If the API request fails or the response cannot be parsed.
141141
*/
142142
export async function embedContent(
143-
apiKey: string,
143+
apiKey: string | undefined,
144144
model: string,
145145
embedContentRequest: EmbedContentRequest,
146146
clientOptions?: ClientOptions
@@ -162,7 +162,7 @@ export async function embedContent(
162162
}
163163

164164
export async function imagenPredict(
165-
apiKey: string,
165+
apiKey: string | undefined,
166166
model: string,
167167
imagenPredictRequest: ImagenPredictRequest,
168168
clientOptions?: ClientOptions
@@ -185,7 +185,7 @@ export async function imagenPredict(
185185
}
186186

187187
export async function veoPredict(
188-
apiKey: string,
188+
apiKey: string | undefined,
189189
model: string,
190190
veoPredictRequest: VeoPredictRequest,
191191
clientOptions?: ClientOptions
@@ -208,7 +208,7 @@ export async function veoPredict(
208208
}
209209

210210
export async function veoCheckOperation(
211-
apiKey: string,
211+
apiKey: string | undefined,
212212
operation: string,
213213
clientOptions?: ClientOptions
214214
): Promise<VeoOperation> {
@@ -265,7 +265,7 @@ export function getGoogleAIUrl(params: {
265265

266266
function getFetchOptions(params: {
267267
method: 'POST' | 'GET';
268-
apiKey: string;
268+
apiKey: string | undefined;
269269
body?: string;
270270
clientOptions?: ClientOptions;
271271
}) {
@@ -310,7 +310,7 @@ function getAbortSignal(
310310
* @returns {HeadersInit} An object containing the headers to be included in the request.
311311
*/
312312
function getHeaders(
313-
apiKey: string,
313+
apiKey?: string,
314314
clientOptions?: ClientOptions
315315
): HeadersInit {
316316
let customHeaders = {};
@@ -322,10 +322,13 @@ function getHeaders(
322322
const headers: HeadersInit = {
323323
...customHeaders,
324324
'Content-Type': 'application/json',
325-
'x-goog-api-key': apiKey,
326325
'x-goog-api-client': getGenkitClientHeader(),
327326
};
328327

328+
if (apiKey) {
329+
headers['x-goog-api-key'] = apiKey;
330+
}
331+
329332
return headers;
330333
}
331334

‎js/plugins/google-genai/src/googleai/embedder.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { embedderRef } from 'genkit/embedder';
2626
import { embedder as pluginEmbedder } from 'genkit/plugin';
2727
import { embedContent } from './client.js';
2828
import {
29+
ClientOptions,
2930
EmbedContentRequest,
3031
GoogleAIPluginOptions,
3132
Model,
@@ -132,6 +133,11 @@ export function defineEmbedder(
132133
): EmbedderAction {
133134
checkApiKey(pluginOptions?.apiKey);
134135
const ref = model(name);
136+
const clientOptions: ClientOptions = {
137+
apiVersion: pluginOptions?.apiVersion,
138+
baseUrl: pluginOptions?.baseUrl,
139+
customHeaders: pluginOptions?.customHeaders,
140+
};
135141

136142
return pluginEmbedder(
137143
{
@@ -148,15 +154,20 @@ export function defineEmbedder(
148154

149155
const embeddings = await Promise.all(
150156
request.input.map(async (doc) => {
151-
const response = await embedContent(embedApiKey, embedVersion, {
152-
taskType: request.options?.taskType,
153-
title: request.options?.title,
154-
content: {
155-
role: '',
156-
parts: [{ text: doc.text }],
157-
},
158-
outputDimensionality: request.options?.outputDimensionality,
159-
} as EmbedContentRequest);
157+
const response = await embedContent(
158+
embedApiKey,
159+
embedVersion,
160+
{
161+
taskType: request.options?.taskType,
162+
title: request.options?.title,
163+
content: {
164+
role: '',
165+
parts: [{ text: doc.text }],
166+
},
167+
outputDimensionality: request.options?.outputDimensionality,
168+
} as EmbedContentRequest,
169+
clientOptions
170+
);
160171
const values = response.embedding.values;
161172
return { embedding: values };
162173
})

‎js/plugins/google-genai/src/googleai/gemini.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ export function defineModel(
575575
const clientOptions: ClientOptions = {
576576
apiVersion: pluginOptions?.apiVersion,
577577
baseUrl: pluginOptions?.baseUrl,
578+
customHeaders: pluginOptions?.customHeaders,
578579
};
579580

580581
const middleware: ModelMiddleware[] = [];

‎js/plugins/google-genai/src/googleai/imagen.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export function defineModel(
180180
const clientOptions: ClientOptions = {
181181
apiVersion: pluginOptions?.apiVersion,
182182
baseUrl: pluginOptions?.baseUrl,
183+
customHeaders: pluginOptions?.customHeaders,
183184
};
184185

185186
return pluginModel(

‎js/plugins/google-genai/src/googleai/types.ts��

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ export interface GoogleAIPluginOptions {
8080
experimental_debugTraces?: boolean;
8181
/** Use `responseSchema` field instead of `responseJsonSchema`. */
8282
legacyResponseSchema?: boolean;
83+
/**
84+
* Additional headers to send along with the request.
85+
*/
86+
customHeaders?: Record<string, string>;
8387
}
8488

8589
/**

‎js/plugins/google-genai/src/googleai/utils.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function checkApiKey(
102102
export function calculateApiKey(
103103
pluginApiKey: string | false | undefined,
104104
requestApiKey: string | undefined
105-
): string {
105+
): string | undefined {
106106
let apiKey: string | undefined;
107107

108108
// Don't get the key from the environment if pluginApiKey is false
@@ -113,7 +113,7 @@ export function calculateApiKey(
113113
apiKey = requestApiKey || apiKey;
114114

115115
if (pluginApiKey === false && !requestApiKey) {
116-
throw API_KEY_FALSE_ERROR;
116+
return undefined;
117117
}
118118

119119
if (!apiKey) {

‎js/plugins/google-genai/src/googleai/veo.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export function defineModel(
176176
const clientOptions: ClientOptions = {
177177
apiVersion: pluginOptions?.apiVersion,
178178
baseUrl: pluginOptions?.baseUrl,
179+
customHeaders: pluginOptions?.customHeaders,
179180
};
180181

181182
return pluginBackgroundModel({

‎js/plugins/google-genai/tests/googleai/embedder_test.ts‎

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import { Document, GenkitError } from 'genkit';
18+
import { Document } from 'genkit';
1919
import { afterEach, beforeEach, describe, it } from 'node:test';
2020
import * as sinon from 'sinon';
2121
import {
@@ -144,23 +144,16 @@ describe('defineGoogleAIEmbedder', () => {
144144
});
145145

146146
it('throws if apiKey is false in pluginOptions and not provided in call options', async () => {
147+
mockFetchResponse({ embedding: { values: [] } });
147148
const embedder = defineEmbedder('text-embedding-004', {
148149
apiKey: false,
149150
});
150-
await assert.rejects(
151-
embedder.run({
151+
assert.ok(
152+
await embedder.run({
152153
input: [new Document({ content: [{ text: 'test' }] })],
153-
}),
154-
(err: GenkitError) => {
155-
assert.strictEqual(err.status, 'INVALID_ARGUMENT');
156-
assert.match(
157-
err.message,
158-
/GoogleAI plugin was initialized with \{apiKey: false\}/
159-
);
160-
return true;
161-
}
154+
})
162155
);
163-
sinon.assert.notCalled(fetchStub);
156+
sinon.assert.calledOnce(fetchStub);
164157
});
165158

166159
it('uses API key from call options if apiKey is false in pluginOptions', async () => {

‎js/plugins/google-genai/tests/googleai/gemini_test.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,11 @@ describe('Google AI Gemini', () => {
139139
);
140140
});
141141

142-
it('throws if apiKey is false and not in call config', async () => {
142+
it('works if apiKey is false and not in call config', async () => {
143+
mockFetchResponse(defaultApiResponse);
143144
const model = defineModel('gemini-2.0-flash', { apiKey: false });
144-
await assert.rejects(
145-
model.run(minimalRequest),
146-
/GoogleAI plugin was initialized with \{apiKey: false\}/
147-
);
148-
sinon.assert.notCalled(fetchStub);
145+
assert.ok(await model.run(minimalRequest));
146+
sinon.assert.calledOnce(fetchStub);
149147
});
150148

151149
it('uses API key from call config if apiKey is false', async () => {

‎js/plugins/google-genai/tests/googleai/imagen_test.ts‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import {
3333
ImagenPredictResponse,
3434
ImagenPrediction,
3535
} from '../../src/googleai/types.js';
36-
import {
37-
API_KEY_FALSE_ERROR,
38-
MISSING_API_KEY_ERROR,
39-
} from '../../src/googleai/utils.js';
36+
import { MISSING_API_KEY_ERROR } from '../../src/googleai/utils.js';
4037

4138
const { toImagenParameters, fromImagenPrediction } = TEST_ONLY;
4239

@@ -394,20 +391,22 @@ describe('Google AI Imagen', () => {
394391
assert.strictEqual(fetchArgs[1].headers['x-goog-api-key'], requestApiKey);
395392
});
396393

397-
it('apiKey false at init, missing in request - throws error', async () => {
394+
it('works with apiKey false at init, missing in request', async () => {
395+
mockFetchResponse({
396+
predictions: [{ bytesBase64Encoded: 'jkl', mimeType: 'image/png' }],
397+
});
398398
const modelRunner = captureModelRunner({ apiKey: false });
399399

400-
await assert.rejects(
401-
modelRunner(
400+
assert.ok(
401+
await modelRunner(
402402
{
403403
messages: [{ role: 'user', content: [{ text: 'A car' }] }],
404404
config: {},
405405
},
406406
{}
407-
),
408-
API_KEY_FALSE_ERROR
407+
)
409408
);
410-
sinon.assert.notCalled(fetchStub);
409+
sinon.assert.calledOnce(fetchStub);
411410
});
412411

413412
it('defineImagenModel throws if key not found in env or args', async () => {

0 commit comments

Comments
 (0)