refactor: share fal provider http auth

This commit is contained in:
Vincent Koc
2026-05-29 07:47:10 +02:00
parent fa9901c78f
commit 667c03f87e
4 changed files with 56 additions and 85 deletions

View File

@@ -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<ReturnType<typeof resolveProviderHttpRequestConfig>> {
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",
});
}

View File

@@ -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<string, unknown> = {
prompt: req.prompt,

View File

@@ -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<string, unknown> {
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}`,

View File

@@ -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);