@llblab/pi-telegram 0.7.1 → 0.8.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 +6 -5
- package/CHANGELOG.md +20 -1
- package/README.md +36 -11
- package/docs/README.md +2 -2
- package/docs/architecture.md +7 -7
- package/docs/callback-namespaces.md +1 -1
- package/docs/inbound-handlers.md +93 -0
- package/docs/outbound-handlers.md +40 -4
- package/index.ts +52 -32
- package/lib/config.ts +9 -3
- package/lib/inbound-handlers.ts +588 -0
- package/lib/menu-queue.ts +1 -1
- package/lib/menu-status.ts +4 -4
- package/lib/menu.ts +7 -3
- package/lib/{attachments.ts → outbound-attachments.ts} +34 -34
- package/lib/outbound-handlers.ts +245 -0
- package/lib/prompts.ts +1 -1
- package/lib/routing.ts +14 -4
- package/lib/text-groups.ts +203 -0
- package/package.json +1 -1
- package/docs/attachment-handlers.md +0 -50
- package/lib/attachment-handlers.ts +0 -423
|
@@ -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(
|
package/lib/outbound-handlers.ts
CHANGED
|
@@ -97,6 +97,55 @@ export interface TelegramVoiceReplySenderDeps {
|
|
|
97
97
|
) => void;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export interface TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup = unknown> {
|
|
101
|
+
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
102
|
+
getHandlers?: () => TelegramOutboundHandlerConfig[] | undefined;
|
|
103
|
+
sendTextReply: (
|
|
104
|
+
chatId: number,
|
|
105
|
+
replyToMessageId: number | undefined,
|
|
106
|
+
text: string,
|
|
107
|
+
options?: { parseMode?: "HTML" },
|
|
108
|
+
) => Promise<number | undefined>;
|
|
109
|
+
sendMarkdownReply: (
|
|
110
|
+
chatId: number,
|
|
111
|
+
replyToMessageId: number | undefined,
|
|
112
|
+
markdown: string,
|
|
113
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
114
|
+
) => Promise<number | undefined>;
|
|
115
|
+
cwd?: string;
|
|
116
|
+
recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface TelegramInlineKeyboardLike {
|
|
120
|
+
inline_keyboard: Array<Array<{ text: string; callback_data: string }>>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface TelegramOutboundTextTransformOptions<TReplyMarkup = unknown> {
|
|
124
|
+
handlers?: TelegramOutboundHandlerConfig[];
|
|
125
|
+
cwd?: string;
|
|
126
|
+
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
127
|
+
recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
|
|
128
|
+
replyMarkup?: TReplyMarkup;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TelegramOutboundTextTransformResult<TReplyMarkup = unknown> {
|
|
132
|
+
text: string;
|
|
133
|
+
replyMarkup?: TReplyMarkup;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup = unknown> {
|
|
137
|
+
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
138
|
+
getHandlers?: () => TelegramOutboundHandlerConfig[] | undefined;
|
|
139
|
+
finalizeMarkdownPreview: (
|
|
140
|
+
chatId: number,
|
|
141
|
+
markdown: string,
|
|
142
|
+
replyToMessageId: number,
|
|
143
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
144
|
+
) => Promise<boolean>;
|
|
145
|
+
cwd?: string;
|
|
146
|
+
recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
|
|
147
|
+
}
|
|
148
|
+
|
|
100
149
|
interface TelegramTopLevelHtmlComment {
|
|
101
150
|
raw: string;
|
|
102
151
|
content: string;
|
|
@@ -665,6 +714,202 @@ export async function generateTelegramVoiceReplyFile(
|
|
|
665
714
|
});
|
|
666
715
|
}
|
|
667
716
|
|
|
717
|
+
function getOutboundTextTemplateValues(text: string): Record<string, string> {
|
|
718
|
+
return { text, type: "text" };
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async function transformTelegramOutboundTextWithHandler(
|
|
722
|
+
text: string,
|
|
723
|
+
options: {
|
|
724
|
+
handler: TelegramOutboundHandlerConfig;
|
|
725
|
+
cwd: string;
|
|
726
|
+
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
727
|
+
},
|
|
728
|
+
): Promise<string> {
|
|
729
|
+
const values = getOutboundTextTemplateValues(text);
|
|
730
|
+
const steps = getTelegramVoiceHandlerCompositionSteps(options.handler);
|
|
731
|
+
if (steps.length > 0) {
|
|
732
|
+
const startedAt = Date.now();
|
|
733
|
+
let stdout = text;
|
|
734
|
+
for (const [index, step] of steps.entries()) {
|
|
735
|
+
try {
|
|
736
|
+
const result = await runVoiceReplyCommand(
|
|
737
|
+
`Outbound text template step ${index + 1}`,
|
|
738
|
+
step,
|
|
739
|
+
values,
|
|
740
|
+
{
|
|
741
|
+
cwd: options.cwd,
|
|
742
|
+
timeout: getVoiceReplyCompositionStepTimeout(
|
|
743
|
+
getVoiceReplyTimeout(options.handler),
|
|
744
|
+
step,
|
|
745
|
+
startedAt,
|
|
746
|
+
),
|
|
747
|
+
execCommand: options.execCommand,
|
|
748
|
+
stdin: stdout,
|
|
749
|
+
},
|
|
750
|
+
);
|
|
751
|
+
stdout = result.stdout;
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (typeof step === "object" && step.critical) throw error;
|
|
754
|
+
stdout = "";
|
|
755
|
+
}
|
|
756
|
+
if (!stdout) stdout = text;
|
|
757
|
+
}
|
|
758
|
+
return stdout.trim() || text;
|
|
759
|
+
}
|
|
760
|
+
const result = await runVoiceReplyCommand(
|
|
761
|
+
"Outbound text template",
|
|
762
|
+
options.handler,
|
|
763
|
+
values,
|
|
764
|
+
{
|
|
765
|
+
cwd: options.cwd,
|
|
766
|
+
timeout: getVoiceReplyTimeout(options.handler),
|
|
767
|
+
execCommand: options.execCommand,
|
|
768
|
+
stdin: text,
|
|
769
|
+
},
|
|
770
|
+
);
|
|
771
|
+
return result.stdout.trim() || text;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export async function transformTelegramOutboundText(
|
|
775
|
+
text: string,
|
|
776
|
+
options: {
|
|
777
|
+
handlers?: TelegramOutboundHandlerConfig[];
|
|
778
|
+
cwd?: string;
|
|
779
|
+
execCommand: TelegramVoiceReplySenderDeps["execCommand"];
|
|
780
|
+
recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
|
|
781
|
+
},
|
|
782
|
+
): Promise<string> {
|
|
783
|
+
let transformed = text;
|
|
784
|
+
for (const handler of findTelegramOutboundHandlers(options.handlers, "text")) {
|
|
785
|
+
try {
|
|
786
|
+
transformed = await transformTelegramOutboundTextWithHandler(
|
|
787
|
+
transformed,
|
|
788
|
+
{
|
|
789
|
+
handler,
|
|
790
|
+
cwd: options.cwd ?? process.cwd(),
|
|
791
|
+
execCommand: options.execCommand,
|
|
792
|
+
},
|
|
793
|
+
);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
options.recordRuntimeEvent?.("outbound-text-handler", error, {
|
|
796
|
+
handler: outboundHandlerMatchesType(handler, "text") ? "text" : "unknown",
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return transformed;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function isTelegramInlineKeyboardLike(
|
|
804
|
+
replyMarkup: unknown,
|
|
805
|
+
): replyMarkup is TelegramInlineKeyboardLike {
|
|
806
|
+
if (!replyMarkup || typeof replyMarkup !== "object") return false;
|
|
807
|
+
const keyboard = (replyMarkup as { inline_keyboard?: unknown }).inline_keyboard;
|
|
808
|
+
return Array.isArray(keyboard);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function transformTelegramOutboundReplyMarkup<TReplyMarkup>(
|
|
812
|
+
replyMarkup: TReplyMarkup | undefined,
|
|
813
|
+
options: Omit<TelegramOutboundTextTransformOptions, "replyMarkup">,
|
|
814
|
+
): Promise<TReplyMarkup | undefined> {
|
|
815
|
+
if (!isTelegramInlineKeyboardLike(replyMarkup)) return replyMarkup;
|
|
816
|
+
const translatedRows = [];
|
|
817
|
+
for (const row of replyMarkup.inline_keyboard) {
|
|
818
|
+
const translatedRow = [];
|
|
819
|
+
for (const button of row) {
|
|
820
|
+
const text = await transformTelegramOutboundText(button.text, options);
|
|
821
|
+
translatedRow.push({ ...button, text });
|
|
822
|
+
}
|
|
823
|
+
translatedRows.push(translatedRow);
|
|
824
|
+
}
|
|
825
|
+
return { ...replyMarkup, inline_keyboard: translatedRows } as TReplyMarkup;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
export async function transformTelegramOutboundTextReply<TReplyMarkup = unknown>(
|
|
829
|
+
text: string,
|
|
830
|
+
options: TelegramOutboundTextTransformOptions<TReplyMarkup>,
|
|
831
|
+
): Promise<TelegramOutboundTextTransformResult<TReplyMarkup>> {
|
|
832
|
+
const transformOptions = {
|
|
833
|
+
handlers: options.handlers,
|
|
834
|
+
cwd: options.cwd,
|
|
835
|
+
execCommand: options.execCommand,
|
|
836
|
+
recordRuntimeEvent: options.recordRuntimeEvent,
|
|
837
|
+
};
|
|
838
|
+
const transformedText = await transformTelegramOutboundText(
|
|
839
|
+
text,
|
|
840
|
+
transformOptions,
|
|
841
|
+
);
|
|
842
|
+
const replyMarkup = await transformTelegramOutboundReplyMarkup(
|
|
843
|
+
options.replyMarkup,
|
|
844
|
+
transformOptions,
|
|
845
|
+
);
|
|
846
|
+
return { text: transformedText, ...(replyMarkup ? { replyMarkup } : {}) };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export function createTelegramOutboundTextReplyRuntime<TReplyMarkup = unknown>(
|
|
850
|
+
deps: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>,
|
|
851
|
+
): Pick<
|
|
852
|
+
TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>,
|
|
853
|
+
"sendTextReply" | "sendMarkdownReply"
|
|
854
|
+
> {
|
|
855
|
+
return {
|
|
856
|
+
sendTextReply: async (chatId, replyToMessageId, text, options) => {
|
|
857
|
+
const transformed = await transformTelegramOutboundText(text, {
|
|
858
|
+
handlers: deps.getHandlers?.(),
|
|
859
|
+
cwd: deps.cwd,
|
|
860
|
+
execCommand: deps.execCommand,
|
|
861
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
862
|
+
});
|
|
863
|
+
return deps.sendTextReply(chatId, replyToMessageId, transformed, options);
|
|
864
|
+
},
|
|
865
|
+
sendMarkdownReply: async (chatId, replyToMessageId, markdown, options) => {
|
|
866
|
+
const transformed = await transformTelegramOutboundTextReply(markdown, {
|
|
867
|
+
handlers: deps.getHandlers?.(),
|
|
868
|
+
cwd: deps.cwd,
|
|
869
|
+
execCommand: deps.execCommand,
|
|
870
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
871
|
+
replyMarkup: options?.replyMarkup,
|
|
872
|
+
});
|
|
873
|
+
return deps.sendMarkdownReply(chatId, replyToMessageId, transformed.text, {
|
|
874
|
+
...options,
|
|
875
|
+
...(transformed.replyMarkup
|
|
876
|
+
? { replyMarkup: transformed.replyMarkup }
|
|
877
|
+
: {}),
|
|
878
|
+
});
|
|
879
|
+
},
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export function createTelegramOutboundTextPreviewRuntime<TReplyMarkup = unknown>(
|
|
884
|
+
deps: TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup>,
|
|
885
|
+
): Pick<
|
|
886
|
+
TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup>,
|
|
887
|
+
"finalizeMarkdownPreview"
|
|
888
|
+
> {
|
|
889
|
+
return {
|
|
890
|
+
finalizeMarkdownPreview: async (chatId, markdown, replyToMessageId, options) => {
|
|
891
|
+
const transformed = await transformTelegramOutboundTextReply(markdown, {
|
|
892
|
+
handlers: deps.getHandlers?.(),
|
|
893
|
+
cwd: deps.cwd,
|
|
894
|
+
execCommand: deps.execCommand,
|
|
895
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
896
|
+
replyMarkup: options?.replyMarkup,
|
|
897
|
+
});
|
|
898
|
+
return deps.finalizeMarkdownPreview(
|
|
899
|
+
chatId,
|
|
900
|
+
transformed.text,
|
|
901
|
+
replyToMessageId,
|
|
902
|
+
{
|
|
903
|
+
...options,
|
|
904
|
+
...(transformed.replyMarkup
|
|
905
|
+
? { replyMarkup: transformed.replyMarkup }
|
|
906
|
+
: {}),
|
|
907
|
+
},
|
|
908
|
+
);
|
|
909
|
+
},
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
668
913
|
export interface TelegramOutboundReplyPlan<TReplyMarkup = unknown> {
|
|
669
914
|
markdown: string;
|
|
670
915
|
replyMarkup?: TReplyMarkup;
|
package/lib/prompts.ts
CHANGED
|
@@ -14,7 +14,7 @@ Telegram bridge extension is active.
|
|
|
14
14
|
Inbound context:
|
|
15
15
|
- \`[telegram]\` marks Telegram-originated messages.
|
|
16
16
|
- \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
|
|
17
|
-
- \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains
|
|
17
|
+
- \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains inbound-handler stdout such as transcriptions or extracted text for those attachments.
|
|
18
18
|
- Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
|
|
19
19
|
|
|
20
20
|
Telegram-visible output:
|
package/lib/routing.ts
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import * as OutboundHandlers from "./outbound-handlers.ts";
|
|
8
8
|
import * as Commands from "./commands.ts";
|
|
9
9
|
import type { TelegramConfigStore } from "./config.ts";
|
|
10
|
-
import type {
|
|
10
|
+
import type { TelegramInboundHandlerRuntime } from "./inbound-handlers.ts";
|
|
11
11
|
import * as Media from "./media.ts";
|
|
12
12
|
import * as Menu from "./menu.ts";
|
|
13
13
|
import * as Model from "./model.ts";
|
|
14
14
|
import * as Queue from "./queue.ts";
|
|
15
15
|
import * as PromptTemplates from "./prompt-templates.ts";
|
|
16
16
|
import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
17
|
+
import * as TextGroups from "./text-groups.ts";
|
|
17
18
|
import * as Turns from "./turns.ts";
|
|
18
19
|
import * as Updates from "./updates.ts";
|
|
19
20
|
|
|
@@ -39,6 +40,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
39
40
|
bridgeRuntime: TelegramBridgeRuntime;
|
|
40
41
|
activeTurnRuntime: Queue.TelegramActiveTurnStore;
|
|
41
42
|
mediaGroupRuntime: Media.TelegramMediaGroupController<TMessage, TContext>;
|
|
43
|
+
textGroupRuntime: TextGroups.TelegramTextGroupController<TMessage, TContext>;
|
|
42
44
|
telegramQueueStore: Queue.TelegramQueueStateStore<TContext>;
|
|
43
45
|
queueMutationRuntime: Queue.TelegramQueueMutationController<TContext>;
|
|
44
46
|
modelMenuRuntime: Menu.TelegramModelMenuRuntime<TModel>;
|
|
@@ -58,7 +60,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
58
60
|
ctx: TContext,
|
|
59
61
|
) => Promise<boolean>;
|
|
60
62
|
buttonActionStore?: OutboundHandlers.TelegramButtonActionStore;
|
|
61
|
-
|
|
63
|
+
inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
|
|
62
64
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
63
65
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
64
66
|
answerCallbackQuery: (
|
|
@@ -203,7 +205,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
203
205
|
>({
|
|
204
206
|
allocateQueueOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
|
205
207
|
downloadFile: deps.downloadFile,
|
|
206
|
-
processAttachments: deps.
|
|
208
|
+
processAttachments: deps.inboundHandlerRuntime.process,
|
|
207
209
|
});
|
|
208
210
|
const enqueueContinueTurn = async (
|
|
209
211
|
message: TMessage,
|
|
@@ -321,6 +323,14 @@ export function createTelegramInboundRouteRuntime<
|
|
|
321
323
|
mediaGroups: deps.mediaGroupRuntime,
|
|
322
324
|
dispatchMessages: commandOrPrompt.dispatchMessages,
|
|
323
325
|
});
|
|
326
|
+
const textDispatch = TextGroups.createTelegramTextGroupDispatchRuntime<
|
|
327
|
+
TMessage,
|
|
328
|
+
TContext
|
|
329
|
+
>({
|
|
330
|
+
textGroups: deps.textGroupRuntime,
|
|
331
|
+
dispatchMessages: commandOrPrompt.dispatchMessages,
|
|
332
|
+
dispatchSingleMessage: mediaDispatch.handleMessage,
|
|
333
|
+
});
|
|
324
334
|
const editRuntime = Turns.createTelegramQueuedPromptEditRuntime<
|
|
325
335
|
TMessage,
|
|
326
336
|
TContext
|
|
@@ -343,7 +353,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
343
353
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
344
354
|
handleAuthorizedTelegramCallbackQuery: callbackHandler,
|
|
345
355
|
sendTextReply: deps.sendTextReply,
|
|
346
|
-
handleAuthorizedTelegramMessage:
|
|
356
|
+
handleAuthorizedTelegramMessage: textDispatch.handleMessage,
|
|
347
357
|
handleAuthorizedTelegramEditedMessage: editRuntime.updateFromEditedMessage,
|
|
348
358
|
});
|
|
349
359
|
}
|