@llblab/pi-telegram 0.7.2 → 0.8.1
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 +5 -5
- package/CHANGELOG.md +25 -1
- package/README.md +40 -10
- package/docs/README.md +2 -2
- package/docs/architecture.md +6 -6
- package/docs/command-templates.md +21 -5
- package/docs/inbound-handlers.md +93 -0
- package/docs/locks.md +13 -11
- package/docs/outbound-handlers.md +41 -5
- package/index.ts +42 -31
- package/lib/config.ts +9 -3
- package/lib/inbound-handlers.ts +588 -0
- package/lib/menu-queue.ts +161 -20
- package/lib/menu-status.ts +1 -1
- package/lib/{attachments.ts → outbound-attachments.ts} +34 -34
- package/lib/outbound-handlers.ts +247 -2
- package/lib/prompts.ts +1 -1
- package/lib/queue.ts +18 -4
- package/lib/routing.ts +3 -3
- package/lib/updates.ts +34 -21
- package/package.json +1 -1
- package/docs/attachment-handlers.md +0 -50
- package/lib/attachment-handlers.ts +0 -423
package/lib/menu-queue.ts
CHANGED
|
@@ -11,11 +11,14 @@ import * as Queue from "./queue.ts";
|
|
|
11
11
|
|
|
12
12
|
// --- Queue Menu ---
|
|
13
13
|
|
|
14
|
+
const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
|
|
15
|
+
const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n… [truncated]";
|
|
14
16
|
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
15
17
|
interface TelegramQueueMenuItem {
|
|
16
18
|
chatId: number;
|
|
17
19
|
replyToMessageId: number;
|
|
18
20
|
isPriority: boolean;
|
|
21
|
+
priorityEmoji?: string;
|
|
19
22
|
hasAttachments: boolean;
|
|
20
23
|
statusSummary: string;
|
|
21
24
|
promptText: string;
|
|
@@ -40,6 +43,7 @@ function toTelegramQueueMenuItems<Context>(
|
|
|
40
43
|
chatId: item.chatId,
|
|
41
44
|
replyToMessageId: item.replyToMessageId,
|
|
42
45
|
isPriority: item.queueLane === "priority",
|
|
46
|
+
priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
|
|
43
47
|
hasAttachments:
|
|
44
48
|
item.kind === "prompt" && item.queuedAttachments.length > 0,
|
|
45
49
|
statusSummary: item.statusSummary,
|
|
@@ -53,7 +57,11 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
53
57
|
const backRow = [{ text: "⬆️ Main menu", callback_data: "menu:back" }];
|
|
54
58
|
if (items.length === 0) return { inline_keyboard: [backRow] };
|
|
55
59
|
const rows = items.map(function buildTelegramQueueMenuRow(item, index) {
|
|
56
|
-
const prefix = item.isPriority
|
|
60
|
+
const prefix = item.isPriority
|
|
61
|
+
? `${item.priorityEmoji ?? "⚡"} `
|
|
62
|
+
: item.hasAttachments
|
|
63
|
+
? "📎 "
|
|
64
|
+
: "";
|
|
57
65
|
const label = `${index + 1}. ${prefix}${item.statusSummary}`;
|
|
58
66
|
return [
|
|
59
67
|
{
|
|
@@ -82,21 +90,44 @@ function findTelegramQueueMenuItem(
|
|
|
82
90
|
return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
|
|
83
91
|
});
|
|
84
92
|
}
|
|
93
|
+
function escapeTelegramQueueMenuHtmlChar(char: string): string {
|
|
94
|
+
if (char === "&") return "&";
|
|
95
|
+
if (char === "<") return "<";
|
|
96
|
+
if (char === ">") return ">";
|
|
97
|
+
return char;
|
|
98
|
+
}
|
|
85
99
|
function escapeTelegramQueueMenuHtml(text: string): string {
|
|
86
|
-
return text
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
return Array.from(text).map(escapeTelegramQueueMenuHtmlChar).join("");
|
|
101
|
+
}
|
|
102
|
+
function escapeTelegramQueueMenuHtmlPreview(text: string): string {
|
|
103
|
+
const suffix = escapeTelegramQueueMenuHtml(QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX);
|
|
104
|
+
let escaped = "";
|
|
105
|
+
let truncated = false;
|
|
106
|
+
for (const char of text) {
|
|
107
|
+
const next = escapeTelegramQueueMenuHtmlChar(char);
|
|
108
|
+
if (
|
|
109
|
+
escaped.length + next.length + suffix.length >
|
|
110
|
+
QUEUE_ITEM_PROMPT_HTML_LIMIT
|
|
111
|
+
) {
|
|
112
|
+
truncated = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
escaped += next;
|
|
116
|
+
}
|
|
117
|
+
return truncated ? escaped + suffix : escaped;
|
|
90
118
|
}
|
|
91
119
|
function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
92
|
-
return
|
|
120
|
+
return `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
93
121
|
}
|
|
94
122
|
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
95
123
|
chatId: number,
|
|
96
124
|
replyToMessageId: number,
|
|
97
125
|
isPriority: boolean,
|
|
126
|
+
priorityEmoji?: string,
|
|
98
127
|
): TelegramQueueMenuReplyMarkup {
|
|
99
|
-
const priorityLabel = isPriority
|
|
128
|
+
const priorityLabel = isPriority
|
|
129
|
+
? `🐢 Deprioritize ${priorityEmoji ?? "⚡"}`
|
|
130
|
+
: "⚡ Prioritize";
|
|
100
131
|
return {
|
|
101
132
|
inline_keyboard: [
|
|
102
133
|
[{ text: "⬆️ Back", callback_data: "queue:list" }],
|
|
@@ -108,8 +139,29 @@ function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
|
108
139
|
],
|
|
109
140
|
[
|
|
110
141
|
{
|
|
111
|
-
text: "
|
|
112
|
-
callback_data: `queue:
|
|
142
|
+
text: "🗑 Delete",
|
|
143
|
+
callback_data: `queue:delete:${chatId}:${replyToMessageId}`,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildTelegramQueueDeleteConfirmationReplyMarkup(
|
|
150
|
+
chatId: number,
|
|
151
|
+
replyToMessageId: number,
|
|
152
|
+
): TelegramQueueMenuReplyMarkup {
|
|
153
|
+
return {
|
|
154
|
+
inline_keyboard: [
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
text: "🗑 Yes, delete",
|
|
158
|
+
callback_data: `queue:confirm-delete:${chatId}:${replyToMessageId}`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
{
|
|
163
|
+
text: "❌ No",
|
|
164
|
+
callback_data: `queue:keep:${chatId}:${replyToMessageId}`,
|
|
113
165
|
},
|
|
114
166
|
],
|
|
115
167
|
],
|
|
@@ -186,14 +238,38 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
186
238
|
);
|
|
187
239
|
return true;
|
|
188
240
|
}
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
await
|
|
241
|
+
const deleteMatch = data.match(/^queue:(?:delete|cancel):(\d+):(\d+)$/);
|
|
242
|
+
if (deleteMatch) {
|
|
243
|
+
await handleTelegramQueueMenuDeleteRequest(
|
|
192
244
|
callbackQueryId,
|
|
193
245
|
replyChatId,
|
|
194
246
|
replyMessageId,
|
|
195
|
-
Number(
|
|
196
|
-
Number(
|
|
247
|
+
Number(deleteMatch[1]),
|
|
248
|
+
Number(deleteMatch[2]),
|
|
249
|
+
deps,
|
|
250
|
+
);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
const keepMatch = data.match(/^queue:keep:(\d+):(\d+)$/);
|
|
254
|
+
if (keepMatch) {
|
|
255
|
+
await handleTelegramQueueMenuKeep(
|
|
256
|
+
callbackQueryId,
|
|
257
|
+
replyChatId,
|
|
258
|
+
replyMessageId,
|
|
259
|
+
Number(keepMatch[1]),
|
|
260
|
+
Number(keepMatch[2]),
|
|
261
|
+
deps,
|
|
262
|
+
);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
const confirmDeleteMatch = data.match(/^queue:confirm-delete:(\d+):(\d+)$/);
|
|
266
|
+
if (confirmDeleteMatch) {
|
|
267
|
+
await handleTelegramQueueMenuConfirmDelete(
|
|
268
|
+
callbackQueryId,
|
|
269
|
+
replyChatId,
|
|
270
|
+
replyMessageId,
|
|
271
|
+
Number(confirmDeleteMatch[1]),
|
|
272
|
+
Number(confirmDeleteMatch[2]),
|
|
197
273
|
ctx,
|
|
198
274
|
deps,
|
|
199
275
|
);
|
|
@@ -204,8 +280,8 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
204
280
|
function getTelegramQueueMenuListText(
|
|
205
281
|
items: readonly TelegramQueueMenuItem[],
|
|
206
282
|
): string {
|
|
207
|
-
if (items.length === 0) return "<b
|
|
208
|
-
return "<b
|
|
283
|
+
if (items.length === 0) return "<b>⏳ Queue is empty.</b>";
|
|
284
|
+
return "<b>⏳ Queue:</b>";
|
|
209
285
|
}
|
|
210
286
|
async function updateTelegramQueueMenuList<Context>(
|
|
211
287
|
callbackQueryId: string,
|
|
@@ -258,7 +334,12 @@ async function handleTelegramQueueMenuPick<Context>(
|
|
|
258
334
|
replyChatId,
|
|
259
335
|
replyMessageId,
|
|
260
336
|
getTelegramQueueMenuItemText(item),
|
|
261
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
337
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
338
|
+
chatId,
|
|
339
|
+
msgId,
|
|
340
|
+
item.isPriority,
|
|
341
|
+
item.priorityEmoji,
|
|
342
|
+
),
|
|
262
343
|
);
|
|
263
344
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
264
345
|
}
|
|
@@ -288,14 +369,74 @@ async function handleTelegramQueueMenuPriority<Context>(
|
|
|
288
369
|
replyChatId,
|
|
289
370
|
replyMessageId,
|
|
290
371
|
getTelegramQueueMenuItemText(item),
|
|
291
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
372
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
373
|
+
chatId,
|
|
374
|
+
msgId,
|
|
375
|
+
newPriority,
|
|
376
|
+
updated?.priorityEmoji ?? item.priorityEmoji,
|
|
377
|
+
),
|
|
292
378
|
);
|
|
293
379
|
await deps.answerCallbackQuery(
|
|
294
380
|
callbackQueryId,
|
|
295
381
|
newPriority ? "Prioritized." : "Deprioritized.",
|
|
296
382
|
);
|
|
297
383
|
}
|
|
298
|
-
async function
|
|
384
|
+
async function handleTelegramQueueMenuDeleteRequest<Context>(
|
|
385
|
+
callbackQueryId: string,
|
|
386
|
+
replyChatId: number,
|
|
387
|
+
replyMessageId: number,
|
|
388
|
+
chatId: number,
|
|
389
|
+
msgId: number,
|
|
390
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
391
|
+
): Promise<void> {
|
|
392
|
+
const item = deps.findItem(chatId, msgId);
|
|
393
|
+
if (!item) {
|
|
394
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
395
|
+
callbackQueryId,
|
|
396
|
+
replyChatId,
|
|
397
|
+
replyMessageId,
|
|
398
|
+
deps,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
await deps.updateQueueMessage(
|
|
402
|
+
replyChatId,
|
|
403
|
+
replyMessageId,
|
|
404
|
+
"<b>Delete this queued prompt?</b>",
|
|
405
|
+
buildTelegramQueueDeleteConfirmationReplyMarkup(chatId, msgId),
|
|
406
|
+
);
|
|
407
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
408
|
+
}
|
|
409
|
+
async function handleTelegramQueueMenuKeep<Context>(
|
|
410
|
+
callbackQueryId: string,
|
|
411
|
+
replyChatId: number,
|
|
412
|
+
replyMessageId: number,
|
|
413
|
+
chatId: number,
|
|
414
|
+
msgId: number,
|
|
415
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
416
|
+
): Promise<void> {
|
|
417
|
+
const item = deps.findItem(chatId, msgId);
|
|
418
|
+
if (!item) {
|
|
419
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
420
|
+
callbackQueryId,
|
|
421
|
+
replyChatId,
|
|
422
|
+
replyMessageId,
|
|
423
|
+
deps,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
await deps.updateQueueMessage(
|
|
427
|
+
replyChatId,
|
|
428
|
+
replyMessageId,
|
|
429
|
+
getTelegramQueueMenuItemText(item),
|
|
430
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
431
|
+
chatId,
|
|
432
|
+
msgId,
|
|
433
|
+
item.isPriority,
|
|
434
|
+
item.priorityEmoji,
|
|
435
|
+
),
|
|
436
|
+
);
|
|
437
|
+
await deps.answerCallbackQuery(callbackQueryId, "Kept in queue.");
|
|
438
|
+
}
|
|
439
|
+
async function handleTelegramQueueMenuConfirmDelete<Context>(
|
|
299
440
|
callbackQueryId: string,
|
|
300
441
|
replyChatId: number,
|
|
301
442
|
replyMessageId: number,
|
|
@@ -311,7 +452,7 @@ async function handleTelegramQueueMenuCancel<Context>(
|
|
|
311
452
|
replyChatId,
|
|
312
453
|
replyMessageId,
|
|
313
454
|
deps,
|
|
314
|
-
removed ? "
|
|
455
|
+
removed ? "Deleted from queue." : "Item not found.",
|
|
315
456
|
);
|
|
316
457
|
}
|
|
317
458
|
|
package/lib/menu-status.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram attachment
|
|
2
|
+
* Telegram outbound attachment helpers
|
|
3
3
|
* Zones: telegram outbound, pi agent tool, filesystem
|
|
4
|
-
* Owns telegram_attach registration, attachment queueing, and
|
|
4
|
+
* Owns telegram_attach registration, outbound attachment queueing, and delivery so Telegram file output stays in one domain module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { stat } from "node:fs/promises";
|
|
@@ -16,7 +16,7 @@ const MAX_ATTACHMENTS_PER_TURN = 10;
|
|
|
16
16
|
|
|
17
17
|
export const TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
18
18
|
|
|
19
|
-
export function
|
|
19
|
+
export function getTelegramOutboundAttachmentByteLimitFromEnv(
|
|
20
20
|
env: NodeJS.ProcessEnv,
|
|
21
21
|
names: string[],
|
|
22
22
|
defaultValue = TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES,
|
|
@@ -31,17 +31,17 @@ export function getTelegramAttachmentByteLimitFromEnv(
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES =
|
|
34
|
-
|
|
34
|
+
getTelegramOutboundAttachmentByteLimitFromEnv(process.env, [
|
|
35
35
|
"PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES",
|
|
36
36
|
"TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES",
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
|
-
export interface
|
|
39
|
+
export interface TelegramOutboundAttachmentToolResult {
|
|
40
40
|
content: Array<{ type: "text"; text: string }>;
|
|
41
41
|
details: { paths: string[] };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export interface
|
|
44
|
+
export interface TelegramOutboundAttachmentRuntimeEventRecorderPort {
|
|
45
45
|
recordRuntimeEvent?: (
|
|
46
46
|
category: string,
|
|
47
47
|
error: unknown,
|
|
@@ -49,28 +49,28 @@ export interface TelegramAttachmentRuntimeEventRecorderPort {
|
|
|
49
49
|
) => void;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export interface
|
|
52
|
+
export interface TelegramOutboundAttachmentToolRegistrationDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
|
|
53
53
|
maxAttachmentsPerTurn?: number;
|
|
54
54
|
maxAttachmentSizeBytes?: number;
|
|
55
|
-
getActiveTurn: () =>
|
|
55
|
+
getActiveTurn: () => TelegramOutboundAttachmentQueueTargetView | undefined;
|
|
56
56
|
statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export interface
|
|
59
|
+
export interface TelegramQueuedOutboundAttachmentView {
|
|
60
60
|
path: string;
|
|
61
61
|
fileName: string;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export interface
|
|
65
|
-
queuedAttachments:
|
|
64
|
+
export interface TelegramOutboundAttachmentQueueTargetView {
|
|
65
|
+
queuedAttachments: TelegramQueuedOutboundAttachmentView[];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export interface
|
|
68
|
+
export interface TelegramQueuedOutboundAttachmentTurnView extends TelegramOutboundAttachmentQueueTargetView {
|
|
69
69
|
chatId: number;
|
|
70
70
|
replyToMessageId: number;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
73
|
+
function isTelegramOutboundPhotoAttachmentPath(path: string): boolean {
|
|
74
74
|
const normalized = path.toLowerCase();
|
|
75
75
|
return (
|
|
76
76
|
normalized.endsWith(".jpg") ||
|
|
@@ -81,7 +81,7 @@ function isTelegramPhotoAttachmentPath(path: string): boolean {
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function
|
|
84
|
+
function formatTelegramOutboundAttachmentSizeLimitError(
|
|
85
85
|
size: number,
|
|
86
86
|
maxSize: number,
|
|
87
87
|
path?: string,
|
|
@@ -90,14 +90,14 @@ function formatTelegramAttachmentSizeLimitError(
|
|
|
90
90
|
return path ? `${message}: ${path}` : message;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
function formatTelegramOutboundAttachmentToolResultText(count: number): string {
|
|
94
94
|
// Pi's compact tool rows need an empty first line to visually separate header and result
|
|
95
95
|
return ["", `Queued ${count} Telegram attachment(s).`].join("\n");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export function
|
|
98
|
+
export function registerTelegramOutboundAttachmentTool(
|
|
99
99
|
pi: ExtensionAPI,
|
|
100
|
-
deps:
|
|
100
|
+
deps: TelegramOutboundAttachmentToolRegistrationDeps,
|
|
101
101
|
): void {
|
|
102
102
|
const maxAttachmentsPerTurn =
|
|
103
103
|
deps.maxAttachmentsPerTurn ?? MAX_ATTACHMENTS_PER_TURN;
|
|
@@ -120,7 +120,7 @@ export function registerTelegramAttachmentTool(
|
|
|
120
120
|
}),
|
|
121
121
|
async execute(_toolCallId, params) {
|
|
122
122
|
try {
|
|
123
|
-
return await
|
|
123
|
+
return await queueTelegramOutboundAttachments({
|
|
124
124
|
activeTurn: deps.getActiveTurn(),
|
|
125
125
|
paths: params.paths,
|
|
126
126
|
maxAttachmentsPerTurn,
|
|
@@ -138,7 +138,7 @@ export function registerTelegramAttachmentTool(
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export interface
|
|
141
|
+
export interface TelegramQueuedOutboundAttachmentDeliveryDeps {
|
|
142
142
|
sendMultipart: (
|
|
143
143
|
method: string,
|
|
144
144
|
fields: Record<string, string>,
|
|
@@ -160,13 +160,13 @@ export interface TelegramQueuedAttachmentDeliveryDeps {
|
|
|
160
160
|
maxAttachmentSizeBytes?: number;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
export async function
|
|
164
|
-
activeTurn:
|
|
163
|
+
export async function queueTelegramOutboundAttachments(options: {
|
|
164
|
+
activeTurn: TelegramOutboundAttachmentQueueTargetView | undefined;
|
|
165
165
|
paths: string[];
|
|
166
166
|
maxAttachmentsPerTurn: number;
|
|
167
167
|
maxAttachmentSizeBytes?: number;
|
|
168
168
|
statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
|
|
169
|
-
}): Promise<
|
|
169
|
+
}): Promise<TelegramOutboundAttachmentToolResult> {
|
|
170
170
|
if (!options.activeTurn) {
|
|
171
171
|
throw new Error(
|
|
172
172
|
"telegram_attach can only be used while replying to an active Telegram turn",
|
|
@@ -180,7 +180,7 @@ export async function queueTelegramAttachments(options: {
|
|
|
180
180
|
`Attachment limit reached (${options.maxAttachmentsPerTurn})`,
|
|
181
181
|
);
|
|
182
182
|
}
|
|
183
|
-
const pendingAttachments:
|
|
183
|
+
const pendingAttachments: TelegramQueuedOutboundAttachmentView[] = [];
|
|
184
184
|
for (const inputPath of options.paths) {
|
|
185
185
|
const stats = await (options.statPath ?? stat)(inputPath);
|
|
186
186
|
if (!stats.isFile()) {
|
|
@@ -192,7 +192,7 @@ export async function queueTelegramAttachments(options: {
|
|
|
192
192
|
stats.size > options.maxAttachmentSizeBytes
|
|
193
193
|
) {
|
|
194
194
|
throw new Error(
|
|
195
|
-
|
|
195
|
+
formatTelegramOutboundAttachmentSizeLimitError(
|
|
196
196
|
stats.size,
|
|
197
197
|
options.maxAttachmentSizeBytes,
|
|
198
198
|
inputPath,
|
|
@@ -210,20 +210,20 @@ export async function queueTelegramAttachments(options: {
|
|
|
210
210
|
content: [
|
|
211
211
|
{
|
|
212
212
|
type: "text",
|
|
213
|
-
text:
|
|
213
|
+
text: formatTelegramOutboundAttachmentToolResultText(added.length),
|
|
214
214
|
},
|
|
215
215
|
],
|
|
216
216
|
details: { paths: added },
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
export function
|
|
221
|
-
deps:
|
|
220
|
+
export function createTelegramQueuedOutboundAttachmentSender(
|
|
221
|
+
deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
|
|
222
222
|
) {
|
|
223
223
|
return async function sendQueuedAttachments(
|
|
224
|
-
turn:
|
|
224
|
+
turn: TelegramQueuedOutboundAttachmentTurnView,
|
|
225
225
|
): Promise<void> {
|
|
226
|
-
await
|
|
226
|
+
await sendQueuedTelegramOutboundAttachments(turn, {
|
|
227
227
|
...deps,
|
|
228
228
|
maxAttachmentSizeBytes:
|
|
229
229
|
deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
|
|
@@ -231,9 +231,9 @@ export function createTelegramQueuedAttachmentSender(
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
export async function
|
|
235
|
-
turn:
|
|
236
|
-
deps:
|
|
234
|
+
export async function sendQueuedTelegramOutboundAttachments(
|
|
235
|
+
turn: TelegramQueuedOutboundAttachmentTurnView,
|
|
236
|
+
deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
|
|
237
237
|
): Promise<void> {
|
|
238
238
|
for (const attachment of turn.queuedAttachments) {
|
|
239
239
|
try {
|
|
@@ -241,14 +241,14 @@ export async function sendQueuedTelegramAttachments(
|
|
|
241
241
|
const stats = await (deps.statPath ?? stat)(attachment.path);
|
|
242
242
|
if (stats.size > deps.maxAttachmentSizeBytes) {
|
|
243
243
|
throw new Error(
|
|
244
|
-
|
|
244
|
+
formatTelegramOutboundAttachmentSizeLimitError(
|
|
245
245
|
stats.size,
|
|
246
246
|
deps.maxAttachmentSizeBytes,
|
|
247
247
|
),
|
|
248
248
|
);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
const isPhoto =
|
|
251
|
+
const isPhoto = isTelegramOutboundPhotoAttachmentPath(attachment.path);
|
|
252
252
|
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
253
253
|
const fieldName = isPhoto ? "photo" : "document";
|
|
254
254
|
const replyParameters = buildTelegramMultipartReplyParameters(
|