From 62583e22357ae0d5ef8b920184392eb596e82909 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 5 Apr 2026 19:01:52 -0400 Subject: [PATCH] fix(google): restore forward-compat provider hooks --- extensions/google/gemini-cli-provider.ts | 1 - extensions/google/provider-models.ts | 118 ++++++++++++++++++++--- src/infra/provider-usage.shared.ts | 2 + src/infra/provider-usage.types.ts | 1 + 4 files changed, 109 insertions(+), 13 deletions(-) diff --git a/extensions/google/gemini-cli-provider.ts b/extensions/google/gemini-cli-provider.ts index 9e1c8b7dfc8..456b36ca7af 100644 --- a/extensions/google/gemini-cli-provider.ts +++ b/extensions/google/gemini-cli-provider.ts @@ -114,7 +114,6 @@ export function registerGoogleGeminiCliProvider(api: OpenClawPluginApi) { resolveDynamicModel: (ctx) => resolveGoogleGeminiForwardCompatModel({ providerId: PROVIDER_ID, - templateProviderId: "google", ctx, }), ...GOOGLE_GEMINI_CLI_PROVIDER_HOOKS, diff --git a/extensions/google/provider-models.ts b/extensions/google/provider-models.ts index 4fd2be925c2..957d3e48d3f 100644 --- a/extensions/google/provider-models.ts +++ b/extensions/google/provider-models.ts @@ -10,6 +10,7 @@ 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 GOOGLE_GEMINI_CLI_PROVIDER_ID = "google-gemini-cli"; 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; @@ -18,18 +19,26 @@ const GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS = ["gemini-3.1-flash-lite-preview"] as const GEMINI_3_1_FLASH_TEMPLATE_IDS = ["gemini-3-flash-preview"] as const; 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.providerId, + providerId: params.templateProviderId, modelId: params.modelId, templateIds: params.templateIds, ctx: params.ctx, @@ -40,36 +49,121 @@ function cloneGoogleTemplateModel(params: { }); } +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 = { templateIds: GEMINI_2_5_PRO_TEMPLATE_IDS }; + 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 = { templateIds: GEMINI_2_5_FLASH_LITE_TEMPLATE_IDS }; + 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 = { templateIds: GEMINI_2_5_FLASH_TEMPLATE_IDS }; + 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 = { templateIds: GEMINI_3_1_PRO_TEMPLATE_IDS }; + 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 = { templateIds: GEMINI_3_1_FLASH_LITE_TEMPLATE_IDS }; + 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 = { templateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS }; + family = { + googleTemplateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS, + cliTemplateIds: GEMINI_3_1_FLASH_TEMPLATE_IDS, + }; } else { return undefined; } - return cloneGoogleTemplateModel({ + for (const source of buildGoogleTemplateSources({ providerId: params.providerId, - modelId: trimmed, - templateIds: family.templateIds, - ctx: params.ctx, - }); + 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 { diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 2c9e27ead5b..5f38ad9f30d 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -10,6 +10,7 @@ export const DEFAULT_TIMEOUT_MS = 5000; export const PROVIDER_LABELS: Record = { anthropic: "Claude", "github-copilot": "Copilot", + "google-gemini-cli": "Gemini", minimax: "MiniMax", "openai-codex": "Codex", xiaomi: "Xiaomi", @@ -19,6 +20,7 @@ export const PROVIDER_LABELS: Record = { export const usageProviders: UsageProviderId[] = [ "anthropic", "github-copilot", + "google-gemini-cli", "minimax", "openai-codex", "xiaomi", diff --git a/src/infra/provider-usage.types.ts b/src/infra/provider-usage.types.ts index fff0fda3d3a..af5e2e93c8b 100644 --- a/src/infra/provider-usage.types.ts +++ b/src/infra/provider-usage.types.ts @@ -20,6 +20,7 @@ export type UsageSummary = { export type UsageProviderId = | "anthropic" | "github-copilot" + | "google-gemini-cli" | "minimax" | "openai-codex" | "xiaomi"