@ai-devkit/channel-connector 0.13.1 → 0.13.3

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.
Files changed (77) hide show
  1. package/dist/{ConfigStore.d.ts → ChannelConfigRepository.d.ts} +2 -2
  2. package/dist/ChannelConfigRepository.d.ts.map +1 -0
  3. package/dist/{ConfigStore.js → ChannelConfigRepository.js} +2 -2
  4. package/dist/ChannelConfigRepository.js.map +1 -0
  5. package/dist/__tests__/{ConfigStore.test.js → ChannelConfigRepository.test.js} +6 -6
  6. package/dist/__tests__/ChannelConfigRepository.test.js.map +1 -0
  7. package/dist/__tests__/adapters/{SlackAdapter.test.js → slack/SlackAdapter.test.js} +1 -1
  8. package/dist/__tests__/adapters/slack/SlackAdapter.test.js.map +1 -0
  9. package/dist/__tests__/{utils/SlackDeliveryQueue.test.js → adapters/slack/SlackMessageDelivery.test.js} +11 -11
  10. package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js.map +1 -0
  11. package/dist/__tests__/{utils → adapters/slack}/slackMarkdown.test.js +1 -1
  12. package/dist/__tests__/adapters/slack/slackMarkdown.test.js.map +1 -0
  13. package/dist/__tests__/adapters/slack/slackQuestion.test.js +88 -0
  14. package/dist/__tests__/adapters/slack/slackQuestion.test.js.map +1 -0
  15. package/dist/__tests__/adapters/{TelegramAdapter.test.js → telegram/TelegramAdapter.test.js} +2 -2
  16. package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js.map +1 -0
  17. package/dist/__tests__/{utils → adapters/telegram}/telegramHtml.test.js +1 -1
  18. package/dist/__tests__/adapters/telegram/telegramHtml.test.js.map +1 -0
  19. package/dist/adapters/{SlackAdapter.d.ts → slack/SlackAdapter.d.ts} +2 -2
  20. package/dist/adapters/slack/SlackAdapter.d.ts.map +1 -0
  21. package/dist/adapters/{SlackAdapter.js → slack/SlackAdapter.js} +4 -41
  22. package/dist/adapters/slack/SlackAdapter.js.map +1 -0
  23. package/dist/{utils/SlackDeliveryQueue.d.ts → adapters/slack/SlackMessageDelivery.d.ts} +3 -3
  24. package/dist/adapters/slack/SlackMessageDelivery.d.ts.map +1 -0
  25. package/dist/{utils/SlackDeliveryQueue.js → adapters/slack/SlackMessageDelivery.js} +3 -3
  26. package/dist/adapters/slack/SlackMessageDelivery.js.map +1 -0
  27. package/dist/adapters/slack/slackMarkdown.d.ts.map +1 -0
  28. package/dist/adapters/slack/slackMarkdown.js.map +1 -0
  29. package/dist/adapters/slack/slackQuestion.d.ts +30 -0
  30. package/dist/adapters/slack/slackQuestion.d.ts.map +1 -0
  31. package/dist/adapters/slack/slackQuestion.js +44 -0
  32. package/dist/adapters/slack/slackQuestion.js.map +1 -0
  33. package/dist/adapters/{TelegramAdapter.d.ts → telegram/TelegramAdapter.d.ts} +4 -4
  34. package/dist/adapters/telegram/TelegramAdapter.d.ts.map +1 -0
  35. package/dist/adapters/telegram/TelegramAdapter.js +120 -0
  36. package/dist/adapters/telegram/TelegramAdapter.js.map +1 -0
  37. package/dist/adapters/telegram/TelegramMessageDelivery.d.ts +11 -0
  38. package/dist/adapters/telegram/TelegramMessageDelivery.d.ts.map +1 -0
  39. package/dist/adapters/telegram/TelegramMessageDelivery.js +38 -0
  40. package/dist/adapters/telegram/TelegramMessageDelivery.js.map +1 -0
  41. package/dist/adapters/telegram/telegramHtml.d.ts.map +1 -0
  42. package/dist/adapters/telegram/telegramHtml.js.map +1 -0
  43. package/dist/adapters/telegram/telegramKeyboard.d.ts +7 -0
  44. package/dist/adapters/telegram/telegramKeyboard.d.ts.map +1 -0
  45. package/dist/adapters/telegram/telegramKeyboard.js +8 -0
  46. package/dist/adapters/telegram/telegramKeyboard.js.map +1 -0
  47. package/dist/adapters/telegram/telegramMarkdown.d.ts +13 -0
  48. package/dist/adapters/telegram/telegramMarkdown.d.ts.map +1 -0
  49. package/dist/adapters/{TelegramAdapter.js → telegram/telegramMarkdown.js} +6 -151
  50. package/dist/adapters/telegram/telegramMarkdown.js.map +1 -0
  51. package/dist/index.d.ts +5 -7
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +3 -5
  54. package/dist/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dist/ConfigStore.d.ts.map +0 -1
  57. package/dist/ConfigStore.js.map +0 -1
  58. package/dist/__tests__/ConfigStore.test.js.map +0 -1
  59. package/dist/__tests__/adapters/SlackAdapter.test.js.map +0 -1
  60. package/dist/__tests__/adapters/TelegramAdapter.test.js.map +0 -1
  61. package/dist/__tests__/utils/SlackDeliveryQueue.test.js.map +0 -1
  62. package/dist/__tests__/utils/slackMarkdown.test.js.map +0 -1
  63. package/dist/__tests__/utils/telegramHtml.test.js.map +0 -1
  64. package/dist/adapters/SlackAdapter.d.ts.map +0 -1
  65. package/dist/adapters/SlackAdapter.js.map +0 -1
  66. package/dist/adapters/TelegramAdapter.d.ts.map +0 -1
  67. package/dist/adapters/TelegramAdapter.js.map +0 -1
  68. package/dist/utils/SlackDeliveryQueue.d.ts.map +0 -1
  69. package/dist/utils/SlackDeliveryQueue.js.map +0 -1
  70. package/dist/utils/slackMarkdown.d.ts.map +0 -1
  71. package/dist/utils/slackMarkdown.js.map +0 -1
  72. package/dist/utils/telegramHtml.d.ts.map +0 -1
  73. package/dist/utils/telegramHtml.js.map +0 -1
  74. /package/dist/{utils → adapters/slack}/slackMarkdown.d.ts +0 -0
  75. /package/dist/{utils → adapters/slack}/slackMarkdown.js +0 -0
  76. /package/dist/{utils → adapters/telegram}/telegramHtml.d.ts +0 -0
  77. /package/dist/{utils → adapters/telegram}/telegramHtml.js +0 -0
@@ -1,7 +1,7 @@
1
1
  import { SocketModeClient } from '@slack/socket-mode';
2
2
  import { WebClient } from '@slack/web-api';
3
- import { SlackDeliveryQueue } from '../utils/SlackDeliveryQueue.js';
4
- import { escapeSlackText } from '../utils/slackMarkdown.js';
3
+ import { SlackMessageDelivery } from './SlackMessageDelivery.js';
4
+ import { buildSlackQuestionMessage } from './slackQuestion.js';
5
5
  export const SLACK_CHANNEL_TYPE = 'slack';
6
6
  export async function validateSlackAppToken(appToken, client) {
7
7
  const connections = client ?? new WebClient(appToken).apps.connections;
@@ -42,7 +42,7 @@ export class SlackAdapter {
42
42
  appToken: config.appToken
43
43
  });
44
44
  this.web = dependencies.webClient ?? new WebClient(config.botToken);
45
- this.delivery = new SlackDeliveryQueue({
45
+ this.delivery = new SlackMessageDelivery({
46
46
  postMessage: (input)=>this.web.chat.postMessage(input)
47
47
  });
48
48
  }
@@ -82,44 +82,7 @@ export class SlackAdapter {
82
82
  return this.running;
83
83
  }
84
84
  async sendQuestion(chatId, question) {
85
- const fallback = escapeSlackText(`${question.header ? `${question.header}: ` : ''}${question.question}`);
86
- const elements = question.options.map((option)=>({
87
- type: 'button',
88
- action_id: 'ai_devkit_question',
89
- text: {
90
- type: 'plain_text',
91
- text: option.label.slice(0, 75)
92
- },
93
- value: `${question.id}:${option.value}`.slice(0, 2000)
94
- }));
95
- if (question.allowSkip) {
96
- elements.push({
97
- type: 'button',
98
- action_id: 'ai_devkit_question',
99
- text: {
100
- type: 'plain_text',
101
- text: 'Skip'
102
- },
103
- value: `${question.id}:skip`
104
- });
105
- }
106
- const response = await this.web.chat.postMessage({
107
- channel: chatId,
108
- text: fallback,
109
- blocks: [
110
- {
111
- type: 'section',
112
- text: {
113
- type: 'mrkdwn',
114
- text: `*${escapeSlackText(question.header ?? 'Question')}*\n${escapeSlackText(question.question)}`
115
- }
116
- },
117
- {
118
- type: 'actions',
119
- elements
120
- }
121
- ]
122
- });
85
+ const response = await this.web.chat.postMessage(buildSlackQuestionMessage(chatId, question));
123
86
  if (!response.ts) throw new Error('Slack did not return a question message timestamp');
124
87
  return {
125
88
  messageId: response.ts
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/slack/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 { SlackMessageDelivery } from './SlackMessageDelivery.js';\nimport { buildSlackQuestionMessage } from './slackQuestion.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: SlackMessageDelivery;\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 SlackMessageDelivery({\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 response = await this.web.chat.postMessage(buildSlackQuestionMessage(chatId, question));\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","SlackMessageDelivery","buildSlackQuestionMessage","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","response","ts","messageId","finalizeInteraction","update","channel","blocks","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","value","delete","action","actions","interactionId","action_ts","id","container","message_ts","action_id","actionId"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { SentMessage } from '../types.js';
1
+ import type { SentMessage } from '../../types.js';
2
2
  interface SlackPoster {
3
3
  postMessage(input: {
4
4
  channel: string;
@@ -18,7 +18,7 @@ interface DeliveryOptions {
18
18
  maxQueueSize?: number;
19
19
  sleep?: (milliseconds: number) => Promise<void>;
20
20
  }
21
- export declare class SlackDeliveryQueue {
21
+ export declare class SlackMessageDelivery {
22
22
  private readonly client;
23
23
  private readonly states;
24
24
  private readonly maxMessageLength;
@@ -30,4 +30,4 @@ export declare class SlackDeliveryQueue {
30
30
  private postWithRateLimit;
31
31
  }
32
32
  export {};
33
- //# sourceMappingURL=SlackDeliveryQueue.d.ts.map
33
+ //# sourceMappingURL=SlackMessageDelivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackMessageDelivery.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/SlackMessageDelivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,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,oBAAoB;IAMjB,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"}
@@ -1,6 +1,6 @@
1
1
  import { chunkMarkdownForSlack, SLACK_MAX_MESSAGE_LENGTH } from './slackMarkdown.js';
2
2
  const defaultSleep = (milliseconds)=>new Promise((resolve)=>setTimeout(resolve, milliseconds));
3
- export class SlackDeliveryQueue {
3
+ export class SlackMessageDelivery {
4
4
  client;
5
5
  states = new Map();
6
6
  maxMessageLength;
@@ -18,7 +18,7 @@ export class SlackDeliveryQueue {
18
18
  pending: 0
19
19
  };
20
20
  if (state.pending >= this.maxQueueSize) {
21
- throw new Error('Slack delivery queue is full');
21
+ throw new Error('Slack message delivery queue is full');
22
22
  }
23
23
  state.pending += 1;
24
24
  this.states.set(channel, state);
@@ -80,4 +80,4 @@ function readRetryAfter(error) {
80
80
  return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
81
81
  }
82
82
 
83
- //# sourceMappingURL=SlackDeliveryQueue.js.map
83
+ //# sourceMappingURL=SlackMessageDelivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/slack/SlackMessageDelivery.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 SlackMessageDelivery {\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 message 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","SlackMessageDelivery","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 @@
1
+ {"version":3,"file":"slackMarkdown.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/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 @@
1
+ {"version":3,"sources":["../../../src/adapters/slack/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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\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, '&amp;').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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"}
@@ -0,0 +1,30 @@
1
+ import type { ChannelQuestion } from '../../types.js';
2
+ interface SlackQuestionButton {
3
+ type: 'button';
4
+ action_id: 'ai_devkit_question';
5
+ text: {
6
+ type: 'plain_text';
7
+ text: string;
8
+ };
9
+ value: string;
10
+ }
11
+ type SlackQuestionMessage = Record<string, unknown> & {
12
+ channel: string;
13
+ text: string;
14
+ blocks: [
15
+ {
16
+ type: 'section';
17
+ text: {
18
+ type: 'mrkdwn';
19
+ text: string;
20
+ };
21
+ },
22
+ {
23
+ type: 'actions';
24
+ elements: SlackQuestionButton[];
25
+ }
26
+ ];
27
+ };
28
+ export declare function buildSlackQuestionMessage(chatId: string, question: ChannelQuestion): SlackQuestionMessage;
29
+ export {};
30
+ //# sourceMappingURL=slackQuestion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slackQuestion.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/slackQuestion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,UAAU,mBAAmB;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,oBAAoB,CAAC;IAChC,IAAI,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACJ;YACI,IAAI,EAAE,SAAS,CAAC;YAChB,IAAI,EAAE;gBAAE,IAAI,EAAE,QAAQ,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAC;SAC1C;QACD;YACI,IAAI,EAAE,SAAS,CAAC;YAChB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;SACnC;KACJ,CAAC;CACL,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,oBAAoB,CAoCzG"}
@@ -0,0 +1,44 @@
1
+ import { escapeSlackText } from './slackMarkdown.js';
2
+ export function buildSlackQuestionMessage(chatId, question) {
3
+ const fallbackPrefix = question.header ? `${question.header}: ` : '';
4
+ const fallback = escapeSlackText(`${fallbackPrefix}${question.question}`);
5
+ const buttons = question.options.map((option)=>({
6
+ type: 'button',
7
+ action_id: 'ai_devkit_question',
8
+ text: {
9
+ type: 'plain_text',
10
+ text: option.label.slice(0, 75)
11
+ },
12
+ value: `${question.id}:${option.value}`.slice(0, 2000)
13
+ }));
14
+ if (question.allowSkip) {
15
+ buttons.push({
16
+ type: 'button',
17
+ action_id: 'ai_devkit_question',
18
+ text: {
19
+ type: 'plain_text',
20
+ text: 'Skip'
21
+ },
22
+ value: `${question.id}:skip`
23
+ });
24
+ }
25
+ return {
26
+ channel: chatId,
27
+ text: fallback,
28
+ blocks: [
29
+ {
30
+ type: 'section',
31
+ text: {
32
+ type: 'mrkdwn',
33
+ text: `*${escapeSlackText(question.header ?? 'Question')}*\n${escapeSlackText(question.question)}`
34
+ }
35
+ },
36
+ {
37
+ type: 'actions',
38
+ elements: buttons
39
+ }
40
+ ]
41
+ };
42
+ }
43
+
44
+ //# sourceMappingURL=slackQuestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/slack/slackQuestion.ts"],"sourcesContent":["import type { ChannelQuestion } from '../../types.js';\nimport { escapeSlackText } from './slackMarkdown.js';\n\ninterface SlackQuestionButton {\n type: 'button';\n action_id: 'ai_devkit_question';\n text: { type: 'plain_text'; text: string };\n value: string;\n}\n\ntype SlackQuestionMessage = Record<string, unknown> & {\n channel: string;\n text: string;\n blocks: [\n {\n type: 'section';\n text: { type: 'mrkdwn'; text: string };\n },\n {\n type: 'actions';\n elements: SlackQuestionButton[];\n },\n ];\n};\n\nexport function buildSlackQuestionMessage(chatId: string, question: ChannelQuestion): SlackQuestionMessage {\n const fallbackPrefix = question.header ? `${question.header}: ` : '';\n const fallback = escapeSlackText(`${fallbackPrefix}${question.question}`);\n const buttons = question.options.map((option): SlackQuestionButton => ({\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\n if (question.allowSkip) {\n buttons.push({\n type: 'button',\n action_id: 'ai_devkit_question',\n text: { type: 'plain_text', text: 'Skip' },\n value: `${question.id}:skip`,\n });\n }\n\n return {\n channel: chatId,\n text: fallback,\n blocks: [\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*${escapeSlackText(question.header ?? 'Question')}*\\n${escapeSlackText(question.question)}`,\n },\n },\n {\n type: 'actions',\n elements: buttons,\n },\n ],\n };\n}\n"],"names":["escapeSlackText","buildSlackQuestionMessage","chatId","question","fallbackPrefix","header","fallback","buttons","options","map","option","type","action_id","text","label","slice","value","id","allowSkip","push","channel","blocks","elements"],"mappings":"AACA,SAASA,eAAe,QAAQ,qBAAqB;AAwBrD,OAAO,SAASC,0BAA0BC,MAAc,EAAEC,QAAyB;IAC/E,MAAMC,iBAAiBD,SAASE,MAAM,GAAG,GAAGF,SAASE,MAAM,CAAC,EAAE,CAAC,GAAG;IAClE,MAAMC,WAAWN,gBAAgB,GAAGI,iBAAiBD,SAASA,QAAQ,EAAE;IACxE,MAAMI,UAAUJ,SAASK,OAAO,CAACC,GAAG,CAAC,CAACC,SAAiC,CAAA;YACnEC,MAAM;YACNC,WAAW;YACXC,MAAM;gBAAEF,MAAM;gBAAcE,MAAMH,OAAOI,KAAK,CAACC,KAAK,CAAC,GAAG;YAAI;YAC5DC,OAAO,GAAGb,SAASc,EAAE,CAAC,CAAC,EAAEP,OAAOM,KAAK,EAAE,CAACD,KAAK,CAAC,GAAG;QACrD,CAAA;IAEA,IAAIZ,SAASe,SAAS,EAAE;QACpBX,QAAQY,IAAI,CAAC;YACTR,MAAM;YACNC,WAAW;YACXC,MAAM;gBAAEF,MAAM;gBAAcE,MAAM;YAAO;YACzCG,OAAO,GAAGb,SAASc,EAAE,CAAC,KAAK,CAAC;QAChC;IACJ;IAEA,OAAO;QACHG,SAASlB;QACTW,MAAMP;QACNe,QAAQ;YACJ;gBACIV,MAAM;gBACNE,MAAM;oBACFF,MAAM;oBACNE,MAAM,CAAC,CAAC,EAAEb,gBAAgBG,SAASE,MAAM,IAAI,YAAY,GAAG,EAAEL,gBAAgBG,SAASA,QAAQ,GAAG;gBACtG;YACJ;YACA;gBACIQ,MAAM;gBACNW,UAAUf;YACd;SACH;IACL;AACJ"}
@@ -1,7 +1,6 @@
1
- import type { ChannelAdapter } from './ChannelAdapter.js';
2
- import type { IncomingMessage, InlineKeyboard, CallbackHandler } from '../types.js';
1
+ import type { ChannelAdapter } from '../ChannelAdapter.js';
2
+ import type { IncomingMessage, InlineKeyboard, CallbackHandler } from '../../types.js';
3
3
  export declare const TELEGRAM_CHANNEL_TYPE = "telegram";
4
- export declare const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
5
4
  export interface TelegramAdapterOptions {
6
5
  botToken: string;
7
6
  }
@@ -11,6 +10,7 @@ export interface TelegramAdapterOptions {
11
10
  export declare class TelegramAdapter implements ChannelAdapter {
12
11
  readonly type = "telegram";
13
12
  private bot;
13
+ private readonly delivery;
14
14
  private messageHandler;
15
15
  private callbackHandler;
16
16
  private running;
@@ -27,7 +27,7 @@ export declare class TelegramAdapter implements ChannelAdapter {
27
27
  onCallback(handler: CallbackHandler): void;
28
28
  /**
29
29
  * Send a message with an inline keyboard. `html` is sent verbatim with
30
- * parse_mode=HTML — callers must pre-escape any user-controlled fields.
30
+ * parse_mode=HTML - callers must pre-escape any user-controlled fields.
31
31
  * Returns the Telegram message_id of the sent message.
32
32
  */
33
33
  sendInlineKeyboard(chatId: string, html: string, keyboard: InlineKeyboard): Promise<number>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelegramAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/TelegramAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAoB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEzG,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAGhD,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,cAAyB;IAEtC,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,cAAc,CAA0D;IAChF,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAKrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIjE,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAI1C;;;;OAIG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAQjG;;;OAGG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3G;;;OAGG;IACG,cAAc,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrE,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC"}
@@ -0,0 +1,120 @@
1
+ import { Telegraf } from 'telegraf';
2
+ import { TelegramMessageDelivery } from './TelegramMessageDelivery.js';
3
+ import { toTelegrafKeyboard } from './telegramKeyboard.js';
4
+ export const TELEGRAM_CHANNEL_TYPE = 'telegram';
5
+ const TELEGRAM_PARSE_MODE = 'HTML';
6
+ /**
7
+ * Telegram Bot API adapter using telegraf with long polling.
8
+ */ export class TelegramAdapter {
9
+ type = TELEGRAM_CHANNEL_TYPE;
10
+ bot;
11
+ delivery;
12
+ messageHandler = null;
13
+ callbackHandler = null;
14
+ running = false;
15
+ constructor(options){
16
+ this.bot = new Telegraf(options.botToken);
17
+ this.delivery = new TelegramMessageDelivery(this.bot.telegram);
18
+ }
19
+ async start() {
20
+ this.bot.on('text', async (ctx)=>{
21
+ if (!this.messageHandler) return;
22
+ const msg = {
23
+ channelType: TELEGRAM_CHANNEL_TYPE,
24
+ chatId: String(ctx.message.chat.id),
25
+ userId: String(ctx.message.from.id),
26
+ text: ctx.message.text,
27
+ timestamp: new Date(ctx.message.date * 1000)
28
+ };
29
+ try {
30
+ await this.messageHandler(msg);
31
+ } catch (error) {
32
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
33
+ await ctx.reply(`Error processing message: ${errorMessage}`);
34
+ }
35
+ });
36
+ this.bot.on('callback_query', async (ctx)=>{
37
+ if (!this.callbackHandler) {
38
+ try {
39
+ await ctx.answerCbQuery();
40
+ } catch {}
41
+ return;
42
+ }
43
+ const query = ctx.callbackQuery;
44
+ const data = typeof query.data === 'string' ? query.data : '';
45
+ if (!query.message) {
46
+ try {
47
+ await ctx.answerCbQuery();
48
+ } catch {}
49
+ return;
50
+ }
51
+ const cb = {
52
+ channelType: TELEGRAM_CHANNEL_TYPE,
53
+ chatId: String(query.message.chat.id),
54
+ userId: String(query.from.id),
55
+ messageId: query.message.message_id,
56
+ callbackData: data,
57
+ callbackQueryId: query.id,
58
+ timestamp: new Date()
59
+ };
60
+ try {
61
+ await this.callbackHandler(cb);
62
+ } catch {
63
+ try {
64
+ await ctx.answerCbQuery('Error');
65
+ } catch {}
66
+ }
67
+ });
68
+ await this.bot.launch();
69
+ this.running = true;
70
+ }
71
+ async stop() {
72
+ this.running = false;
73
+ await this.bot.stop();
74
+ }
75
+ /**
76
+ * Input is treated as markdown and rendered as Telegram-compatible HTML.
77
+ * Long messages are chunked as markdown source before rendering so each
78
+ * Telegram HTML payload is independently valid.
79
+ */ async sendMessage(chatId, text) {
80
+ await this.delivery.send(chatId, text);
81
+ }
82
+ onMessage(handler) {
83
+ this.messageHandler = handler;
84
+ }
85
+ onCallback(handler) {
86
+ this.callbackHandler = handler;
87
+ }
88
+ /**
89
+ * Send a message with an inline keyboard. `html` is sent verbatim with
90
+ * parse_mode=HTML - callers must pre-escape any user-controlled fields.
91
+ * Returns the Telegram message_id of the sent message.
92
+ */ async sendInlineKeyboard(chatId, html, keyboard) {
93
+ const result = await this.bot.telegram.sendMessage(chatId, html, {
94
+ parse_mode: TELEGRAM_PARSE_MODE,
95
+ reply_markup: {
96
+ inline_keyboard: toTelegrafKeyboard(keyboard)
97
+ }
98
+ });
99
+ return result.message_id;
100
+ }
101
+ /**
102
+ * Replace the inline keyboard on an existing message. Pass `null` to remove
103
+ * the keyboard entirely.
104
+ */ async editInlineKeyboard(chatId, messageId, keyboard) {
105
+ await this.bot.telegram.editMessageReplyMarkup(chatId, messageId, undefined, keyboard ? {
106
+ inline_keyboard: toTelegrafKeyboard(keyboard)
107
+ } : undefined);
108
+ }
109
+ /**
110
+ * Acknowledge a callback_query. Without this Telegram leaves a spinner on
111
+ * the tapped button. Pass `text` to show a transient toast.
112
+ */ async answerCallback(callbackQueryId, text) {
113
+ await this.bot.telegram.answerCbQuery(callbackQueryId, text);
114
+ }
115
+ async isHealthy() {
116
+ return this.running;
117
+ }
118
+ }
119
+
120
+ //# sourceMappingURL=TelegramAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/telegram/TelegramAdapter.ts"],"sourcesContent":["import { Telegraf } from 'telegraf';\nimport type { ChannelAdapter } from '../ChannelAdapter.js';\nimport { TelegramMessageDelivery } from './TelegramMessageDelivery.js';\nimport { toTelegrafKeyboard } from './telegramKeyboard.js';\nimport type { IncomingMessage, InlineKeyboard, IncomingCallback, CallbackHandler } from '../../types.js';\n\nexport const TELEGRAM_CHANNEL_TYPE = 'telegram';\nconst TELEGRAM_PARSE_MODE = 'HTML' as const;\n\nexport interface TelegramAdapterOptions {\n botToken: string;\n}\n\n/**\n * Telegram Bot API adapter using telegraf with long polling.\n */\nexport class TelegramAdapter implements ChannelAdapter {\n readonly type = TELEGRAM_CHANNEL_TYPE;\n\n private bot: Telegraf;\n private readonly delivery: TelegramMessageDelivery;\n private messageHandler: ((msg: IncomingMessage) => Promise<void>) | null = null;\n private callbackHandler: CallbackHandler | null = null;\n private running = false;\n\n constructor(options: TelegramAdapterOptions) {\n this.bot = new Telegraf(options.botToken);\n this.delivery = new TelegramMessageDelivery(this.bot.telegram);\n }\n\n async start(): Promise<void> {\n this.bot.on('text', async (ctx) => {\n if (!this.messageHandler) return;\n\n const msg: IncomingMessage = {\n channelType: TELEGRAM_CHANNEL_TYPE,\n chatId: String(ctx.message.chat.id),\n userId: String(ctx.message.from.id),\n text: ctx.message.text,\n timestamp: new Date(ctx.message.date * 1000),\n };\n\n try {\n await this.messageHandler(msg);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n await ctx.reply(`Error processing message: ${errorMessage}`);\n }\n });\n\n this.bot.on('callback_query', async (ctx) => {\n if (!this.callbackHandler) {\n try { await ctx.answerCbQuery(); } catch { /* ignore */ }\n return;\n }\n\n const query = ctx.callbackQuery as {\n id: string;\n data?: string;\n message?: { message_id: number; chat: { id: number | string } };\n from: { id: number | string };\n };\n\n const data = typeof query.data === 'string' ? query.data : '';\n if (!query.message) {\n try { await ctx.answerCbQuery(); } catch { /* ignore */ }\n return;\n }\n\n const cb: IncomingCallback = {\n channelType: TELEGRAM_CHANNEL_TYPE,\n chatId: String(query.message.chat.id),\n userId: String(query.from.id),\n messageId: query.message.message_id,\n callbackData: data,\n callbackQueryId: query.id,\n timestamp: new Date(),\n };\n\n try {\n await this.callbackHandler(cb);\n } catch {\n try { await ctx.answerCbQuery('Error'); } catch { /* ignore */ }\n }\n });\n\n await this.bot.launch();\n this.running = true;\n }\n\n async stop(): Promise<void> {\n this.running = false;\n await this.bot.stop();\n }\n\n /**\n * Input is treated as markdown and rendered as Telegram-compatible HTML.\n * Long messages are chunked as markdown source before rendering so each\n * Telegram HTML payload is independently valid.\n */\n async sendMessage(chatId: string, text: string): Promise<void> {\n await this.delivery.send(chatId, text);\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.messageHandler = handler;\n }\n\n onCallback(handler: CallbackHandler): void {\n this.callbackHandler = handler;\n }\n\n /**\n * Send a message with an inline keyboard. `html` is sent verbatim with\n * parse_mode=HTML - callers must pre-escape any user-controlled fields.\n * Returns the Telegram message_id of the sent message.\n */\n async sendInlineKeyboard(chatId: string, html: string, keyboard: InlineKeyboard): Promise<number> {\n const result = await this.bot.telegram.sendMessage(chatId, html, {\n parse_mode: TELEGRAM_PARSE_MODE,\n reply_markup: { inline_keyboard: toTelegrafKeyboard(keyboard) },\n }) as { message_id: number };\n return result.message_id;\n }\n\n /**\n * Replace the inline keyboard on an existing message. Pass `null` to remove\n * the keyboard entirely.\n */\n async editInlineKeyboard(chatId: string, messageId: number, keyboard: InlineKeyboard | null): Promise<void> {\n await this.bot.telegram.editMessageReplyMarkup(chatId, messageId, undefined, keyboard\n ? { inline_keyboard: toTelegrafKeyboard(keyboard) }\n : undefined);\n }\n\n /**\n * Acknowledge a callback_query. Without this Telegram leaves a spinner on\n * the tapped button. Pass `text` to show a transient toast.\n */\n async answerCallback(callbackQueryId: string, text?: string): Promise<void> {\n await this.bot.telegram.answerCbQuery(callbackQueryId, text);\n }\n\n async isHealthy(): Promise<boolean> {\n return this.running;\n }\n}\n"],"names":["Telegraf","TelegramMessageDelivery","toTelegrafKeyboard","TELEGRAM_CHANNEL_TYPE","TELEGRAM_PARSE_MODE","TelegramAdapter","type","bot","delivery","messageHandler","callbackHandler","running","options","botToken","telegram","start","on","ctx","msg","channelType","chatId","String","message","chat","id","userId","from","text","timestamp","Date","date","error","errorMessage","Error","reply","answerCbQuery","query","callbackQuery","data","cb","messageId","message_id","callbackData","callbackQueryId","launch","stop","sendMessage","send","onMessage","handler","onCallback","sendInlineKeyboard","html","keyboard","result","parse_mode","reply_markup","inline_keyboard","editInlineKeyboard","editMessageReplyMarkup","undefined","answerCallback","isHealthy"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
2
+ interface TelegramSender {
3
+ sendMessage(chatId: string, text: string, extra?: Record<string, unknown>): Promise<unknown>;
4
+ }
5
+ export declare class TelegramMessageDelivery {
6
+ private readonly sender;
7
+ constructor(sender: TelegramSender);
8
+ send(chatId: string, text: string): Promise<void>;
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=TelegramMessageDelivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelegramMessageDelivery.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/TelegramMessageDelivery.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,UAAU,cAAc;IACpB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChG;AAED,qBAAa,uBAAuB;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAE7C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2B1D"}
@@ -0,0 +1,38 @@
1
+ import { chunkMarkdownForTelegram, chunkMessage, htmlToPlainText, isParseEntitiesError } from './telegramMarkdown.js';
2
+ export const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
3
+ const TELEGRAM_PARSE_MODE = 'HTML';
4
+ export class TelegramMessageDelivery {
5
+ sender;
6
+ constructor(sender){
7
+ this.sender = sender;
8
+ }
9
+ async send(chatId, text) {
10
+ let chunks;
11
+ try {
12
+ chunks = chunkMarkdownForTelegram(text, TELEGRAM_MAX_MESSAGE_LENGTH);
13
+ } catch {
14
+ for (const chunk of chunkMessage(text, TELEGRAM_MAX_MESSAGE_LENGTH)){
15
+ await this.sender.sendMessage(chatId, chunk);
16
+ }
17
+ return;
18
+ }
19
+ for (const chunk of chunks){
20
+ if (!chunk.html) {
21
+ await this.sender.sendMessage(chatId, chunk.text);
22
+ continue;
23
+ }
24
+ try {
25
+ await this.sender.sendMessage(chatId, chunk.text, {
26
+ parse_mode: TELEGRAM_PARSE_MODE
27
+ });
28
+ } catch (error) {
29
+ if (!isParseEntitiesError(error)) throw error;
30
+ // Telegram rejected the rendered HTML - fall back to plain text
31
+ // so the user still gets the content (just unformatted).
32
+ await this.sender.sendMessage(chatId, htmlToPlainText(chunk.text));
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ //# sourceMappingURL=TelegramMessageDelivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/telegram/TelegramMessageDelivery.ts"],"sourcesContent":["import {\n chunkMarkdownForTelegram,\n chunkMessage,\n htmlToPlainText,\n isParseEntitiesError,\n type TelegramMessageChunk,\n} from './telegramMarkdown.js';\n\nexport const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;\nconst TELEGRAM_PARSE_MODE = 'HTML' as const;\n\ninterface TelegramSender {\n sendMessage(chatId: string, text: string, extra?: Record<string, unknown>): Promise<unknown>;\n}\n\nexport class TelegramMessageDelivery {\n constructor(private readonly sender: TelegramSender) {}\n\n async send(chatId: string, text: string): Promise<void> {\n let chunks: TelegramMessageChunk[];\n try {\n chunks = chunkMarkdownForTelegram(text, TELEGRAM_MAX_MESSAGE_LENGTH);\n } catch {\n for (const chunk of chunkMessage(text, TELEGRAM_MAX_MESSAGE_LENGTH)) {\n await this.sender.sendMessage(chatId, chunk);\n }\n return;\n }\n\n for (const chunk of chunks) {\n if (!chunk.html) {\n await this.sender.sendMessage(chatId, chunk.text);\n continue;\n }\n\n try {\n await this.sender.sendMessage(chatId, chunk.text, { parse_mode: TELEGRAM_PARSE_MODE });\n } catch (error) {\n if (!isParseEntitiesError(error)) throw error;\n // Telegram rejected the rendered HTML - fall back to plain text\n // so the user still gets the content (just unformatted).\n await this.sender.sendMessage(chatId, htmlToPlainText(chunk.text));\n }\n }\n }\n}\n"],"names":["chunkMarkdownForTelegram","chunkMessage","htmlToPlainText","isParseEntitiesError","TELEGRAM_MAX_MESSAGE_LENGTH","TELEGRAM_PARSE_MODE","TelegramMessageDelivery","sender","send","chatId","text","chunks","chunk","sendMessage","html","parse_mode","error"],"mappings":"AAAA,SACIA,wBAAwB,EACxBC,YAAY,EACZC,eAAe,EACfC,oBAAoB,QAEjB,wBAAwB;AAE/B,OAAO,MAAMC,8BAA8B,KAAK;AAChD,MAAMC,sBAAsB;AAM5B,OAAO,MAAMC;;IACT,YAAY,AAAiBC,MAAsB,CAAE;aAAxBA,SAAAA;IAAyB;IAEtD,MAAMC,KAAKC,MAAc,EAAEC,IAAY,EAAiB;QACpD,IAAIC;QACJ,IAAI;YACAA,SAASX,yBAAyBU,MAAMN;QAC5C,EAAE,OAAM;YACJ,KAAK,MAAMQ,SAASX,aAAaS,MAAMN,6BAA8B;gBACjE,MAAM,IAAI,CAACG,MAAM,CAACM,WAAW,CAACJ,QAAQG;YAC1C;YACA;QACJ;QAEA,KAAK,MAAMA,SAASD,OAAQ;YACxB,IAAI,CAACC,MAAME,IAAI,EAAE;gBACb,MAAM,IAAI,CAACP,MAAM,CAACM,WAAW,CAACJ,QAAQG,MAAMF,IAAI;gBAChD;YACJ;YAEA,IAAI;gBACA,MAAM,IAAI,CAACH,MAAM,CAACM,WAAW,CAACJ,QAAQG,MAAMF,IAAI,EAAE;oBAAEK,YAAYV;gBAAoB;YACxF,EAAE,OAAOW,OAAO;gBACZ,IAAI,CAACb,qBAAqBa,QAAQ,MAAMA;gBACxC,gEAAgE;gBAChE,yDAAyD;gBACzD,MAAM,IAAI,CAACT,MAAM,CAACM,WAAW,CAACJ,QAAQP,gBAAgBU,MAAMF,IAAI;YACpE;QACJ;IACJ;AACJ"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegramHtml.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/telegramHtml.ts"],"names":[],"mappings":"AAyFA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/D"}
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/telegram/telegramHtml.ts"],"sourcesContent":["import { Marked, type RendererObject, type Tokens } from 'marked';\n\nconst escapeHtml = (s: string): string =>\n s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\nconst escapeUrl = (s: string): string =>\n s.replace(/&/g, '&amp;').replace(/\"/g, '&quot;');\n\nconst renderer: RendererObject = {\n heading({ tokens }: Tokens.Heading): string {\n return `<b>${this.parser.parseInline(tokens)}</b>\\n\\n`;\n },\n paragraph({ tokens }: Tokens.Paragraph): string {\n return `${this.parser.parseInline(tokens)}\\n\\n`;\n },\n strong({ tokens }: Tokens.Strong): string {\n return `<b>${this.parser.parseInline(tokens)}</b>`;\n },\n em({ tokens }: Tokens.Em): string {\n return `<i>${this.parser.parseInline(tokens)}</i>`;\n },\n del({ tokens }: Tokens.Del): string {\n return `<s>${this.parser.parseInline(tokens)}</s>`;\n },\n codespan({ text }: Tokens.Codespan): string {\n return `<code>${escapeHtml(text)}</code>`;\n },\n code({ text, lang }: Tokens.Code): string {\n const langAttr = lang ? ` class=\"language-${escapeHtml(lang.split(/\\s/)[0])}\"` : '';\n return `<pre><code${langAttr}>${escapeHtml(text)}</code></pre>\\n\\n`;\n },\n link({ href, tokens }: Tokens.Link): string {\n return `<a href=\"${escapeUrl(href)}\">${this.parser.parseInline(tokens)}</a>`;\n },\n image({ href, text }: Tokens.Image): string {\n const label = text || href;\n return `<a href=\"${escapeUrl(href)}\">${escapeHtml(label)}</a>`;\n },\n list({ items, ordered, start }: Tokens.List): string {\n const startNum = typeof start === 'number' ? start : 1;\n const lines = items.map((item, i) => {\n const marker = ordered ? `${startNum + i}.` : '•';\n const inner = item.tokens\n .map((token) =>\n token.type === 'text'\n ? this.parser.parseInline(token.tokens!)\n : this.parser.parse([token])\n )\n .join('')\n .trimEnd();\n return `${marker} ${inner}`;\n });\n return `${lines.join('\\n')}\\n\\n`;\n },\n blockquote({ tokens }: Tokens.Blockquote): string {\n return `<blockquote>${this.parser.parse(tokens).trimEnd()}</blockquote>\\n\\n`;\n },\n table({ header, rows }: Tokens.Table): string {\n const headerCells = header.map((c) => c.text);\n const bodyRows = rows.map((row) => row.map((c) => c.text));\n const widths = headerCells.map((h, i) =>\n Math.max(h.length, ...bodyRows.map((row) => (row[i] ?? '').length))\n );\n const pad = (cells: string[]) =>\n cells.map((c, i) => c.padEnd(widths[i] ?? 0)).join(' | ');\n\n const lines = [\n pad(headerCells),\n widths.map((w) => '-'.repeat(w)).join('-+-'),\n ...bodyRows.map(pad),\n ];\n return `<pre>${escapeHtml(lines.join('\\n'))}</pre>\\n\\n`;\n },\n hr(): string {\n return '———\\n\\n';\n },\n br(): string {\n return '\\n';\n },\n html({ block, text }: Tokens.HTML | Tokens.Tag): string {\n return block ? '' : escapeHtml(text);\n },\n text(token: Tokens.Text | Tokens.Escape): string {\n return escapeHtml(token.text);\n },\n};\n\nconst marked = new Marked({ renderer });\n\n/**\n * Convert markdown to Telegram-compatible HTML (uses only the tag subset\n * Telegram's Bot API accepts for parse_mode: 'HTML').\n */\nexport function markdownToTelegramHtml(markdown: string): string {\n return (marked.parse(markdown, { async: false }) as string).trimEnd();\n}\n"],"names":["Marked","escapeHtml","s","replace","escapeUrl","renderer","heading","tokens","parser","parseInline","paragraph","strong","em","del","codespan","text","code","lang","langAttr","split","link","href","image","label","list","items","ordered","start","startNum","lines","map","item","i","marker","inner","token","type","parse","join","trimEnd","blockquote","table","header","rows","headerCells","c","bodyRows","row","widths","h","Math","max","length","pad","cells","padEnd","w","repeat","hr","br","html","block","marked","markdownToTelegramHtml","markdown","async"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { InlineKeyboard } from '../../types.js';
2
+ export interface TelegrafInlineKeyboardButton {
3
+ text: string;
4
+ callback_data: string;
5
+ }
6
+ export declare function toTelegrafKeyboard(keyboard: InlineKeyboard): TelegrafInlineKeyboardButton[][];
7
+ //# sourceMappingURL=telegramKeyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegramKeyboard.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/telegramKeyboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,4BAA4B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,4BAA4B,EAAE,EAAE,CAE7F"}
@@ -0,0 +1,8 @@
1
+ export function toTelegrafKeyboard(keyboard) {
2
+ return keyboard.map((row)=>row.map((btn)=>({
3
+ text: btn.text,
4
+ callback_data: btn.callbackData
5
+ })));
6
+ }
7
+
8
+ //# sourceMappingURL=telegramKeyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/telegram/telegramKeyboard.ts"],"sourcesContent":["import type { InlineKeyboard } from '../../types.js';\n\nexport interface TelegrafInlineKeyboardButton {\n text: string;\n callback_data: string;\n}\n\nexport function toTelegrafKeyboard(keyboard: InlineKeyboard): TelegrafInlineKeyboardButton[][] {\n return keyboard.map((row) => row.map((btn) => ({ text: btn.text, callback_data: btn.callbackData })));\n}\n"],"names":["toTelegrafKeyboard","keyboard","map","row","btn","text","callback_data","callbackData"],"mappings":"AAOA,OAAO,SAASA,mBAAmBC,QAAwB;IACvD,OAAOA,SAASC,GAAG,CAAC,CAACC,MAAQA,IAAID,GAAG,CAAC,CAACE,MAAS,CAAA;gBAAEC,MAAMD,IAAIC,IAAI;gBAAEC,eAAeF,IAAIG,YAAY;YAAC,CAAA;AACrG"}
@@ -0,0 +1,13 @@
1
+ export type TelegramMessageChunk = {
2
+ text: string;
3
+ html: boolean;
4
+ };
5
+ export declare function isParseEntitiesError(error: unknown): boolean;
6
+ export declare function htmlToPlainText(html: string): string;
7
+ export declare function chunkMarkdownForTelegram(markdown: string, maxLen: number): TelegramMessageChunk[];
8
+ /**
9
+ * Split text into chunks of maxLen or fewer characters. Prefers paragraph
10
+ * boundaries (\n\n), then single newlines (\n), then hard-splits at maxLen.
11
+ */
12
+ export declare function chunkMessage(text: string, maxLen: number): string[];
13
+ //# sourceMappingURL=telegramMarkdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegramMarkdown.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/telegramMarkdown.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAiBjG;AAqOD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BnE"}