Skip to content

Commit b73e135

Browse files
1052326311xin
andauthored
fix: resolve google provider default API to google-generative-ai (#88480) (#88512)
When a configured Google provider/model row had no explicit but had a baseUrl set, the fallback defaulted to openai-completions, causing Gemini requests to route through the OpenAI Responses transport instead of the native @google/genai transport. Made resolveConfiguredProviderDefaultApi provider-aware: for the google provider, the default API is now google-generative-ai. Root cause: the generic fallback assumed any provider with a baseUrl should use openai-completions, which is incorrect for Google's native Gemini API. Co-authored-by: xin <1052326311+xin@users.noreply.github.com>
1 parent 9b6c981 commit b73e135

6 files changed

Lines changed: 85 additions & 11 deletions

File tree

extensions/google/api.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ describe("google generative ai helpers", () => {
8686
});
8787

8888
it("normalizes transport baseUrls only for Google Generative AI", () => {
89+
expect(
90+
resolveGoogleGenerativeAiTransport({
91+
provider: "google",
92+
api: undefined,
93+
baseUrl: "https://generativelanguage.googleapis.com",
94+
}),
95+
).toEqual({
96+
api: "google-generative-ai",
97+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
98+
});
8999
expect(
90100
resolveGoogleGenerativeAiTransport({
91101
api: "google-generative-ai",

extensions/google/provider-policy.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,16 @@ export function normalizeGoogleGenerativeAiBaseUrl(baseUrl?: string): string | u
8282
}
8383

8484
export function resolveGoogleGenerativeAiTransport<TApi extends string | null | undefined>(params: {
85+
provider?: string;
8586
api: TApi;
8687
baseUrl?: string;
87-
}): { api: TApi; baseUrl?: string } {
88+
}): { api: TApi | "google-generative-ai"; baseUrl?: string } {
89+
const api =
90+
params.api ??
91+
(params.provider === "google" && params.baseUrl ? "google-generative-ai" : params.api);
8892
return {
89-
api: params.api,
90-
baseUrl: isGoogleGenerativeAiApi(params.api)
93+
api,
94+
baseUrl: isGoogleGenerativeAiApi(api)
9195
? normalizeGoogleGenerativeAiBaseUrl(params.baseUrl)
9296
: params.baseUrl,
9397
};

extensions/google/provider-registration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export function buildGoogleProvider(): ProviderPlugin {
4444
},
4545
}),
4646
],
47-
normalizeTransport: ({ api, baseUrl }) => resolveGoogleGenerativeAiTransport({ api, baseUrl }),
47+
normalizeTransport: ({ provider, api, baseUrl }) =>
48+
resolveGoogleGenerativeAiTransport({ provider, api, baseUrl }),
4849
normalizeConfig: ({ provider, providerConfig }) =>
4950
normalizeGoogleProviderConfig(provider, providerConfig),
5051
staticCatalog: {

src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ function normalizeTransport(params: {
172172
baseUrl: GOOGLE_GENERATIVE_AI_BASE_URL,
173173
};
174174
}
175+
if (
176+
params.provider === "google" &&
177+
params.context.api == null &&
178+
params.context.baseUrl === "https://generativelanguage.googleapis.com"
179+
) {
180+
return {
181+
api: "google-generative-ai",
182+
baseUrl: GOOGLE_GENERATIVE_AI_BASE_URL,
183+
};
184+
}
175185
if (isNativeOpenAiTransport) {
176186
return {
177187
api: "openai-responses",

src/agents/embedded-agent-runner/model.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,27 @@ describe("resolveModel", () => {
989989
expect(model.api).toBe("openai-completions");
990990
});
991991

992+
it("defaults baseUrl-only Google fallback models to native Gemini transport", () => {
993+
const cfg = {
994+
models: {
995+
providers: {
996+
google: {
997+
baseUrl: "https://generativelanguage.googleapis.com",
998+
models: [],
999+
},
1000+
},
1001+
},
1002+
} as unknown as OpenClawConfig;
1003+
1004+
const result = resolveModelForTest("google", "gemini-2.5-flash-lite", "/tmp/agent", cfg);
1005+
const model = expectResolvedModel(result);
1006+
1007+
expect(model.provider).toBe("google");
1008+
expect(model.id).toBe("gemini-2.5-flash-lite");
1009+
expect(model.api).toBe("google-generative-ai");
1010+
expect(model.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
1011+
});
1012+
9921013
it("uses bundled static metadata for configured provider fallback token limits", () => {
9931014
resolveBundledStaticCatalogModelMock.mockReturnValueOnce({
9941015
provider: "xiaomi-token-plan",

src/agents/embedded-agent-runner/model.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,30 @@ function resolveProviderTransport(params: {
338338
};
339339
}
340340

341-
function resolveConfiguredProviderDefaultApi(
342-
providerConfig: InlineProviderConfig | undefined,
343-
): Api | undefined {
341+
function resolveConfiguredProviderDefaultApi(params: {
342+
provider: string;
343+
providerConfig: InlineProviderConfig | undefined;
344+
cfg?: OpenClawConfig;
345+
workspaceDir?: string;
346+
runtimeHooks?: ProviderRuntimeHooks;
347+
}): Api | undefined {
348+
const { providerConfig } = params;
344349
const explicit = normalizeResolvedTransportApi(providerConfig?.api);
345350
if (explicit) {
346351
return explicit;
347352
}
348-
return providerConfig?.baseUrl ? "openai-completions" : undefined;
353+
if (!providerConfig?.baseUrl) {
354+
return undefined;
355+
}
356+
const normalized = resolveProviderTransport({
357+
provider: params.provider,
358+
api: undefined,
359+
baseUrl: providerConfig.baseUrl,
360+
cfg: params.cfg,
361+
workspaceDir: params.workspaceDir,
362+
runtimeHooks: params.runtimeHooks,
363+
});
364+
return normalized.api ?? "openai-completions";
349365
}
350366

351367
function resolveProviderRequestTimeoutMs(timeoutSeconds: unknown): number | undefined {
@@ -660,7 +676,13 @@ function applyConfiguredProviderOverrides(params: {
660676
input: metadataOverrideModel?.input,
661677
fallbackInput: discoveredModel.input,
662678
});
663-
const providerDefaultApi = resolveConfiguredProviderDefaultApi(providerConfig);
679+
const providerDefaultApi = resolveConfiguredProviderDefaultApi({
680+
provider: params.provider,
681+
providerConfig,
682+
cfg: params.cfg,
683+
workspaceDir: params.workspaceDir,
684+
runtimeHooks: params.runtimeHooks,
685+
});
664686
const resolvedTransportApi =
665687
metadataOverrideModel?.api ??
666688
(params.preferDiscoveredTransport
@@ -697,7 +719,7 @@ function applyConfiguredProviderOverrides(params: {
697719
api:
698720
resolvedTransport.api ??
699721
normalizeResolvedTransportApi(discoveredModel.api) ??
700-
resolveConfiguredProviderDefaultApi(providerConfig) ??
722+
providerDefaultApi ??
701723
"openai-responses",
702724
baseUrl: resolvedTransport.baseUrl ?? discoveredModel.baseUrl,
703725
discoveredHeaders,
@@ -1040,7 +1062,13 @@ function resolveConfiguredFallbackModel(params: {
10401062
provider,
10411063
api:
10421064
normalizeResolvedTransportApi(configuredModel?.api) ??
1043-
resolveConfiguredProviderDefaultApi(providerConfig) ??
1065+
resolveConfiguredProviderDefaultApi({
1066+
provider,
1067+
providerConfig,
1068+
cfg,
1069+
workspaceDir,
1070+
runtimeHooks,
1071+
}) ??
10441072
normalizeResolvedTransportApi(staticCatalogModel?.api) ??
10451073
"openai-responses",
10461074
baseUrl: configuredModel?.baseUrl ?? providerConfig?.baseUrl ?? staticCatalogModel?.baseUrl,

0 commit comments

Comments
 (0)