From 667c03f87e710110d5f45b1fa30ddc14bd2d28f8 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 29 May 2026 07:47:10 +0200 Subject: [PATCH] refactor: share fal provider http auth --- extensions/fal/http-config.ts | 48 +++++++++++++++++++++ extensions/fal/image-generation-provider.ts | 27 +----------- extensions/fal/music-generation-provider.ts | 36 ++-------------- extensions/fal/video-generation-provider.ts | 30 ++----------- 4 files changed, 56 insertions(+), 85 deletions(-) create mode 100644 extensions/fal/http-config.ts diff --git a/extensions/fal/http-config.ts b/extensions/fal/http-config.ts new file mode 100644 index 00000000000..d1f6b4d2cbe --- /dev/null +++ b/extensions/fal/http-config.ts @@ -0,0 +1,48 @@ +import { type AuthProfileStore, type OpenClawConfig } from "openclaw/plugin-sdk/provider-auth"; +import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; +import { + resolveProviderHttpRequestConfig, + type ProviderRequestCapability, +} from "openclaw/plugin-sdk/provider-http"; +import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; + +const DEFAULT_FAL_BASE_URL = "https://fal.run"; + +type FalAuthenticatedRequest = { + cfg?: OpenClawConfig; + agentDir?: string; + authStore?: AuthProfileStore; +}; + +function resolveFalConfiguredBaseUrl(cfg?: OpenClawConfig): string | undefined { + return normalizeOptionalString(cfg?.models?.providers?.fal?.baseUrl); +} + +export async function resolveFalHttpRequestConfig(params: { + req: FalAuthenticatedRequest; + baseUrl?: string; + capability: ProviderRequestCapability; +}): Promise> { + const auth = await resolveApiKeyForProvider({ + provider: "fal", + cfg: params.req.cfg, + agentDir: params.req.agentDir, + store: params.req.authStore, + }); + if (!auth.apiKey) { + throw new Error("fal API key missing"); + } + + return resolveProviderHttpRequestConfig({ + baseUrl: params.baseUrl ?? resolveFalConfiguredBaseUrl(params.req.cfg), + defaultBaseUrl: DEFAULT_FAL_BASE_URL, + allowPrivateNetwork: false, + defaultHeaders: { + Authorization: `Key ${auth.apiKey}`, + "Content-Type": "application/json", + }, + provider: "fal", + capability: params.capability, + transport: "http", + }); +} diff --git a/extensions/fal/image-generation-provider.ts b/extensions/fal/image-generation-provider.ts index 0638d9f1960..d3ea2c71dfc 100644 --- a/extensions/fal/image-generation-provider.ts +++ b/extensions/fal/image-generation-provider.ts @@ -8,11 +8,9 @@ import { toImageDataUrl, } 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, assertOkOrThrowProviderError, - resolveProviderHttpRequestConfig, } from "openclaw/plugin-sdk/provider-http"; import { buildHostnameAllowlistPolicyFromSuffixAllowlist, @@ -26,8 +24,8 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { resolveFalHttpRequestConfig } from "./http-config.js"; -const DEFAULT_FAL_BASE_URL = "https://fal.run"; const DEFAULT_FAL_IMAGE_MODEL = "fal-ai/flux/dev"; const DEFAULT_FAL_EDIT_SUBPATH = "image-to-image"; const FAL_KREA_2_MODEL_PREFIX = "krea/v2/"; @@ -551,15 +549,6 @@ export function buildFalImageGenerationProvider(): ImageGenerationProvider { }, }, async generateImage(req) { - const auth = await resolveApiKeyForProvider({ - provider: "fal", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("fal API key missing"); - } const inputImageCount = req.inputImages?.length ?? 0; const hasInputImages = inputImageCount > 0; const requestedModel = req.model?.trim() || DEFAULT_FAL_IMAGE_MODEL; @@ -588,20 +577,8 @@ export function buildFalImageGenerationProvider(): ImageGenerationProvider { if (!schema.supportsOutputFormat && req.outputFormat) { throw new Error(`fal ${requestedModel} does not support outputFormat overrides`); } - const explicitBaseUrl = req.cfg?.models?.providers?.fal?.baseUrl?.trim(); const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - baseUrl: explicitBaseUrl, - defaultBaseUrl: DEFAULT_FAL_BASE_URL, - allowPrivateNetwork: false, - defaultHeaders: { - Authorization: `Key ${auth.apiKey}`, - "Content-Type": "application/json", - }, - provider: "fal", - capability: "image", - transport: "http", - }); + await resolveFalHttpRequestConfig({ req, capability: "image" }); const networkPolicy = resolveFalNetworkPolicy({ baseUrl, allowPrivateNetwork }); const requestBody: Record = { prompt: req.prompt, diff --git a/extensions/fal/music-generation-provider.ts b/extensions/fal/music-generation-provider.ts index 101e6e8fc63..bb53fc639c5 100644 --- a/extensions/fal/music-generation-provider.ts +++ b/extensions/fal/music-generation-provider.ts @@ -5,15 +5,10 @@ import { type MusicGenerationRequest, } from "openclaw/plugin-sdk/music-generation"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; -import { - assertOkOrThrowHttpError, - postJsonRequest, - resolveProviderHttpRequestConfig, -} from "openclaw/plugin-sdk/provider-http"; +import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { resolveFalHttpRequestConfig } from "./http-config.js"; -const DEFAULT_FAL_BASE_URL = "https://fal.run"; const DEFAULT_FAL_MUSIC_MODEL = "fal-ai/minimax-music/v2.6"; const FAL_ACE_STEP_MODEL = "fal-ai/ace-step/prompt-to-audio"; const FAL_STABLE_AUDIO_MODEL = "fal-ai/stable-audio-25/text-to-audio"; @@ -29,10 +24,6 @@ function resolveFalMusicModel(model: string | undefined): string { return normalizeOptionalString(model) ?? DEFAULT_FAL_MUSIC_MODEL; } -function resolveFalMusicBaseUrl(req: MusicGenerationRequest): string | undefined { - return normalizeOptionalString(req.cfg?.models?.providers?.fal?.baseUrl); -} - function buildFalMinimaxBody(req: MusicGenerationRequest): Record { const lyrics = normalizeOptionalString(req.lyrics); if (lyrics && req.instrumental === true) { @@ -145,29 +136,8 @@ export function buildFalMusicGenerationProvider(): MusicGenerationProvider { throw new Error("fal music generation does not support image reference inputs."); } - const auth = await resolveApiKeyForProvider({ - provider: "fal", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("fal API key missing"); - } - const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - baseUrl: resolveFalMusicBaseUrl(req), - defaultBaseUrl: DEFAULT_FAL_BASE_URL, - allowPrivateNetwork: false, - defaultHeaders: { - Authorization: `Key ${auth.apiKey}`, - "Content-Type": "application/json", - }, - provider: "fal", - capability: "audio", - transport: "http", - }); + await resolveFalHttpRequestConfig({ req, capability: "audio" }); const model = resolveFalMusicModel(req.model); const { response, release } = await postJsonRequest({ url: `${baseUrl}/${model}`, diff --git a/extensions/fal/video-generation-provider.ts b/extensions/fal/video-generation-provider.ts index be44461c385..ee904230cc7 100644 --- a/extensions/fal/video-generation-provider.ts +++ b/extensions/fal/video-generation-provider.ts @@ -1,10 +1,6 @@ import { extensionForMime } from "openclaw/plugin-sdk/media-mime"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; -import { - assertOkOrThrowHttpError, - resolveProviderHttpRequestConfig, -} from "openclaw/plugin-sdk/provider-http"; +import { assertOkOrThrowHttpError } from "openclaw/plugin-sdk/provider-http"; import { fetchWithSsrFGuard, type SsrFPolicy, @@ -20,8 +16,8 @@ import type { VideoGenerationProvider, VideoGenerationRequest, } from "openclaw/plugin-sdk/video-generation"; +import { resolveFalHttpRequestConfig } from "./http-config.js"; -const DEFAULT_FAL_BASE_URL = "https://fal.run"; const DEFAULT_FAL_QUEUE_BASE_URL = "https://queue.fal.run"; const DEFAULT_FAL_VIDEO_MODEL = "fal-ai/minimax/video-01-live"; const HEYGEN_VIDEO_AGENT_MODEL = "fal-ai/heygen/v2/video-agent"; @@ -573,28 +569,8 @@ export function buildFalVideoGenerationProvider(): VideoGenerationProvider { async generateVideo(req) { const model = normalizeOptionalString(req.model) || DEFAULT_FAL_VIDEO_MODEL; validateFalVideoReferenceInputs({ req, model }); - const auth = await resolveApiKeyForProvider({ - provider: "fal", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("fal API key missing"); - } const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - baseUrl: normalizeOptionalString(req.cfg?.models?.providers?.fal?.baseUrl), - defaultBaseUrl: DEFAULT_FAL_BASE_URL, - allowPrivateNetwork: false, - defaultHeaders: { - Authorization: `Key ${auth.apiKey}`, - "Content-Type": "application/json", - }, - provider: "fal", - capability: "video", - transport: "http", - }); + await resolveFalHttpRequestConfig({ req, capability: "video" }); const requestBody = buildFalVideoRequestBody({ req, model }); const policy = buildPolicy(allowPrivateNetwork); const queueBaseUrl = resolveFalQueueBaseUrl(baseUrl);