@krischoichoi/channel-telegram 0.6.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 +129 -0
- package/lib/adapter.d.ts +94 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +417 -0
- package/lib/adapter.js.map +1 -0
- package/lib/api-error.d.ts +74 -0
- package/lib/api-error.d.ts.map +1 -0
- package/lib/api-error.js +103 -0
- package/lib/api-error.js.map +1 -0
- package/lib/config.d.ts +115 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +67 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +59 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +158 -0
- package/lib/definition.js.map +1 -0
- package/lib/inbound.d.ts +61 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +125 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +114 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +16 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +26 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +51 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +314 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +46 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +85 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/outbound.d.ts +42 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +215 -0
- package/lib/outbound.js.map +1 -0
- package/lib/render/html.d.ts +13 -0
- package/lib/render/html.d.ts.map +1 -0
- package/lib/render/html.js +110 -0
- package/lib/render/html.js.map +1 -0
- package/lib/render/index.d.ts +64 -0
- package/lib/render/index.d.ts.map +1 -0
- package/lib/render/index.js +76 -0
- package/lib/render/index.js.map +1 -0
- package/lib/render/markdown.d.ts +38 -0
- package/lib/render/markdown.d.ts.map +1 -0
- package/lib/render/markdown.js +187 -0
- package/lib/render/markdown.js.map +1 -0
- package/lib/render/plain.d.ts +43 -0
- package/lib/render/plain.d.ts.map +1 -0
- package/lib/render/plain.js +97 -0
- package/lib/render/plain.js.map +1 -0
- package/lib/render/segment.d.ts +39 -0
- package/lib/render/segment.d.ts.map +1 -0
- package/lib/render/segment.js +117 -0
- package/lib/render/segment.js.map +1 -0
- package/lib/rich-message.d.ts +75 -0
- package/lib/rich-message.d.ts.map +1 -0
- package/lib/rich-message.js +11 -0
- package/lib/rich-message.js.map +1 -0
- package/lib/rich-streaming-reply.d.ts +69 -0
- package/lib/rich-streaming-reply.d.ts.map +1 -0
- package/lib/rich-streaming-reply.js +207 -0
- package/lib/rich-streaming-reply.js.map +1 -0
- package/lib/streaming-reply.d.ts +98 -0
- package/lib/streaming-reply.d.ts.map +1 -0
- package/lib/streaming-reply.js +274 -0
- package/lib/streaming-reply.js.map +1 -0
- package/lib/transport.d.ts +38 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +91 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream.d.ts +174 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +452 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +52 -0
package/lib/outbound.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { ChannelError, ChannelSendError } from '@krischoichoi/channel-core';
|
|
2
|
+
import { sendWithFallback } from './render/index.js';
|
|
3
|
+
/** Maximum Telegram `callback_data` size (1–64 bytes, plan §11). */
|
|
4
|
+
const CALLBACK_DATA_MAX_BYTES = 64;
|
|
5
|
+
/** Telegram ForceReply input placeholder length (Bot API 10.2). */
|
|
6
|
+
const FORCE_REPLY_PLACEHOLDER_MAX_CHARS = 64;
|
|
7
|
+
export class OutboundSender {
|
|
8
|
+
upstream;
|
|
9
|
+
logger;
|
|
10
|
+
formatting;
|
|
11
|
+
constructor(upstream, logger, options = {}) {
|
|
12
|
+
this.upstream = upstream;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.formatting = { mode: 'auto', fallback: 'plain', ...options.formatting };
|
|
15
|
+
}
|
|
16
|
+
async send(target, message) {
|
|
17
|
+
try {
|
|
18
|
+
const replyMarkup = replyMarkupFor(message);
|
|
19
|
+
const media = collectSendableMedia(message.parts);
|
|
20
|
+
if (media.length > 0) {
|
|
21
|
+
let last;
|
|
22
|
+
for (const [index, item] of media.entries()) {
|
|
23
|
+
// The first attachment carries the textual caption; controls belong
|
|
24
|
+
// on the last message so SendResult.messageId is the interaction id.
|
|
25
|
+
last = await this.sendMediaWithFormatting(target, item, index === 0 ? message : { ...message, text: undefined }, index === media.length - 1 ? replyMarkup : undefined);
|
|
26
|
+
}
|
|
27
|
+
if (!last)
|
|
28
|
+
throw new ChannelSendError('telegram media send produced no message');
|
|
29
|
+
return { delivered: true, messageId: last.messageId, raw: last.raw };
|
|
30
|
+
}
|
|
31
|
+
const text = message.text ?? '';
|
|
32
|
+
if (!text) {
|
|
33
|
+
throw new ChannelSendError('telegram message has no sendable text or media');
|
|
34
|
+
}
|
|
35
|
+
const response = await this.sendTextWithFormatting(target, text, sendOptions(target, message), replyMarkup);
|
|
36
|
+
return { delivered: true, messageId: response.messageId, raw: response.raw };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.logger.error(`[channel-telegram] send failed to '${target.conversationId}'`, error instanceof Error ? error.message : error);
|
|
40
|
+
// Preserve TelegramApiError fields (kind/errorCode/retryAfter) and
|
|
41
|
+
// adapter-generated ChannelSendError diagnostics for outer policy.
|
|
42
|
+
if (error instanceof ChannelError)
|
|
43
|
+
throw error;
|
|
44
|
+
throw new ChannelSendError(`telegram send failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Send text through the configured renderer with exact-once fallback. */
|
|
48
|
+
async sendTextWithFormatting(target, source, options, replyMarkup) {
|
|
49
|
+
const send = async (plan) => {
|
|
50
|
+
if (plan.kind === 'rich') {
|
|
51
|
+
let last;
|
|
52
|
+
for (const [index, text] of plan.texts.entries()) {
|
|
53
|
+
last = await this.upstream.sendRichMessage(target.conversationId, { markdown: text }, options, { ...(replyMarkup && index === plan.texts.length - 1 ? { replyMarkup } : {}) });
|
|
54
|
+
}
|
|
55
|
+
if (!last)
|
|
56
|
+
throw new ChannelSendError('telegram rich send produced no message');
|
|
57
|
+
return last;
|
|
58
|
+
}
|
|
59
|
+
let last;
|
|
60
|
+
for (const [index, chunk] of plan.chunks.entries()) {
|
|
61
|
+
last = await this.upstream.sendMessage(target.conversationId, chunk, options, {
|
|
62
|
+
...(plan.parseMode ? { parseMode: plan.parseMode } : {}),
|
|
63
|
+
...(replyMarkup && index === plan.chunks.length - 1 ? { replyMarkup } : {}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!last)
|
|
67
|
+
throw new ChannelSendError('telegram text send produced no message');
|
|
68
|
+
return last;
|
|
69
|
+
};
|
|
70
|
+
return sendWithFallback(source, { mode: this.formatting.mode }, send);
|
|
71
|
+
}
|
|
72
|
+
/** Send media with the text rendered as a ≤1024 caption (plain fallback). */
|
|
73
|
+
async sendMediaWithFormatting(target, media, message, replyMarkup) {
|
|
74
|
+
const sendOptions = {
|
|
75
|
+
replyToMessageId: message.replyTo ?? target.replyToMessageId,
|
|
76
|
+
messageThreadId: target.threadId,
|
|
77
|
+
};
|
|
78
|
+
const sendWithoutCaption = async () => {
|
|
79
|
+
return this.upstream.sendMedia(target.conversationId, media, sendOptions, { ...(replyMarkup ? { replyMarkup } : {}) });
|
|
80
|
+
};
|
|
81
|
+
const caption = message.text ?? '';
|
|
82
|
+
if (!caption)
|
|
83
|
+
return sendWithoutCaption();
|
|
84
|
+
const plainCaption = markdownToPlainCaption(caption);
|
|
85
|
+
// Captions are rendered as plain text (rich/HTML media captions are out of
|
|
86
|
+
// scope); any format error falls back to the stripped caption once.
|
|
87
|
+
try {
|
|
88
|
+
return await this.upstream.sendMedia(target.conversationId, { ...media, caption: truncateCaption(plainCaption) }, sendOptions, { ...(replyMarkup ? { replyMarkup } : {}) });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (isFormattingFailure(error)) {
|
|
92
|
+
return this.upstream.sendMedia(target.conversationId, { ...media, caption: truncateCaption(markdownToPlainCaption(caption)) }, sendOptions, { ...(replyMarkup ? { replyMarkup } : {}) });
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Whether an error is a formatting failure eligible for plain fallback. */
|
|
99
|
+
function isFormattingFailure(error) {
|
|
100
|
+
return (typeof error === 'object' &&
|
|
101
|
+
error !== null &&
|
|
102
|
+
error.kind === 'format');
|
|
103
|
+
}
|
|
104
|
+
/** Plain caption text (stripped of markdown) for the fallback path. */
|
|
105
|
+
function markdownToPlainCaption(source) {
|
|
106
|
+
return source
|
|
107
|
+
.replace(/\*\*(.+?)\*\*/g, '$1')
|
|
108
|
+
.replace(/\*(.+?)\*/g, '$1')
|
|
109
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
110
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
|
|
111
|
+
}
|
|
112
|
+
/** Caption cap: 1024 grapheme clusters (plan §7.1). */
|
|
113
|
+
const CAPTION_MAX_GRAPHEMES = 1024;
|
|
114
|
+
/** Truncate a caption to at most 1024 grapheme clusters, never mid-cluster. */
|
|
115
|
+
function truncateCaption(text) {
|
|
116
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
117
|
+
const graphemes = [...segmenter.segment(text)].map((s) => s.segment);
|
|
118
|
+
if (graphemes.length <= CAPTION_MAX_GRAPHEMES)
|
|
119
|
+
return text;
|
|
120
|
+
return graphemes.slice(0, CAPTION_MAX_GRAPHEMES).join('');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Map `OutboundMessage.actions` to a Telegram `InlineKeyboardMarkup`.
|
|
124
|
+
*
|
|
125
|
+
* Each row becomes one inline-keyboard row; each action becomes a button with
|
|
126
|
+
* `text = label`, `callback_data = id` and the mapped style. An action id that
|
|
127
|
+
* exceeds Telegram's 64-byte `callback_data` cap fails closed (we cannot
|
|
128
|
+
* round-trip a truncated id), rather than silently dropping the button.
|
|
129
|
+
*/
|
|
130
|
+
export function actionsToReplyMarkup(actions) {
|
|
131
|
+
if (!actions || actions.length === 0)
|
|
132
|
+
return undefined;
|
|
133
|
+
const rows = [];
|
|
134
|
+
for (const row of actions) {
|
|
135
|
+
const buttons = [];
|
|
136
|
+
for (const action of row.actions) {
|
|
137
|
+
const idBytes = new TextEncoder().encode(action.id).byteLength;
|
|
138
|
+
if (idBytes > CALLBACK_DATA_MAX_BYTES) {
|
|
139
|
+
throw new ChannelSendError(`telegram action id exceeds ${CALLBACK_DATA_MAX_BYTES}-byte callback_data limit`);
|
|
140
|
+
}
|
|
141
|
+
buttons.push({
|
|
142
|
+
text: action.label,
|
|
143
|
+
callback_data: action.id,
|
|
144
|
+
...(action.style && action.style !== 'default' ? { style: action.style } : {}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (buttons.length > 0)
|
|
148
|
+
rows.push(buttons);
|
|
149
|
+
}
|
|
150
|
+
if (rows.length === 0)
|
|
151
|
+
return undefined;
|
|
152
|
+
return { inline_keyboard: rows };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Convert every binary part to media. An unsupported carrier fails before
|
|
156
|
+
* delivery begins, so a message can never silently omit a later attachment.
|
|
157
|
+
*/
|
|
158
|
+
function collectSendableMedia(parts) {
|
|
159
|
+
const media = [];
|
|
160
|
+
for (const part of parts ?? []) {
|
|
161
|
+
switch (part.type) {
|
|
162
|
+
case 'image':
|
|
163
|
+
case 'file':
|
|
164
|
+
case 'audio':
|
|
165
|
+
case 'video': {
|
|
166
|
+
if (part.localData) {
|
|
167
|
+
media.push({
|
|
168
|
+
type: part.type,
|
|
169
|
+
localData: part.localData,
|
|
170
|
+
mimeType: part.mimeType,
|
|
171
|
+
name: part.name,
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
const ref = part.url ?? part.resourceRef;
|
|
176
|
+
if (ref) {
|
|
177
|
+
media.push({ type: part.type, url: ref, mimeType: part.mimeType, name: part.name });
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
throw new ChannelSendError(`telegram ${part.type} media has no supported localData, url, or resourceRef carrier`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return media;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Telegram does not permit ForceReply and an inline keyboard on the same
|
|
188
|
+
* message. Treat the generic contract as mutually exclusive so a caller never
|
|
189
|
+
* accidentally creates an uncorrelatable custom-answer prompt.
|
|
190
|
+
*/
|
|
191
|
+
function replyMarkupFor(message) {
|
|
192
|
+
if (message.replyPrompt) {
|
|
193
|
+
if (message.actions?.length) {
|
|
194
|
+
throw new ChannelSendError('telegram replyPrompt cannot be combined with inline actions');
|
|
195
|
+
}
|
|
196
|
+
const placeholder = message.replyPrompt.placeholder;
|
|
197
|
+
if (placeholder && [...placeholder].length > FORCE_REPLY_PLACEHOLDER_MAX_CHARS) {
|
|
198
|
+
throw new ChannelSendError(`telegram replyPrompt placeholder exceeds ${FORCE_REPLY_PLACEHOLDER_MAX_CHARS}-character limit`);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
force_reply: true,
|
|
202
|
+
...(placeholder
|
|
203
|
+
? { input_field_placeholder: placeholder }
|
|
204
|
+
: {}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return actionsToReplyMarkup(message.actions);
|
|
208
|
+
}
|
|
209
|
+
function sendOptions(target, message) {
|
|
210
|
+
return {
|
|
211
|
+
replyToMessageId: message.replyTo ?? target.replyToMessageId,
|
|
212
|
+
messageThreadId: target.threadId,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type MarkdownBlock } from './markdown.js';
|
|
2
|
+
export declare function escapeHtml(text: string): string;
|
|
3
|
+
/** Kept as a public helper; parsing now happens through mdast rather than regex. */
|
|
4
|
+
export declare function inlineMarkdownToHtml(text: string): string;
|
|
5
|
+
export declare function blockToHtml(block: MarkdownBlock): string;
|
|
6
|
+
/**
|
|
7
|
+
* Oversized generated HTML degrades to escaped text chunks. Every returned
|
|
8
|
+
* chunk is independently valid Telegram HTML; no tag is left open across a
|
|
9
|
+
* message boundary.
|
|
10
|
+
*/
|
|
11
|
+
export declare function safeHtmlSplit(html: string, maxLength?: number): string[];
|
|
12
|
+
export declare function renderHtml(source: string, limit?: number): string[];
|
|
13
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/render/html.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAIrE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AA6CD,oFAAoF;AACpF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAExD;AAID;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAW,GAAG,MAAM,EAAE,CAqB1E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAW,GAAG,MAAM,EAAE,CAoBrE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { toString } from 'mdast-util-to-string';
|
|
2
|
+
import { tokenizeMarkdown } from './markdown.js';
|
|
3
|
+
import { blockToPlain } from './plain.js';
|
|
4
|
+
import { splitByGraphemes } from './segment.js';
|
|
5
|
+
export function escapeHtml(text) {
|
|
6
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
7
|
+
}
|
|
8
|
+
function escapeAttr(value) {
|
|
9
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
10
|
+
}
|
|
11
|
+
function childrenToHtml(node) {
|
|
12
|
+
return node.children.map(nodeToHtml).join('');
|
|
13
|
+
}
|
|
14
|
+
function nodeToHtml(node) {
|
|
15
|
+
switch (node.type) {
|
|
16
|
+
case 'text': return escapeHtml(node.value);
|
|
17
|
+
case 'strong': return `<b>${childrenToHtml(node)}</b>`;
|
|
18
|
+
case 'emphasis': return `<i>${childrenToHtml(node)}</i>`;
|
|
19
|
+
case 'delete': return `<s>${childrenToHtml(node)}</s>`;
|
|
20
|
+
case 'inlineCode': return `<code>${escapeHtml(node.value)}</code>`;
|
|
21
|
+
case 'code': return `<pre><code>${escapeHtml(node.value)}</code></pre>`;
|
|
22
|
+
case 'link': return `<a href="${escapeAttr(node.url)}">${childrenToHtml(node)}</a>`;
|
|
23
|
+
case 'image': return escapeHtml(node.alt ?? node.url);
|
|
24
|
+
case 'break': return '\n';
|
|
25
|
+
case 'heading': return `<b>${childrenToHtml(node)}</b>`;
|
|
26
|
+
case 'blockquote': return `<blockquote>${childrenToHtml(node)}</blockquote>`;
|
|
27
|
+
case 'paragraph': return childrenToHtml(node);
|
|
28
|
+
case 'list':
|
|
29
|
+
return node.children.map((item, index) => {
|
|
30
|
+
const marker = node.ordered ? `${(node.start ?? 1) + index}.` : '-';
|
|
31
|
+
return `${marker} ${childrenToHtml(item)}`;
|
|
32
|
+
}).join('\n');
|
|
33
|
+
case 'listItem': return childrenToHtml(node);
|
|
34
|
+
case 'table': return `<pre>${escapeHtml(toString(node))}</pre>`;
|
|
35
|
+
case 'tableRow': return node.children.map((cell) => toString(cell)).join(' | ');
|
|
36
|
+
case 'tableCell': return childrenToHtml(node);
|
|
37
|
+
case 'html': return escapeHtml(node.value);
|
|
38
|
+
case 'thematicBreak': return '---';
|
|
39
|
+
case 'footnoteDefinition': return childrenToHtml(node);
|
|
40
|
+
case 'footnoteReference': return escapeHtml(node.label ?? node.identifier);
|
|
41
|
+
case 'linkReference': return childrenToHtml(node);
|
|
42
|
+
case 'imageReference': return escapeHtml(node.alt ?? node.label ?? node.identifier);
|
|
43
|
+
case 'definition': return '';
|
|
44
|
+
case 'yaml': return escapeHtml(node.value);
|
|
45
|
+
case 'root': return childrenToHtml(node);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Kept as a public helper; parsing now happens through mdast rather than regex. */
|
|
49
|
+
export function inlineMarkdownToHtml(text) {
|
|
50
|
+
const block = tokenizeMarkdown(text)[0];
|
|
51
|
+
return block ? nodeToHtml(block.node) : '';
|
|
52
|
+
}
|
|
53
|
+
export function blockToHtml(block) {
|
|
54
|
+
return nodeToHtml(block.node);
|
|
55
|
+
}
|
|
56
|
+
const HTML_MAX = 4096;
|
|
57
|
+
/**
|
|
58
|
+
* Oversized generated HTML degrades to escaped text chunks. Every returned
|
|
59
|
+
* chunk is independently valid Telegram HTML; no tag is left open across a
|
|
60
|
+
* message boundary.
|
|
61
|
+
*/
|
|
62
|
+
export function safeHtmlSplit(html, maxLength = HTML_MAX) {
|
|
63
|
+
if (html.length <= maxLength)
|
|
64
|
+
return [html];
|
|
65
|
+
const plain = html
|
|
66
|
+
.replace(/<[^>]*>/g, '')
|
|
67
|
+
.replace(/</g, '<')
|
|
68
|
+
.replace(/>/g, '>')
|
|
69
|
+
.replace(/"/g, '"')
|
|
70
|
+
.replace(/&/g, '&');
|
|
71
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
72
|
+
const chunks = [];
|
|
73
|
+
let current = '';
|
|
74
|
+
for (const item of segmenter.segment(plain)) {
|
|
75
|
+
const escaped = escapeHtml(item.segment);
|
|
76
|
+
if (current && current.length + escaped.length > maxLength) {
|
|
77
|
+
chunks.push(current);
|
|
78
|
+
current = '';
|
|
79
|
+
}
|
|
80
|
+
current += escaped;
|
|
81
|
+
}
|
|
82
|
+
if (current)
|
|
83
|
+
chunks.push(current);
|
|
84
|
+
return chunks;
|
|
85
|
+
}
|
|
86
|
+
export function renderHtml(source, limit = HTML_MAX) {
|
|
87
|
+
if (!source)
|
|
88
|
+
return [];
|
|
89
|
+
const messages = [];
|
|
90
|
+
let buffer = '';
|
|
91
|
+
const flush = () => {
|
|
92
|
+
if (buffer)
|
|
93
|
+
messages.push(buffer);
|
|
94
|
+
buffer = '';
|
|
95
|
+
};
|
|
96
|
+
for (const block of tokenizeMarkdown(source)) {
|
|
97
|
+
const html = blockToHtml(block);
|
|
98
|
+
if (html.length > limit) {
|
|
99
|
+
flush();
|
|
100
|
+
messages.push(...splitByGraphemes(blockToPlain(block), limit).map(escapeHtml));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (buffer && buffer.length + html.length + 1 > limit)
|
|
104
|
+
flush();
|
|
105
|
+
buffer += `${buffer ? '\n' : ''}${html}`;
|
|
106
|
+
}
|
|
107
|
+
flush();
|
|
108
|
+
return messages;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/render/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer entry point (plan §Phase 2 / §20.9).
|
|
3
|
+
*
|
|
4
|
+
* `renderMessage(source, options)` turns Agent Markdown into a concrete
|
|
5
|
+
* outbound plan for one of the configured output modes. The plan is consumed by
|
|
6
|
+
* the adapter's buffered send path and by the rich streaming final edit — it
|
|
7
|
+
* never touches the network.
|
|
8
|
+
*
|
|
9
|
+
* The formatting fallback is contained here too: `sendWithFallback` tries the
|
|
10
|
+
* requested mode once and, ONLY on a `format`-kind `TelegramApiError`, retries
|
|
11
|
+
* the same content once as plain text. 401/403 / 429 / network / 5xx are never
|
|
12
|
+
* treated as a formatting problem (plan §20.9), so there is no infinite
|
|
13
|
+
* rich→plain→rich loop.
|
|
14
|
+
*/
|
|
15
|
+
import type { TelegramApiError } from '../api-error.js';
|
|
16
|
+
export type RenderMode = 'auto' | 'rich-markdown' | 'html' | 'markdown-v2' | 'plain';
|
|
17
|
+
/** A concrete, ready-to-send outbound plan for one message. */
|
|
18
|
+
export type RenderPlan = {
|
|
19
|
+
kind: 'rich';
|
|
20
|
+
mode: Extract<RenderMode, 'auto' | 'rich-markdown'>;
|
|
21
|
+
texts: string[];
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'regular';
|
|
24
|
+
mode: Exclude<RenderMode, 'auto' | 'rich-markdown'>;
|
|
25
|
+
parseMode?: 'HTML' | 'MarkdownV2';
|
|
26
|
+
chunks: string[];
|
|
27
|
+
};
|
|
28
|
+
export interface RenderOptions {
|
|
29
|
+
mode: RenderMode;
|
|
30
|
+
/** Produce a caption-sized plan (1024) instead of the regular 4096. */
|
|
31
|
+
forCaption?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Resolve the default renderer for the adapter's Bot API 10.2 baseline. */
|
|
34
|
+
export declare function resolveMode(mode: RenderMode): Exclude<RenderMode, 'auto'>;
|
|
35
|
+
/**
|
|
36
|
+
* Render Markdown source to a plan for the given mode.
|
|
37
|
+
*
|
|
38
|
+
* - `plain`: plain fallback, 4096 (or 1024 caption) grapheme segments.
|
|
39
|
+
* - `html`: Telegram-safe HTML, 4096, tags/entities never split.
|
|
40
|
+
* - `markdown-v2`: fully escaped MarkdownV2, 4096.
|
|
41
|
+
* - `rich-markdown`: block-aware Rich Message markdown, 32768 UTF-8 bytes.
|
|
42
|
+
* - `auto`: Rich Markdown (the adapter requires Bot API 10.2 or newer).
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderMessage(source: string, options: RenderOptions): RenderPlan;
|
|
45
|
+
/**
|
|
46
|
+
* Whether an error is a formatting failure that warrants a one-shot plain
|
|
47
|
+
* fallback. Only `format`-kind `TelegramApiError`s qualify; everything else
|
|
48
|
+
* (401/403 / 429 / network / 5xx) propagates (plan §20.9).
|
|
49
|
+
*/
|
|
50
|
+
export declare function isFormattingFailure(error: unknown): error is TelegramApiError;
|
|
51
|
+
export type SendPlan = (plan: RenderPlan) => Promise<unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Send a rendered plan with exactly-once formatting fallback:
|
|
54
|
+
*
|
|
55
|
+
* - try the requested mode;
|
|
56
|
+
* - if it throws a `format`-kind error and the requested mode was not already
|
|
57
|
+
* plain, retry the SAME content once as plain text;
|
|
58
|
+
* - any other error, or a format error on the plain retry, rethrows unchanged.
|
|
59
|
+
*
|
|
60
|
+
* This guarantees the fallback runs at most once and can never enter an
|
|
61
|
+
* infinite rich→plain→rich loop.
|
|
62
|
+
*/
|
|
63
|
+
export declare function sendWithFallback(source: string, options: RenderOptions, send: SendPlan): Promise<unknown>;
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKxD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,eAAe,GAAG,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC;AAErF,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACtF;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAGzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,UAAU,CAmBhF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAM7E;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAY/G"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { renderRichMarkdown } from './markdown.js';
|
|
2
|
+
import { renderHtml } from './html.js';
|
|
3
|
+
import { renderMarkdownV2, renderPlain, renderPlainCaption } from './plain.js';
|
|
4
|
+
/** Resolve the default renderer for the adapter's Bot API 10.2 baseline. */
|
|
5
|
+
export function resolveMode(mode) {
|
|
6
|
+
if (mode === 'auto')
|
|
7
|
+
return 'rich-markdown';
|
|
8
|
+
return mode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render Markdown source to a plan for the given mode.
|
|
12
|
+
*
|
|
13
|
+
* - `plain`: plain fallback, 4096 (or 1024 caption) grapheme segments.
|
|
14
|
+
* - `html`: Telegram-safe HTML, 4096, tags/entities never split.
|
|
15
|
+
* - `markdown-v2`: fully escaped MarkdownV2, 4096.
|
|
16
|
+
* - `rich-markdown`: block-aware Rich Message markdown, 32768 UTF-8 bytes.
|
|
17
|
+
* - `auto`: Rich Markdown (the adapter requires Bot API 10.2 or newer).
|
|
18
|
+
*/
|
|
19
|
+
export function renderMessage(source, options) {
|
|
20
|
+
const mode = resolveMode(options.mode);
|
|
21
|
+
if (mode === 'plain') {
|
|
22
|
+
const texts = options.forCaption ? renderPlainCaption(source) : renderPlain(source);
|
|
23
|
+
return { kind: 'regular', mode: 'plain', chunks: texts };
|
|
24
|
+
}
|
|
25
|
+
if (mode === 'html') {
|
|
26
|
+
return { kind: 'regular', mode: 'html', parseMode: 'HTML', chunks: renderHtml(source) };
|
|
27
|
+
}
|
|
28
|
+
if (mode === 'markdown-v2') {
|
|
29
|
+
return {
|
|
30
|
+
kind: 'regular',
|
|
31
|
+
mode: 'markdown-v2',
|
|
32
|
+
parseMode: 'MarkdownV2',
|
|
33
|
+
chunks: renderMarkdownV2(source),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// rich-markdown
|
|
37
|
+
return { kind: 'rich', mode: 'rich-markdown', texts: renderRichMarkdown(source) };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether an error is a formatting failure that warrants a one-shot plain
|
|
41
|
+
* fallback. Only `format`-kind `TelegramApiError`s qualify; everything else
|
|
42
|
+
* (401/403 / 429 / network / 5xx) propagates (plan §20.9).
|
|
43
|
+
*/
|
|
44
|
+
export function isFormattingFailure(error) {
|
|
45
|
+
return (typeof error === 'object' &&
|
|
46
|
+
error !== null &&
|
|
47
|
+
error.kind === 'format');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Send a rendered plan with exactly-once formatting fallback:
|
|
51
|
+
*
|
|
52
|
+
* - try the requested mode;
|
|
53
|
+
* - if it throws a `format`-kind error and the requested mode was not already
|
|
54
|
+
* plain, retry the SAME content once as plain text;
|
|
55
|
+
* - any other error, or a format error on the plain retry, rethrows unchanged.
|
|
56
|
+
*
|
|
57
|
+
* This guarantees the fallback runs at most once and can never enter an
|
|
58
|
+
* infinite rich→plain→rich loop.
|
|
59
|
+
*/
|
|
60
|
+
export async function sendWithFallback(source, options, send) {
|
|
61
|
+
const requested = resolveMode(options.mode);
|
|
62
|
+
const first = renderMessage(source, options);
|
|
63
|
+
try {
|
|
64
|
+
return await send(first);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!isFormattingFailure(error))
|
|
68
|
+
throw error;
|
|
69
|
+
// Only downgrade if we weren't already plain.
|
|
70
|
+
if (requested === 'plain')
|
|
71
|
+
throw error;
|
|
72
|
+
const plain = renderMessage(source, { ...options, mode: 'plain' });
|
|
73
|
+
return await send(plain);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAoB/E,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC1C,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,eAAe,CAAC;IAC5C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAsB;IAClE,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACpF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IAC1F,CAAC;IACD,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;QAC3B,OAAO;YACL,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,aAAa;YACnB,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC;SACjC,CAAC;IACJ,CAAC;IACD,gBAAgB;IAChB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAChD,CAAC;AACJ,CAAC;AAID;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAsB,EAAE,IAAc;IAC3F,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;QAC7C,8CAA8C;QAC9C,IAAI,SAAS,KAAK,OAAO;YAAE,MAAM,KAAK,CAAC;QACvC,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnE,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** GFM-aware Markdown parsing and Telegram Rich Message segmentation. */
|
|
2
|
+
import type { Root, RootContent } from 'mdast';
|
|
3
|
+
interface BlockBase {
|
|
4
|
+
node: RootContent;
|
|
5
|
+
markdown: string;
|
|
6
|
+
}
|
|
7
|
+
/** Compatibility surface consumed by the plain and HTML renderers. */
|
|
8
|
+
export type MarkdownBlock = (BlockBase & {
|
|
9
|
+
kind: 'code';
|
|
10
|
+
lang: string;
|
|
11
|
+
code: string;
|
|
12
|
+
}) | (BlockBase & {
|
|
13
|
+
kind: 'heading';
|
|
14
|
+
level: number;
|
|
15
|
+
text: string;
|
|
16
|
+
}) | (BlockBase & {
|
|
17
|
+
kind: 'table';
|
|
18
|
+
header: string[];
|
|
19
|
+
rows: string[][];
|
|
20
|
+
}) | (BlockBase & {
|
|
21
|
+
kind: 'blockquote';
|
|
22
|
+
content: string;
|
|
23
|
+
}) | (BlockBase & {
|
|
24
|
+
kind: 'list';
|
|
25
|
+
ordered: boolean;
|
|
26
|
+
items: string[];
|
|
27
|
+
}) | (BlockBase & {
|
|
28
|
+
kind: 'paragraph';
|
|
29
|
+
text: string;
|
|
30
|
+
});
|
|
31
|
+
export declare function parseMarkdown(source: string): Root;
|
|
32
|
+
export declare function tokenizeMarkdown(source: string): MarkdownBlock[];
|
|
33
|
+
export declare function renderBlock(block: MarkdownBlock): string;
|
|
34
|
+
/** Segment parsed blocks under Telegram's byte, block-count, and depth limits. */
|
|
35
|
+
export declare function segmentRich(blocks: MarkdownBlock[], limit?: number): string[];
|
|
36
|
+
export declare function renderRichMarkdown(source: string, limit?: number): string[];
|
|
37
|
+
export { tokenizeMarkdown as tokenize };
|
|
38
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/render/markdown.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,KAAK,EAAQ,IAAI,EAAE,WAAW,EAAmB,MAAM,OAAO,CAAC;AActE,UAAU,SAAS;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,sEAAsE;AACtE,MAAM,MAAM,aAAa,GACrB,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1D,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9D,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,CAAC,GACnE,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACrD,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GACjE,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEtD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAKlD;AAkDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAGhE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAExD;AAmFD,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,KAAK,SAAwB,GAAG,MAAM,EAAE,CAiC5F;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAwB,GAAG,MAAM,EAAE,CAE1F;AAED,OAAO,EAAE,gBAAgB,IAAI,QAAQ,EAAE,CAAC"}
|