mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix: honor Google image private-network opt-in
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Providers/Google: honor the private-network SSRF opt-in for Gemini image generation requests, so trusted proxy setups that resolve Google API hosts to private addresses can use `image_generate`. Fixes #67216.
|
||||
- Agents/transport: stop embedded runs from lowering the process-wide undici stream timeouts, so slow Gemini image generation and other long-running provider requests no longer inherit short run-attempt headers timeouts. Fixes #70423. Thanks @giangthb.
|
||||
- Providers/OpenRouter: send image-understanding prompts as user text before image parts, restoring non-empty vision responses for OpenRouter multimodal models. Fixes #70410.
|
||||
- Memory/QMD: recreate stale managed QMD collections when startup repair finds the collection name already exists, so root memory narrows back to `MEMORY.md` instead of staying on broad workspace markdown indexing.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ProviderRequestTransportOverrides } from "openclaw/plugin-sdk/provider-http";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isGoogleGenerativeAiApi,
|
||||
@@ -217,7 +216,7 @@ describe("google generative ai helpers", () => {
|
||||
expect(normalized).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
|
||||
});
|
||||
|
||||
it("rejects non-Google Gemini base URLs and ignores smuggled private-network flags", () => {
|
||||
it("rejects non-Google Gemini base URLs and honors explicit private-network opt-in", () => {
|
||||
expect(() =>
|
||||
resolveGoogleGenerativeAiHttpRequestConfig({
|
||||
apiKey: "api-key-123",
|
||||
@@ -241,8 +240,8 @@ describe("google generative ai helpers", () => {
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
capability: "image",
|
||||
transport: "http",
|
||||
request: { allowPrivateNetwork: true } as unknown as ProviderRequestTransportOverrides,
|
||||
request: { allowPrivateNetwork: true },
|
||||
});
|
||||
expect(config.allowPrivateNetwork).toBe(false);
|
||||
expect(config.allowPrivateNetwork).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,6 +42,10 @@ export {
|
||||
export { buildGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
|
||||
export { buildGoogleProvider } from "./provider-registration.js";
|
||||
|
||||
type GoogleGenerativeAiRequestOverrides = ProviderRequestTransportOverrides & {
|
||||
allowPrivateNetwork?: boolean;
|
||||
};
|
||||
|
||||
function resolveTrustedGoogleGenerativeAiBaseUrl(baseUrl?: string): string {
|
||||
const normalized =
|
||||
normalizeGoogleGenerativeAiBaseUrl(baseUrl ?? DEFAULT_GOOGLE_API_BASE_URL) ??
|
||||
@@ -69,14 +73,14 @@ export function resolveGoogleGenerativeAiHttpRequestConfig(params: {
|
||||
apiKey: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
request?: ProviderRequestTransportOverrides;
|
||||
request?: GoogleGenerativeAiRequestOverrides;
|
||||
capability: "image" | "audio" | "video";
|
||||
transport: "http" | "media-understanding";
|
||||
}) {
|
||||
return resolveProviderHttpRequestConfig({
|
||||
baseUrl: resolveTrustedGoogleGenerativeAiBaseUrl(params.baseUrl),
|
||||
defaultBaseUrl: DEFAULT_GOOGLE_API_BASE_URL,
|
||||
allowPrivateNetwork: false,
|
||||
allowPrivateNetwork: params.request?.allowPrivateNetwork,
|
||||
headers: params.headers,
|
||||
request: params.request,
|
||||
defaultHeaders: parseGeminiAuth(params.apiKey).headers,
|
||||
|
||||
@@ -278,6 +278,36 @@ describe("Google image-generation provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("honors configured private-network opt-in for Google image generation", async () => {
|
||||
mockGoogleApiKeyAuth();
|
||||
installGoogleFetchMock();
|
||||
const postJsonRequestSpy = vi.spyOn(providerHttp, "postJsonRequest");
|
||||
|
||||
const provider = buildGoogleImageGenerationProvider();
|
||||
await provider.generateImage({
|
||||
provider: "google",
|
||||
model: "gemini-3.1-flash-image-preview",
|
||||
prompt: "draw a fox",
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
google: {
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
request: { allowPrivateNetwork: true },
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(postJsonRequestSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
allowPrivateNetwork: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes a configured bare Google host to the v1beta API root", async () => {
|
||||
mockGoogleApiKeyAuth();
|
||||
const fetchMock = installGoogleFetchMock();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
assertOkOrThrowHttpError,
|
||||
postJsonRequest,
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { normalizeGoogleModelId, resolveGoogleGenerativeAiHttpRequestConfig } from "./api.js";
|
||||
|
||||
@@ -132,6 +136,9 @@ export function buildGoogleImageGenerationProvider(): ImageGenerationProvider {
|
||||
resolveGoogleGenerativeAiHttpRequestConfig({
|
||||
apiKey: auth.apiKey,
|
||||
baseUrl: req.cfg?.models?.providers?.google?.baseUrl,
|
||||
request: sanitizeConfiguredModelProviderRequest(
|
||||
req.cfg?.models?.providers?.google?.request,
|
||||
),
|
||||
capability: "image",
|
||||
transport: "http",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user