UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

656 lines (655 loc) 25.2 kB
import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js"; import { t as DEFAULT_ACCOUNT_ID } from "./account-id-Df9e41E6.js"; import { f as resolveOutboundMediaUrls } from "./reply-payload-D-VMfpYI.js"; import { n as resolveChannelGroupRequireMention } from "./group-policy-CShy7eWv.js"; import { r as createLazyRuntimeModule } from "./lazy-runtime-D-7_JraP.js"; import { t as clearAccountEntryFields } from "./config-helpers-DU5uyx8S.js"; import { s as createScopedChannelConfigAdapter } from "./channel-config-helpers-lj5quus3.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { r as describeWebhookAccountSnapshot } from "./account-helpers-pcH3lGFY.js"; import { i as createChatChannelPlugin } from "./core-DSxVv-v1.js"; import "./channel-core-IukkNCd-.js"; import { r as createRestrictSendersChannelSecurity } from "./channel-policy-CIW58SA5.js"; import { n as createEmptyChannelDirectoryAdapter } from "./directory-runtime-om8jJAZt.js"; import { T as defineChannelMessageAdapter } from "./channel-outbound-B3_Zy-kG.js"; import { d as createDefaultChannelRuntimeState, f as createDependentCredentialStatusIssueCollector, o as buildTokenChannelStatusSummary, u as createComputedAccountStatusAdapter } from "./status-helpers-BevxX6Pu.js"; import { i as createPairingPrefixStripper } from "./channel-pairing-Bk6_JhTm.js"; import { a as createEmptyChannelResult, i as createAttachedChannelResultAdapter } from "./channel-send-result-Dn_C6AJS.js"; import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-9LM3JYQX.js"; import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-DVluRC70.js"; import { i as resolveExactLineGroupConfigKey, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-CsrscOdC.js"; import { n as parseLineDirectives, o as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-BnoztI3w.js"; import { n as resolveLineOutboundMedia, t as createLineSendReceipt } from "./send-receipt-RvOqfi-N.js"; //#region extensions/line/src/bindings.ts function normalizeLineConversationId(raw) { const trimmed = raw?.trim() ?? ""; if (!trimmed) return null; return (trimmed.match(/^line:(?:(?:user|group|room):)?(.+)$/i)?.[1] ?? trimmed).trim() || null; } function resolveLineCommandConversation(params) { const conversationId = normalizeLineConversationId(params.originatingTo) ?? normalizeLineConversationId(params.commandTo) ?? normalizeLineConversationId(params.fallbackTo); return conversationId ? { conversationId } : null; } function resolveLineInboundConversation(params) { const conversationId = normalizeLineConversationId(params.conversationId) ?? normalizeLineConversationId(params.to); return conversationId ? { conversationId } : null; } const lineBindingsAdapter = { compileConfiguredBinding: ({ conversationId }) => { const normalized = normalizeLineConversationId(conversationId); return normalized ? { conversationId: normalized } : null; }, matchInboundConversation: ({ compiledBinding, conversationId }) => { const normalizedIncoming = normalizeLineConversationId(conversationId); if (!normalizedIncoming || compiledBinding.conversationId !== normalizedIncoming) return null; return { conversationId: normalizedIncoming, matchPriority: 2 }; }, resolveCommandConversation: ({ originatingTo, commandTo, fallbackTo }) => resolveLineCommandConversation({ originatingTo, commandTo, fallbackTo }), resolveInboundConversation: ({ to, conversationId }) => resolveLineInboundConversation({ to, conversationId }) }; //#endregion //#region extensions/line/src/config-adapter.ts function normalizeLineAllowFrom(entry) { return entry.replace(/^line:(?:user:)?/i, ""); } const lineChannelPluginCommon = { meta: { id: "line", label: "LINE", selectionLabel: "LINE (Messaging API)", detailLabel: "LINE Bot", docsPath: "/channels/line", docsLabel: "line", blurb: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", systemImage: "message.fill", quickstartAllowFrom: true }, capabilities: { chatTypes: ["direct", "group"], reactions: false, threads: false, media: true, nativeCommands: false, blockStreaming: true }, reload: { configPrefixes: ["channels.line"] }, configSchema: LineChannelConfigSchema, config: { ...createScopedChannelConfigAdapter({ sectionKey: "line", listAccountIds: listLineAccountIds, resolveAccount: (cfg, accountId) => resolveLineAccount({ cfg, accountId: accountId ?? void 0 }), defaultAccountId: resolveDefaultLineAccountId, clearBaseFields: [ "channelSecret", "tokenFile", "secretFile" ], resolveAllowFrom: (account) => account.config.allowFrom, formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom) }), isConfigured: (account) => hasLineCredentials(account), describeAccount: (account) => describeWebhookAccountSnapshot({ account, configured: hasLineCredentials(account), extra: { tokenSource: account.tokenSource ?? void 0 } }) } }; //#endregion //#region extensions/line/src/gateway.ts const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-CkscotN3.js")); const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime.js")); const lineGatewayAdapter = { startAccount: async (ctx) => { const account = ctx.account; const token = account.channelAccessToken.trim(); const secret = account.channelSecret.trim(); if (!token) throw new Error(`LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`); if (!secret) throw new Error(`LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`); let lineBotLabel = ""; try { const probe = await (await loadLineProbeRuntime$1()).probeLineBot(token, 2500); const displayName = probe.ok ? probe.bot?.displayName?.trim() : null; if (displayName) lineBotLabel = ` (${displayName})`; } catch (err) { if (getLineRuntime().logging.shouldLogVerbose()) ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`); } ctx.log?.info(`[${account.accountId}] starting LINE provider${lineBotLabel}`); return await (getLineRuntime().channel.line?.monitorLineProvider ?? (await loadLineMonitorRuntime()).monitorLineProvider)({ channelAccessToken: token, channelSecret: secret, accountId: account.accountId, config: ctx.cfg, runtime: ctx.runtime, abortSignal: ctx.abortSignal, webhookPath: account.config.webhookPath }); }, logoutAccount: async ({ accountId, cfg }) => { const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim() ?? ""; const nextCfg = { ...cfg }; const nextLine = { ...cfg.channels?.line ?? {} }; let cleared = false; let changed = false; if (accountId === "default") { if (nextLine.channelAccessToken || nextLine.channelSecret || nextLine.tokenFile || nextLine.secretFile) { delete nextLine.channelAccessToken; delete nextLine.channelSecret; delete nextLine.tokenFile; delete nextLine.secretFile; cleared = true; changed = true; } } const accountCleanup = clearAccountEntryFields({ accounts: nextLine.accounts, accountId, fields: [ "channelAccessToken", "channelSecret", "tokenFile", "secretFile" ], markClearedOnFieldPresence: true }); if (accountCleanup.changed) { changed = true; if (accountCleanup.cleared) cleared = true; if (accountCleanup.nextAccounts) nextLine.accounts = accountCleanup.nextAccounts; else delete nextLine.accounts; } if (changed) { if (Object.keys(nextLine).length > 0) nextCfg.channels = { ...nextCfg.channels, line: nextLine }; else { const nextChannels = { ...nextCfg.channels }; delete nextChannels.line; if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels; else delete nextCfg.channels; } await getLineRuntime().config.replaceConfigFile({ nextConfig: nextCfg, afterWrite: { mode: "auto" } }); } const loggedOut = resolveLineAccount({ cfg: changed ? nextCfg : cfg, accountId }).tokenSource === "none"; return { cleared, envToken: Boolean(envToken), loggedOut }; } }; //#endregion //#region extensions/line/src/group-policy.ts function resolveLineGroupRequireMention(params) { const exactGroupId = resolveExactLineGroupConfigKey({ cfg: params.cfg, accountId: params.accountId, groupId: params.groupId }); return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "line", groupId: exactGroupId ?? params.groupId, accountId: params.accountId }); } //#endregion //#region extensions/line/src/outbound.ts const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime.js")); function isLineUserTarget(target) { const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, ""); return /^U/i.test(normalized); } function hasLineSpecificMediaOptions(lineData) { return Boolean(lineData.mediaKind ?? lineData.previewImageUrl?.trim() ?? (typeof lineData.durationMs === "number" ? lineData.durationMs : void 0) ?? lineData.trackingId?.trim()); } function buildLineMediaMessageObject(resolved, opts) { switch (resolved.mediaKind) { case "video": { const previewImageUrl = resolved.previewImageUrl?.trim(); if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL"); return { type: "video", originalContentUrl: resolved.mediaUrl, previewImageUrl, ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {} }; } case "audio": return { type: "audio", originalContentUrl: resolved.mediaUrl, duration: resolved.durationMs ?? 6e4 }; default: return { type: "image", originalContentUrl: resolved.mediaUrl, previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl }; } } const lineOutboundAdapter = { deliveryMode: "direct", chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit), textChunkLimit: 5e3, sendPayload: async ({ to, payload, accountId, cfg }) => { const runtime = getLineRuntime(); const outboundRuntime = await loadLineOutboundRuntime(); const lineData = payload.channelData?.line ?? {}; const lineRuntime = runtime.channel.line; const sendText = lineRuntime?.pushMessageLine ?? outboundRuntime.pushMessageLine; const sendBatch = lineRuntime?.pushMessagesLine ?? outboundRuntime.pushMessagesLine; const sendFlex = lineRuntime?.pushFlexMessage ?? outboundRuntime.pushFlexMessage; const sendTemplate = lineRuntime?.pushTemplateMessage ?? outboundRuntime.pushTemplateMessage; const sendLocation = lineRuntime?.pushLocationMessage ?? outboundRuntime.pushLocationMessage; const sendQuickReplies = lineRuntime?.pushTextMessageWithQuickReplies ?? outboundRuntime.pushTextMessageWithQuickReplies; const buildTemplate = lineRuntime?.buildTemplateMessageFromPayload ?? outboundRuntime.buildTemplateMessageFromPayload; let lastResult = null; const quickReplies = lineData.quickReplies ?? []; const hasQuickReplies = quickReplies.length > 0; const quickReply = hasQuickReplies ? (lineRuntime?.createQuickReplyItems ?? outboundRuntime.createQuickReplyItems)(quickReplies) : void 0; const sendMessageBatch = async (messages) => { if (messages.length === 0) return; for (let i = 0; i < messages.length; i += 5) lastResult = await sendBatch(to, messages.slice(i, i + 5), { verbose: false, cfg, accountId: accountId ?? void 0 }); }; const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : { text: "", flexMessages: [] }; const chunkLimit = runtime.channel.text.resolveTextChunkLimit?.(cfg, "line", accountId ?? void 0, { fallbackLimit: 5e3 }) ?? 5e3; const chunks = processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : []; const mediaUrls = resolveOutboundMediaUrls(payload); const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData); const shouldSendQuickRepliesInline = chunks.length === 0 && hasQuickReplies; const sendMediaMessages = async () => { for (const url of mediaUrls) { const trimmed = url?.trim(); if (!trimmed) continue; if (!useLineSpecificMedia) { lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", { verbose: false, mediaUrl: trimmed, cfg, accountId: accountId ?? void 0 }); continue; } const resolved = await resolveLineOutboundMedia(trimmed, { mediaKind: lineData.mediaKind, previewImageUrl: lineData.previewImageUrl, durationMs: lineData.durationMs, trackingId: lineData.trackingId }); lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", { verbose: false, mediaUrl: resolved.mediaUrl, mediaKind: resolved.mediaKind, previewImageUrl: resolved.previewImageUrl, durationMs: resolved.durationMs, trackingId: resolved.trackingId, cfg, accountId: accountId ?? void 0 }); } }; if (!shouldSendQuickRepliesInline) { if (lineData.flexMessage) { const flexContents = lineData.flexMessage.contents; lastResult = await sendFlex(to, lineData.flexMessage.altText, flexContents, { verbose: false, cfg, accountId: accountId ?? void 0 }); } if (lineData.templateMessage) { const template = buildTemplate(lineData.templateMessage); if (template) lastResult = await sendTemplate(to, template, { verbose: false, cfg, accountId: accountId ?? void 0 }); } if (lineData.location) lastResult = await sendLocation(to, lineData.location, { verbose: false, cfg, accountId: accountId ?? void 0 }); for (const flexMsg of processed.flexMessages) { const flexContents = flexMsg.contents; lastResult = await sendFlex(to, flexMsg.altText, flexContents, { verbose: false, cfg, accountId: accountId ?? void 0 }); } } const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0); if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages(); if (chunks.length > 0) for (let i = 0; i < chunks.length; i += 1) if (i === chunks.length - 1 && hasQuickReplies) lastResult = await sendQuickReplies(to, chunks[i], quickReplies, { verbose: false, cfg, accountId: accountId ?? void 0 }); else lastResult = await sendText(to, chunks[i], { verbose: false, cfg, accountId: accountId ?? void 0 }); else if (shouldSendQuickRepliesInline) { const quickReplyMessages = []; if (lineData.flexMessage) quickReplyMessages.push({ type: "flex", altText: lineData.flexMessage.altText.slice(0, 400), contents: lineData.flexMessage.contents }); if (lineData.templateMessage) { const template = buildTemplate(lineData.templateMessage); if (template) quickReplyMessages.push(template); } if (lineData.location) quickReplyMessages.push({ type: "location", title: lineData.location.title.slice(0, 100), address: lineData.location.address.slice(0, 100), latitude: lineData.location.latitude, longitude: lineData.location.longitude }); for (const flexMsg of processed.flexMessages) quickReplyMessages.push({ type: "flex", altText: flexMsg.altText.slice(0, 400), contents: flexMsg.contents }); for (const url of mediaUrls) { const trimmed = url?.trim(); if (!trimmed) continue; if (!useLineSpecificMedia) { quickReplyMessages.push({ type: "image", originalContentUrl: trimmed, previewImageUrl: trimmed }); continue; } const resolved = await resolveLineOutboundMedia(trimmed, { mediaKind: lineData.mediaKind, previewImageUrl: lineData.previewImageUrl, durationMs: lineData.durationMs, trackingId: lineData.trackingId }); quickReplyMessages.push(buildLineMediaMessageObject(resolved, { allowTrackingId: isLineUserTarget(to) })); } if (quickReplyMessages.length > 0 && quickReply) { const lastIndex = quickReplyMessages.length - 1; quickReplyMessages[lastIndex] = { ...quickReplyMessages[lastIndex], quickReply }; await sendMessageBatch(quickReplyMessages); } else if (quickReply) lastResult = await sendQuickReplies(to, buildLineQuickReplyFallbackText(quickReplies), quickReplies, { verbose: false, cfg, accountId: accountId ?? void 0 }); } if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && sendMediaAfterText) await sendMediaMessages(); if (lastResult) return createEmptyChannelResult("line", { ...lastResult }); return createEmptyChannelResult("line", { messageId: "empty", chatId: to }); }, ...createAttachedChannelResultAdapter({ channel: "line", sendText: async ({ cfg, to, text, accountId }) => { const outboundRuntime = await loadLineOutboundRuntime(); const sendText = outboundRuntime.pushMessageLine; const sendFlex = outboundRuntime.pushFlexMessage; const processed = outboundRuntime.processLineMessage(text); let result; if (processed.text.trim()) result = await sendText(to, processed.text, { verbose: false, cfg, accountId: accountId ?? void 0 }); else result = { messageId: "processed", chatId: to, receipt: createLineSendReceipt({ messageId: "processed", chatId: to, kind: "card" }) }; for (const flexMsg of processed.flexMessages) { const flexContents = flexMsg.contents; await sendFlex(to, flexMsg.altText, flexContents, { verbose: false, cfg, accountId: accountId ?? void 0 }); } return result; }, sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => await (await loadLineOutboundRuntime()).sendMessageLine(to, text, { verbose: false, mediaUrl, cfg, accountId: accountId ?? void 0 }) }) }; function toLineMessageSendResult(result, kind) { const source = result; const receipt = result.receipt ?? (result.messageId ? createLineSendReceipt({ messageId: result.messageId, chatId: source.chatId ?? "", kind }) : void 0); if (!receipt) throw new Error("LINE message adapter send did not return a receipt"); return { messageId: result.messageId || receipt.primaryPlatformMessageId, receipt }; } const lineMessageAdapter = defineChannelMessageAdapter({ id: "line", durableFinal: { capabilities: { text: true, media: true, messageSendingHooks: true } }, send: { text: async ({ cfg, to, text, accountId }) => { return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({ cfg, to, text, accountId, payload: { text } }), "text"); }, media: async ({ cfg, to, text, mediaUrl, accountId }) => { return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({ cfg, to, text, mediaUrl, accountId, payload: { text, mediaUrl } }), "media"); } }, receive: { defaultAckPolicy: "after_receive_record", supportedAckPolicies: ["after_receive_record"] } }); //#endregion //#region extensions/line/src/status.ts const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-CkscotN3.js")); const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({ channel: "line", dependencySourceKey: "tokenSource", missingPrimaryMessage: "LINE channel access token not configured", missingDependentMessage: "LINE channel secret not configured" }); const lineStatusAdapter = createComputedAccountStatusAdapter({ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID), collectStatusIssues: collectLineStatusIssues, buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot), probeAccount: async ({ account, timeoutMs }) => await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs), resolveAccountSnapshot: ({ account }) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, configured: hasLineCredentials(account), extra: { tokenSource: account.tokenSource, mode: "webhook" } }) }); //#endregion //#region extensions/line/src/channel.ts const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-CVkVy9SR.js")); const lineSecurityAdapter = createRestrictSendersChannelSecurity({ channelKey: "line", resolveDmPolicy: (account) => account.config.dmPolicy, resolveDmAllowFrom: (account) => account.config.allowFrom, resolveGroupPolicy: (account) => account.config.groupPolicy, surface: "LINE groups", openScope: "any member in groups", groupPolicyPath: "channels.line.groupPolicy", groupAllowFromPath: "channels.line.groupAllowFrom", mentionGated: false, policyPathSuffix: "dmPolicy", approveHint: "openclaw pairing approve line <code>", normalizeDmEntry: (raw) => raw.replace(/^line:(?:user:)?/i, "") }); const linePlugin = createChatChannelPlugin({ base: { id: "line", ...lineChannelPluginCommon, setupWizard: lineSetupWizard, groups: { resolveRequireMention: resolveLineGroupRequireMention }, messaging: { targetPrefixes: ["line"], normalizeTarget: (target) => { const trimmed = target.trim(); if (!trimmed) return; return trimmed.replace(/^line:(group|room|user):/i, "").replace(/^line:/i, ""); }, resolveInboundConversation: lineBindingsAdapter.resolveInboundConversation, transformReplyPayload: ({ payload }) => { if (!payload.text || !hasLineDirectives(payload.text)) return payload; return parseLineDirectives(payload); }, targetResolver: { looksLikeId: (id) => { const trimmed = id?.trim(); if (!trimmed) return false; return /^[UCR][a-f0-9]{32}$/i.test(trimmed) || /^line:/i.test(trimmed); }, hint: "<userId|groupId|roomId>" } }, directory: createEmptyChannelDirectoryAdapter(), setup: lineSetupAdapter, status: lineStatusAdapter, gateway: lineGatewayAdapter, message: lineMessageAdapter, bindings: lineBindingsAdapter, conversationBindings: { defaultTopLevelPlacement: "current" }, agentPrompt: { messageToolHints: () => [ "", "### LINE Rich Messages", "LINE supports rich visual messages. Use these directives in your reply when appropriate:", "", "**Quick Replies** (bottom button suggestions):", " [[quick_replies: Option 1, Option 2, Option 3]]", "", "**Location** (map pin):", " [[location: Place Name | Address | latitude | longitude]]", "", "**Confirm Dialog** (yes/no prompt):", " [[confirm: Question text? | Yes Label | No Label]]", "", "**Button Menu** (title + text + buttons):", " [[buttons: Title | Description | Btn1:action1, Btn2:https://url.com]]", "", "**Media Player Card** (music status):", " [[media_player: Song Title | Artist Name | Source | https://albumart.url | playing]]", " - Status: 'playing' or 'paused' (optional)", "", "**Event Card** (calendar events, meetings):", " [[event: Event Title | Date | Time | Location | Description]]", " - Time, Location, Description are optional", "", "**Agenda Card** (multiple events/schedule):", " [[agenda: Schedule Title | Event1:9:00 AM, Event2:12:00 PM, Event3:3:00 PM]]", "", "**Device Control Card** (smart devices, TVs, etc.):", " [[device: Device Name | Device Type | Status | Control1:data1, Control2:data2]]", "", "**Apple TV Remote** (full D-pad + transport):", " [[appletv_remote: Apple TV | Playing]]", "", "**Auto-converted**: Markdown tables become Flex cards, code blocks become styled cards.", "", "When to use rich messages:", "- Use [[quick_replies:...]] when offering 2-4 clear options", "- Use [[confirm:...]] for yes/no decisions", "- Use [[buttons:...]] for menus with actions/links", "- Use [[location:...]] when sharing a place", "- Use [[media_player:...]] when showing what's playing", "- Use [[event:...]] for calendar event details", "- Use [[agenda:...]] for a day's schedule or event list", "- Use [[device:...]] for smart device status/controls", "- Tables/code in your response auto-convert to visual cards" ] } }, pairing: { text: { idLabel: "lineUserId", message: "OpenClaw: your access has been approved.", normalizeAllowEntry: createPairingPrefixStripper(/^line:(?:user:)?/i), notify: async ({ cfg, id, message }) => { const account = (getLineRuntime().channel.line?.resolveLineAccount ?? resolveLineAccount)({ cfg }); if (!account.channelAccessToken) throw new Error("LINE channel access token not configured"); await (getLineRuntime().channel.line?.pushMessageLine ?? (await loadLineChannelRuntime()).pushMessageLine)(id, message, { cfg, accountId: account.accountId, channelAccessToken: account.channelAccessToken }); } } }, security: lineSecurityAdapter, outbound: lineOutboundAdapter }); //#endregion export { lineChannelPluginCommon as n, linePlugin as t };