import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { applyAgentDefaultModelPrimary, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { normalizeAntigravityModelId, normalizeGoogleModelId } from "./model-id.js"; export { normalizeAntigravityModelId, normalizeGoogleModelId }; type GoogleApiCarrier = { api?: string | null; }; type GoogleProviderConfigLike = GoogleApiCarrier & { models?: ReadonlyArray | null; }; const DEFAULT_GOOGLE_API_HOST = "generativelanguage.googleapis.com"; export const DEFAULT_GOOGLE_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"; function trimTrailingSlashes(value: string): string { return value.replace(/\/+$/, ""); } export function normalizeGoogleApiBaseUrl(baseUrl?: string): string { const raw = trimTrailingSlashes(baseUrl?.trim() || DEFAULT_GOOGLE_API_BASE_URL); try { const url = new URL(raw); url.hash = ""; url.search = ""; if ( url.hostname.toLowerCase() === DEFAULT_GOOGLE_API_HOST && trimTrailingSlashes(url.pathname || "") === "" ) { url.pathname = "/v1beta"; } return trimTrailingSlashes(url.toString()); } catch { if (/^https:\/\/generativelanguage\.googleapis\.com\/?$/i.test(raw)) { return DEFAULT_GOOGLE_API_BASE_URL; } return raw; } } export function isGoogleGenerativeAiApi(api?: string | null): boolean { return api === "google-generative-ai"; } export function normalizeGoogleGenerativeAiBaseUrl(baseUrl?: string): string | undefined { return baseUrl ? normalizeGoogleApiBaseUrl(baseUrl) : baseUrl; } export function resolveGoogleGenerativeAiTransport(params: { api: TApi; baseUrl?: string; }): { api: TApi; baseUrl?: string } { return { api: params.api, baseUrl: isGoogleGenerativeAiApi(params.api) ? normalizeGoogleGenerativeAiBaseUrl(params.baseUrl) : params.baseUrl, }; } export function resolveGoogleGenerativeAiApiOrigin(baseUrl?: string): string { return normalizeGoogleApiBaseUrl(baseUrl).replace(/\/v1beta$/i, ""); } export function shouldNormalizeGoogleGenerativeAiProviderConfig( providerKey: string, provider: GoogleProviderConfigLike, ): boolean { if (providerKey === "google" || providerKey === "google-vertex") { return true; } if (isGoogleGenerativeAiApi(provider.api)) { return true; } return provider.models?.some((model) => isGoogleGenerativeAiApi(model?.api)) ?? false; } export function shouldNormalizeGoogleProviderConfig( providerKey: string, provider: GoogleProviderConfigLike, ): boolean { return ( providerKey === "google-antigravity" || shouldNormalizeGoogleGenerativeAiProviderConfig(providerKey, provider) ); } function normalizeProviderModels( provider: ModelProviderConfig, normalizeId: (id: string) => string, ): ModelProviderConfig { const models = provider.models; if (!Array.isArray(models) || models.length === 0) { return provider; } let mutated = false; const nextModels = models.map((model) => { const nextId = normalizeId(model.id); if (nextId === model.id) { return model; } mutated = true; return { ...model, id: nextId }; }); return mutated ? { ...provider, models: nextModels } : provider; } export function normalizeGoogleProviderConfig( providerKey: string, provider: ModelProviderConfig, ): ModelProviderConfig { let nextProvider = provider; if (shouldNormalizeGoogleGenerativeAiProviderConfig(providerKey, nextProvider)) { const modelNormalized = normalizeProviderModels(nextProvider, normalizeGoogleModelId); const normalizedBaseUrl = normalizeGoogleGenerativeAiBaseUrl(modelNormalized.baseUrl); nextProvider = normalizedBaseUrl !== modelNormalized.baseUrl ? { ...modelNormalized, baseUrl: normalizedBaseUrl ?? modelNormalized.baseUrl } : modelNormalized; } if (providerKey === "google-antigravity") { nextProvider = normalizeProviderModels(nextProvider, normalizeAntigravityModelId); } return nextProvider; } export function parseGeminiAuth(apiKey: string): { headers: Record } { 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 { // Fall back to API key mode. } } return { headers: { "x-goog-api-key": apiKey, "Content-Type": "application/json", }, }; } export const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3.1-pro-preview"; export function applyGoogleGeminiModelDefault(cfg: OpenClawConfig): { next: OpenClawConfig; changed: boolean; } { const current = cfg.agents?.defaults?.model as unknown; const currentPrimary = typeof current === "string" ? current.trim() || undefined : current && typeof current === "object" && typeof (current as { primary?: unknown }).primary === "string" ? ((current as { primary: string }).primary || "").trim() || undefined : undefined; if (currentPrimary === GOOGLE_GEMINI_DEFAULT_MODEL) { return { next: cfg, changed: false }; } return { next: applyAgentDefaultModelPrimary(cfg, GOOGLE_GEMINI_DEFAULT_MODEL), changed: true, }; }