@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/README.md +40 -26
- package/docs/architecture.md +62 -35
- package/index.ts +388 -1936
- package/lib/api.ts +647 -76
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +721 -0
- package/lib/config.ts +157 -0
- package/lib/media.ts +211 -36
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +80 -0
- package/lib/polling.ts +264 -18
- package/lib/preview.ts +451 -29
- package/lib/queue.ts +1134 -110
- package/lib/registration.ts +127 -28
- package/lib/rendering.ts +575 -281
- package/lib/replies.ts +198 -8
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +129 -1
- package/lib/status.ts +428 -13
- package/lib/turns.ts +207 -17
- package/lib/updates.ts +392 -99
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -23
- package/lib/model-switch.ts +0 -62
- package/tests/api.test.ts +0 -89
- package/tests/attachments.test.ts +0 -132
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -77
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -129
- package/tests/preview.test.ts +0 -441
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -475
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -132
- package/tests/updates.test.ts +0 -357
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 {
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
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<
|
|
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,
|
|
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
|
+
}
|