UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

377 lines (376 loc) 14.8 kB
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js"; import { t as DEFAULT_ACCOUNT_ID } from "./account-id-Df9e41E6.js"; import { At as boolean, Rn as string, Tn as object, wn as number } from "./schemas-6cH6bZ7o.js"; import { h as MarkdownConfigSchema, l as GroupPolicySchema, o as DmPolicySchema } from "./zod-schema.core-1wQTyhOa.js"; import { n as buildCatchallMultiAccountChannelSchema, r as buildChannelConfigSchema, t as AllowFromListSchema } from "./config-schema-CO2ikh7C.js"; import { b as sendPayloadWithChunkedTextAndMedia, l as isNumericTargetId } from "./reply-payload-D-VMfpYI.js"; import { i as createLazyRuntimeNamedExport, r as createLazyRuntimeModule } from "./lazy-runtime-D-7_JraP.js"; import { b as readStringParam, l as jsonResult } from "./common-C4yy9V-D.js"; import { l as createScopedDmSecurityResolver, m as mapAllowFromEntries, s as createScopedChannelConfigAdapter, t as adaptScopedAccountAccessor } from "./channel-config-helpers-lj5quus3.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { t as chunkTextForOutbound } from "./text-chunking-D45TeeEs.js"; import { r as describeWebhookAccountSnapshot } from "./account-helpers-pcH3lGFY.js"; import { n as createStaticReplyToModeResolver } from "./threading-helpers-CMXJIj4M.js"; import { d as stripTargetKindPrefix, i as createChatChannelPlugin, t as buildChannelOutboundSessionRoute, u as stripChannelTargetPrefix } from "./core-DSxVv-v1.js"; import "./channel-core-IukkNCd-.js"; import { r as buildSecretInputSchema } from "./secret-input-DVCzFGxN.js"; import { t as formatAllowFromLowercase } from "./allow-from-DCaV3hzI.js"; import "./channel-config-schema-D7l1xlvD.js"; import { C as createOpenProviderGroupPolicyWarningCollector, o as buildOpenGroupPolicyRestrictSendersWarning, s as buildOpenGroupPolicyWarning } from "./channel-policy-CIW58SA5.js"; import { t as createChannelDirectoryAdapter } from "./directory-runtime-om8jJAZt.js"; import { p as listResolvedDirectoryUserEntriesFromAllowFrom } from "./directory-config-helpers-DEDTHx2P.js"; import { t as resolveApprovalApprovers } from "./approval-approvers-CM-4zRvR.js"; import { t as createResolvedApproverActionAuthAdapter } from "./approval-auth-helpers-DU3OxkhL.js"; import { T as defineChannelMessageAdapter } from "./channel-outbound-B3_Zy-kG.js"; import "./conversation-runtime-VjM1-D7a.js"; import { d as createDefaultChannelRuntimeState, o as buildTokenChannelStatusSummary, u as createComputedAccountStatusAdapter } from "./status-helpers-BevxX6Pu.js"; import "./channel-status-DxlXr0Xh.js"; import { a as coerceStatusIssueAccountId, d as readStatusIssueFields } from "./extension-shared-B8fkO3TV.js"; import "./channel-actions-BOfvC-Gl.js"; import { a as createEmptyChannelResult, o as createRawChannelSendResultAdapter } from "./channel-send-result-Dn_C6AJS.js"; import { t as extractToolSend } from "./tool-send-DzTXHBeR.js"; import { i as resolveZaloAccount, n as listZaloAccountIds, r as resolveDefaultZaloAccountId, t as listEnabledZaloAccounts } from "./accounts-Yvv1ZXr_.js"; import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-C31K1PWL.js"; import { r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-M9kuD06E.js"; //#region extensions/zalo/src/actions.ts const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-D_KVkVc2.js"), "zaloActionsRuntime"); const providerId = "zalo"; function listEnabledAccounts(cfg, accountId) { return (accountId ? [resolveZaloAccount({ cfg, accountId })] : listEnabledZaloAccounts(cfg)).filter((account) => account.enabled && account.tokenSource !== "none"); } const zaloMessageActions = { describeMessageTool: ({ cfg, accountId }) => { if (listEnabledAccounts(cfg, accountId).length === 0) return null; const actions = new Set(["send"]); return { actions: Array.from(actions), capabilities: [] }; }, extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"), handleAction: async ({ action, params, cfg, accountId }) => { if (action === "send") { const to = readStringParam(params, "to", { required: true }); const content = readStringParam(params, "message", { required: true, allowEmpty: true }); const mediaUrl = readStringParam(params, "media", { trim: false }); const { sendMessageZalo } = await loadZaloActionsRuntime(); const result = await sendMessageZalo(to ?? "", content ?? "", { accountId: accountId ?? void 0, mediaUrl: mediaUrl ?? void 0, cfg }); if (!result.ok) return jsonResult({ ok: false, error: result.error ?? "Failed to send Zalo message" }); return jsonResult({ ok: true, to, messageId: result.messageId }); } throw new Error(`Action ${action} is not supported for provider ${providerId}.`); } }; //#endregion //#region extensions/zalo/src/approval-auth.ts function normalizeZaloApproverId(value) { const normalized = String(value).trim().replace(/^(zalo|zl):/i, "").trim(); return /^\d+$/.test(normalized) ? normalized : void 0; } const zaloApprovalAuth = createResolvedApproverActionAuthAdapter({ channelLabel: "Zalo", resolveApprovers: ({ cfg, accountId }) => { const account = resolveZaloAccount({ cfg, accountId }).config; return resolveApprovalApprovers({ allowFrom: account.allowFrom, normalizeApprover: normalizeZaloApproverId }); }, normalizeSenderId: (value) => normalizeZaloApproverId(value) }); const ZaloConfigSchema = buildCatchallMultiAccountChannelSchema(object({ name: string().optional(), enabled: boolean().optional(), markdown: MarkdownConfigSchema, botToken: buildSecretInputSchema().optional(), tokenFile: string().optional(), webhookUrl: string().optional(), webhookSecret: buildSecretInputSchema().optional(), webhookPath: string().optional(), dmPolicy: DmPolicySchema.optional(), allowFrom: AllowFromListSchema, groupPolicy: GroupPolicySchema.optional(), groupAllowFrom: AllowFromListSchema, mediaMaxMb: number().optional(), proxy: string().optional(), responsePrefix: string().optional() })); //#endregion //#region extensions/zalo/src/session-route.ts function resolveZaloOutboundSessionRoute(params) { const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl"); if (!trimmed) return null; const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:"); const peerId = stripTargetKindPrefix(trimmed); if (!peerId) return null; return buildChannelOutboundSessionRoute({ cfg: params.cfg, agentId: params.agentId, channel: "zalo", accountId: params.accountId, peer: { kind: isGroup ? "group" : "direct", id: peerId }, chatType: isGroup ? "group" : "direct", from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`, to: `zalo:${peerId}` }); } //#endregion //#region extensions/zalo/src/status-issues.ts const ZALO_STATUS_FIELDS = [ "accountId", "enabled", "configured", "dmPolicy" ]; function collectZaloStatusIssues(accounts) { const issues = []; for (const entry of accounts) { const account = readStatusIssueFields(entry, ZALO_STATUS_FIELDS); if (!account) continue; const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default"; const enabled = account.enabled !== false; const configured = account.configured === true; if (!enabled || !configured) continue; if (account.dmPolicy === "open") issues.push({ channel: "zalo", accountId, kind: "config", message: "Zalo dmPolicy is \"open\", allowing any user to message the bot without pairing.", fix: "Set channels.zalo.dmPolicy to \"pairing\" or \"allowlist\" to restrict access." }); } return issues; } //#endregion //#region extensions/zalo/src/channel.ts const meta = { id: "zalo", label: "Zalo", selectionLabel: "Zalo (Bot API)", docsPath: "/channels/zalo", docsLabel: "zalo", blurb: "Vietnam-focused messaging platform with Bot API.", aliases: ["zl"], order: 80, quickstartAllowFrom: true }; function normalizeZaloMessagingTarget(raw) { const trimmed = raw?.trim(); if (!trimmed) return; return trimmed.replace(/^(zalo|zl):/i, "").trim(); } const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-CYcJRE5z.js")); const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-DecoNDGY.js")).zaloSetupWizard); const zaloTextChunkLimit = 2e3; const zaloRawSendResultAdapter = createRawChannelSendResultAdapter({ channel: "zalo", sendText: async ({ to, text, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({ to, text, accountId: accountId ?? void 0, cfg }), sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({ to, text, accountId: accountId ?? void 0, mediaUrl, cfg }) }); const zaloMessageAdapter = defineChannelMessageAdapter({ id: "zalo", durableFinal: { capabilities: { text: true, media: true, messageSendingHooks: true } }, send: { text: async ({ to, text, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({ to, text, accountId: accountId ?? void 0, cfg }), media: async ({ to, text, mediaUrl, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({ to, text, accountId: accountId ?? void 0, mediaUrl, cfg }) } }); const zaloConfigAdapter = createScopedChannelConfigAdapter({ sectionKey: "zalo", listAccountIds: listZaloAccountIds, resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount), defaultAccountId: resolveDefaultZaloAccountId, clearBaseFields: [ "botToken", "tokenFile", "name" ], resolveAllowFrom: (account) => account.config.allowFrom, formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }) }); const resolveZaloDmPolicy = createScopedDmSecurityResolver({ channelKey: "zalo", resolvePolicy: (account) => account.config.dmPolicy, resolveAllowFrom: (account) => account.config.allowFrom, policyPathSuffix: "dmPolicy", normalizeEntry: (raw) => raw.trim().replace(/^(zalo|zl):/i, "") }); const collectZaloSecurityWarnings = createOpenProviderGroupPolicyWarningCollector({ providerConfigPresent: (cfg) => cfg.channels?.zalo !== void 0, resolveGroupPolicy: ({ account }) => account.config.groupPolicy, collect: ({ account, groupPolicy }) => { if (groupPolicy !== "open") return []; const explicitGroupAllowFrom = mapAllowFromEntries(account.config.groupAllowFrom); const dmAllowFrom = mapAllowFromEntries(account.config.allowFrom); if ((explicitGroupAllowFrom.length > 0 ? explicitGroupAllowFrom : dmAllowFrom).length > 0) return [buildOpenGroupPolicyRestrictSendersWarning({ surface: "Zalo groups", openScope: "any member", groupPolicyPath: "channels.zalo.groupPolicy", groupAllowFromPath: "channels.zalo.groupAllowFrom" })]; return [buildOpenGroupPolicyWarning({ surface: "Zalo groups", openBehavior: "with no groupAllowFrom/allowFrom allowlist; any member can trigger (mention-gated)", remediation: "Set channels.zalo.groupPolicy=\"allowlist\" + channels.zalo.groupAllowFrom" })]; } }); const zaloPlugin = createChatChannelPlugin({ base: { id: "zalo", meta, setup: zaloSetupAdapter, setupWizard: zaloSetupWizard, capabilities: { chatTypes: ["direct", "group"], media: true, reactions: false, threads: false, polls: false, nativeCommands: false, blockStreaming: true }, reload: { configPrefixes: ["channels.zalo"] }, configSchema: buildChannelConfigSchema(ZaloConfigSchema), config: { ...zaloConfigAdapter, isConfigured: (account) => Boolean(account.token?.trim()), describeAccount: (account) => describeWebhookAccountSnapshot({ account, configured: Boolean(account.token?.trim()), mode: account.config.webhookUrl ? "webhook" : "polling", extra: { tokenSource: account.tokenSource } }) }, approvalCapability: zaloApprovalAuth, secrets: { secretTargetRegistryEntries, collectRuntimeConfigAssignments }, groups: { resolveRequireMention: () => true }, actions: zaloMessageActions, messaging: { targetPrefixes: ["zalo", "zl"], normalizeTarget: normalizeZaloMessagingTarget, resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params), targetResolver: { looksLikeId: isNumericTargetId, hint: "<chatId>" } }, directory: createChannelDirectoryAdapter({ listPeers: async (params) => listResolvedDirectoryUserEntriesFromAllowFrom({ ...params, resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount), resolveAllowFrom: (account) => account.config.allowFrom, normalizeId: (entry) => entry.trim().replace(/^(zalo|zl):/i, "") }), listGroups: async () => [] }), status: createComputedAccountStatusAdapter({ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID), collectStatusIssues: collectZaloStatusIssues, buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot), probeAccount: async ({ account, timeoutMs }) => await (await loadZaloChannelRuntime()).probeZaloAccount({ account, timeoutMs }), resolveAccountSnapshot: ({ account }) => { const configured = Boolean(account.token?.trim()); return { accountId: account.accountId, name: account.name, enabled: account.enabled, configured, extra: { tokenSource: account.tokenSource, mode: account.config.webhookUrl ? "webhook" : "polling", dmPolicy: account.config.dmPolicy ?? "pairing" } }; } }), gateway: { startAccount: async (ctx) => await (await loadZaloChannelRuntime()).startZaloGatewayAccount(ctx) }, message: zaloMessageAdapter }, security: { resolveDmPolicy: resolveZaloDmPolicy, collectWarnings: collectZaloSecurityWarnings }, pairing: { text: { idLabel: "zaloUserId", message: "Your pairing request has been approved.", normalizeAllowEntry: (entry) => entry.trim().replace(/^(zalo|zl):/i, ""), notify: async (params) => await (await loadZaloChannelRuntime()).notifyZaloPairingApproval(params) } }, threading: { resolveReplyToMode: createStaticReplyToModeResolver("off") }, outbound: { deliveryMode: "direct", chunker: chunkTextForOutbound, chunkerMode: "text", textChunkLimit: zaloTextChunkLimit, sendPayload: async (ctx) => await sendPayloadWithChunkedTextAndMedia({ ctx, textChunkLimit: zaloTextChunkLimit, chunker: chunkTextForOutbound, sendText: (nextCtx) => zaloRawSendResultAdapter.sendText(nextCtx), sendMedia: (nextCtx) => zaloRawSendResultAdapter.sendMedia(nextCtx), emptyResult: createEmptyChannelResult("zalo") }), ...zaloRawSendResultAdapter } }); //#endregion export { zaloPlugin as t };