fix(gemini): reuse google provider config for web search

This commit is contained in:
Peter Steinberger
2026-05-02 05:14:51 +01:00
parent 7dc5b9484f
commit ed6df7dd8b
18 changed files with 504 additions and 48 deletions

View File

@@ -155,7 +155,8 @@ function resolveGeminiTimeRangeFilter(
export function resolveGeminiRuntimeApiKey(gemini?: GeminiConfig): string | undefined {
return (
readConfiguredSecretString(gemini?.apiKey, "tools.web.search.gemini.apiKey") ??
readProviderEnvValue(["GEMINI_API_KEY"])
readProviderEnvValue(["GEMINI_API_KEY"]) ??
readConfiguredSecretString(gemini?.providerApiKey, "models.providers.google.apiKey")
);
}
@@ -267,7 +268,7 @@ export async function executeGeminiSearch(
return {
error: "missing_gemini_api_key",
message:
"web_search (gemini) needs an API key. Set GEMINI_API_KEY in the Gateway environment, or configure tools.web.search.gemini.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.",
"web_search (gemini) needs an API key. Set GEMINI_API_KEY in the Gateway environment, configure plugins.entries.google.config.webSearch.apiKey, or reuse models.providers.google.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.",
docs: "https://docs.openclaw.ai/tools/web",
};
}

View File

@@ -6,6 +6,8 @@ export type GeminiConfig = {
apiKey?: unknown;
baseUrl?: unknown;
model?: unknown;
providerApiKey?: unknown;
providerBaseUrl?: unknown;
};
function isRecord(value: unknown): value is Record<string, unknown> {
@@ -25,7 +27,11 @@ export function resolveGeminiApiKey(
gemini?: GeminiConfig,
env: Record<string, string | undefined> = process.env,
): string | undefined {
return trimToUndefined(gemini?.apiKey) ?? trimToUndefined(env.GEMINI_API_KEY);
return (
trimToUndefined(gemini?.apiKey) ??
trimToUndefined(env.GEMINI_API_KEY) ??
trimToUndefined(gemini?.providerApiKey)
);
}
export function resolveGeminiModel(gemini?: GeminiConfig): string {
@@ -33,5 +39,7 @@ export function resolveGeminiModel(gemini?: GeminiConfig): string {
}
export function resolveGeminiBaseUrl(gemini?: GeminiConfig): string {
return normalizeGoogleApiBaseUrl(trimToUndefined(gemini?.baseUrl));
return normalizeGoogleApiBaseUrl(
trimToUndefined(gemini?.baseUrl) ?? trimToUndefined(gemini?.providerBaseUrl),
);
}

View File

@@ -1,3 +1,4 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import {
createWebSearchProviderContractFields,
mergeScopedSearchConfig,
@@ -12,6 +13,7 @@ import {
} from "./gemini-web-search-provider.shared.js";
const GEMINI_CREDENTIAL_PATH = "plugins.entries.google.config.webSearch.apiKey";
const GOOGLE_PROVIDER_CREDENTIAL_PATH = "models.providers.google.apiKey";
type GeminiWebSearchRuntime = typeof import("./gemini-web-search-provider.runtime.js");
@@ -64,7 +66,54 @@ function createGeminiToolDefinition(
};
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function resolveGoogleModelProviderConfig(
config?: OpenClawConfig,
): Record<string, unknown> | undefined {
const provider = config?.models?.providers?.google;
return isRecord(provider) ? provider : undefined;
}
function getGoogleModelProviderCredentialFallback(
config?: OpenClawConfig,
): { path: string; value: unknown } | undefined {
const provider = resolveGoogleModelProviderConfig(config);
return provider && provider.apiKey !== undefined
? { path: GOOGLE_PROVIDER_CREDENTIAL_PATH, value: provider.apiKey }
: undefined;
}
function withGoogleModelProviderFallbacks(
searchConfig: Record<string, unknown> | undefined,
config?: OpenClawConfig,
): Record<string, unknown> | undefined {
const provider = resolveGoogleModelProviderConfig(config);
if (!provider || (provider.apiKey === undefined && provider.baseUrl === undefined)) {
return searchConfig;
}
const gemini = isRecord(searchConfig?.gemini) ? { ...searchConfig.gemini } : {};
if (provider.apiKey !== undefined) {
gemini.providerApiKey = provider.apiKey;
}
if (provider.baseUrl !== undefined) {
gemini.providerBaseUrl = provider.baseUrl;
}
return {
...(searchConfig ?? {}),
gemini,
};
}
export function createGeminiWebSearchProvider(): WebSearchProviderPlugin {
const contractFields = createWebSearchProviderContractFields({
credentialPath: GEMINI_CREDENTIAL_PATH,
searchCredential: { type: "scoped", scopeId: "gemini" },
configuredCredential: { pluginId: "google" },
});
return {
id: "gemini",
label: "Gemini (Google Search)",
@@ -77,17 +126,17 @@ export function createGeminiWebSearchProvider(): WebSearchProviderPlugin {
docsUrl: "https://docs.openclaw.ai/tools/web",
autoDetectOrder: 20,
credentialPath: GEMINI_CREDENTIAL_PATH,
...createWebSearchProviderContractFields({
credentialPath: GEMINI_CREDENTIAL_PATH,
searchCredential: { type: "scoped", scopeId: "gemini" },
configuredCredential: { pluginId: "google" },
}),
...contractFields,
getConfiguredCredentialFallback: getGoogleModelProviderCredentialFallback,
createTool: (ctx) =>
createGeminiToolDefinition(
mergeScopedSearchConfig(
ctx.searchConfig,
"gemini",
resolveProviderWebSearchPluginConfig(ctx.config, "google"),
withGoogleModelProviderFallbacks(
mergeScopedSearchConfig(
ctx.searchConfig,
"gemini",
resolveProviderWebSearchPluginConfig(ctx.config, "google"),
),
ctx.config,
),
),
};