mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 19:01:44 +00:00
185 lines
5.4 KiB
TypeScript
185 lines
5.4 KiB
TypeScript
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-models";
|
|
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<GoogleApiCarrier | null | undefined> | 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<TApi extends string | null | undefined>(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<string, string> } {
|
|
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,
|
|
};
|
|
}
|