import type { ProviderResolveDynamicModelContext, ProviderRuntimeModel, } from "openclaw/plugin-sdk/plugin-entry"; import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared"; const GOOGLE_GEMINI_CLI_PROVIDER_ID = "google-gemini-cli"; const GEMINI_2_5_PRO_PREFIX = "gemini-2.5-pro"; const GEMINI_2_5_FLASH_LITE_PREFIX = "gemini-2.5-flash-lite"; const GEMINI_2_5_FLASH_PREFIX = "gemini-2.5-flash"; const GEMINI_3_1_PRO_PREFIX = "gemini-3.1-pro"; const GEMINI_3_1_FLASH_LITE_PREFIX = "gemini-3.1-flash-lite"; const GEMINI_3_1_FLASH_PREFIX = "gemini-3.1-flash"; const GEMMA_PREFIX = "gemma-"; const GEMINI_2_5_PRO_TEMPLATE_IDS = ["gemini-2.5-pro"] as const; const GEMINI_2_5_FLASH_LITE_TEMPLATE_IDS = ["gemini-2.5-flash-lite"] as const; const GEMINI_2_5_FLASH_TEMPLATE_IDS = ["gemini-2.5-flash"] as const; const GEMINI_3_1_PRO_TEMPLATE_IDS = ["gemini-3-pro-preview"] as const; const GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS = ["gemini-3.1-flash-lite-preview"] as const; const GEMINI_3_1_FLASH_TEMPLATE_IDS = ["gemini-3-flash-preview"] as const; // Gemma uses the Gemini flash template as a forward-compat approximation // until a dedicated Gemma template is registered in the catalog. const GEMMA_TEMPLATE_IDS = GEMINI_3_1_FLASH_TEMPLATE_IDS; type GoogleForwardCompatFamily = { googleTemplateIds: readonly string[]; cliTemplateIds: readonly string[]; preferExternalFirstForCli?: boolean; }; type GoogleTemplateSource = { templateProviderId: string; templateIds: readonly string[]; }; function cloneGoogleTemplateModel(params: { providerId: string; modelId: string; templateProviderId: string; templateIds: readonly string[]; ctx: ProviderResolveDynamicModelContext; patch?: Partial; }): ProviderRuntimeModel | undefined { return cloneFirstTemplateModel({ providerId: params.templateProviderId, modelId: params.modelId, templateIds: params.templateIds, ctx: params.ctx, patch: { ...params.patch, provider: params.providerId, }, }); } function isGoogleGeminiCliProvider(providerId: string): boolean { return providerId.trim().toLowerCase() === GOOGLE_GEMINI_CLI_PROVIDER_ID; } function templateIdsForProvider( templateProviderId: string, family: GoogleForwardCompatFamily, ): readonly string[] { return isGoogleGeminiCliProvider(templateProviderId) ? family.cliTemplateIds : family.googleTemplateIds; } function buildGoogleTemplateSources(params: { providerId: string; templateProviderId?: string; family: GoogleForwardCompatFamily; }): GoogleTemplateSource[] { const defaultTemplateProviderId = params.templateProviderId?.trim() ? params.templateProviderId : isGoogleGeminiCliProvider(params.providerId) ? "google" : GOOGLE_GEMINI_CLI_PROVIDER_ID; const preferredExternalFirst = isGoogleGeminiCliProvider(params.providerId) && params.family.preferExternalFirstForCli === true; const orderedTemplateProviderIds = preferredExternalFirst ? [defaultTemplateProviderId, params.providerId] : [params.providerId, defaultTemplateProviderId]; const seen = new Set(); const sources: GoogleTemplateSource[] = []; for (const providerId of orderedTemplateProviderIds) { const trimmed = providerId?.trim(); if (!trimmed || seen.has(trimmed)) { continue; } seen.add(trimmed); sources.push({ templateProviderId: trimmed, templateIds: templateIdsForProvider(trimmed, params.family), }); } return sources; } export function resolveGoogleGeminiForwardCompatModel(params: { providerId: string; templateProviderId?: string; ctx: ProviderResolveDynamicModelContext; }): ProviderRuntimeModel | undefined { const trimmed = params.ctx.modelId.trim(); const lower = trimmed.toLowerCase(); let family: GoogleForwardCompatFamily; let patch: Partial | undefined; if (lower.startsWith(GEMINI_2_5_PRO_PREFIX)) { family = { googleTemplateIds: GEMINI_2_5_PRO_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_PRO_TEMPLATE_IDS, preferExternalFirstForCli: true, }; } else if (lower.startsWith(GEMINI_2_5_FLASH_LITE_PREFIX)) { family = { googleTemplateIds: GEMINI_2_5_FLASH_LITE_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS, preferExternalFirstForCli: true, }; } else if (lower.startsWith(GEMINI_2_5_FLASH_PREFIX)) { family = { googleTemplateIds: GEMINI_2_5_FLASH_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS, preferExternalFirstForCli: true, }; } else if (lower.startsWith(GEMINI_3_1_PRO_PREFIX)) { family = { googleTemplateIds: GEMINI_3_1_PRO_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_PRO_TEMPLATE_IDS, }; if (params.providerId === "google" || params.providerId === GOOGLE_GEMINI_CLI_PROVIDER_ID) { patch = { reasoning: true }; } } else if (lower.startsWith(GEMINI_3_1_FLASH_LITE_PREFIX)) { family = { googleTemplateIds: GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS, }; } else if (lower.startsWith(GEMINI_3_1_FLASH_PREFIX)) { family = { googleTemplateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS, cliTemplateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS, }; } else if (lower.startsWith(GEMMA_PREFIX)) { family = { googleTemplateIds: GEMMA_TEMPLATE_IDS, cliTemplateIds: GEMMA_TEMPLATE_IDS, }; patch = { reasoning: false }; } else { return undefined; } for (const source of buildGoogleTemplateSources({ providerId: params.providerId, templateProviderId: params.templateProviderId, family, })) { const model = cloneGoogleTemplateModel({ providerId: params.providerId, modelId: trimmed, templateProviderId: source.templateProviderId, templateIds: source.templateIds, ctx: params.ctx, patch, }); if (model) { return model; } } return undefined; } export function isModernGoogleModel(modelId: string): boolean { const lower = modelId.trim().toLowerCase(); return ( lower.startsWith("gemini-2.5") || lower.startsWith("gemini-3") || lower.startsWith(GEMMA_PREFIX) ); }