@llblab/pi-telegram 0.2.10 → 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.
@@ -1,132 +0,0 @@
1
- /**
2
- * Regression tests for the Telegram attachments domain
3
- * Covers attachment queueing and attachment delivery behavior in one domain-level suite
4
- */
5
-
6
- import assert from "node:assert/strict";
7
- import test from "node:test";
8
-
9
- import {
10
- queueTelegramAttachments,
11
- sendQueuedTelegramAttachments,
12
- } from "../lib/attachments.ts";
13
-
14
- test("Attachment queueing adds files to the active Telegram turn", async () => {
15
- const activeTurn = {
16
- queuedAttachments: [],
17
- } as unknown as {
18
- queuedAttachments: Array<{ path: string; fileName: string }>;
19
- } & Parameters<typeof queueTelegramAttachments>[0]["activeTurn"];
20
- const result = await queueTelegramAttachments({
21
- activeTurn,
22
- paths: ["/tmp/demo.txt"],
23
- maxAttachmentsPerTurn: 2,
24
- statPath: async () => ({ isFile: () => true }),
25
- });
26
- assert.deepEqual(activeTurn.queuedAttachments, [
27
- { path: "/tmp/demo.txt", fileName: "demo.txt" },
28
- ]);
29
- assert.deepEqual(result.details.paths, ["/tmp/demo.txt"]);
30
- });
31
-
32
- test("Attachment queueing rejects missing turns, non-files, and full queues", async () => {
33
- await assert.rejects(
34
- () =>
35
- queueTelegramAttachments({
36
- activeTurn: undefined,
37
- paths: ["/tmp/demo.txt"],
38
- maxAttachmentsPerTurn: 1,
39
- statPath: async () => ({ isFile: () => true }),
40
- }),
41
- { message: /active Telegram turn/ },
42
- );
43
- await assert.rejects(
44
- () =>
45
- queueTelegramAttachments({
46
- activeTurn: { queuedAttachments: [] } as never,
47
- paths: ["/tmp/demo.txt"],
48
- maxAttachmentsPerTurn: 1,
49
- statPath: async () => ({ isFile: () => false }),
50
- }),
51
- { message: "Not a file: /tmp/demo.txt" },
52
- );
53
- await assert.rejects(
54
- () =>
55
- queueTelegramAttachments({
56
- activeTurn: {
57
- queuedAttachments: [{ path: "/tmp/a.txt", fileName: "a.txt" }],
58
- } as never,
59
- paths: ["/tmp/demo.txt"],
60
- maxAttachmentsPerTurn: 1,
61
- statPath: async () => ({ isFile: () => true }),
62
- }),
63
- { message: "Attachment limit reached (1)" },
64
- );
65
- });
66
-
67
- test("Attachment delivery chooses photo vs document methods from file paths", async () => {
68
- const sent: Array<string> = [];
69
- await sendQueuedTelegramAttachments(
70
- {
71
- kind: "prompt",
72
- chatId: 1,
73
- replyToMessageId: 2,
74
- sourceMessageIds: [],
75
- queueOrder: 1,
76
- queueLane: "default",
77
- laneOrder: 1,
78
- queuedAttachments: [
79
- { path: "/tmp/a.png", fileName: "a.png" },
80
- { path: "/tmp/b.txt", fileName: "b.txt" },
81
- ],
82
- content: [{ type: "text", text: "prompt" }],
83
- historyText: "history",
84
- statusSummary: "summary",
85
- },
86
- {
87
- sendMultipart: async (
88
- method,
89
- _fields,
90
- fileField,
91
- _filePath,
92
- fileName,
93
- ) => {
94
- sent.push(`${method}:${fileField}:${fileName}`);
95
- },
96
- sendTextReply: async () => undefined,
97
- },
98
- );
99
- assert.deepEqual(sent, [
100
- "sendPhoto:photo:a.png",
101
- "sendDocument:document:b.txt",
102
- ]);
103
- });
104
-
105
- test("Attachment delivery reports per-file failures via text replies", async () => {
106
- const replies: string[] = [];
107
- await sendQueuedTelegramAttachments(
108
- {
109
- kind: "prompt",
110
- chatId: 1,
111
- replyToMessageId: 2,
112
- sourceMessageIds: [],
113
- queueOrder: 1,
114
- queueLane: "default",
115
- laneOrder: 1,
116
- queuedAttachments: [{ path: "/tmp/a.png", fileName: "a.png" }],
117
- content: [{ type: "text", text: "prompt" }],
118
- historyText: "history",
119
- statusSummary: "summary",
120
- },
121
- {
122
- sendMultipart: async () => {
123
- throw new Error("upload failed");
124
- },
125
- sendTextReply: async (_chatId, _replyToMessageId, text) => {
126
- replies.push(text);
127
- return undefined;
128
- },
129
- },
130
- );
131
- assert.deepEqual(replies, ["Failed to send attachment a.png: upload failed"]);
132
- });
@@ -1,85 +0,0 @@
1
- /**
2
- * Regression tests for Telegram command helpers
3
- * Covers slash-command normalization, bot suffix stripping, arguments, and non-command input
4
- */
5
-
6
- import assert from "node:assert/strict";
7
- import test from "node:test";
8
-
9
- import {
10
- buildTelegramCommandAction,
11
- executeTelegramCommandAction,
12
- parseTelegramCommand,
13
- } from "../lib/commands.ts";
14
-
15
- test("Command helpers parse slash commands with args", () => {
16
- assert.deepEqual(parseTelegramCommand(" /Model@DemoBot claude opus "), {
17
- name: "model",
18
- args: "claude opus",
19
- });
20
- assert.deepEqual(parseTelegramCommand("/status"), {
21
- name: "status",
22
- args: "",
23
- });
24
- });
25
-
26
- test("Command helpers ignore non-command input and empty names", () => {
27
- assert.equal(parseTelegramCommand("hello /status"), undefined);
28
- assert.equal(parseTelegramCommand("/"), undefined);
29
- });
30
-
31
- test("Command helpers build command actions", () => {
32
- assert.deepEqual(buildTelegramCommandAction("stop"), { kind: "stop" });
33
- assert.deepEqual(buildTelegramCommandAction("compact"), { kind: "compact" });
34
- assert.deepEqual(buildTelegramCommandAction("status"), { kind: "status" });
35
- assert.deepEqual(buildTelegramCommandAction("model"), { kind: "model" });
36
- assert.deepEqual(buildTelegramCommandAction("help"), {
37
- kind: "help",
38
- commandName: "help",
39
- });
40
- assert.deepEqual(buildTelegramCommandAction("start"), {
41
- kind: "help",
42
- commandName: "start",
43
- });
44
- assert.deepEqual(buildTelegramCommandAction("unknown"), { kind: "ignore" });
45
- assert.deepEqual(buildTelegramCommandAction(undefined), { kind: "ignore" });
46
- });
47
-
48
- test("Command helpers execute command actions through provided handlers", async () => {
49
- const events: string[] = [];
50
- const deps = {
51
- handleStop: async () => {
52
- events.push("stop");
53
- },
54
- handleCompact: async () => {
55
- events.push("compact");
56
- },
57
- handleStatus: async () => {
58
- events.push("status");
59
- },
60
- handleModel: async () => {
61
- events.push("model");
62
- },
63
- handleHelp: async (_message: unknown, commandName: "help" | "start") => {
64
- events.push(`help:${commandName}`);
65
- },
66
- };
67
- assert.equal(
68
- await executeTelegramCommandAction({ kind: "ignore" }, {}, {}, deps),
69
- false,
70
- );
71
- assert.equal(
72
- await executeTelegramCommandAction({ kind: "stop" }, {}, {}, deps),
73
- true,
74
- );
75
- assert.equal(
76
- await executeTelegramCommandAction(
77
- { kind: "help", commandName: "start" },
78
- {},
79
- {},
80
- deps,
81
- ),
82
- true,
83
- );
84
- assert.deepEqual(events, ["stop", "help:start"]);
85
- });
@@ -1,80 +0,0 @@
1
- /**
2
- * Regression tests for Telegram setup prompt defaults
3
- * Covers token-prefill priority across stored config, environment variables, and placeholder fallback
4
- */
5
-
6
- import assert from "node:assert/strict";
7
- import test from "node:test";
8
-
9
- import { __telegramTestUtils } from "../index.ts";
10
-
11
- test("Bot token input prefers stored config over env vars", () => {
12
- const value = __telegramTestUtils.getTelegramBotTokenInputDefault(
13
- {
14
- TELEGRAM_KEY: "key-last",
15
- TELEGRAM_TOKEN: "token-third",
16
- TELEGRAM_BOT_KEY: "key-second",
17
- TELEGRAM_BOT_TOKEN: "token-first",
18
- },
19
- "stored-token",
20
- );
21
- assert.equal(value, "stored-token");
22
- });
23
-
24
- test("Bot token input prefers the first configured Telegram env var when no config exists", () => {
25
- const value = __telegramTestUtils.getTelegramBotTokenInputDefault({
26
- TELEGRAM_KEY: "key-last",
27
- TELEGRAM_TOKEN: "token-third",
28
- TELEGRAM_BOT_KEY: "key-second",
29
- TELEGRAM_BOT_TOKEN: "token-first",
30
- });
31
- assert.equal(value, "token-first");
32
- });
33
-
34
- test("Bot token prompt uses the editor when a real prefill exists", () => {
35
- const prompt = __telegramTestUtils.getTelegramBotTokenPromptSpec({
36
- TELEGRAM_BOT_TOKEN: "token-first",
37
- });
38
- assert.deepEqual(prompt, {
39
- method: "editor",
40
- value: "token-first",
41
- });
42
- });
43
-
44
- test("Bot token prompt shows stored config before env values", () => {
45
- const prompt = __telegramTestUtils.getTelegramBotTokenPromptSpec(
46
- {
47
- TELEGRAM_BOT_TOKEN: "token-first",
48
- },
49
- "stored-token",
50
- );
51
- assert.deepEqual(prompt, {
52
- method: "editor",
53
- value: "stored-token",
54
- });
55
- });
56
-
57
- test("Bot token input skips blank env vars and falls back to config", () => {
58
- const value = __telegramTestUtils.getTelegramBotTokenInputDefault(
59
- {
60
- TELEGRAM_BOT_TOKEN: " ",
61
- TELEGRAM_BOT_KEY: "",
62
- TELEGRAM_TOKEN: " ",
63
- },
64
- "stored-token",
65
- );
66
- assert.equal(value, "stored-token");
67
- });
68
-
69
- test("Bot token input falls back to placeholder when no value exists", () => {
70
- const value = __telegramTestUtils.getTelegramBotTokenInputDefault({});
71
- assert.equal(value, "123456:ABCDEF...");
72
- });
73
-
74
- test("Bot token prompt uses placeholder input when no prefill exists", () => {
75
- const prompt = __telegramTestUtils.getTelegramBotTokenPromptSpec({});
76
- assert.deepEqual(prompt, {
77
- method: "input",
78
- value: "123456:ABCDEF...",
79
- });
80
- });
@@ -1,166 +0,0 @@
1
- /**
2
- * Regression tests for Telegram media and text extraction helpers
3
- * Covers inbound file-info collection, text extraction, media groups, id collection, and history formatting
4
- */
5
-
6
- import assert from "node:assert/strict";
7
- import test from "node:test";
8
-
9
- import {
10
- collectTelegramFileInfos,
11
- collectTelegramMessageIds,
12
- extractFirstTelegramMessageText,
13
- extractTelegramMessagesText,
14
- formatTelegramHistoryText,
15
- getTelegramMediaGroupKey,
16
- guessMediaType,
17
- queueTelegramMediaGroupMessage,
18
- removePendingTelegramMediaGroupMessages,
19
- type TelegramMediaGroupState,
20
- } from "../lib/media.ts";
21
-
22
- test("Media helpers collect file infos across Telegram message variants", () => {
23
- const files = collectTelegramFileInfos([
24
- {
25
- message_id: 1,
26
- text: "hello",
27
- photo: [
28
- { file_id: "small", file_size: 1 },
29
- { file_id: "large", file_size: 10 },
30
- ],
31
- document: {
32
- file_id: "doc",
33
- file_name: "report.png",
34
- mime_type: "image/png",
35
- },
36
- voice: {
37
- file_id: "voice",
38
- mime_type: "audio/ogg",
39
- },
40
- sticker: {
41
- file_id: "sticker",
42
- },
43
- },
44
- ]);
45
- assert.deepEqual(
46
- files.map((file) => ({
47
- id: file.file_id,
48
- name: file.fileName,
49
- image: file.isImage,
50
- })),
51
- [
52
- { id: "large", name: "photo-1.jpg", image: true },
53
- { id: "doc", name: "report.png", image: true },
54
- { id: "voice", name: "voice-1.ogg", image: false },
55
- { id: "sticker", name: "sticker-1.webp", image: true },
56
- ],
57
- );
58
- });
59
-
60
- test("Media helpers extract text, ids, and history summaries", () => {
61
- const messages = [
62
- { message_id: 1, text: "first" },
63
- { message_id: 2, caption: "second" },
64
- { message_id: 2, text: "duplicate id" },
65
- ];
66
- assert.equal(
67
- extractTelegramMessagesText(messages),
68
- "first\n\nsecond\n\nduplicate id",
69
- );
70
- assert.equal(extractFirstTelegramMessageText(messages), "first");
71
- assert.deepEqual(collectTelegramMessageIds(messages), [1, 2]);
72
- assert.equal(
73
- formatTelegramHistoryText("hello", [{ path: "/tmp/demo.txt" }]),
74
- "hello\nAttachments:\n- /tmp/demo.txt",
75
- );
76
- });
77
-
78
- test("Media helpers infer outgoing image media types from file paths", () => {
79
- assert.equal(guessMediaType("/tmp/demo.png"), "image/png");
80
- assert.equal(guessMediaType("/tmp/demo.txt"), undefined);
81
- });
82
-
83
- test("Media helpers key messages by chat and media group", () => {
84
- assert.equal(
85
- getTelegramMediaGroupKey({
86
- message_id: 1,
87
- chat: { id: 7 },
88
- media_group_id: "album",
89
- }),
90
- "7:album",
91
- );
92
- assert.equal(
93
- getTelegramMediaGroupKey({ message_id: 1, chat: { id: 7 } }),
94
- undefined,
95
- );
96
- });
97
-
98
- test("Media helpers replace debounce timers and dispatch grouped messages", () => {
99
- const groups = new Map<
100
- string,
101
- TelegramMediaGroupState<{
102
- message_id: number;
103
- chat: { id: number };
104
- media_group_id?: string;
105
- }>
106
- >();
107
- const cleared: number[] = [];
108
- const callbacks: Array<() => void> = [];
109
- const dispatched: number[][] = [];
110
- let nextTimer = 1;
111
- const setTimer = (callback: () => void): ReturnType<typeof setTimeout> => {
112
- callbacks.push(callback);
113
- return nextTimer++ as unknown as ReturnType<typeof setTimeout>;
114
- };
115
- const clearTimer = (timer: ReturnType<typeof setTimeout>): void => {
116
- cleared.push(timer as unknown as number);
117
- };
118
- assert.equal(
119
- queueTelegramMediaGroupMessage({
120
- message: { message_id: 1, chat: { id: 7 }, media_group_id: "album" },
121
- groups,
122
- debounceMs: 100,
123
- setTimer,
124
- clearTimer,
125
- dispatchMessages: (messages) =>
126
- dispatched.push(messages.map((message) => message.message_id)),
127
- }),
128
- true,
129
- );
130
- queueTelegramMediaGroupMessage({
131
- message: { message_id: 2, chat: { id: 7 }, media_group_id: "album" },
132
- groups,
133
- debounceMs: 100,
134
- setTimer,
135
- clearTimer,
136
- dispatchMessages: (messages) =>
137
- dispatched.push(messages.map((message) => message.message_id)),
138
- });
139
- assert.deepEqual(cleared, [1]);
140
- callbacks.at(-1)?.();
141
- assert.deepEqual(dispatched, [[1, 2]]);
142
- assert.equal(groups.size, 0);
143
- });
144
-
145
- test("Media helpers remove pending groups by message id", () => {
146
- const groups = new Map<
147
- string,
148
- TelegramMediaGroupState<{ message_id: number; chat: { id: number } }>
149
- >();
150
- groups.set("7:album", {
151
- messages: [
152
- { message_id: 1, chat: { id: 7 } },
153
- { message_id: 2, chat: { id: 7 } },
154
- ],
155
- flushTimer: 10 as unknown as ReturnType<typeof setTimeout>,
156
- });
157
- const cleared: number[] = [];
158
- assert.equal(
159
- removePendingTelegramMediaGroupMessages(groups, [2], (timer) => {
160
- cleared.push(timer as unknown as number);
161
- }),
162
- 1,
163
- );
164
- assert.deepEqual(cleared, [10]);
165
- assert.equal(groups.size, 0);
166
- });