fix(google): honor models.providers.google.request.allowPrivateNetwork in TTS

Image generation and media understanding both thread the
sanitized models.providers.google.request config (including
allowPrivateNetwork) into resolveGoogleGenerativeAiHttpRequestConfig.
Speech synthesis omitted that arg, so TTS always saw
allowPrivateNetwork: false regardless of config — silently falling
back to a different speech provider when the configured Google TTS
endpoint resolved to a private/internal IP (proxies, custom backends,
test mocks).

Mirror the image-generation-provider pattern: thread request through
synthesizeGoogleTtsPcm at both call sites (synthesize and
synthesizeTelephony).

Follow-up to #67216.
This commit is contained in:
Rohan Shiralkar
2026-04-26 00:51:49 +05:30
committed by Peter Steinberger
parent 6e1017d88a
commit cab66c5556
2 changed files with 42 additions and 1 deletions

View File

@@ -1,3 +1,4 @@
import * as providerHttp from "openclaw/plugin-sdk/provider-http";
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildGoogleSpeechProvider, __testing } from "./speech-provider.js";
@@ -315,4 +316,32 @@ describe("Google speech provider", () => {
"Google TTS failed (429): Quota exceeded [code=RESOURCE_EXHAUSTED] [request_id=google_req_123]",
);
});
it("honors configured private-network opt-in for Google TTS", async () => {
installGoogleTtsFetchMock();
const postJsonRequestSpy = vi.spyOn(providerHttp, "postJsonRequest");
const provider = buildGoogleSpeechProvider();
await provider.synthesize({
text: "hello",
cfg: {
models: {
providers: {
google: {
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
request: { allowPrivateNetwork: true },
models: [],
},
},
},
},
providerConfig: { apiKey: "google-test-key" },
target: "audio-file",
timeoutMs: 12_345,
});
expect(postJsonRequestSpy).toHaveBeenCalledWith(
expect.objectContaining({ allowPrivateNetwork: true }),
);
});
});

View File

@@ -1,4 +1,8 @@
import { assertOkOrThrowProviderError, postJsonRequest } from "openclaw/plugin-sdk/provider-http";
import {
assertOkOrThrowProviderError,
postJsonRequest,
sanitizeConfiguredModelProviderRequest,
} from "openclaw/plugin-sdk/provider-http";
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard";
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import type {
@@ -264,6 +268,7 @@ async function synthesizeGoogleTtsPcm(params: {
text: string;
apiKey: string;
baseUrl?: string;
request?: ReturnType<typeof sanitizeConfiguredModelProviderRequest>;
model: string;
voiceName: string;
audioProfile?: string;
@@ -274,6 +279,7 @@ async function synthesizeGoogleTtsPcm(params: {
resolveGoogleGenerativeAiHttpRequestConfig({
apiKey: params.apiKey,
baseUrl: params.baseUrl,
request: params.request,
capability: "audio",
transport: "http",
});
@@ -379,6 +385,9 @@ export function buildGoogleSpeechProvider(): SpeechProviderPlugin {
text: req.text,
apiKey,
baseUrl: resolveGoogleTtsBaseUrl({ cfg: req.cfg, providerConfig: config }),
request: sanitizeConfiguredModelProviderRequest(
req.cfg?.models?.providers?.google?.request,
),
model: normalizeGoogleTtsModel(overrides.model ?? config.model),
voiceName: normalizeGoogleTtsVoiceName(overrides.voiceName ?? config.voiceName),
audioProfile: overrides.audioProfile ?? config.audioProfile,
@@ -405,6 +414,9 @@ export function buildGoogleSpeechProvider(): SpeechProviderPlugin {
text: req.text,
apiKey,
baseUrl: resolveGoogleTtsBaseUrl({ cfg: req.cfg, providerConfig: config }),
request: sanitizeConfiguredModelProviderRequest(
req.cfg?.models?.providers?.google?.request,
),
model: config.model,
voiceName: config.voiceName,
audioProfile: config.audioProfile,