diff --git a/src/infra/gemini-auth.ts b/src/infra/gemini-auth.ts new file mode 100644 index 00000000000..3ab9b8ddd6e --- /dev/null +++ b/src/infra/gemini-auth.ts @@ -0,0 +1,40 @@ +/** + * Shared Gemini authentication utilities. + * + * Supports both traditional API keys and OAuth JSON format. + */ + +/** + * Parse Gemini API key and return appropriate auth headers. + * + * OAuth format: `{"token": "...", "projectId": "..."}` + * + * @param apiKey - Either a traditional API key string or OAuth JSON + * @returns Headers object with appropriate authentication + */ +export function parseGeminiAuth(apiKey: string): { headers: Record } { + // Try parsing as OAuth JSON format + if (apiKey.startsWith("{")) { + try { + const parsed = JSON.parse(apiKey) as { token?: string; projectId?: string }; + if (typeof parsed.token === "string" && parsed.token) { + return { + headers: { + Authorization: `Bearer ${parsed.token}`, + "Content-Type": "application/json", + }, + }; + } + } catch { + // Parse failed, fallback to API key mode + } + } + + // Default: traditional API key + return { + headers: { + "x-goog-api-key": apiKey, + "Content-Type": "application/json", + }, + }; +} diff --git a/src/media-understanding/providers/google/inline-data.ts b/src/media-understanding/providers/google/inline-data.ts index 39c1093644a..e83b52ac102 100644 --- a/src/media-understanding/providers/google/inline-data.ts +++ b/src/media-understanding/providers/google/inline-data.ts @@ -1,4 +1,5 @@ import { normalizeGoogleModelId } from "../../../agents/models-config.providers.js"; +import { parseGeminiAuth } from "../../../infra/gemini-auth.js"; import { assertOkOrThrowHttpError, fetchWithTimeoutGuarded, normalizeBaseUrl } from "../shared.js"; export async function generateGeminiInlineDataText(params: { @@ -30,12 +31,12 @@ export async function generateGeminiInlineDataText(params: { })(); const url = `${baseUrl}/models/${model}:generateContent`; + const authHeaders = parseGeminiAuth(params.apiKey); const headers = new Headers(params.headers); - if (!headers.has("content-type")) { - headers.set("content-type", "application/json"); - } - if (!headers.has("x-goog-api-key")) { - headers.set("x-goog-api-key", params.apiKey); + for (const [key, value] of Object.entries(authHeaders.headers)) { + if (!headers.has(key)) { + headers.set(key, value); + } } const prompt = (() => { diff --git a/src/memory/embeddings-gemini.ts b/src/memory/embeddings-gemini.ts index a1b16e5dc10..8c4f1a66fb4 100644 --- a/src/memory/embeddings-gemini.ts +++ b/src/memory/embeddings-gemini.ts @@ -1,6 +1,7 @@ import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js"; import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js"; import { isTruthyEnvValue } from "../infra/env.js"; +import { parseGeminiAuth } from "../infra/gemini-auth.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; export type GeminiEmbeddingClient = { @@ -37,39 +38,6 @@ function resolveRemoteApiKey(remoteApiKey?: string): string | undefined { return trimmed; } -/** - * Parse Gemini API key and return appropriate auth headers. - * Supports both traditional API keys and OAuth JSON format. - * - * OAuth format: `{"token": "...", "projectId": "..."}` - */ -function parseGeminiAuth(apiKey: string): { headers: Record } { - // Try parsing as OAuth JSON format - if (apiKey.startsWith("{")) { - try { - const parsed = JSON.parse(apiKey) as { token?: string; projectId?: string }; - if (typeof parsed.token === "string" && parsed.token) { - return { - headers: { - Authorization: `Bearer ${parsed.token}`, - "Content-Type": "application/json", - }, - }; - } - } catch { - // Parse failed, fallback to API key mode - } - } - - // Default: traditional API key - return { - headers: { - "x-goog-api-key": apiKey, - "Content-Type": "application/json", - }, - }; -} - function normalizeGeminiModel(model: string): string { const trimmed = model.trim(); if (!trimmed) {