@llblab/pi-telegram 0.17.5 → 0.18.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/AGENTS.md +67 -32
- package/BACKLOG.md +59 -19
- package/CHANGELOG.md +36 -15
- package/README.md +63 -35
- package/docs/README.md +3 -1
- package/docs/architecture.md +55 -23
- package/docs/callback-namespaces.md +1 -1
- package/docs/inbound.md +1 -1
- package/docs/locks.md +0 -2
- package/docs/multi-instance-bus.md +483 -0
- package/docs/outbound.md +4 -3
- package/docs/public-api.md +12 -10
- package/docs/sections.md +2 -2
- package/docs/ui-style.md +76 -0
- package/index.ts +789 -32
- package/lib/bindings.ts +68 -12
- package/lib/bus-api.ts +314 -0
- package/lib/bus-follower.ts +853 -0
- package/lib/bus-leader.ts +915 -0
- package/lib/bus.ts +866 -0
- package/lib/command-templates.ts +9 -11
- package/lib/commands.ts +133 -47
- package/lib/config.ts +53 -5
- package/lib/lifecycle.ts +23 -7
- package/lib/locks.ts +230 -66
- package/lib/media.ts +30 -2
- package/lib/menu-model.ts +48 -17
- package/lib/menu-queue.ts +51 -20
- package/lib/menu-settings.ts +9 -5
- package/lib/menu-status.ts +3 -0
- package/lib/menu-thinking.ts +3 -0
- package/lib/menu.ts +67 -26
- package/lib/outbound-attachments.ts +102 -17
- package/lib/outbound-buttons.ts +6 -2
- package/lib/outbound-voice.ts +31 -11
- package/lib/outbound.ts +6 -4
- package/lib/ownership.ts +119 -0
- package/lib/pi.ts +26 -3
- package/lib/polling.ts +477 -7
- package/lib/preview.ts +141 -88
- package/lib/prompt-templates.ts +3 -3
- package/lib/prompts.ts +80 -30
- package/lib/queue.ts +193 -91
- package/lib/rendering.ts +0 -25
- package/lib/replies.ts +187 -55
- package/lib/routing.ts +1673 -9
- package/lib/runtime-log.ts +123 -0
- package/lib/runtime.ts +84 -12
- package/lib/sections.ts +28 -21
- package/lib/setup.ts +1 -1
- package/lib/status.ts +532 -9
- package/lib/sync.ts +618 -0
- package/lib/target.ts +49 -0
- package/lib/telegram-api.ts +405 -40
- package/lib/text-groups.ts +5 -1
- package/lib/thread-reconciler.ts +915 -0
- package/lib/threads.ts +2205 -0
- package/lib/turns.ts +48 -3
- package/lib/updates.ts +355 -32
- package/package.json +24 -2
- package/docs/telegram-bot-api-rich-messages.md +0 -890
package/lib/replies.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { assertTelegramInlineKeyboardCallbackData } from "./keyboard.ts";
|
|
8
|
+
import {
|
|
9
|
+
getTelegramTargetThreadParams,
|
|
10
|
+
type TelegramTarget,
|
|
11
|
+
} from "./target.ts";
|
|
8
12
|
import type {
|
|
9
13
|
TelegramInputRichMessage,
|
|
10
14
|
TelegramReplyParameters,
|
|
@@ -29,7 +33,7 @@ export const TELEGRAM_RICH_MESSAGE_MAX_BLOCKS = 500;
|
|
|
29
33
|
// --- Reply Dedup ---
|
|
30
34
|
|
|
31
35
|
/** Non-persistent reply deduplication for a single agent turn.
|
|
32
|
-
* First reply to a prompt gets `reply_parameters.
|
|
36
|
+
* First reply to a prompt gets `reply_parameters.message_id`;
|
|
33
37
|
* subsequent replies in the same turn skip it to avoid stacking
|
|
34
38
|
* duplicate reply headers in the chat viewport. */
|
|
35
39
|
export interface ReplyDedupRuntime {
|
|
@@ -56,29 +60,45 @@ export function createReplyDedupRuntime(): ReplyDedupRuntime {
|
|
|
56
60
|
|
|
57
61
|
// --- Transport-level dedup ---
|
|
58
62
|
|
|
59
|
-
const
|
|
63
|
+
const lastRepliedToMessageIdByTarget = new Map<string, number>();
|
|
64
|
+
|
|
65
|
+
function getReplyDedupTargetKey(
|
|
66
|
+
chatId: number,
|
|
67
|
+
target?: TelegramTarget,
|
|
68
|
+
): string {
|
|
69
|
+
const threadId = target?.threadId;
|
|
70
|
+
return typeof threadId === "number"
|
|
71
|
+
? `${chatId}:thread:${threadId}`
|
|
72
|
+
: `${chatId}:private`;
|
|
73
|
+
}
|
|
60
74
|
|
|
61
75
|
export function resetTransportReplyDedup(): void {
|
|
62
|
-
|
|
76
|
+
lastRepliedToMessageIdByTarget.clear();
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
export function buildTelegramReplyParameters(
|
|
66
80
|
chatId: number,
|
|
67
81
|
messageId: number | undefined,
|
|
82
|
+
target?: TelegramTarget,
|
|
68
83
|
): TelegramReplyParameters | undefined {
|
|
69
|
-
if (messageId === undefined) return undefined;
|
|
70
|
-
|
|
84
|
+
if (messageId === undefined || messageId <= 0) return undefined;
|
|
85
|
+
const key = getReplyDedupTargetKey(chatId, target);
|
|
86
|
+
if (lastRepliedToMessageIdByTarget.get(key) === messageId) {
|
|
71
87
|
return undefined;
|
|
72
88
|
}
|
|
73
|
-
|
|
74
|
-
return {
|
|
89
|
+
lastRepliedToMessageIdByTarget.set(key, messageId);
|
|
90
|
+
return {
|
|
91
|
+
message_id: messageId,
|
|
92
|
+
allow_sending_without_reply: true,
|
|
93
|
+
};
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
export function buildTelegramMultipartReplyParameters(
|
|
78
97
|
chatId: number,
|
|
79
98
|
messageId: number | undefined,
|
|
99
|
+
target?: TelegramTarget,
|
|
80
100
|
): string | undefined {
|
|
81
|
-
const parameters = buildTelegramReplyParameters(chatId, messageId);
|
|
101
|
+
const parameters = buildTelegramReplyParameters(chatId, messageId, target);
|
|
82
102
|
return parameters ? JSON.stringify(parameters) : undefined;
|
|
83
103
|
}
|
|
84
104
|
|
|
@@ -132,13 +152,24 @@ export function extractLatestAssistantMessageText(
|
|
|
132
152
|
return {};
|
|
133
153
|
}
|
|
134
154
|
|
|
155
|
+
export interface TelegramReplyOwnershipRecorder {
|
|
156
|
+
record: (input: {
|
|
157
|
+
chatId: number;
|
|
158
|
+
messageId: number;
|
|
159
|
+
target?: TelegramTarget;
|
|
160
|
+
}) => void;
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
|
|
164
|
+
recordOwnership?: TelegramReplyOwnershipRecorder["record"];
|
|
136
165
|
sendMessage: (body: {
|
|
137
166
|
chat_id: number;
|
|
138
167
|
text: string;
|
|
139
168
|
parse_mode?: "HTML";
|
|
140
169
|
reply_markup?: TReplyMarkup;
|
|
141
170
|
reply_parameters?: TelegramReplyParameters;
|
|
171
|
+
reply_to_message_id?: number;
|
|
172
|
+
message_thread_id?: number;
|
|
142
173
|
}) => Promise<TelegramSentMessage>;
|
|
143
174
|
editMessage: (body: {
|
|
144
175
|
chat_id: number;
|
|
@@ -147,20 +178,28 @@ export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
|
|
|
147
178
|
rich_message?: TelegramInputRichMessage;
|
|
148
179
|
parse_mode?: "HTML";
|
|
149
180
|
reply_markup?: TReplyMarkup;
|
|
181
|
+
message_thread_id?: number;
|
|
150
182
|
}) => Promise<unknown>;
|
|
151
183
|
}
|
|
152
184
|
|
|
185
|
+
export interface TelegramReplyTargetOptions {
|
|
186
|
+
target?: TelegramTarget;
|
|
187
|
+
replyToMessageId?: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
153
190
|
export interface TelegramReplyTransport<TReplyMarkup> {
|
|
154
191
|
sendRenderedChunks: (
|
|
155
192
|
chatId: number,
|
|
156
193
|
chunks: TelegramRenderedChunk[],
|
|
157
|
-
options?:
|
|
194
|
+
options?: TelegramReplyTargetOptions & {
|
|
195
|
+
replyMarkup?: TReplyMarkup;
|
|
196
|
+
},
|
|
158
197
|
) => Promise<number | undefined>;
|
|
159
198
|
editRenderedMessage: (
|
|
160
199
|
chatId: number,
|
|
161
200
|
messageId: number,
|
|
162
201
|
chunks: TelegramRenderedChunk[],
|
|
163
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
202
|
+
options?: TelegramReplyTargetOptions & { replyMarkup?: TReplyMarkup },
|
|
164
203
|
) => Promise<number | undefined>;
|
|
165
204
|
}
|
|
166
205
|
|
|
@@ -187,24 +226,37 @@ export async function sendTelegramRenderedChunks<TReplyMarkup>(
|
|
|
187
226
|
chatId: number,
|
|
188
227
|
chunks: TelegramRenderedChunk[],
|
|
189
228
|
deps: TelegramReplyDeliveryDeps<TReplyMarkup>,
|
|
190
|
-
options?:
|
|
229
|
+
options?: TelegramReplyTargetOptions & {
|
|
230
|
+
replyMarkup?: TReplyMarkup;
|
|
231
|
+
},
|
|
191
232
|
): Promise<number | undefined> {
|
|
192
233
|
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
193
234
|
let lastMessageId: number | undefined;
|
|
194
235
|
for (const [index, chunk] of chunks.entries()) {
|
|
195
236
|
const replyParameters =
|
|
196
237
|
index === 0
|
|
197
|
-
? buildTelegramReplyParameters(
|
|
238
|
+
? buildTelegramReplyParameters(
|
|
239
|
+
chatId,
|
|
240
|
+
options?.replyToMessageId,
|
|
241
|
+
options?.target,
|
|
242
|
+
)
|
|
198
243
|
: undefined;
|
|
199
|
-
const
|
|
244
|
+
const body = {
|
|
200
245
|
chat_id: chatId,
|
|
201
246
|
text: chunk.text,
|
|
202
247
|
parse_mode: chunk.parseMode,
|
|
203
248
|
reply_markup:
|
|
204
249
|
index === chunks.length - 1 ? options?.replyMarkup : undefined,
|
|
205
250
|
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
206
|
-
|
|
251
|
+
...(options?.target ? getTelegramTargetThreadParams(options.target) : {}),
|
|
252
|
+
};
|
|
253
|
+
const sent = await deps.sendMessage(body);
|
|
207
254
|
lastMessageId = sent.message_id;
|
|
255
|
+
deps.recordOwnership?.({
|
|
256
|
+
chatId,
|
|
257
|
+
messageId: sent.message_id,
|
|
258
|
+
target: options?.target,
|
|
259
|
+
});
|
|
208
260
|
}
|
|
209
261
|
return lastMessageId;
|
|
210
262
|
}
|
|
@@ -214,11 +266,12 @@ export async function editTelegramRenderedMessage<TReplyMarkup>(
|
|
|
214
266
|
messageId: number,
|
|
215
267
|
chunks: TelegramRenderedChunk[],
|
|
216
268
|
deps: TelegramReplyDeliveryDeps<TReplyMarkup>,
|
|
217
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
269
|
+
options?: TelegramReplyTargetOptions & { replyMarkup?: TReplyMarkup },
|
|
218
270
|
): Promise<number | undefined> {
|
|
219
271
|
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
220
272
|
if (chunks.length === 0) return messageId;
|
|
221
273
|
const [firstChunk, ...remainingChunks] = chunks;
|
|
274
|
+
deps.recordOwnership?.({ chatId, messageId, target: options?.target });
|
|
222
275
|
await deps.editMessage({
|
|
223
276
|
chat_id: chatId,
|
|
224
277
|
message_id: messageId,
|
|
@@ -226,15 +279,21 @@ export async function editTelegramRenderedMessage<TReplyMarkup>(
|
|
|
226
279
|
parse_mode: firstChunk.parseMode,
|
|
227
280
|
reply_markup:
|
|
228
281
|
remainingChunks.length === 0 ? options?.replyMarkup : undefined,
|
|
282
|
+
...(options?.target ? getTelegramTargetThreadParams(options.target) : {}),
|
|
229
283
|
});
|
|
230
284
|
if (remainingChunks.length > 0) {
|
|
231
285
|
return sendTelegramRenderedChunks(chatId, remainingChunks, deps, {
|
|
232
286
|
replyMarkup: options?.replyMarkup,
|
|
287
|
+
target: options?.target,
|
|
233
288
|
});
|
|
234
289
|
}
|
|
235
290
|
return messageId;
|
|
236
291
|
}
|
|
237
292
|
|
|
293
|
+
export interface TelegramTextReplyOptions extends TelegramReplyTargetOptions {
|
|
294
|
+
parseMode?: "HTML";
|
|
295
|
+
}
|
|
296
|
+
|
|
238
297
|
export interface TelegramReplyRuntimeDeps<TReplyMarkup = unknown> {
|
|
239
298
|
renderTelegramMessage: (
|
|
240
299
|
text: string,
|
|
@@ -242,19 +301,22 @@ export interface TelegramReplyRuntimeDeps<TReplyMarkup = unknown> {
|
|
|
242
301
|
) => TelegramRenderedChunk[];
|
|
243
302
|
sendRenderedChunks: (
|
|
244
303
|
chunks: TelegramRenderedChunk[],
|
|
245
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
304
|
+
options?: { replyMarkup?: TReplyMarkup } & TelegramReplyTargetOptions,
|
|
246
305
|
) => Promise<number | undefined>;
|
|
247
306
|
}
|
|
248
307
|
|
|
249
308
|
export async function sendTelegramPlainReply(
|
|
250
309
|
text: string,
|
|
251
310
|
deps: TelegramReplyRuntimeDeps,
|
|
252
|
-
options?:
|
|
311
|
+
options?: TelegramTextReplyOptions,
|
|
253
312
|
): Promise<number | undefined> {
|
|
254
313
|
const chunks = deps.renderTelegramMessage(text, {
|
|
255
314
|
mode: options?.parseMode === "HTML" ? "html" : "plain",
|
|
256
315
|
});
|
|
257
|
-
return deps.sendRenderedChunks(chunks
|
|
316
|
+
return deps.sendRenderedChunks(chunks, {
|
|
317
|
+
target: options?.target,
|
|
318
|
+
replyToMessageId: options?.replyToMessageId,
|
|
319
|
+
});
|
|
258
320
|
}
|
|
259
321
|
|
|
260
322
|
function normalizeIndentedTelegramNativeMarkdownList(line: string): string {
|
|
@@ -283,10 +345,16 @@ function normalizeTelegramNativeMarkdownLine(line: string): string {
|
|
|
283
345
|
/(^|[^\\$])\$([A-Z][A-Z0-9]{1,})(?!\$)(?=\b|[.,;:)/-])/g,
|
|
284
346
|
(_match, prefix: string, ticker: string) => `${prefix}\\$${ticker}`,
|
|
285
347
|
);
|
|
286
|
-
return result.replace(
|
|
348
|
+
return result.replace(
|
|
349
|
+
/\u0000(\d+)\u0000/g,
|
|
350
|
+
(_match, index) => codeSpans[Number(index)] ?? "",
|
|
351
|
+
);
|
|
287
352
|
}
|
|
288
353
|
|
|
289
|
-
function hasClosingDisplayMathDelimiter(
|
|
354
|
+
function hasClosingDisplayMathDelimiter(
|
|
355
|
+
lines: readonly string[],
|
|
356
|
+
startIndex: number,
|
|
357
|
+
): boolean {
|
|
290
358
|
let fence: { marker: "`" | "~"; length: number } | undefined;
|
|
291
359
|
for (let index = startIndex + 1; index < lines.length; index += 1) {
|
|
292
360
|
const line = lines[index] ?? "";
|
|
@@ -328,7 +396,10 @@ export function normalizeTelegramNativeMarkdown(markdown: string): string {
|
|
|
328
396
|
if (displayMath) return line;
|
|
329
397
|
if (!inFence && fenceMatch) {
|
|
330
398
|
const markerText = fenceMatch[1] ?? "```";
|
|
331
|
-
fence = {
|
|
399
|
+
fence = {
|
|
400
|
+
marker: markerText[0] as "`" | "~",
|
|
401
|
+
length: markerText.length,
|
|
402
|
+
};
|
|
332
403
|
return line;
|
|
333
404
|
}
|
|
334
405
|
if (
|
|
@@ -348,19 +419,23 @@ export function splitTelegramNativeMarkdown(markdown: string): string[] {
|
|
|
348
419
|
const normalizedMarkdown = normalizeTelegramNativeMarkdown(markdown);
|
|
349
420
|
if (
|
|
350
421
|
normalizedMarkdown.length <= TELEGRAM_RICH_MESSAGE_MAX_CHARS &&
|
|
351
|
-
countTelegramNativeMarkdownBlocks(normalizedMarkdown) <=
|
|
422
|
+
countTelegramNativeMarkdownBlocks(normalizedMarkdown) <=
|
|
423
|
+
TELEGRAM_RICH_MESSAGE_MAX_BLOCKS
|
|
352
424
|
) {
|
|
353
425
|
return [normalizedMarkdown];
|
|
354
426
|
}
|
|
355
427
|
const chunks: string[] = [];
|
|
356
428
|
let current = "";
|
|
357
429
|
let currentBlockCount = 0;
|
|
358
|
-
for (const rawBlock of splitTelegramNativeMarkdownBlocks(
|
|
430
|
+
for (const rawBlock of splitTelegramNativeMarkdownBlocks(
|
|
431
|
+
normalizedMarkdown,
|
|
432
|
+
)) {
|
|
359
433
|
for (const block of splitTelegramNativeMarkdownCountedBlocks(rawBlock)) {
|
|
360
434
|
const blockCount = countTelegramNativeMarkdownBlocks(block);
|
|
361
435
|
const candidate = current ? `${current}\n\n${block}` : block;
|
|
362
436
|
const exceedsChars = candidate.length > TELEGRAM_RICH_MESSAGE_MAX_CHARS;
|
|
363
|
-
const exceedsBlocks =
|
|
437
|
+
const exceedsBlocks =
|
|
438
|
+
currentBlockCount + blockCount > TELEGRAM_RICH_MESSAGE_MAX_BLOCKS;
|
|
364
439
|
if (!exceedsChars && !exceedsBlocks) {
|
|
365
440
|
current = candidate;
|
|
366
441
|
currentBlockCount += blockCount;
|
|
@@ -417,7 +492,9 @@ function splitTelegramNativeMarkdownBlocks(markdown: string): string[] {
|
|
|
417
492
|
}
|
|
418
493
|
|
|
419
494
|
function splitTelegramNativeMarkdownCountedBlocks(block: string): string[] {
|
|
420
|
-
if (
|
|
495
|
+
if (
|
|
496
|
+
countTelegramNativeMarkdownBlocks(block) <= TELEGRAM_RICH_MESSAGE_MAX_BLOCKS
|
|
497
|
+
) {
|
|
421
498
|
return [block];
|
|
422
499
|
}
|
|
423
500
|
const chunks: string[] = [];
|
|
@@ -456,9 +533,11 @@ function countTelegramNativeMarkdownBlocks(block: string): number {
|
|
|
456
533
|
}
|
|
457
534
|
|
|
458
535
|
function splitTelegramNativeMarkdownLongBlock(block: string): string[] {
|
|
459
|
-
return
|
|
536
|
+
return (
|
|
537
|
+
splitTelegramNativeMarkdownLongFenceBlock(block) ??
|
|
460
538
|
splitTelegramNativeMarkdownLongWrappedInlineBlock(block) ??
|
|
461
|
-
splitTelegramNativeMarkdownLongPlainBlock(block)
|
|
539
|
+
splitTelegramNativeMarkdownLongPlainBlock(block)
|
|
540
|
+
);
|
|
462
541
|
}
|
|
463
542
|
|
|
464
543
|
function splitTelegramNativeMarkdownLongPlainBlock(block: string): string[] {
|
|
@@ -474,7 +553,9 @@ function splitTelegramNativeMarkdownLongPlainBlock(block: string): string[] {
|
|
|
474
553
|
return chunks;
|
|
475
554
|
}
|
|
476
555
|
|
|
477
|
-
function splitTelegramNativeMarkdownLongFenceBlock(
|
|
556
|
+
function splitTelegramNativeMarkdownLongFenceBlock(
|
|
557
|
+
block: string,
|
|
558
|
+
): string[] | undefined {
|
|
478
559
|
const lines = block.split("\n");
|
|
479
560
|
const opening = lines[0] ?? "";
|
|
480
561
|
const closing = lines[lines.length - 1] ?? "";
|
|
@@ -482,33 +563,35 @@ function splitTelegramNativeMarkdownLongFenceBlock(block: string): string[] | un
|
|
|
482
563
|
if (!openingMatch || !closing || lines.length < 2) return undefined;
|
|
483
564
|
const markerText = openingMatch[1] ?? "```";
|
|
484
565
|
const marker = markerText[0] as "`" | "~";
|
|
485
|
-
if (
|
|
566
|
+
if (
|
|
567
|
+
!new RegExp(`^ {0,3}${marker}{${markerText.length},}\\s*$`).test(closing)
|
|
568
|
+
) {
|
|
486
569
|
return undefined;
|
|
487
570
|
}
|
|
488
|
-
const maxContentLength =
|
|
489
|
-
opening.length -
|
|
490
|
-
closing.length -
|
|
491
|
-
2;
|
|
571
|
+
const maxContentLength =
|
|
572
|
+
TELEGRAM_RICH_MESSAGE_MAX_CHARS - opening.length - closing.length - 2;
|
|
492
573
|
if (maxContentLength <= 0) return undefined;
|
|
493
574
|
const content = lines.slice(1, -1).join("\n");
|
|
494
575
|
return splitTelegramNativeMarkdownWrappedContent(
|
|
495
576
|
content,
|
|
496
577
|
maxContentLength,
|
|
497
|
-
(chunk) =>
|
|
578
|
+
(chunk) =>
|
|
579
|
+
`${opening}\n${chunk}${chunk.endsWith("\n") ? "" : "\n"}${closing}`,
|
|
498
580
|
);
|
|
499
581
|
}
|
|
500
582
|
|
|
501
583
|
function splitTelegramNativeMarkdownLongWrappedInlineBlock(
|
|
502
584
|
block: string,
|
|
503
585
|
): string[] | undefined {
|
|
504
|
-
const delimiter = ["**", "__", "~~", "`", "*", "_"]
|
|
505
|
-
|
|
586
|
+
const delimiter = ["**", "__", "~~", "`", "*", "_"].find(
|
|
587
|
+
(candidate) =>
|
|
506
588
|
block.startsWith(candidate) &&
|
|
507
589
|
block.endsWith(candidate) &&
|
|
508
|
-
block.length > candidate.length * 2
|
|
509
|
-
|
|
590
|
+
block.length > candidate.length * 2,
|
|
591
|
+
);
|
|
510
592
|
if (!delimiter) return undefined;
|
|
511
|
-
const maxContentLength =
|
|
593
|
+
const maxContentLength =
|
|
594
|
+
TELEGRAM_RICH_MESSAGE_MAX_CHARS - delimiter.length * 2;
|
|
512
595
|
if (maxContentLength <= 0) return undefined;
|
|
513
596
|
return splitTelegramNativeMarkdownWrappedContent(
|
|
514
597
|
block.slice(delimiter.length, -delimiter.length),
|
|
@@ -526,7 +609,10 @@ function splitTelegramNativeMarkdownWrappedContent(
|
|
|
526
609
|
let remaining = content;
|
|
527
610
|
while (remaining.length > maxContentLength) {
|
|
528
611
|
const window = remaining.slice(0, maxContentLength + 1);
|
|
529
|
-
const splitIndex = findTelegramNativeMarkdownSplitIndex(
|
|
612
|
+
const splitIndex = findTelegramNativeMarkdownSplitIndex(
|
|
613
|
+
window,
|
|
614
|
+
maxContentLength,
|
|
615
|
+
);
|
|
530
616
|
chunks.push(wrap(remaining.slice(0, splitIndex)));
|
|
531
617
|
remaining = remaining.slice(splitIndex);
|
|
532
618
|
}
|
|
@@ -552,11 +638,12 @@ export async function sendTelegramNativeMarkdownReply<TReplyMarkup = unknown>(
|
|
|
552
638
|
replyToMessageId: number | undefined,
|
|
553
639
|
markdown: string,
|
|
554
640
|
deps: {
|
|
641
|
+
recordOwnership?: TelegramReplyOwnershipRecorder["record"];
|
|
555
642
|
sendRichMessage: (
|
|
556
643
|
body: TelegramSendRichMessageBody,
|
|
557
644
|
) => Promise<TelegramSentMessage>;
|
|
558
645
|
},
|
|
559
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
646
|
+
options?: TelegramReplyTargetOptions & { replyMarkup?: TReplyMarkup },
|
|
560
647
|
): Promise<number | undefined> {
|
|
561
648
|
assertTelegramInlineKeyboardCallbackData(options?.replyMarkup);
|
|
562
649
|
let lastMessageId: number | undefined;
|
|
@@ -564,15 +651,26 @@ export async function sendTelegramNativeMarkdownReply<TReplyMarkup = unknown>(
|
|
|
564
651
|
for (const [index, chunk] of chunks.entries()) {
|
|
565
652
|
const replyParameters =
|
|
566
653
|
index === 0
|
|
567
|
-
? buildTelegramReplyParameters(
|
|
654
|
+
? buildTelegramReplyParameters(
|
|
655
|
+
chatId,
|
|
656
|
+
replyToMessageId,
|
|
657
|
+
options?.target,
|
|
658
|
+
)
|
|
568
659
|
: undefined;
|
|
569
660
|
const sent = await deps.sendRichMessage({
|
|
570
661
|
chat_id: chatId,
|
|
571
662
|
rich_message: { markdown: chunk, skip_entity_detection: true },
|
|
572
|
-
reply_markup:
|
|
663
|
+
reply_markup:
|
|
664
|
+
index === chunks.length - 1 ? options?.replyMarkup : undefined,
|
|
573
665
|
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
666
|
+
...(options?.target ? getTelegramTargetThreadParams(options.target) : {}),
|
|
574
667
|
});
|
|
575
668
|
lastMessageId = sent.message_id;
|
|
669
|
+
deps.recordOwnership?.({
|
|
670
|
+
chatId,
|
|
671
|
+
messageId: sent.message_id,
|
|
672
|
+
target: options?.target,
|
|
673
|
+
});
|
|
576
674
|
}
|
|
577
675
|
return lastMessageId;
|
|
578
676
|
}
|
|
@@ -586,7 +684,10 @@ export interface TelegramRenderedMessageRuntimeDeps<TReplyMarkup> {
|
|
|
586
684
|
options?: { mode?: TelegramRenderMode },
|
|
587
685
|
) => TelegramRenderedChunk[];
|
|
588
686
|
replyTransport: TelegramReplyTransport<TReplyMarkup>;
|
|
589
|
-
|
|
687
|
+
recordOwnership?: TelegramReplyOwnershipRecorder["record"];
|
|
688
|
+
sendRichMessage: (
|
|
689
|
+
body: TelegramSendRichMessageBody,
|
|
690
|
+
) => Promise<TelegramSentMessage>;
|
|
590
691
|
}
|
|
591
692
|
|
|
592
693
|
export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
|
|
@@ -594,13 +695,13 @@ export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
|
|
|
594
695
|
chatId: number,
|
|
595
696
|
replyToMessageId: number | undefined,
|
|
596
697
|
text: string,
|
|
597
|
-
options?:
|
|
698
|
+
options?: TelegramTextReplyOptions,
|
|
598
699
|
) => Promise<number | undefined>;
|
|
599
700
|
sendMarkdownReply: (
|
|
600
701
|
chatId: number,
|
|
601
702
|
replyToMessageId: number | undefined,
|
|
602
703
|
markdown: string,
|
|
603
|
-
options?: { replyMarkup?:
|
|
704
|
+
options?: TelegramReplyTargetOptions & { replyMarkup?: TReplyMarkup },
|
|
604
705
|
) => Promise<number | undefined>;
|
|
605
706
|
editInteractiveMessage: (
|
|
606
707
|
chatId: number,
|
|
@@ -614,6 +715,7 @@ export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
|
|
|
614
715
|
text: string,
|
|
615
716
|
mode: TelegramRenderMode,
|
|
616
717
|
replyMarkup: TReplyMarkup,
|
|
718
|
+
options?: TelegramReplyTargetOptions,
|
|
617
719
|
) => Promise<number | undefined>;
|
|
618
720
|
}
|
|
619
721
|
|
|
@@ -630,13 +732,16 @@ export interface TelegramRenderedMessageDeliveryRuntimeDeps<
|
|
|
630
732
|
text: string,
|
|
631
733
|
options?: { mode?: TelegramRenderMode },
|
|
632
734
|
) => TelegramRenderedChunk[];
|
|
633
|
-
sendRichMessage: (
|
|
735
|
+
sendRichMessage: (
|
|
736
|
+
body: TelegramSendRichMessageBody,
|
|
737
|
+
) => Promise<TelegramSentMessage>;
|
|
634
738
|
}
|
|
635
739
|
|
|
636
740
|
export function createTelegramRenderedMessageDeliveryRuntime<TReplyMarkup>(
|
|
637
741
|
deps: TelegramRenderedMessageDeliveryRuntimeDeps<TReplyMarkup>,
|
|
638
742
|
): TelegramRenderedMessageDeliveryRuntime<TReplyMarkup> {
|
|
639
743
|
const replyTransport = buildTelegramReplyTransport({
|
|
744
|
+
recordOwnership: deps.recordOwnership,
|
|
640
745
|
sendMessage: deps.sendMessage,
|
|
641
746
|
editMessage: deps.editMessage,
|
|
642
747
|
});
|
|
@@ -646,6 +751,7 @@ export function createTelegramRenderedMessageDeliveryRuntime<TReplyMarkup>(
|
|
|
646
751
|
renderTelegramMessage:
|
|
647
752
|
deps.renderTelegramMessage ?? renderTelegramMessage,
|
|
648
753
|
replyTransport,
|
|
754
|
+
recordOwnership: deps.recordOwnership,
|
|
649
755
|
sendRichMessage: deps.sendRichMessage,
|
|
650
756
|
}),
|
|
651
757
|
};
|
|
@@ -660,20 +766,36 @@ export function createTelegramRenderedMessageRuntime<TReplyMarkup>(
|
|
|
660
766
|
text,
|
|
661
767
|
{
|
|
662
768
|
renderTelegramMessage: deps.renderTelegramMessage,
|
|
663
|
-
sendRenderedChunks: (chunks) =>
|
|
769
|
+
sendRenderedChunks: (chunks, chunkOptions) =>
|
|
664
770
|
deps.replyTransport.sendRenderedChunks(chatId, chunks, {
|
|
665
|
-
|
|
771
|
+
target: chunkOptions?.target,
|
|
772
|
+
replyToMessageId:
|
|
773
|
+
chunkOptions?.replyToMessageId ?? replyToMessageId,
|
|
666
774
|
}),
|
|
667
775
|
},
|
|
668
776
|
options,
|
|
669
777
|
);
|
|
670
778
|
},
|
|
671
779
|
sendMarkdownReply: async (chatId, replyToMessageId, markdown, options) => {
|
|
780
|
+
if (typeof options?.target?.threadId === "number" && replyToMessageId && replyToMessageId > 0) {
|
|
781
|
+
return deps.replyTransport.sendRenderedChunks(
|
|
782
|
+
chatId,
|
|
783
|
+
deps.renderTelegramMessage(markdown, { mode: "markdown" }),
|
|
784
|
+
{
|
|
785
|
+
replyMarkup: options.replyMarkup,
|
|
786
|
+
target: options.target,
|
|
787
|
+
replyToMessageId,
|
|
788
|
+
},
|
|
789
|
+
);
|
|
790
|
+
}
|
|
672
791
|
return sendTelegramNativeMarkdownReply(
|
|
673
792
|
chatId,
|
|
674
793
|
replyToMessageId,
|
|
675
794
|
markdown,
|
|
676
|
-
{
|
|
795
|
+
{
|
|
796
|
+
recordOwnership: deps.recordOwnership,
|
|
797
|
+
sendRichMessage: deps.sendRichMessage,
|
|
798
|
+
},
|
|
677
799
|
options,
|
|
678
800
|
);
|
|
679
801
|
},
|
|
@@ -691,11 +813,21 @@ export function createTelegramRenderedMessageRuntime<TReplyMarkup>(
|
|
|
691
813
|
{ replyMarkup },
|
|
692
814
|
);
|
|
693
815
|
},
|
|
694
|
-
sendInteractiveMessage: async (
|
|
816
|
+
sendInteractiveMessage: async (
|
|
817
|
+
chatId,
|
|
818
|
+
text,
|
|
819
|
+
mode,
|
|
820
|
+
replyMarkup,
|
|
821
|
+
options,
|
|
822
|
+
) => {
|
|
695
823
|
return deps.replyTransport.sendRenderedChunks(
|
|
696
824
|
chatId,
|
|
697
825
|
deps.renderTelegramMessage(text, { mode }),
|
|
698
|
-
{
|
|
826
|
+
{
|
|
827
|
+
replyMarkup,
|
|
828
|
+
target: options?.target,
|
|
829
|
+
replyToMessageId: options?.replyToMessageId,
|
|
830
|
+
},
|
|
699
831
|
);
|
|
700
832
|
},
|
|
701
833
|
};
|
|
@@ -704,20 +836,20 @@ export function createTelegramRenderedMessageRuntime<TReplyMarkup>(
|
|
|
704
836
|
// --- Dedup-wrapped Reply Wrappers ---
|
|
705
837
|
|
|
706
838
|
/** Wrap a sendTextReply with reply dedup so only the first message
|
|
707
|
-
* in a turn carries
|
|
839
|
+
* in a turn carries reply metadata. */
|
|
708
840
|
export function dedupSendTextReply(
|
|
709
841
|
dedup: ReplyDedupRuntime,
|
|
710
842
|
inner: (
|
|
711
843
|
chatId: number,
|
|
712
844
|
replyToMessageId: number | undefined,
|
|
713
845
|
text: string,
|
|
714
|
-
options?:
|
|
846
|
+
options?: TelegramTextReplyOptions,
|
|
715
847
|
) => Promise<number | undefined>,
|
|
716
848
|
): (
|
|
717
849
|
chatId: number,
|
|
718
850
|
replyToMessageId: number,
|
|
719
851
|
text: string,
|
|
720
|
-
options?:
|
|
852
|
+
options?: TelegramTextReplyOptions,
|
|
721
853
|
) => Promise<number | undefined> {
|
|
722
854
|
return async (chatId, replyToMessageId, text, options) => {
|
|
723
855
|
const effectiveReplyTo = dedup.shouldReply(replyToMessageId)
|