@llblab/pi-telegram 0.2.9 → 0.3.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/lib/replies.ts CHANGED
@@ -3,10 +3,75 @@
3
3
  * Owns rendered-message delivery, reply transport wiring, and plain or markdown final replies
4
4
  */
5
5
 
6
- import type { TelegramRenderedChunk, TelegramRenderMode } from "./rendering.ts";
6
+ import type { TelegramReplyParameters, TelegramSentMessage } from "./api.ts";
7
+ import {
8
+ renderTelegramMessage,
9
+ type TelegramRenderedChunk,
10
+ type TelegramRenderMode,
11
+ } from "./rendering.ts";
7
12
 
8
- export interface TelegramSentMessageLike {
9
- message_id: number;
13
+ export function buildTelegramReplyParameters(
14
+ messageId: number | undefined,
15
+ ): TelegramReplyParameters | undefined {
16
+ if (messageId === undefined) return undefined;
17
+ return { message_id: messageId, allow_sending_without_reply: true };
18
+ }
19
+
20
+ export function buildTelegramMultipartReplyParameters(
21
+ messageId: number | undefined,
22
+ ): string | undefined {
23
+ const parameters = buildTelegramReplyParameters(messageId);
24
+ return parameters ? JSON.stringify(parameters) : undefined;
25
+ }
26
+
27
+ function getAgentMessageField(message: unknown, field: string): unknown {
28
+ if (typeof message !== "object" || message === null || !(field in message)) {
29
+ return undefined;
30
+ }
31
+ return Reflect.get(message, field);
32
+ }
33
+
34
+ export function isAssistantAgentMessage(message: unknown): boolean {
35
+ return getAgentMessageField(message, "role") === "assistant";
36
+ }
37
+
38
+ function extractAgentTextContent(content: unknown): string {
39
+ const blocks = Array.isArray(content) ? content : [];
40
+ return blocks
41
+ .filter(
42
+ (block): block is { type: string; text?: string } =>
43
+ typeof block === "object" && block !== null && "type" in block,
44
+ )
45
+ .filter((block) => block.type === "text" && typeof block.text === "string")
46
+ .map((block) => block.text as string)
47
+ .join("")
48
+ .trim();
49
+ }
50
+
51
+ export function getAgentMessageText(message: unknown): string {
52
+ return extractAgentTextContent(getAgentMessageField(message, "content"));
53
+ }
54
+
55
+ export function extractLatestAssistantMessageText(
56
+ messages: readonly unknown[],
57
+ ): {
58
+ text?: string;
59
+ stopReason?: string;
60
+ errorMessage?: string;
61
+ } {
62
+ for (let i = messages.length - 1; i >= 0; i--) {
63
+ const message = messages[i];
64
+ if (!message || !isAssistantAgentMessage(message)) continue;
65
+ const rawStopReason = getAgentMessageField(message, "stopReason");
66
+ const rawErrorMessage = getAgentMessageField(message, "errorMessage");
67
+ const stopReason =
68
+ typeof rawStopReason === "string" ? rawStopReason : undefined;
69
+ const errorMessage =
70
+ typeof rawErrorMessage === "string" ? rawErrorMessage : undefined;
71
+ const text = getAgentMessageText(message);
72
+ return { text: text || undefined, stopReason, errorMessage };
73
+ }
74
+ return {};
10
75
  }
11
76
 
12
77
  export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
@@ -15,21 +80,22 @@ export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
15
80
  text: string;
16
81
  parse_mode?: "HTML";
17
82
  reply_markup?: TReplyMarkup;
18
- }) => Promise<TelegramSentMessageLike>;
83
+ reply_parameters?: TelegramReplyParameters;
84
+ }) => Promise<TelegramSentMessage>;
19
85
  editMessage: (body: {
20
86
  chat_id: number;
21
87
  message_id: number;
22
88
  text: string;
23
89
  parse_mode?: "HTML";
24
90
  reply_markup?: TReplyMarkup;
25
- }) => Promise<void>;
91
+ }) => Promise<unknown>;
26
92
  }
27
93
 
28
94
  export interface TelegramReplyTransport<TReplyMarkup> {
29
95
  sendRenderedChunks: (
30
96
  chatId: number,
31
97
  chunks: TelegramRenderedChunk[],
32
- options?: { replyMarkup?: TReplyMarkup },
98
+ options?: { replyMarkup?: TReplyMarkup; replyToMessageId?: number },
33
99
  ) => Promise<number | undefined>;
34
100
  editRenderedMessage: (
35
101
  chatId: number,
@@ -62,16 +128,21 @@ export async function sendTelegramRenderedChunks<TReplyMarkup>(
62
128
  chatId: number,
63
129
  chunks: TelegramRenderedChunk[],
64
130
  deps: TelegramReplyDeliveryDeps<TReplyMarkup>,
65
- options?: { replyMarkup?: TReplyMarkup },
131
+ options?: { replyMarkup?: TReplyMarkup; replyToMessageId?: number },
66
132
  ): Promise<number | undefined> {
67
133
  let lastMessageId: number | undefined;
68
134
  for (const [index, chunk] of chunks.entries()) {
135
+ const replyParameters =
136
+ index === 0
137
+ ? buildTelegramReplyParameters(options?.replyToMessageId)
138
+ : undefined;
69
139
  const sent = await deps.sendMessage({
70
140
  chat_id: chatId,
71
141
  text: chunk.text,
72
142
  parse_mode: chunk.parseMode,
73
143
  reply_markup:
74
144
  index === chunks.length - 1 ? options?.replyMarkup : undefined,
145
+ ...(replyParameters ? { reply_parameters: replyParameters } : {}),
75
146
  });
76
147
  lastMessageId = sent.message_id;
77
148
  }
@@ -96,7 +167,9 @@ export async function editTelegramRenderedMessage<TReplyMarkup>(
96
167
  remainingChunks.length === 0 ? options?.replyMarkup : undefined,
97
168
  });
98
169
  if (remainingChunks.length > 0) {
99
- return sendTelegramRenderedChunks(chatId, remainingChunks, deps, options);
170
+ return sendTelegramRenderedChunks(chatId, remainingChunks, deps, {
171
+ replyMarkup: options?.replyMarkup,
172
+ });
100
173
  }
101
174
  return messageId;
102
175
  }
@@ -132,3 +205,120 @@ export async function sendTelegramMarkdownReply(
132
205
  }
133
206
  return deps.sendRenderedChunks(chunks);
134
207
  }
208
+
209
+ export interface TelegramRenderedMessageRuntimeDeps<TReplyMarkup> {
210
+ renderTelegramMessage: (
211
+ text: string,
212
+ options?: { mode?: TelegramRenderMode },
213
+ ) => TelegramRenderedChunk[];
214
+ replyTransport: TelegramReplyTransport<TReplyMarkup>;
215
+ }
216
+
217
+ export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
218
+ sendTextReply: (
219
+ chatId: number,
220
+ replyToMessageId: number,
221
+ text: string,
222
+ options?: { parseMode?: "HTML" },
223
+ ) => Promise<number | undefined>;
224
+ sendMarkdownReply: (
225
+ chatId: number,
226
+ replyToMessageId: number,
227
+ markdown: string,
228
+ ) => Promise<number | undefined>;
229
+ editInteractiveMessage: (
230
+ chatId: number,
231
+ messageId: number,
232
+ text: string,
233
+ mode: TelegramRenderMode,
234
+ replyMarkup: TReplyMarkup,
235
+ ) => Promise<void>;
236
+ sendInteractiveMessage: (
237
+ chatId: number,
238
+ text: string,
239
+ mode: TelegramRenderMode,
240
+ replyMarkup: TReplyMarkup,
241
+ ) => Promise<number | undefined>;
242
+ }
243
+
244
+ export interface TelegramRenderedMessageDeliveryRuntime<
245
+ TReplyMarkup,
246
+ > extends TelegramRenderedMessageRuntime<TReplyMarkup> {
247
+ replyTransport: TelegramReplyTransport<TReplyMarkup>;
248
+ }
249
+
250
+ export interface TelegramRenderedMessageDeliveryRuntimeDeps<
251
+ TReplyMarkup,
252
+ > extends TelegramReplyDeliveryDeps<TReplyMarkup> {
253
+ renderTelegramMessage?: (
254
+ text: string,
255
+ options?: { mode?: TelegramRenderMode },
256
+ ) => TelegramRenderedChunk[];
257
+ }
258
+
259
+ export function createTelegramRenderedMessageDeliveryRuntime<TReplyMarkup>(
260
+ deps: TelegramRenderedMessageDeliveryRuntimeDeps<TReplyMarkup>,
261
+ ): TelegramRenderedMessageDeliveryRuntime<TReplyMarkup> {
262
+ const replyTransport = buildTelegramReplyTransport({
263
+ sendMessage: deps.sendMessage,
264
+ editMessage: deps.editMessage,
265
+ });
266
+ return {
267
+ replyTransport,
268
+ ...createTelegramRenderedMessageRuntime({
269
+ renderTelegramMessage:
270
+ deps.renderTelegramMessage ?? renderTelegramMessage,
271
+ replyTransport,
272
+ }),
273
+ };
274
+ }
275
+
276
+ export function createTelegramRenderedMessageRuntime<TReplyMarkup>(
277
+ deps: TelegramRenderedMessageRuntimeDeps<TReplyMarkup>,
278
+ ): TelegramRenderedMessageRuntime<TReplyMarkup> {
279
+ return {
280
+ sendTextReply: async (chatId, replyToMessageId, text, options) => {
281
+ return sendTelegramPlainReply(
282
+ text,
283
+ {
284
+ renderTelegramMessage: deps.renderTelegramMessage,
285
+ sendRenderedChunks: (chunks) =>
286
+ deps.replyTransport.sendRenderedChunks(chatId, chunks, {
287
+ replyToMessageId,
288
+ }),
289
+ },
290
+ options,
291
+ );
292
+ },
293
+ sendMarkdownReply: async (chatId, replyToMessageId, markdown) => {
294
+ return sendTelegramMarkdownReply(markdown, {
295
+ renderTelegramMessage: deps.renderTelegramMessage,
296
+ sendRenderedChunks: (chunks) =>
297
+ deps.replyTransport.sendRenderedChunks(chatId, chunks, {
298
+ replyToMessageId,
299
+ }),
300
+ });
301
+ },
302
+ editInteractiveMessage: async (
303
+ chatId,
304
+ messageId,
305
+ text,
306
+ mode,
307
+ replyMarkup,
308
+ ) => {
309
+ await deps.replyTransport.editRenderedMessage(
310
+ chatId,
311
+ messageId,
312
+ deps.renderTelegramMessage(text, { mode }),
313
+ { replyMarkup },
314
+ );
315
+ },
316
+ sendInteractiveMessage: async (chatId, text, mode, replyMarkup) => {
317
+ return deps.replyTransport.sendRenderedChunks(
318
+ chatId,
319
+ deps.renderTelegramMessage(text, { mode }),
320
+ { replyMarkup },
321
+ );
322
+ },
323
+ };
324
+ }