diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index 856f0cac1ee..3b0413f24e9 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -67,19 +67,24 @@ type AzureIdentityModule = { const AZURE_IDENTITY_MODULE = "@azure/identity"; +let azureIdentityModulePromise: Promise | null = null; + async function loadAzureIdentity(): Promise { - return (await import(AZURE_IDENTITY_MODULE)) as AzureIdentityModule; + azureIdentityModulePromise ??= import(AZURE_IDENTITY_MODULE) as Promise; + return azureIdentityModulePromise; } +let msTeamsSdkPromise: Promise | null = null; + export async function loadMSTeamsSdk(): Promise { - const [appsModule, apiModule] = await Promise.all([ + msTeamsSdkPromise ??= Promise.all([ import("@microsoft/teams.apps"), import("@microsoft/teams.api"), - ]); - return { + ]).then(([appsModule, apiModule]) => ({ App: appsModule.App, Client: apiModule.Client, - }; + })); + return msTeamsSdkPromise; } /** @@ -653,6 +658,20 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{ }, ]; +type BotFrameworkJwtDeps = { + jwt: typeof import("jsonwebtoken"); + JwksClient: typeof import("jwks-rsa").JwksClient; +}; + +let botFrameworkJwtDepsPromise: Promise | null = null; + +async function loadBotFrameworkJwtDeps(): Promise { + botFrameworkJwtDepsPromise ??= Promise.all([import("jsonwebtoken"), import("jwks-rsa")]).then( + ([jwt, { JwksClient }]) => ({ jwt, JwksClient }), + ); + return botFrameworkJwtDepsPromise; +} + /** * Create a Bot Framework JWT validator using jsonwebtoken + jwks-rsa directly. * @@ -670,8 +689,7 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{ export async function createBotFrameworkJwtValidator(creds: MSTeamsCredentials): Promise<{ validate: (authHeader: string) => Promise; }> { - const jwt = await import("jsonwebtoken"); - const { JwksClient } = await import("jwks-rsa"); + const { jwt, JwksClient } = await loadBotFrameworkJwtDeps(); const allowedAudiences: [string, ...string[]] = [ creds.appId, diff --git a/extensions/qqbot/src/utils/audio-convert.ts b/extensions/qqbot/src/utils/audio-convert.ts index 6d2d86a04ec..6978fa22e71 100644 --- a/extensions/qqbot/src/utils/audio-convert.ts +++ b/extensions/qqbot/src/utils/audio-convert.ts @@ -728,10 +728,19 @@ function ffmpegToPCM( }); } +type MpegDecoderConstructor = typeof import("mpg123-decoder").MPEGDecoder; + +let mpegDecoderConstructorPromise: Promise | null = null; + +async function loadMpegDecoderConstructor(): Promise { + mpegDecoderConstructorPromise ??= import("mpg123-decoder").then(({ MPEGDecoder }) => MPEGDecoder); + return mpegDecoderConstructorPromise; +} + /** Decode MP3 into PCM through mpg123-decoder when ffmpeg is unavailable. */ async function wasmDecodeMp3ToPCM(buf: Buffer, targetRate: number): Promise { try { - const { MPEGDecoder } = await import("mpg123-decoder"); + const MPEGDecoder = await loadMpegDecoderConstructor(); debugLog(`[audio-convert] WASM MP3 decode: size=${buf.length} bytes`); const decoder = new MPEGDecoder(); await decoder.ready; diff --git a/extensions/whatsapp/src/session.ts b/extensions/whatsapp/src/session.ts index 2cf568d6130..3449423d6c3 100644 --- a/extensions/whatsapp/src/session.ts +++ b/extensions/whatsapp/src/session.ts @@ -219,6 +219,18 @@ async function resolveEnvProxyAgent( }); } +type UndiciProxyAgentsModule = Pick; + +let undiciProxyAgentsModulePromise: Promise | null = null; + +async function loadUndiciProxyAgents(): Promise { + undiciProxyAgentsModulePromise ??= import("undici").then(({ EnvHttpProxyAgent, ProxyAgent }) => ({ + EnvHttpProxyAgent, + ProxyAgent, + })); + return undiciProxyAgentsModulePromise; +} + async function resolveEnvFetchDispatcher( logger: ReturnType, agent?: unknown, @@ -229,7 +241,7 @@ async function resolveEnvFetchDispatcher( return undefined; } try { - const { EnvHttpProxyAgent, ProxyAgent } = await import("undici"); + const { EnvHttpProxyAgent, ProxyAgent } = await loadUndiciProxyAgents(); return proxyUrl ? new ProxyAgent({ allowH2: false, uri: proxyUrl }) : new EnvHttpProxyAgent({ allowH2: false }); diff --git a/src/infra/bonjour.ts b/src/infra/bonjour.ts index 17890d894ac..51e0c532cd2 100644 --- a/src/infra/bonjour.ts +++ b/src/infra/bonjour.ts @@ -136,6 +136,13 @@ function installCiaoConsoleNoiseFilter(): () => void { }; } +let ciaoModulePromise: Promise | null = null; + +async function loadCiaoModule(): Promise { + ciaoModulePromise ??= import("@homebridge/ciao"); + return ciaoModulePromise; +} + export async function startGatewayBonjourAdvertiser( opts: GatewayBonjourAdvertiseOpts, ): Promise { @@ -143,7 +150,7 @@ export async function startGatewayBonjourAdvertiser( return { stop: async () => {} }; } - const { getResponder, Protocol } = await import("@homebridge/ciao"); + const { getResponder, Protocol } = await loadCiaoModule(); const restoreConsoleLog = installCiaoConsoleNoiseFilter(); try { // mDNS service instance names are single DNS labels; dots in hostnames (like diff --git a/src/media/image-ops.ts b/src/media/image-ops.ts index 87e4b8968fc..cccc25efdeb 100644 --- a/src/media/image-ops.ts +++ b/src/media/image-ops.ts @@ -4,6 +4,7 @@ import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { runExec } from "../process/exec.js"; type Sharp = typeof import("sharp"); +type SharpFactory = (buffer: Buffer) => ReturnType; export type ImageMetadata = { width: number; @@ -31,14 +32,18 @@ function prefersSips(): boolean { ); } -async function loadSharp(): Promise<(buffer: Buffer) => ReturnType> { - const mod = (await import("sharp")) as unknown as { default?: Sharp }; - const sharp = mod.default ?? (mod as unknown as Sharp); - return (buffer) => - sharp(buffer, { - failOnError: false, - limitInputPixels: MAX_IMAGE_INPUT_PIXELS, - }); +let sharpFactoryPromise: Promise | null = null; + +async function loadSharp(): Promise { + sharpFactoryPromise ??= import("sharp").then((mod) => { + const sharp = (mod as unknown as { default?: Sharp }).default ?? (mod as unknown as Sharp); + return (buffer: Buffer) => + sharp(buffer, { + failOnError: false, + limitInputPixels: MAX_IMAGE_INPUT_PIXELS, + }); + }); + return sharpFactoryPromise; } function isPositiveImageDimension(value: number): boolean { diff --git a/src/memory-host-sdk/host/sqlite-vec.ts b/src/memory-host-sdk/host/sqlite-vec.ts index 34794032659..67294a17ef1 100644 --- a/src/memory-host-sdk/host/sqlite-vec.ts +++ b/src/memory-host-sdk/host/sqlite-vec.ts @@ -2,12 +2,19 @@ import type { DatabaseSync } from "node:sqlite"; import { formatErrorMessage } from "../../infra/errors.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +let sqliteVecModulePromise: Promise | null = null; + +async function loadSqliteVecModule(): Promise { + sqliteVecModulePromise ??= import("sqlite-vec"); + return sqliteVecModulePromise; +} + export async function loadSqliteVecExtension(params: { db: DatabaseSync; extensionPath?: string; }): Promise<{ ok: boolean; extensionPath?: string; error?: string }> { try { - const sqliteVec = await import("sqlite-vec"); + const sqliteVec = await loadSqliteVecModule(); const resolvedPath = normalizeOptionalString(params.extensionPath); const extensionPath = resolvedPath ?? sqliteVec.getLoadablePath(); diff --git a/src/plugin-sdk/extension-shared.ts b/src/plugin-sdk/extension-shared.ts index 2da583eab3f..66f99dd158c 100644 --- a/src/plugin-sdk/extension-shared.ts +++ b/src/plugin-sdk/extension-shared.ts @@ -134,6 +134,13 @@ export function createDeferred() { return { promise, resolve, reject }; } +let proxyAgentConstructorPromise: Promise | null = null; + +async function loadProxyAgentConstructor(): Promise { + proxyAgentConstructorPromise ??= import("proxy-agent").then(({ ProxyAgent }) => ProxyAgent); + return proxyAgentConstructorPromise; +} + export async function resolveAmbientNodeProxyAgent(params?: { onError?: (error: unknown) => void; onUsingProxy?: () => void; @@ -143,7 +150,7 @@ export async function resolveAmbientNodeProxyAgent(params?: { return undefined; } try { - const { ProxyAgent } = await import("proxy-agent"); + const ProxyAgent = await loadProxyAgentConstructor(); params?.onUsingProxy?.(); return new ProxyAgent() as TAgent; } catch (error) { diff --git a/src/process/supervisor/adapters/pty.ts b/src/process/supervisor/adapters/pty.ts index 4d20ea33d39..bbc7a53d578 100644 --- a/src/process/supervisor/adapters/pty.ts +++ b/src/process/supervisor/adapters/pty.ts @@ -34,6 +34,13 @@ type PtyModule = { export type PtyAdapter = SpawnProcessAdapter; +let ptyModulePromise: Promise | null = null; + +async function loadPtyModule(): Promise { + ptyModulePromise ??= import("@lydell/node-pty") as Promise as Promise; + return ptyModulePromise; +} + export async function createPtyAdapter(params: { shell: string; args: string[]; @@ -43,7 +50,7 @@ export async function createPtyAdapter(params: { rows?: number; name?: string; }): Promise { - const module = (await import("@lydell/node-pty")) as unknown as PtyModule; + const module = await loadPtyModule(); const spawn = module.spawn ?? module.default?.spawn; if (!spawn) { throw new Error("PTY support is unavailable (node-pty spawn not found).");