Files
DeskClaw/electron/utils/openclaw-sdk.ts
2026-04-09 18:51:06 +08:00

200 lines
7.2 KiB
TypeScript

/**
* Dynamic imports for openclaw plugin-sdk subpath exports.
*
* openclaw is NOT in the asar's node_modules — it lives at resources/openclaw/
* (extraResources). Static `import ... from 'openclaw/plugin-sdk/...'` would
* produce a runtime require() that fails inside the asar.
*
* Instead, we create a require context from the openclaw directory itself.
* Node.js package self-referencing allows a package to require its own exports
* by name, so `openclawRequire('openclaw/plugin-sdk/discord')` resolves via the
* exports map in openclaw's package.json.
*
* In dev mode (pnpm), the resolved path is in the pnpm virtual store where
* self-referencing also works. The projectRequire fallback covers edge cases.
*
* openclaw 2026.4.5 removed the per-channel plugin-sdk subpath exports
* (discord, telegram-surface, slack, whatsapp-shared). The functions now live
* in the extension bundles (dist/extensions/<channel>/api.js) which pull in
* heavy optional dependencies (grammy, @buape/carbon, @slack/web-api …).
*
* Since ClawX only uses the lightweight normalize / directory helpers, we load
* these from the extension API files directly. If the optional dependency is
* missing (common in dev without full install), we fall back to no-op stubs so
* the app can still start — the target picker will simply be empty for that
* channel.
*/
import { createRequire } from 'module';
import { join } from 'node:path';
import { getOpenClawDir, getOpenClawResolvedDir } from './paths';
const _openclawResolvedPath = getOpenClawResolvedDir();
const _openclawPath = getOpenClawDir();
const _openclawSdkRequire = createRequire(join(_openclawResolvedPath, 'package.json'));
const _projectSdkRequire = createRequire(join(_openclawPath, 'package.json'));
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function requireOpenClawSdk(subpath: string): Record<string, unknown> {
try {
return _openclawSdkRequire(subpath);
} catch {
return _projectSdkRequire(subpath);
}
}
/**
* Load an openclaw extension API module by relative path under the openclaw
* dist directory. Falls back to no-op stubs when the optional dependency
* tree is incomplete.
*/
function requireExtensionApi(relativePath: string): Record<string, unknown> | null {
try {
// Require relative to the openclaw dist directory.
return _openclawSdkRequire(relativePath);
} catch {
try {
return _projectSdkRequire(relativePath);
} catch {
return null;
}
}
}
// ---------------------------------------------------------------------------
// Generic no-op stubs used when channel SDK is unavailable.
// ---------------------------------------------------------------------------
const noopAsyncList = async (..._args: unknown[]): Promise<unknown[]> => [];
const noopNormalize = (_target: string): string | undefined => undefined;
// ---------------------------------------------------------------------------
// Legacy plugin-sdk subpath imports (openclaw <2026.4.5)
// ---------------------------------------------------------------------------
function tryLegacySdkImport(subpath: string): Record<string, unknown> | null {
try {
return requireOpenClawSdk(subpath);
} catch {
return null;
}
}
// ---------------------------------------------------------------------------
// Channel SDK loaders — try legacy plugin-sdk first, then extension api, then stubs
// ---------------------------------------------------------------------------
type ChannelSdk<T> = T;
interface DiscordSdk {
listDiscordDirectoryGroupsFromConfig: (...args: unknown[]) => Promise<unknown[]>;
listDiscordDirectoryPeersFromConfig: (...args: unknown[]) => Promise<unknown[]>;
normalizeDiscordMessagingTarget: (target: string) => string | undefined;
}
interface TelegramSdk {
listTelegramDirectoryGroupsFromConfig: (...args: unknown[]) => Promise<unknown[]>;
listTelegramDirectoryPeersFromConfig: (...args: unknown[]) => Promise<unknown[]>;
normalizeTelegramMessagingTarget: (target: string) => string | undefined;
}
interface SlackSdk {
listSlackDirectoryGroupsFromConfig: (...args: unknown[]) => Promise<unknown[]>;
listSlackDirectoryPeersFromConfig: (...args: unknown[]) => Promise<unknown[]>;
normalizeSlackMessagingTarget: (target: string) => string | undefined;
}
interface WhatsappSdk {
normalizeWhatsAppMessagingTarget: (target: string) => string | undefined;
}
function loadChannelSdk<T>(
legacySubpath: string,
extensionRelPath: string,
fallback: T,
keys: (keyof T)[],
): ChannelSdk<T> {
// 1. Try legacy plugin-sdk subpath (openclaw <4.5)
const legacy = tryLegacySdkImport(legacySubpath);
if (legacy && keys.every((k) => typeof legacy[k as string] === 'function')) {
return legacy as unknown as T;
}
// 2. Try extension API file (openclaw >=4.5)
const ext = requireExtensionApi(extensionRelPath);
if (ext && keys.every((k) => typeof ext[k as string] === 'function')) {
return ext as unknown as T;
}
// 3. Fallback to no-op stubs
return fallback;
}
const _discordSdk = loadChannelSdk<DiscordSdk>(
'openclaw/plugin-sdk/discord',
'./dist/extensions/discord/api.js',
{
listDiscordDirectoryGroupsFromConfig: noopAsyncList,
listDiscordDirectoryPeersFromConfig: noopAsyncList,
normalizeDiscordMessagingTarget: noopNormalize,
},
['listDiscordDirectoryGroupsFromConfig', 'listDiscordDirectoryPeersFromConfig', 'normalizeDiscordMessagingTarget'],
);
const _telegramSdk = loadChannelSdk<TelegramSdk>(
'openclaw/plugin-sdk/telegram-surface',
'./dist/extensions/telegram/api.js',
{
listTelegramDirectoryGroupsFromConfig: noopAsyncList,
listTelegramDirectoryPeersFromConfig: noopAsyncList,
normalizeTelegramMessagingTarget: noopNormalize,
},
['listTelegramDirectoryGroupsFromConfig', 'listTelegramDirectoryPeersFromConfig', 'normalizeTelegramMessagingTarget'],
);
const _slackSdk = loadChannelSdk<SlackSdk>(
'openclaw/plugin-sdk/slack',
'./dist/extensions/slack/api.js',
{
listSlackDirectoryGroupsFromConfig: noopAsyncList,
listSlackDirectoryPeersFromConfig: noopAsyncList,
normalizeSlackMessagingTarget: noopNormalize,
},
['listSlackDirectoryGroupsFromConfig', 'listSlackDirectoryPeersFromConfig', 'normalizeSlackMessagingTarget'],
);
const _whatsappSdk = loadChannelSdk<WhatsappSdk>(
'openclaw/plugin-sdk/whatsapp-shared',
'./dist/extensions/whatsapp/api.js',
{
normalizeWhatsAppMessagingTarget: noopNormalize,
},
['normalizeWhatsAppMessagingTarget'],
);
// ---------------------------------------------------------------------------
// Public re-exports — identical API surface as before.
// ---------------------------------------------------------------------------
export const {
listDiscordDirectoryGroupsFromConfig,
listDiscordDirectoryPeersFromConfig,
normalizeDiscordMessagingTarget,
} = _discordSdk;
export const {
listTelegramDirectoryGroupsFromConfig,
listTelegramDirectoryPeersFromConfig,
normalizeTelegramMessagingTarget,
} = _telegramSdk;
export const {
listSlackDirectoryGroupsFromConfig,
listSlackDirectoryPeersFromConfig,
normalizeSlackMessagingTarget,
} = _slackSdk;
export const { normalizeWhatsAppMessagingTarget } = _whatsappSdk;