@ai-devkit/channel-connector 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/ConfigStore.d.ts.map +1 -1
- package/dist/ConfigStore.js +1 -0
- package/dist/ConfigStore.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +3 -1
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/ConfigStore.test.js +33 -4
- package/dist/__tests__/ConfigStore.test.js.map +1 -1
- package/dist/__tests__/adapters/SlackAdapter.test.js +429 -0
- package/dist/__tests__/adapters/SlackAdapter.test.js.map +1 -0
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js +127 -0
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js.map +1 -0
- package/dist/__tests__/utils/slackMarkdown.test.js +44 -0
- package/dist/__tests__/utils/slackMarkdown.test.js.map +1 -0
- package/dist/adapters/ChannelAdapter.d.ts +8 -2
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +3 -6
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/SlackAdapter.d.ts +71 -0
- package/dist/adapters/SlackAdapter.d.ts.map +1 -0
- package/dist/adapters/SlackAdapter.js +205 -0
- package/dist/adapters/SlackAdapter.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +59 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/dist/utils/SlackDeliveryQueue.d.ts +33 -0
- package/dist/utils/SlackDeliveryQueue.d.ts.map +1 -0
- package/dist/utils/SlackDeliveryQueue.js +83 -0
- package/dist/utils/SlackDeliveryQueue.js.map +1 -0
- package/dist/utils/slackMarkdown.d.ts +5 -0
- package/dist/utils/slackMarkdown.d.ts.map +1 -0
- package/dist/utils/slackMarkdown.js +150 -0
- package/dist/utils/slackMarkdown.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/SlackAdapter.ts"],"sourcesContent":["import { SocketModeClient } from '@slack/socket-mode';\nimport { WebClient } from '@slack/web-api';\nimport type { InteractiveChannelAdapter } from './ChannelAdapter.js';\nimport type {\n ChannelQuestion,\n IncomingInteraction,\n IncomingMessage,\n SentMessage,\n SlackConfig,\n} from '../types.js';\nimport { SlackDeliveryQueue } from '../utils/SlackDeliveryQueue.js';\nimport { escapeSlackText } from '../utils/slackMarkdown.js';\n\nexport const SLACK_CHANNEL_TYPE = 'slack';\n\ninterface SlackAuthClient {\n authTest(): Promise<{ ok?: boolean; app_id?: string; user_id?: string; team_id?: string; team?: string }>;\n}\n\nexport interface SlackIdentity {\n appId?: string;\n botUserId: string;\n workspaceId: string;\n workspaceName?: string;\n}\n\ninterface SlackConnectionsClient {\n open(): Promise<{ ok?: boolean; url?: string }>;\n}\n\nexport async function validateSlackAppToken(appToken: string, client?: SlackConnectionsClient): Promise<void> {\n const connections = client ?? new WebClient(appToken).apps.connections;\n const result = await connections.open();\n if (!result.ok || !result.url) throw new Error('Slack app token cannot open Socket Mode');\n}\n\nexport async function validateSlackCredentials(botToken: string, client?: SlackAuthClient): Promise<SlackIdentity> {\n const authClient = client ?? {\n authTest: () => new WebClient(botToken).auth.test(),\n };\n const identity = await authClient.authTest();\n if (!identity.ok || !identity.user_id || !identity.team_id) {\n throw new Error('Slack bot token returned incomplete identity');\n }\n return {\n ...(identity.app_id ? { appId: identity.app_id } : {}),\n botUserId: identity.user_id,\n workspaceId: identity.team_id,\n workspaceName: identity.team,\n };\n}\n\ninterface SocketClientLike {\n on(event: string, listener: (payload?: unknown) => void): unknown;\n start(): Promise<unknown>;\n disconnect(): Promise<unknown>;\n}\n\ninterface WebClientLike {\n chat: {\n postMessage(input: Record<string, unknown>): Promise<{ ok?: boolean; ts?: string }>;\n update?(input: Record<string, unknown>): Promise<unknown>;\n };\n}\n\ninterface SlackAdapterDependencies {\n socketClient?: SocketClientLike;\n webClient?: WebClientLike;\n}\n\ninterface SlackEventEnvelope {\n type?: string;\n ack?: () => Promise<void>;\n body?: {\n team_id?: string;\n event_id?: string;\n is_ext_shared_channel?: boolean;\n event?: Record<string, unknown>;\n };\n}\n\nexport class SlackAdapter implements InteractiveChannelAdapter {\n readonly type = SLACK_CHANNEL_TYPE;\n private readonly socket: SocketClientLike;\n private readonly web: WebClientLike;\n private readonly delivery: SlackDeliveryQueue;\n private readonly recentEventIds = new Map<string, number>();\n private messageHandler: ((message: IncomingMessage) => Promise<void>) | null = null;\n private interactionHandler: ((interaction: IncomingInteraction) => Promise<void>) | null = null;\n private running = false;\n private listenersRegistered = false;\n\n constructor(private readonly config: SlackConfig, dependencies: SlackAdapterDependencies = {}) {\n this.socket = dependencies.socketClient ?? new SocketModeClient({ appToken: config.appToken });\n this.web = dependencies.webClient ?? new WebClient(config.botToken) as unknown as WebClientLike;\n this.delivery = new SlackDeliveryQueue({\n postMessage: (input) => this.web.chat.postMessage(input),\n });\n }\n\n async start(): Promise<void> {\n if (!this.listenersRegistered) {\n this.socket.on('slack_event', (payload) => {\n void this.handleSlackEvent(payload as SlackEventEnvelope).catch(() => undefined);\n });\n this.socket.on('interactive', (payload) => {\n void this.handleInteraction(payload as SlackInteractionEnvelope).catch(() => undefined);\n });\n this.socket.on('connected', () => { this.running = true; });\n this.socket.on('disconnected', () => { this.running = false; });\n this.listenersRegistered = true;\n }\n await this.socket.start();\n this.running = true;\n }\n\n async stop(): Promise<void> {\n this.running = false;\n await this.socket.disconnect();\n }\n\n onMessage(handler: (message: IncomingMessage) => Promise<void>): void {\n this.messageHandler = handler;\n }\n\n onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void {\n this.interactionHandler = handler;\n }\n\n async sendMessage(chatId: string, text: string): Promise<SentMessage> {\n return this.delivery.send(chatId, text);\n }\n\n async isHealthy(): Promise<boolean> {\n return this.running;\n }\n\n async sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage> {\n const fallback = escapeSlackText(`${question.header ? `${question.header}: ` : ''}${question.question}`);\n const elements = question.options.map((option) => ({\n type: 'button',\n action_id: 'ai_devkit_question',\n text: { type: 'plain_text', text: option.label.slice(0, 75) },\n value: `${question.id}:${option.value}`.slice(0, 2000),\n }));\n if (question.allowSkip) {\n elements.push({\n type: 'button', action_id: 'ai_devkit_question',\n text: { type: 'plain_text', text: 'Skip' }, value: `${question.id}:skip`,\n });\n }\n const response = await this.web.chat.postMessage({\n channel: chatId,\n text: fallback,\n blocks: [\n {\n type: 'section',\n text: { type: 'mrkdwn', text: `*${escapeSlackText(question.header ?? 'Question')}*\\n${escapeSlackText(question.question)}` },\n },\n { type: 'actions', elements },\n ],\n });\n if (!response.ts) throw new Error('Slack did not return a question message timestamp');\n return { messageId: response.ts };\n }\n\n async finalizeInteraction(chatId: string, messageId: string): Promise<void> {\n if (!this.web.chat.update) throw new Error('Slack Web API chat.update is unavailable');\n await this.web.chat.update({ channel: chatId, ts: messageId, blocks: [] });\n }\n\n private async handleSlackEvent(envelope: SlackEventEnvelope): Promise<void> {\n if (envelope.type !== 'events_api') return;\n await envelope.ack?.();\n const body = envelope.body;\n const event = body?.event;\n const eventId = body?.event_id;\n if (!body || !event || !eventId || !this.messageHandler) return;\n if (!this.isValidDirectMessage(body, event)) return;\n if (this.recentEventIds.has(eventId)) return;\n this.rememberEvent(eventId);\n\n try {\n await this.messageHandler({\n channelType: SLACK_CHANNEL_TYPE,\n chatId: String(event.channel),\n userId: String(event.user),\n text: String(event.text),\n timestamp: new Date(Number(event.event_ts ?? event.ts) * 1000),\n messageId: eventId,\n threadId: typeof event.thread_ts === 'string' ? event.thread_ts : undefined,\n workspaceId: body.team_id,\n metadata: { slackTs: event.ts },\n });\n } catch {\n // The channel consumer reports terminal errors; never reject a Socket Mode listener.\n }\n }\n\n private isValidDirectMessage(body: NonNullable<SlackEventEnvelope['body']>, event: Record<string, unknown>): boolean {\n return body.team_id === this.config.workspaceId\n && body.is_ext_shared_channel !== true\n && event.type === 'message'\n && event.channel_type === 'im'\n && event.user !== this.config.botUserId\n && typeof event.text === 'string'\n && typeof event.ts === 'string'\n && event.subtype === undefined\n && event.bot_id === undefined;\n }\n\n private rememberEvent(eventId: string): void {\n this.recentEventIds.set(eventId, Date.now());\n while (this.recentEventIds.size > 1000) {\n const oldest = this.recentEventIds.keys().next().value as string | undefined;\n if (!oldest) break;\n this.recentEventIds.delete(oldest);\n }\n }\n\n private async handleInteraction(envelope: SlackInteractionEnvelope): Promise<void> {\n await envelope.ack?.();\n const body = envelope.body;\n const action = body?.actions?.[0];\n const interactionId = action?.action_ts;\n const workspaceId = body?.team?.id;\n const userId = body?.user?.id;\n const chatId = body?.channel?.id;\n const messageId = body?.container?.message_ts;\n if (!body || !action || !interactionId || !this.interactionHandler) return;\n if (body.type !== 'block_actions'\n || typeof workspaceId !== 'string'\n || typeof userId !== 'string'\n || typeof chatId !== 'string'\n || workspaceId !== this.config.workspaceId\n || typeof messageId !== 'string'\n || action.action_id !== 'ai_devkit_question'\n || typeof action.value !== 'string'\n || this.recentEventIds.has(`interaction:${interactionId}`)) return;\n this.rememberEvent(`interaction:${interactionId}`);\n try {\n await this.interactionHandler({\n channelType: SLACK_CHANNEL_TYPE,\n chatId,\n userId,\n workspaceId,\n interactionId,\n messageId,\n actionId: action.action_id,\n value: action.value,\n timestamp: new Date(Number(interactionId) * 1000),\n });\n } catch {\n // Keep Socket Mode listener failures isolated from the SDK event loop.\n }\n }\n}\n\ninterface SlackInteractionEnvelope {\n ack?: () => Promise<void>;\n body?: {\n type?: string;\n team?: { id?: string };\n user?: { id?: string };\n channel?: { id?: string };\n container?: { message_ts?: string };\n actions?: Array<{ action_id?: string; action_ts?: string; value?: string }>;\n };\n}\n"],"names":["SocketModeClient","WebClient","SlackDeliveryQueue","escapeSlackText","SLACK_CHANNEL_TYPE","validateSlackAppToken","appToken","client","connections","apps","result","open","ok","url","Error","validateSlackCredentials","botToken","authClient","authTest","auth","test","identity","user_id","team_id","app_id","appId","botUserId","workspaceId","workspaceName","team","SlackAdapter","type","socket","web","delivery","recentEventIds","Map","messageHandler","interactionHandler","running","listenersRegistered","config","dependencies","socketClient","webClient","postMessage","input","chat","start","on","payload","handleSlackEvent","catch","undefined","handleInteraction","stop","disconnect","onMessage","handler","onInteraction","sendMessage","chatId","text","send","isHealthy","sendQuestion","question","fallback","header","elements","options","map","option","action_id","label","slice","value","id","allowSkip","push","response","channel","blocks","ts","messageId","finalizeInteraction","update","envelope","ack","body","event","eventId","event_id","isValidDirectMessage","has","rememberEvent","channelType","String","userId","user","timestamp","Date","Number","event_ts","threadId","thread_ts","metadata","slackTs","is_ext_shared_channel","channel_type","subtype","bot_id","set","now","size","oldest","keys","next","delete","action","actions","interactionId","action_ts","container","message_ts","actionId"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export { ChannelManager } from './ChannelManager.js';
|
|
2
2
|
export { ConfigStore } from './ConfigStore.js';
|
|
3
3
|
export { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';
|
|
4
|
+
export { SlackAdapter, SLACK_CHANNEL_TYPE, validateSlackAppToken, validateSlackCredentials, } from './adapters/SlackAdapter.js';
|
|
5
|
+
export type { SlackIdentity } from './adapters/SlackAdapter.js';
|
|
6
|
+
export { SLACK_MAX_MESSAGE_LENGTH, chunkMarkdownForSlack, escapeSlackText, markdownToSlackMrkdwn, } from './utils/slackMarkdown.js';
|
|
7
|
+
export { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';
|
|
4
8
|
export type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';
|
|
5
|
-
export
|
|
6
|
-
export type {
|
|
9
|
+
export { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
10
|
+
export type { ChannelAdapter, InteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
11
|
+
export type { IncomingMessage, MessageHandler, ChannelConfig, ChannelEntry, ChannelType, TelegramConfig, SlackConfig, SlackChannelEntry, SendMessageOptions, SentMessage, ChannelQuestion, ChannelQuestionOption, IncomingInteraction, InteractionHandler, InlineKeyboardButton, InlineKeyboard, IncomingCallback, CallbackHandler, } from './types.js';
|
|
7
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACpH,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACpH,OAAO,EACH,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACH,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,YAAY,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,YAAY,EACR,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,WAAW,EACX,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,eAAe,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { ChannelManager } from './ChannelManager.js';
|
|
2
2
|
export { ConfigStore } from './ConfigStore.js';
|
|
3
3
|
export { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';
|
|
4
|
+
export { SlackAdapter, SLACK_CHANNEL_TYPE, validateSlackAppToken, validateSlackCredentials } from './adapters/SlackAdapter.js';
|
|
5
|
+
export { SLACK_MAX_MESSAGE_LENGTH, chunkMarkdownForSlack, escapeSlackText, markdownToSlackMrkdwn } from './utils/slackMarkdown.js';
|
|
6
|
+
export { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';
|
|
7
|
+
export { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
4
8
|
|
|
5
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { ChannelManager } from './ChannelManager.js';\nexport { ConfigStore } from './ConfigStore.js';\nexport { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';\nexport type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';\n\nexport type { ChannelAdapter } from './adapters/ChannelAdapter.js';\n\nexport type {\n IncomingMessage,\n MessageHandler,\n ChannelConfig,\n ChannelEntry,\n ChannelType,\n TelegramConfig,\n InlineKeyboardButton,\n InlineKeyboard,\n IncomingCallback,\n CallbackHandler,\n} from './types.js';\n"],"names":["ChannelManager","ConfigStore","TelegramAdapter","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAsB;AACrD,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,eAAe,EAAEC,qBAAqB,EAAEC,2BAA2B,QAAQ,gCAAgC"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { ChannelManager } from './ChannelManager.js';\nexport { ConfigStore } from './ConfigStore.js';\nexport { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';\nexport {\n SlackAdapter,\n SLACK_CHANNEL_TYPE,\n validateSlackAppToken,\n validateSlackCredentials,\n} from './adapters/SlackAdapter.js';\nexport type { SlackIdentity } from './adapters/SlackAdapter.js';\nexport {\n SLACK_MAX_MESSAGE_LENGTH,\n chunkMarkdownForSlack,\n escapeSlackText,\n markdownToSlackMrkdwn,\n} from './utils/slackMarkdown.js';\nexport { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';\nexport type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';\n\nexport { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';\nexport type { ChannelAdapter, InteractiveChannelAdapter } from './adapters/ChannelAdapter.js';\n\nexport type {\n IncomingMessage,\n MessageHandler,\n ChannelConfig,\n ChannelEntry,\n ChannelType,\n TelegramConfig,\n SlackConfig,\n SlackChannelEntry,\n SendMessageOptions,\n SentMessage,\n ChannelQuestion,\n ChannelQuestionOption,\n IncomingInteraction,\n InteractionHandler,\n InlineKeyboardButton,\n InlineKeyboard,\n IncomingCallback,\n CallbackHandler,\n} from './types.js';\n"],"names":["ChannelManager","ConfigStore","TelegramAdapter","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH","SlackAdapter","SLACK_CHANNEL_TYPE","validateSlackAppToken","validateSlackCredentials","SLACK_MAX_MESSAGE_LENGTH","chunkMarkdownForSlack","escapeSlackText","markdownToSlackMrkdwn","SlackDeliveryQueue","isInteractiveChannelAdapter"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAsB;AACrD,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,eAAe,EAAEC,qBAAqB,EAAEC,2BAA2B,QAAQ,gCAAgC;AACpH,SACIC,YAAY,EACZC,kBAAkB,EAClBC,qBAAqB,EACrBC,wBAAwB,QACrB,6BAA6B;AAEpC,SACIC,wBAAwB,EACxBC,qBAAqB,EACrBC,eAAe,EACfC,qBAAqB,QAClB,2BAA2B;AAClC,SAASC,kBAAkB,QAAQ,gCAAgC;AAGnE,SAASC,2BAA2B,QAAQ,+BAA+B"}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export interface IncomingMessage {
|
|
|
8
8
|
userId: string;
|
|
9
9
|
text: string;
|
|
10
10
|
timestamp: Date;
|
|
11
|
+
messageId?: string;
|
|
12
|
+
threadId?: string;
|
|
13
|
+
workspaceId?: string;
|
|
11
14
|
metadata?: Record<string, unknown>;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
@@ -24,12 +27,23 @@ export interface ChannelConfig {
|
|
|
24
27
|
/**
|
|
25
28
|
* Configuration entry for a single channel.
|
|
26
29
|
*/
|
|
27
|
-
|
|
28
|
-
type: ChannelType;
|
|
30
|
+
interface BaseChannelEntry {
|
|
29
31
|
enabled: boolean;
|
|
30
32
|
createdAt: string;
|
|
33
|
+
}
|
|
34
|
+
export interface TelegramChannelEntry extends BaseChannelEntry {
|
|
35
|
+
type: 'telegram';
|
|
31
36
|
config: TelegramConfig;
|
|
32
37
|
}
|
|
38
|
+
export interface SlackChannelEntry extends BaseChannelEntry {
|
|
39
|
+
type: 'slack';
|
|
40
|
+
config: SlackConfig;
|
|
41
|
+
}
|
|
42
|
+
export interface OtherChannelEntry extends BaseChannelEntry {
|
|
43
|
+
type: Exclude<ChannelType, 'telegram' | 'slack'>;
|
|
44
|
+
config: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
export type ChannelEntry = TelegramChannelEntry | SlackChannelEntry | OtherChannelEntry;
|
|
33
47
|
/**
|
|
34
48
|
* Supported channel types.
|
|
35
49
|
*/
|
|
@@ -42,6 +56,48 @@ export interface TelegramConfig {
|
|
|
42
56
|
botUsername: string;
|
|
43
57
|
authorizedChatId?: number;
|
|
44
58
|
}
|
|
59
|
+
export interface SlackConfig {
|
|
60
|
+
appToken: string;
|
|
61
|
+
botToken: string;
|
|
62
|
+
appId?: string;
|
|
63
|
+
botUserId: string;
|
|
64
|
+
workspaceId: string;
|
|
65
|
+
workspaceName?: string;
|
|
66
|
+
transport: 'socket-mode';
|
|
67
|
+
audience: 'dm';
|
|
68
|
+
}
|
|
69
|
+
export declare function isSlackEntry(entry: ChannelEntry): entry is SlackChannelEntry;
|
|
70
|
+
export interface SendMessageOptions {
|
|
71
|
+
threadId?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface SentMessage {
|
|
74
|
+
messageId: string;
|
|
75
|
+
threadId?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface ChannelQuestionOption {
|
|
78
|
+
label: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
value: string;
|
|
81
|
+
}
|
|
82
|
+
export interface ChannelQuestion {
|
|
83
|
+
id: string;
|
|
84
|
+
question: string;
|
|
85
|
+
header?: string;
|
|
86
|
+
options: ChannelQuestionOption[];
|
|
87
|
+
allowSkip: boolean;
|
|
88
|
+
}
|
|
89
|
+
export interface IncomingInteraction {
|
|
90
|
+
channelType: string;
|
|
91
|
+
chatId: string;
|
|
92
|
+
userId: string;
|
|
93
|
+
interactionId: string;
|
|
94
|
+
messageId: string;
|
|
95
|
+
actionId: string;
|
|
96
|
+
value: string;
|
|
97
|
+
workspaceId?: string;
|
|
98
|
+
timestamp: Date;
|
|
99
|
+
}
|
|
100
|
+
export type InteractionHandler = (interaction: IncomingInteraction) => Promise<void>;
|
|
45
101
|
/**
|
|
46
102
|
* A single button in a Telegram-style inline keyboard.
|
|
47
103
|
*/
|
|
@@ -69,4 +125,5 @@ export interface IncomingCallback {
|
|
|
69
125
|
* Handler for inline-keyboard taps.
|
|
70
126
|
*/
|
|
71
127
|
export type CallbackHandler = (callback: IncomingCallback) => Promise<void>;
|
|
128
|
+
export {};
|
|
72
129
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An incoming message from a messaging platform.
|
|
3
3
|
* Generic — no agent-specific concepts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
*/ export function isSlackEntry(entry) {
|
|
5
|
+
return entry.type === 'slack';
|
|
6
|
+
}
|
|
7
7
|
|
|
8
8
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * An incoming message from a messaging platform.\n * Generic — no agent-specific concepts.\n */\nexport interface IncomingMessage {\n channelType: string;\n chatId: string;\n userId: string;\n text: string;\n timestamp: Date;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Handler function provided by the consumer (e.g., CLI).\n * Fire-and-forget — returns void. Responses are sent separately via sendMessage().\n */\nexport type MessageHandler = (message: IncomingMessage) => Promise<void>;\n\n/**\n * Root configuration for all channels.\n */\nexport interface ChannelConfig {\n channels: Record<string, ChannelEntry>;\n}\n\n/**\n * Configuration entry for a single channel.\n */\nexport interface
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * An incoming message from a messaging platform.\n * Generic — no agent-specific concepts.\n */\nexport interface IncomingMessage {\n channelType: string;\n chatId: string;\n userId: string;\n text: string;\n timestamp: Date;\n messageId?: string;\n threadId?: string;\n workspaceId?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Handler function provided by the consumer (e.g., CLI).\n * Fire-and-forget — returns void. Responses are sent separately via sendMessage().\n */\nexport type MessageHandler = (message: IncomingMessage) => Promise<void>;\n\n/**\n * Root configuration for all channels.\n */\nexport interface ChannelConfig {\n channels: Record<string, ChannelEntry>;\n}\n\n/**\n * Configuration entry for a single channel.\n */\ninterface BaseChannelEntry {\n enabled: boolean;\n createdAt: string;\n}\n\nexport interface TelegramChannelEntry extends BaseChannelEntry {\n type: 'telegram';\n config: TelegramConfig;\n}\n\nexport interface SlackChannelEntry extends BaseChannelEntry {\n type: 'slack';\n config: SlackConfig;\n}\n\nexport interface OtherChannelEntry extends BaseChannelEntry {\n type: Exclude<ChannelType, 'telegram' | 'slack'>;\n config: Record<string, unknown>;\n}\n\nexport type ChannelEntry = TelegramChannelEntry | SlackChannelEntry | OtherChannelEntry;\n\n/**\n * Supported channel types.\n */\nexport type ChannelType = 'telegram' | 'slack' | 'whatsapp';\n\n/**\n * Telegram-specific configuration.\n */\nexport interface TelegramConfig {\n botToken: string;\n botUsername: string;\n authorizedChatId?: number;\n}\n\nexport interface SlackConfig {\n appToken: string;\n botToken: string;\n appId?: string;\n botUserId: string;\n workspaceId: string;\n workspaceName?: string;\n transport: 'socket-mode';\n audience: 'dm';\n}\n\nexport function isSlackEntry(entry: ChannelEntry): entry is SlackChannelEntry {\n return entry.type === 'slack';\n}\n\nexport interface SendMessageOptions {\n threadId?: string;\n}\n\nexport interface SentMessage {\n messageId: string;\n threadId?: string;\n}\n\nexport interface ChannelQuestionOption {\n label: string;\n description?: string;\n value: string;\n}\n\nexport interface ChannelQuestion {\n id: string;\n question: string;\n header?: string;\n options: ChannelQuestionOption[];\n allowSkip: boolean;\n}\n\nexport interface IncomingInteraction {\n channelType: string;\n chatId: string;\n userId: string;\n interactionId: string;\n messageId: string;\n actionId: string;\n value: string;\n workspaceId?: string;\n timestamp: Date;\n}\n\nexport type InteractionHandler = (interaction: IncomingInteraction) => Promise<void>;\n\n/**\n * A single button in a Telegram-style inline keyboard.\n */\nexport interface InlineKeyboardButton {\n text: string;\n callbackData: string;\n}\n\n/**\n * An inline keyboard layout: rows of buttons.\n */\nexport type InlineKeyboard = InlineKeyboardButton[][];\n\n/**\n * An inline-keyboard tap delivered as a callback_query.\n */\nexport interface IncomingCallback {\n channelType: string;\n chatId: string;\n userId: string;\n messageId: number;\n callbackData: string;\n callbackQueryId: string;\n timestamp: Date;\n}\n\n/**\n * Handler for inline-keyboard taps.\n */\nexport type CallbackHandler = (callback: IncomingCallback) => Promise<void>;\n"],"names":["isSlackEntry","entry","type"],"mappings":"AAAA;;;CAGC,GA4ED,OAAO,SAASA,aAAaC,KAAmB;IAC5C,OAAOA,MAAMC,IAAI,KAAK;AAC1B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SentMessage } from '../types.js';
|
|
2
|
+
interface SlackPoster {
|
|
3
|
+
postMessage(input: {
|
|
4
|
+
channel: string;
|
|
5
|
+
text: string;
|
|
6
|
+
mrkdwn: true;
|
|
7
|
+
unfurl_links: false;
|
|
8
|
+
unfurl_media: false;
|
|
9
|
+
thread_ts?: string;
|
|
10
|
+
blocks?: unknown[];
|
|
11
|
+
}): Promise<{
|
|
12
|
+
ok?: boolean;
|
|
13
|
+
ts?: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
interface DeliveryOptions {
|
|
17
|
+
maxMessageLength?: number;
|
|
18
|
+
maxQueueSize?: number;
|
|
19
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare class SlackDeliveryQueue {
|
|
22
|
+
private readonly client;
|
|
23
|
+
private readonly states;
|
|
24
|
+
private readonly maxMessageLength;
|
|
25
|
+
private readonly maxQueueSize;
|
|
26
|
+
private readonly sleep;
|
|
27
|
+
constructor(client: SlackPoster, options?: DeliveryOptions);
|
|
28
|
+
send(channel: string, markdown: string): Promise<SentMessage>;
|
|
29
|
+
private deliver;
|
|
30
|
+
private postWithRateLimit;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=SlackDeliveryQueue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlackDeliveryQueue.d.ts","sourceRoot":"","sources":["../../src/utils/SlackDeliveryQueue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,UAAU,WAAW;IACjB,WAAW,CAAC,KAAK,EAAE;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,IAAI,CAAC;QACb,YAAY,EAAE,KAAK,CAAC;QACpB,YAAY,EAAE,KAAK,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;KACtB,GAAG,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,UAAU,eAAe;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAUD,qBAAa,kBAAkB;IAMf,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;gBAEnC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,eAAoB;IAMzE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YA4BrD,OAAO;YAqBP,iBAAiB;CAUlC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { chunkMarkdownForSlack, SLACK_MAX_MESSAGE_LENGTH } from './slackMarkdown.js';
|
|
2
|
+
const defaultSleep = (milliseconds)=>new Promise((resolve)=>setTimeout(resolve, milliseconds));
|
|
3
|
+
export class SlackDeliveryQueue {
|
|
4
|
+
client;
|
|
5
|
+
states = new Map();
|
|
6
|
+
maxMessageLength;
|
|
7
|
+
maxQueueSize;
|
|
8
|
+
sleep;
|
|
9
|
+
constructor(client, options = {}){
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.maxMessageLength = options.maxMessageLength ?? SLACK_MAX_MESSAGE_LENGTH;
|
|
12
|
+
this.maxQueueSize = options.maxQueueSize ?? 100;
|
|
13
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
14
|
+
}
|
|
15
|
+
async send(channel, markdown) {
|
|
16
|
+
const state = this.states.get(channel) ?? {
|
|
17
|
+
tail: Promise.resolve(),
|
|
18
|
+
pending: 0
|
|
19
|
+
};
|
|
20
|
+
if (state.pending >= this.maxQueueSize) {
|
|
21
|
+
throw new Error('Slack delivery queue is full');
|
|
22
|
+
}
|
|
23
|
+
state.pending += 1;
|
|
24
|
+
this.states.set(channel, state);
|
|
25
|
+
let resolveResult;
|
|
26
|
+
let rejectResult;
|
|
27
|
+
const result = new Promise((resolve, reject)=>{
|
|
28
|
+
resolveResult = resolve;
|
|
29
|
+
rejectResult = reject;
|
|
30
|
+
});
|
|
31
|
+
state.tail = state.tail.then(async ()=>{
|
|
32
|
+
try {
|
|
33
|
+
resolveResult(await this.deliver(channel, markdown));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
rejectResult(error);
|
|
36
|
+
} finally{
|
|
37
|
+
state.pending -= 1;
|
|
38
|
+
if (state.pending === 0) this.states.delete(channel);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
async deliver(channel, markdown) {
|
|
44
|
+
const chunks = chunkMarkdownForSlack(markdown, this.maxMessageLength);
|
|
45
|
+
if (chunks.length === 0) throw new Error('Cannot send an empty Slack message');
|
|
46
|
+
let parentTs;
|
|
47
|
+
for (const text of chunks){
|
|
48
|
+
const response = await this.postWithRateLimit({
|
|
49
|
+
channel,
|
|
50
|
+
text,
|
|
51
|
+
mrkdwn: true,
|
|
52
|
+
unfurl_links: false,
|
|
53
|
+
unfurl_media: false,
|
|
54
|
+
...parentTs ? {
|
|
55
|
+
thread_ts: parentTs
|
|
56
|
+
} : {}
|
|
57
|
+
});
|
|
58
|
+
if (!response.ts) throw new Error('Slack did not return a message timestamp');
|
|
59
|
+
parentTs ??= response.ts;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
messageId: parentTs,
|
|
63
|
+
threadId: parentTs
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async postWithRateLimit(input) {
|
|
67
|
+
try {
|
|
68
|
+
return await this.client.postMessage(input);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const retryAfter = readRetryAfter(error);
|
|
71
|
+
if (retryAfter === undefined) throw error;
|
|
72
|
+
await this.sleep(Math.min(retryAfter * 1000, 60_000));
|
|
73
|
+
return this.client.postMessage(input);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function readRetryAfter(error) {
|
|
78
|
+
if (!error || typeof error !== 'object') return undefined;
|
|
79
|
+
const value = error.retryAfter;
|
|
80
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=SlackDeliveryQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/SlackDeliveryQueue.ts"],"sourcesContent":["import type { SentMessage } from '../types.js';\nimport { chunkMarkdownForSlack, SLACK_MAX_MESSAGE_LENGTH } from './slackMarkdown.js';\n\ninterface SlackPoster {\n postMessage(input: {\n channel: string;\n text: string;\n mrkdwn: true;\n unfurl_links: false;\n unfurl_media: false;\n thread_ts?: string;\n blocks?: unknown[];\n }): Promise<{ ok?: boolean; ts?: string }>;\n}\n\ninterface DeliveryOptions {\n maxMessageLength?: number;\n maxQueueSize?: number;\n sleep?: (milliseconds: number) => Promise<void>;\n}\n\ninterface ConversationState {\n tail: Promise<void>;\n pending: number;\n}\n\nconst defaultSleep = (milliseconds: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, milliseconds));\n\nexport class SlackDeliveryQueue {\n private readonly states = new Map<string, ConversationState>();\n private readonly maxMessageLength: number;\n private readonly maxQueueSize: number;\n private readonly sleep: (milliseconds: number) => Promise<void>;\n\n constructor(private readonly client: SlackPoster, options: DeliveryOptions = {}) {\n this.maxMessageLength = options.maxMessageLength ?? SLACK_MAX_MESSAGE_LENGTH;\n this.maxQueueSize = options.maxQueueSize ?? 100;\n this.sleep = options.sleep ?? defaultSleep;\n }\n\n async send(channel: string, markdown: string): Promise<SentMessage> {\n const state = this.states.get(channel) ?? { tail: Promise.resolve(), pending: 0 };\n if (state.pending >= this.maxQueueSize) {\n throw new Error('Slack delivery queue is full');\n }\n state.pending += 1;\n this.states.set(channel, state);\n\n let resolveResult!: (result: SentMessage) => void;\n let rejectResult!: (error: unknown) => void;\n const result = new Promise<SentMessage>((resolve, reject) => {\n resolveResult = resolve;\n rejectResult = reject;\n });\n\n state.tail = state.tail.then(async () => {\n try {\n resolveResult(await this.deliver(channel, markdown));\n } catch (error) {\n rejectResult(error);\n } finally {\n state.pending -= 1;\n if (state.pending === 0) this.states.delete(channel);\n }\n });\n return result;\n }\n\n private async deliver(channel: string, markdown: string): Promise<SentMessage> {\n const chunks = chunkMarkdownForSlack(markdown, this.maxMessageLength);\n if (chunks.length === 0) throw new Error('Cannot send an empty Slack message');\n let parentTs: string | undefined;\n\n for (const text of chunks) {\n const response = await this.postWithRateLimit({\n channel,\n text,\n mrkdwn: true,\n unfurl_links: false,\n unfurl_media: false,\n ...(parentTs ? { thread_ts: parentTs } : {}),\n });\n if (!response.ts) throw new Error('Slack did not return a message timestamp');\n parentTs ??= response.ts;\n }\n\n return { messageId: parentTs!, threadId: parentTs };\n }\n\n private async postWithRateLimit(input: Parameters<SlackPoster['postMessage']>[0]): Promise<{ ok?: boolean; ts?: string }> {\n try {\n return await this.client.postMessage(input);\n } catch (error) {\n const retryAfter = readRetryAfter(error);\n if (retryAfter === undefined) throw error;\n await this.sleep(Math.min(retryAfter * 1000, 60_000));\n return this.client.postMessage(input);\n }\n }\n}\n\nfunction readRetryAfter(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const value = (error as { retryAfter?: unknown }).retryAfter;\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n"],"names":["chunkMarkdownForSlack","SLACK_MAX_MESSAGE_LENGTH","defaultSleep","milliseconds","Promise","resolve","setTimeout","SlackDeliveryQueue","states","Map","maxMessageLength","maxQueueSize","sleep","client","options","send","channel","markdown","state","get","tail","pending","Error","set","resolveResult","rejectResult","result","reject","then","deliver","error","delete","chunks","length","parentTs","text","response","postWithRateLimit","mrkdwn","unfurl_links","unfurl_media","thread_ts","ts","messageId","threadId","input","postMessage","retryAfter","readRetryAfter","undefined","Math","min","value","Number","isFinite"],"mappings":"AACA,SAASA,qBAAqB,EAAEC,wBAAwB,QAAQ,qBAAqB;AAyBrF,MAAMC,eAAe,CAACC,eAClB,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAASF;AAEjD,OAAO,MAAMI;;IACQC,SAAS,IAAIC,MAAiC;IAC9CC,iBAAyB;IACzBC,aAAqB;IACrBC,MAA+C;IAEhE,YAAY,AAAiBC,MAAmB,EAAEC,UAA2B,CAAC,CAAC,CAAE;aAApDD,SAAAA;QACzB,IAAI,CAACH,gBAAgB,GAAGI,QAAQJ,gBAAgB,IAAIT;QACpD,IAAI,CAACU,YAAY,GAAGG,QAAQH,YAAY,IAAI;QAC5C,IAAI,CAACC,KAAK,GAAGE,QAAQF,KAAK,IAAIV;IAClC;IAEA,MAAMa,KAAKC,OAAe,EAAEC,QAAgB,EAAwB;QAChE,MAAMC,QAAQ,IAAI,CAACV,MAAM,CAACW,GAAG,CAACH,YAAY;YAAEI,MAAMhB,QAAQC,OAAO;YAAIgB,SAAS;QAAE;QAChF,IAAIH,MAAMG,OAAO,IAAI,IAAI,CAACV,YAAY,EAAE;YACpC,MAAM,IAAIW,MAAM;QACpB;QACAJ,MAAMG,OAAO,IAAI;QACjB,IAAI,CAACb,MAAM,CAACe,GAAG,CAACP,SAASE;QAEzB,IAAIM;QACJ,IAAIC;QACJ,MAAMC,SAAS,IAAItB,QAAqB,CAACC,SAASsB;YAC9CH,gBAAgBnB;YAChBoB,eAAeE;QACnB;QAEAT,MAAME,IAAI,GAAGF,MAAME,IAAI,CAACQ,IAAI,CAAC;YACzB,IAAI;gBACAJ,cAAc,MAAM,IAAI,CAACK,OAAO,CAACb,SAASC;YAC9C,EAAE,OAAOa,OAAO;gBACZL,aAAaK;YACjB,SAAU;gBACNZ,MAAMG,OAAO,IAAI;gBACjB,IAAIH,MAAMG,OAAO,KAAK,GAAG,IAAI,CAACb,MAAM,CAACuB,MAAM,CAACf;YAChD;QACJ;QACA,OAAOU;IACX;IAEA,MAAcG,QAAQb,OAAe,EAAEC,QAAgB,EAAwB;QAC3E,MAAMe,SAAShC,sBAAsBiB,UAAU,IAAI,CAACP,gBAAgB;QACpE,IAAIsB,OAAOC,MAAM,KAAK,GAAG,MAAM,IAAIX,MAAM;QACzC,IAAIY;QAEJ,KAAK,MAAMC,QAAQH,OAAQ;YACvB,MAAMI,WAAW,MAAM,IAAI,CAACC,iBAAiB,CAAC;gBAC1CrB;gBACAmB;gBACAG,QAAQ;gBACRC,cAAc;gBACdC,cAAc;gBACd,GAAIN,WAAW;oBAAEO,WAAWP;gBAAS,IAAI,CAAC,CAAC;YAC/C;YACA,IAAI,CAACE,SAASM,EAAE,EAAE,MAAM,IAAIpB,MAAM;YAClCY,aAAaE,SAASM,EAAE;QAC5B;QAEA,OAAO;YAAEC,WAAWT;YAAWU,UAAUV;QAAS;IACtD;IAEA,MAAcG,kBAAkBQ,KAAgD,EAA0C;QACtH,IAAI;YACA,OAAO,MAAM,IAAI,CAAChC,MAAM,CAACiC,WAAW,CAACD;QACzC,EAAE,OAAOf,OAAO;YACZ,MAAMiB,aAAaC,eAAelB;YAClC,IAAIiB,eAAeE,WAAW,MAAMnB;YACpC,MAAM,IAAI,CAAClB,KAAK,CAACsC,KAAKC,GAAG,CAACJ,aAAa,MAAM;YAC7C,OAAO,IAAI,CAAClC,MAAM,CAACiC,WAAW,CAACD;QACnC;IACJ;AACJ;AAEA,SAASG,eAAelB,KAAc;IAClC,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU,OAAOmB;IAChD,MAAMG,QAAQ,AAACtB,MAAmCiB,UAAU;IAC5D,OAAO,OAAOK,UAAU,YAAYC,OAAOC,QAAQ,CAACF,UAAUA,SAAS,IAAIA,QAAQH;AACvF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const SLACK_MAX_MESSAGE_LENGTH = 4000;
|
|
2
|
+
export declare function escapeSlackText(text: string): string;
|
|
3
|
+
export declare function markdownToSlackMrkdwn(markdown: string): string;
|
|
4
|
+
export declare function chunkMarkdownForSlack(markdown: string, maxLength?: number): string[];
|
|
5
|
+
//# sourceMappingURL=slackMarkdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slackMarkdown.d.ts","sourceRoot":"","sources":["../../src/utils/slackMarkdown.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAG7C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,SAA2B,GACrC,MAAM,EAAE,CAwCV"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Marked } from 'marked';
|
|
2
|
+
export const SLACK_MAX_MESSAGE_LENGTH = 4000;
|
|
3
|
+
const lexer = new Marked();
|
|
4
|
+
export function escapeSlackText(text) {
|
|
5
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
6
|
+
}
|
|
7
|
+
export function markdownToSlackMrkdwn(markdown) {
|
|
8
|
+
return renderBlocks(lexer.lexer(markdown)).trimEnd();
|
|
9
|
+
}
|
|
10
|
+
export function chunkMarkdownForSlack(markdown, maxLength = SLACK_MAX_MESSAGE_LENGTH) {
|
|
11
|
+
const tokens = lexer.lexer(markdown);
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let current = '';
|
|
14
|
+
const append = (rendered)=>{
|
|
15
|
+
if (!rendered) return;
|
|
16
|
+
if (rendered.length > maxLength) {
|
|
17
|
+
if (current) {
|
|
18
|
+
chunks.push(current.trimEnd());
|
|
19
|
+
current = '';
|
|
20
|
+
}
|
|
21
|
+
chunks.push(...splitRendered(rendered.trimEnd(), maxLength));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const candidate = current ? `${current}${rendered}` : rendered;
|
|
25
|
+
if (candidate.trimEnd().length <= maxLength) {
|
|
26
|
+
current = candidate;
|
|
27
|
+
} else {
|
|
28
|
+
chunks.push(current.trimEnd());
|
|
29
|
+
current = rendered;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
for (const token of tokens){
|
|
33
|
+
if (token.type === 'code') {
|
|
34
|
+
const rendered = renderCode(token);
|
|
35
|
+
if (rendered.length > maxLength) {
|
|
36
|
+
if (current) {
|
|
37
|
+
chunks.push(current.trimEnd());
|
|
38
|
+
current = '';
|
|
39
|
+
}
|
|
40
|
+
chunks.push(...splitCode(token.text, maxLength));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
append(renderBlock(token));
|
|
45
|
+
}
|
|
46
|
+
if (current.trimEnd()) chunks.push(current.trimEnd());
|
|
47
|
+
return chunks;
|
|
48
|
+
}
|
|
49
|
+
function renderBlocks(tokens) {
|
|
50
|
+
return tokens.map(renderBlock).join('');
|
|
51
|
+
}
|
|
52
|
+
function renderBlock(token) {
|
|
53
|
+
switch(token.type){
|
|
54
|
+
case 'heading':
|
|
55
|
+
return `*${renderInline(token.tokens)}*\n\n`;
|
|
56
|
+
case 'paragraph':
|
|
57
|
+
return `${renderInline(token.tokens)}\n\n`;
|
|
58
|
+
case 'text':
|
|
59
|
+
{
|
|
60
|
+
const text = token;
|
|
61
|
+
return text.tokens ? renderInline(text.tokens) : escapeSlackText(text.text);
|
|
62
|
+
}
|
|
63
|
+
case 'code':
|
|
64
|
+
return `${renderCode(token)}\n\n`;
|
|
65
|
+
case 'blockquote':
|
|
66
|
+
return renderBlocks(token.tokens).trimEnd().split('\n').map((line)=>`> ${line}`).join('\n') + '\n\n';
|
|
67
|
+
case 'list':
|
|
68
|
+
return renderList(token);
|
|
69
|
+
case 'space':
|
|
70
|
+
return '';
|
|
71
|
+
case 'hr':
|
|
72
|
+
return '—\n\n';
|
|
73
|
+
default:
|
|
74
|
+
return escapeSlackText(token.raw ?? '');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function renderInline(tokens) {
|
|
78
|
+
return tokens.map((token)=>{
|
|
79
|
+
switch(token.type){
|
|
80
|
+
case 'text':
|
|
81
|
+
return escapeSlackText(token.text);
|
|
82
|
+
case 'strong':
|
|
83
|
+
return `*${renderInline(token.tokens)}*`;
|
|
84
|
+
case 'em':
|
|
85
|
+
return `_${renderInline(token.tokens)}_`;
|
|
86
|
+
case 'del':
|
|
87
|
+
return `~${renderInline(token.tokens)}~`;
|
|
88
|
+
case 'codespan':
|
|
89
|
+
return `\`${escapeSlackText(token.text)}\``;
|
|
90
|
+
case 'link':
|
|
91
|
+
{
|
|
92
|
+
const link = token;
|
|
93
|
+
return `<${escapeLinkTarget(link.href)}|${renderInline(link.tokens)}>`;
|
|
94
|
+
}
|
|
95
|
+
case 'br':
|
|
96
|
+
return '\n';
|
|
97
|
+
default:
|
|
98
|
+
return escapeSlackText(token.raw ?? '');
|
|
99
|
+
}
|
|
100
|
+
}).join('');
|
|
101
|
+
}
|
|
102
|
+
function renderCode(token) {
|
|
103
|
+
return `\`\`\`\n${escapeSlackText(token.text)}\n\`\`\``;
|
|
104
|
+
}
|
|
105
|
+
function renderList(token) {
|
|
106
|
+
return token.items.map((item, index)=>{
|
|
107
|
+
const marker = token.ordered ? `${(token.start || 1) + index}.` : '•';
|
|
108
|
+
return `${marker} ${renderBlocks(item.tokens).trim()}\n`;
|
|
109
|
+
}).join('') + '\n';
|
|
110
|
+
}
|
|
111
|
+
function escapeLinkTarget(href) {
|
|
112
|
+
return href.replace(/&/g, '&').replace(/>/g, '%3E').replace(/\|/g, '%7C');
|
|
113
|
+
}
|
|
114
|
+
function splitCode(code, maxLength) {
|
|
115
|
+
const wrapperLength = 8;
|
|
116
|
+
const contentLimit = Math.max(1, maxLength - wrapperLength);
|
|
117
|
+
const parts = [];
|
|
118
|
+
let current = '';
|
|
119
|
+
for (const char of Array.from(code)){
|
|
120
|
+
const escaped = escapeSlackText(char);
|
|
121
|
+
if (current.length + escaped.length > contentLimit && current) {
|
|
122
|
+
parts.push(current);
|
|
123
|
+
current = '';
|
|
124
|
+
}
|
|
125
|
+
current += escaped;
|
|
126
|
+
}
|
|
127
|
+
if (current) parts.push(current);
|
|
128
|
+
return parts.map((part)=>`\`\`\`\n${part}\n\`\`\``);
|
|
129
|
+
}
|
|
130
|
+
function splitRendered(text, maxLength) {
|
|
131
|
+
return splitText(text, maxLength);
|
|
132
|
+
}
|
|
133
|
+
function splitText(text, maxLength) {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
let current = '';
|
|
136
|
+
for (const unit of text.match(/.*(?:\n|$)|./gu) ?? []){
|
|
137
|
+
if (!unit) continue;
|
|
138
|
+
for (const char of Array.from(unit)){
|
|
139
|
+
if (current.length + char.length > maxLength && current) {
|
|
140
|
+
chunks.push(current);
|
|
141
|
+
current = '';
|
|
142
|
+
}
|
|
143
|
+
current += char;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (current) chunks.push(current);
|
|
147
|
+
return chunks;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=slackMarkdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/slackMarkdown.ts"],"sourcesContent":["import { Marked, type Token, type Tokens } from 'marked';\n\nexport const SLACK_MAX_MESSAGE_LENGTH = 4000;\nconst lexer = new Marked();\n\nexport function escapeSlackText(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\nexport function markdownToSlackMrkdwn(markdown: string): string {\n return renderBlocks(lexer.lexer(markdown)).trimEnd();\n}\n\nexport function chunkMarkdownForSlack(\n markdown: string,\n maxLength = SLACK_MAX_MESSAGE_LENGTH,\n): string[] {\n const tokens = lexer.lexer(markdown);\n const chunks: string[] = [];\n let current = '';\n\n const append = (rendered: string): void => {\n if (!rendered) return;\n if (rendered.length > maxLength) {\n if (current) {\n chunks.push(current.trimEnd());\n current = '';\n }\n chunks.push(...splitRendered(rendered.trimEnd(), maxLength));\n return;\n }\n const candidate = current ? `${current}${rendered}` : rendered;\n if (candidate.trimEnd().length <= maxLength) {\n current = candidate;\n } else {\n chunks.push(current.trimEnd());\n current = rendered;\n }\n };\n\n for (const token of tokens) {\n if (token.type === 'code') {\n const rendered = renderCode(token as Tokens.Code);\n if (rendered.length > maxLength) {\n if (current) {\n chunks.push(current.trimEnd());\n current = '';\n }\n chunks.push(...splitCode((token as Tokens.Code).text, maxLength));\n continue;\n }\n }\n append(renderBlock(token));\n }\n if (current.trimEnd()) chunks.push(current.trimEnd());\n return chunks;\n}\n\nfunction renderBlocks(tokens: Token[]): string {\n return tokens.map(renderBlock).join('');\n}\n\nfunction renderBlock(token: Token): string {\n switch (token.type) {\n case 'heading':\n return `*${renderInline((token as Tokens.Heading).tokens)}*\\n\\n`;\n case 'paragraph':\n return `${renderInline((token as Tokens.Paragraph).tokens)}\\n\\n`;\n case 'text': {\n const text = token as Tokens.Text;\n return text.tokens ? renderInline(text.tokens) : escapeSlackText(text.text);\n }\n case 'code':\n return `${renderCode(token as Tokens.Code)}\\n\\n`;\n case 'blockquote':\n return renderBlocks((token as Tokens.Blockquote).tokens)\n .trimEnd().split('\\n').map((line) => `> ${line}`).join('\\n') + '\\n\\n';\n case 'list':\n return renderList(token as Tokens.List);\n case 'space':\n return '';\n case 'hr':\n return '—\\n\\n';\n default:\n return escapeSlackText(token.raw ?? '');\n }\n}\n\nfunction renderInline(tokens: Token[]): string {\n return tokens.map((token) => {\n switch (token.type) {\n case 'text':\n return escapeSlackText((token as Tokens.Text).text);\n case 'strong':\n return `*${renderInline((token as Tokens.Strong).tokens)}*`;\n case 'em':\n return `_${renderInline((token as Tokens.Em).tokens)}_`;\n case 'del':\n return `~${renderInline((token as Tokens.Del).tokens)}~`;\n case 'codespan':\n return `\\`${escapeSlackText((token as Tokens.Codespan).text)}\\``;\n case 'link': {\n const link = token as Tokens.Link;\n return `<${escapeLinkTarget(link.href)}|${renderInline(link.tokens)}>`;\n }\n case 'br':\n return '\\n';\n default:\n return escapeSlackText(token.raw ?? '');\n }\n }).join('');\n}\n\nfunction renderCode(token: Tokens.Code): string {\n return `\\`\\`\\`\\n${escapeSlackText(token.text)}\\n\\`\\`\\``;\n}\n\nfunction renderList(token: Tokens.List): string {\n return token.items.map((item, index) => {\n const marker = token.ordered ? `${(token.start || 1) + index}.` : '•';\n return `${marker} ${renderBlocks(item.tokens).trim()}\\n`;\n }).join('') + '\\n';\n}\n\nfunction escapeLinkTarget(href: string): string {\n return href.replace(/&/g, '&').replace(/>/g, '%3E').replace(/\\|/g, '%7C');\n}\n\nfunction splitCode(code: string, maxLength: number): string[] {\n const wrapperLength = 8;\n const contentLimit = Math.max(1, maxLength - wrapperLength);\n const parts: string[] = [];\n let current = '';\n for (const char of Array.from(code)) {\n const escaped = escapeSlackText(char);\n if (current.length + escaped.length > contentLimit && current) {\n parts.push(current);\n current = '';\n }\n current += escaped;\n }\n if (current) parts.push(current);\n return parts.map((part) => `\\`\\`\\`\\n${part}\\n\\`\\`\\``);\n}\n\nfunction splitRendered(text: string, maxLength: number): string[] {\n return splitText(text, maxLength);\n}\n\nfunction splitText(text: string, maxLength: number): string[] {\n const chunks: string[] = [];\n let current = '';\n for (const unit of text.match(/.*(?:\\n|$)|./gu) ?? []) {\n if (!unit) continue;\n for (const char of Array.from(unit)) {\n if (current.length + char.length > maxLength && current) {\n chunks.push(current);\n current = '';\n }\n current += char;\n }\n }\n if (current) chunks.push(current);\n return chunks;\n}\n"],"names":["Marked","SLACK_MAX_MESSAGE_LENGTH","lexer","escapeSlackText","text","replace","markdownToSlackMrkdwn","markdown","renderBlocks","trimEnd","chunkMarkdownForSlack","maxLength","tokens","chunks","current","append","rendered","length","push","splitRendered","candidate","token","type","renderCode","splitCode","renderBlock","map","join","renderInline","split","line","renderList","raw","link","escapeLinkTarget","href","items","item","index","marker","ordered","start","trim","code","wrapperLength","contentLimit","Math","max","parts","char","Array","from","escaped","part","splitText","unit","match"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"nx": {
|
|
4
4
|
"name": "channel-connector"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.13.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"description": "Generic messaging bridge for connecting to external communication platforms",
|
|
9
9
|
"main": "dist/index.js",
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"directory": "packages/channel-connector"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
+
"@slack/socket-mode": "^3.0.0",
|
|
43
|
+
"@slack/web-api": "^8.0.0",
|
|
42
44
|
"marked": "^15.0.12",
|
|
43
45
|
"telegraf": "^4.16.3",
|
|
44
46
|
"uuid": "14.0.0"
|