@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/preview.ts
CHANGED
|
@@ -3,15 +3,30 @@
|
|
|
3
3
|
* Owns preview transport selection, runtime updates, and preview finalization
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type {
|
|
7
|
+
TelegramEditMessageTextBody,
|
|
8
|
+
TelegramReplyParameters,
|
|
9
|
+
TelegramSendMessageBody,
|
|
10
|
+
TelegramSentMessage,
|
|
11
|
+
} from "./api.ts";
|
|
6
12
|
import {
|
|
7
13
|
buildTelegramPreviewSnapshot,
|
|
14
|
+
MAX_MESSAGE_LENGTH,
|
|
15
|
+
renderMarkdownPreviewText,
|
|
16
|
+
renderTelegramMessage,
|
|
8
17
|
type TelegramPreviewRenderStrategy,
|
|
9
18
|
type TelegramPreviewSnapshot,
|
|
10
19
|
type TelegramRenderedChunk,
|
|
11
20
|
type TelegramRenderMode,
|
|
12
21
|
} from "./rendering.ts";
|
|
22
|
+
import { buildTelegramReplyParameters } from "./replies.ts";
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
const TELEGRAM_PREVIEW_THROTTLE_MS = 750;
|
|
25
|
+
const TELEGRAM_DRAFT_ID_MAX = 2_147_483_647;
|
|
26
|
+
|
|
27
|
+
export type TelegramDraftSupport = "unknown" | "supported" | "unsupported";
|
|
28
|
+
|
|
29
|
+
export interface TelegramPreviewState {
|
|
15
30
|
mode: "draft" | "message";
|
|
16
31
|
draftId?: number;
|
|
17
32
|
messageId?: number;
|
|
@@ -21,13 +36,13 @@ export interface TelegramPreviewStateLike {
|
|
|
21
36
|
lastSentStrategy?: TelegramPreviewRenderStrategy;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
export interface TelegramPreviewRuntimeState extends
|
|
39
|
+
export interface TelegramPreviewRuntimeState extends TelegramPreviewState {
|
|
25
40
|
flushTimer?: ReturnType<typeof setTimeout>;
|
|
41
|
+
flushPromise?: Promise<void>;
|
|
42
|
+
flushRequested?: boolean;
|
|
26
43
|
}
|
|
27
44
|
|
|
28
|
-
export
|
|
29
|
-
message_id: number;
|
|
30
|
-
}
|
|
45
|
+
export type TelegramSentPreviewMessage = TelegramSentMessage;
|
|
31
46
|
|
|
32
47
|
export interface TelegramPreviewRuntimeDeps {
|
|
33
48
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
@@ -35,21 +50,25 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
35
50
|
clearScheduledFlush: (state: TelegramPreviewRuntimeState) => void;
|
|
36
51
|
maxMessageLength: number;
|
|
37
52
|
renderPreviewText: (markdown: string) => string;
|
|
38
|
-
getDraftSupport: () =>
|
|
39
|
-
setDraftSupport: (support:
|
|
53
|
+
getDraftSupport: () => TelegramDraftSupport;
|
|
54
|
+
setDraftSupport: (support: TelegramDraftSupport) => void;
|
|
40
55
|
allocateDraftId: () => number;
|
|
41
|
-
sendDraft: (
|
|
56
|
+
sendDraft: (
|
|
57
|
+
chatId: number,
|
|
58
|
+
draftId: number,
|
|
59
|
+
text: string,
|
|
60
|
+
) => Promise<unknown>;
|
|
42
61
|
sendMessage: (
|
|
43
62
|
chatId: number,
|
|
44
63
|
text: string,
|
|
45
64
|
options?: { parseMode?: "HTML" },
|
|
46
|
-
) => Promise<
|
|
65
|
+
) => Promise<TelegramSentPreviewMessage>;
|
|
47
66
|
editMessageText: (
|
|
48
67
|
chatId: number,
|
|
49
68
|
messageId: number,
|
|
50
69
|
text: string,
|
|
51
70
|
options?: { parseMode?: "HTML" },
|
|
52
|
-
) => Promise<
|
|
71
|
+
) => Promise<unknown>;
|
|
53
72
|
renderTelegramMessage: (
|
|
54
73
|
text: string,
|
|
55
74
|
options?: { mode?: TelegramRenderMode },
|
|
@@ -65,8 +84,381 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
65
84
|
) => Promise<number | undefined>;
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
export interface TelegramPreviewActiveTurn {
|
|
88
|
+
chatId: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface TelegramAssistantMessagePreviewStartDeps<TMessage> {
|
|
92
|
+
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
93
|
+
isAssistantMessage: (message: TMessage) => boolean;
|
|
94
|
+
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
95
|
+
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
96
|
+
createPreviewState: () => TelegramPreviewRuntimeState;
|
|
97
|
+
finalizePreview: (chatId: number) => Promise<boolean>;
|
|
98
|
+
finalizeMarkdownPreview: (
|
|
99
|
+
chatId: number,
|
|
100
|
+
markdown: string,
|
|
101
|
+
) => Promise<boolean>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
|
|
105
|
+
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
106
|
+
isAssistantMessage: (message: TMessage) => boolean;
|
|
107
|
+
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
108
|
+
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
109
|
+
createPreviewState: () => TelegramPreviewRuntimeState;
|
|
110
|
+
getMessageText: (message: TMessage) => string;
|
|
111
|
+
schedulePreviewFlush: (chatId: number) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type TelegramAssistantMessagePreviewHookDeps<TMessage> =
|
|
115
|
+
TelegramAssistantMessagePreviewStartDeps<TMessage> &
|
|
116
|
+
TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
|
|
117
|
+
|
|
118
|
+
export interface TelegramAssistantMessagePreviewHookEvent<TMessage> {
|
|
119
|
+
message: TMessage;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
123
|
+
onMessageStart: (
|
|
124
|
+
event: TelegramAssistantMessagePreviewHookEvent<TMessage>,
|
|
125
|
+
) => Promise<void>;
|
|
126
|
+
onMessageUpdate: (
|
|
127
|
+
event: TelegramAssistantMessagePreviewHookEvent<TMessage>,
|
|
128
|
+
) => Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TelegramPreviewControllerDeps {
|
|
132
|
+
getDefaultReplyToMessageId?: () => number | undefined;
|
|
133
|
+
maxMessageLength?: number;
|
|
134
|
+
renderPreviewText?: (markdown: string) => string;
|
|
135
|
+
initialDraftSupport?: TelegramDraftSupport;
|
|
136
|
+
sendDraft: (
|
|
137
|
+
chatId: number,
|
|
138
|
+
draftId: number,
|
|
139
|
+
text: string,
|
|
140
|
+
) => Promise<unknown>;
|
|
141
|
+
sendMessage: (
|
|
142
|
+
chatId: number,
|
|
143
|
+
text: string,
|
|
144
|
+
options: { parseMode?: "HTML" } | undefined,
|
|
145
|
+
replyToMessageId: number | undefined,
|
|
146
|
+
) => Promise<TelegramSentPreviewMessage>;
|
|
147
|
+
editMessageText: (
|
|
148
|
+
chatId: number,
|
|
149
|
+
messageId: number,
|
|
150
|
+
text: string,
|
|
151
|
+
options?: { parseMode?: "HTML" },
|
|
152
|
+
) => Promise<unknown>;
|
|
153
|
+
renderTelegramMessage?: (
|
|
154
|
+
text: string,
|
|
155
|
+
options?: { mode?: TelegramRenderMode },
|
|
156
|
+
) => TelegramRenderedChunk[];
|
|
157
|
+
sendRenderedChunks: (
|
|
158
|
+
chatId: number,
|
|
159
|
+
chunks: TelegramRenderedChunk[],
|
|
160
|
+
replyToMessageId: number | undefined,
|
|
161
|
+
) => Promise<number | undefined>;
|
|
162
|
+
editRenderedMessage: (
|
|
163
|
+
chatId: number,
|
|
164
|
+
messageId: number,
|
|
165
|
+
chunks: TelegramRenderedChunk[],
|
|
166
|
+
) => Promise<number | undefined>;
|
|
167
|
+
throttleMs?: number;
|
|
168
|
+
maxDraftId?: number;
|
|
169
|
+
setTimer?: (
|
|
170
|
+
callback: () => void,
|
|
171
|
+
ms: number,
|
|
172
|
+
) => ReturnType<typeof setTimeout>;
|
|
173
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface TelegramPreviewController {
|
|
177
|
+
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
178
|
+
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
179
|
+
setPendingText: (text: string) => void;
|
|
180
|
+
createState: () => TelegramPreviewRuntimeState;
|
|
181
|
+
resetState: () => void;
|
|
182
|
+
clear: (chatId: number) => Promise<void>;
|
|
183
|
+
flush: (chatId: number) => Promise<void>;
|
|
184
|
+
scheduleFlush: (chatId: number) => void;
|
|
185
|
+
finalize: (chatId: number, replyToMessageId?: number) => Promise<boolean>;
|
|
186
|
+
finalizeMarkdown: (
|
|
187
|
+
chatId: number,
|
|
188
|
+
markdown: string,
|
|
189
|
+
replyToMessageId?: number,
|
|
190
|
+
) => Promise<boolean>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface TelegramPreviewMessageTransportDeps {
|
|
194
|
+
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
195
|
+
editMessageText: (body: TelegramEditMessageTextBody) => Promise<unknown>;
|
|
196
|
+
buildReplyParameters?: (
|
|
197
|
+
replyToMessageId: number | undefined,
|
|
198
|
+
) => TelegramReplyParameters | undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createTelegramPreviewMessageTransport(
|
|
202
|
+
deps: TelegramPreviewMessageTransportDeps,
|
|
203
|
+
): Pick<TelegramPreviewControllerDeps, "sendMessage" | "editMessageText"> {
|
|
204
|
+
const getReplyParameters =
|
|
205
|
+
deps.buildReplyParameters ?? buildTelegramReplyParameters;
|
|
206
|
+
return {
|
|
207
|
+
sendMessage: (chatId, text, options, replyToMessageId) => {
|
|
208
|
+
const replyParameters = getReplyParameters(replyToMessageId);
|
|
209
|
+
return deps.sendMessage({
|
|
210
|
+
chat_id: chatId,
|
|
211
|
+
text,
|
|
212
|
+
parse_mode: options?.parseMode,
|
|
213
|
+
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
editMessageText: (chatId, messageId, text, options) =>
|
|
217
|
+
deps.editMessageText({
|
|
218
|
+
chat_id: chatId,
|
|
219
|
+
message_id: messageId,
|
|
220
|
+
text,
|
|
221
|
+
parse_mode: options?.parseMode,
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface TelegramPreviewRenderedChunkTransportDeps {
|
|
227
|
+
sendRenderedChunks: (
|
|
228
|
+
chatId: number,
|
|
229
|
+
chunks: TelegramRenderedChunk[],
|
|
230
|
+
options?: { replyToMessageId?: number },
|
|
231
|
+
) => Promise<number | undefined>;
|
|
232
|
+
editRenderedMessage: (
|
|
233
|
+
chatId: number,
|
|
234
|
+
messageId: number,
|
|
235
|
+
chunks: TelegramRenderedChunk[],
|
|
236
|
+
) => Promise<number | undefined>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function createTelegramPreviewRenderedChunkTransport(
|
|
240
|
+
deps: TelegramPreviewRenderedChunkTransportDeps,
|
|
241
|
+
): Pick<
|
|
242
|
+
TelegramPreviewControllerDeps,
|
|
243
|
+
"sendRenderedChunks" | "editRenderedMessage"
|
|
244
|
+
> {
|
|
245
|
+
return {
|
|
246
|
+
sendRenderedChunks: (chatId, chunks, replyToMessageId) =>
|
|
247
|
+
deps.sendRenderedChunks(chatId, chunks, { replyToMessageId }),
|
|
248
|
+
editRenderedMessage: deps.editRenderedMessage,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type TelegramPreviewControllerRuntimeDeps = Omit<
|
|
253
|
+
TelegramPreviewControllerDeps,
|
|
254
|
+
| "sendMessage"
|
|
255
|
+
| "editMessageText"
|
|
256
|
+
| "sendRenderedChunks"
|
|
257
|
+
| "editRenderedMessage"
|
|
258
|
+
> &
|
|
259
|
+
TelegramPreviewMessageTransportDeps &
|
|
260
|
+
TelegramPreviewRenderedChunkTransportDeps;
|
|
261
|
+
|
|
262
|
+
export function createTelegramPreviewControllerRuntime(
|
|
263
|
+
deps: TelegramPreviewControllerRuntimeDeps,
|
|
264
|
+
): TelegramPreviewController {
|
|
265
|
+
return createTelegramPreviewController({
|
|
266
|
+
getDefaultReplyToMessageId: deps.getDefaultReplyToMessageId,
|
|
267
|
+
maxMessageLength: deps.maxMessageLength,
|
|
268
|
+
renderPreviewText: deps.renderPreviewText,
|
|
269
|
+
initialDraftSupport: deps.initialDraftSupport,
|
|
270
|
+
sendDraft: deps.sendDraft,
|
|
271
|
+
...createTelegramPreviewMessageTransport({
|
|
272
|
+
sendMessage: deps.sendMessage,
|
|
273
|
+
editMessageText: deps.editMessageText,
|
|
274
|
+
buildReplyParameters: deps.buildReplyParameters,
|
|
275
|
+
}),
|
|
276
|
+
renderTelegramMessage: deps.renderTelegramMessage,
|
|
277
|
+
...createTelegramPreviewRenderedChunkTransport({
|
|
278
|
+
sendRenderedChunks: deps.sendRenderedChunks,
|
|
279
|
+
editRenderedMessage: deps.editRenderedMessage,
|
|
280
|
+
}),
|
|
281
|
+
throttleMs: deps.throttleMs,
|
|
282
|
+
maxDraftId: deps.maxDraftId,
|
|
283
|
+
setTimer: deps.setTimer,
|
|
284
|
+
clearTimer: deps.clearTimer,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface TelegramAssistantPreviewRuntimeDeps<
|
|
289
|
+
TMessage,
|
|
290
|
+
> extends TelegramPreviewControllerRuntimeDeps {
|
|
291
|
+
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
292
|
+
isAssistantMessage: (message: TMessage) => boolean;
|
|
293
|
+
getMessageText: (message: TMessage) => string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export type TelegramAssistantPreviewRuntime<TMessage> =
|
|
297
|
+
TelegramPreviewController & TelegramAssistantMessagePreviewHooks<TMessage>;
|
|
298
|
+
|
|
299
|
+
export function createTelegramAssistantPreviewRuntime<TMessage>(
|
|
300
|
+
deps: TelegramAssistantPreviewRuntimeDeps<TMessage>,
|
|
301
|
+
): TelegramAssistantPreviewRuntime<TMessage> {
|
|
302
|
+
const controller = createTelegramPreviewControllerRuntime(deps);
|
|
303
|
+
return {
|
|
304
|
+
...controller,
|
|
305
|
+
...createTelegramAssistantMessagePreviewHooks({
|
|
306
|
+
getActiveTurn: deps.getActiveTurn,
|
|
307
|
+
isAssistantMessage: deps.isAssistantMessage,
|
|
308
|
+
getState: controller.getState,
|
|
309
|
+
setState: controller.setState,
|
|
310
|
+
createPreviewState: controller.createState,
|
|
311
|
+
finalizePreview: controller.finalize,
|
|
312
|
+
finalizeMarkdownPreview: controller.finalizeMarkdown,
|
|
313
|
+
getMessageText: deps.getMessageText,
|
|
314
|
+
schedulePreviewFlush: controller.scheduleFlush,
|
|
315
|
+
}),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function createTelegramPreviewController(
|
|
320
|
+
deps: TelegramPreviewControllerDeps,
|
|
321
|
+
): TelegramPreviewController {
|
|
322
|
+
let state: TelegramPreviewRuntimeState | undefined;
|
|
323
|
+
const clearTimer = deps.clearTimer ?? clearTimeout;
|
|
324
|
+
const setTimer =
|
|
325
|
+
deps.setTimer ??
|
|
326
|
+
((callback: () => void, ms: number): ReturnType<typeof setTimeout> =>
|
|
327
|
+
setTimeout(callback, ms));
|
|
328
|
+
const throttleMs = deps.throttleMs ?? TELEGRAM_PREVIEW_THROTTLE_MS;
|
|
329
|
+
const maxDraftId = deps.maxDraftId ?? TELEGRAM_DRAFT_ID_MAX;
|
|
330
|
+
const maxMessageLength = deps.maxMessageLength ?? MAX_MESSAGE_LENGTH;
|
|
331
|
+
const renderPreview = deps.renderPreviewText ?? renderMarkdownPreviewText;
|
|
332
|
+
const renderMessage = deps.renderTelegramMessage ?? renderTelegramMessage;
|
|
333
|
+
let draftSupport = deps.initialDraftSupport ?? "unknown";
|
|
334
|
+
let nextDraftId = 0;
|
|
335
|
+
const getRuntimeDeps = (
|
|
336
|
+
replyToMessageId?: number,
|
|
337
|
+
): TelegramPreviewRuntimeDeps => ({
|
|
338
|
+
getState: () => state,
|
|
339
|
+
setState: (nextState) => {
|
|
340
|
+
state = nextState;
|
|
341
|
+
},
|
|
342
|
+
clearScheduledFlush: (nextState) => {
|
|
343
|
+
if (!nextState.flushTimer) return;
|
|
344
|
+
clearTimer(nextState.flushTimer);
|
|
345
|
+
nextState.flushTimer = undefined;
|
|
346
|
+
},
|
|
347
|
+
maxMessageLength,
|
|
348
|
+
renderPreviewText: renderPreview,
|
|
349
|
+
getDraftSupport: () => draftSupport,
|
|
350
|
+
setDraftSupport: (support) => {
|
|
351
|
+
draftSupport = support;
|
|
352
|
+
},
|
|
353
|
+
allocateDraftId: () => {
|
|
354
|
+
nextDraftId = allocateTelegramDraftId(nextDraftId, maxDraftId);
|
|
355
|
+
return nextDraftId;
|
|
356
|
+
},
|
|
357
|
+
sendDraft: deps.sendDraft,
|
|
358
|
+
sendMessage: (chatId, text, options) =>
|
|
359
|
+
deps.sendMessage(
|
|
360
|
+
chatId,
|
|
361
|
+
text,
|
|
362
|
+
options,
|
|
363
|
+
replyToMessageId ?? deps.getDefaultReplyToMessageId?.(),
|
|
364
|
+
),
|
|
365
|
+
editMessageText: deps.editMessageText,
|
|
366
|
+
renderTelegramMessage: renderMessage,
|
|
367
|
+
sendRenderedChunks: (chatId, chunks) =>
|
|
368
|
+
deps.sendRenderedChunks(
|
|
369
|
+
chatId,
|
|
370
|
+
chunks,
|
|
371
|
+
replyToMessageId ?? deps.getDefaultReplyToMessageId?.(),
|
|
372
|
+
),
|
|
373
|
+
editRenderedMessage: deps.editRenderedMessage,
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
getState: () => state,
|
|
377
|
+
setState: (nextState) => {
|
|
378
|
+
state = nextState;
|
|
379
|
+
},
|
|
380
|
+
setPendingText: (text) => {
|
|
381
|
+
if (state) state.pendingText = text;
|
|
382
|
+
},
|
|
383
|
+
createState: () => createTelegramPreviewRuntimeState(draftSupport),
|
|
384
|
+
resetState: () => {
|
|
385
|
+
state = createTelegramPreviewRuntimeState(draftSupport);
|
|
386
|
+
},
|
|
387
|
+
clear: (chatId) => clearTelegramPreview(chatId, getRuntimeDeps()),
|
|
388
|
+
flush: (chatId) => flushTelegramPreview(chatId, getRuntimeDeps()),
|
|
389
|
+
scheduleFlush: (chatId) => {
|
|
390
|
+
if (!state || state.flushTimer) return;
|
|
391
|
+
state.flushTimer = setTimer(() => {
|
|
392
|
+
void flushTelegramPreview(chatId, getRuntimeDeps());
|
|
393
|
+
}, throttleMs);
|
|
394
|
+
},
|
|
395
|
+
finalize: (chatId, replyToMessageId) =>
|
|
396
|
+
finalizeTelegramPreview(chatId, getRuntimeDeps(replyToMessageId)),
|
|
397
|
+
finalizeMarkdown: (chatId, markdown, replyToMessageId) =>
|
|
398
|
+
finalizeTelegramMarkdownPreview(
|
|
399
|
+
chatId,
|
|
400
|
+
markdown,
|
|
401
|
+
getRuntimeDeps(replyToMessageId),
|
|
402
|
+
),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function createTelegramAssistantMessagePreviewHooks<TMessage>(
|
|
407
|
+
deps: TelegramAssistantMessagePreviewHookDeps<TMessage>,
|
|
408
|
+
): TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
409
|
+
return {
|
|
410
|
+
onMessageStart: async (
|
|
411
|
+
event: TelegramAssistantMessagePreviewHookEvent<TMessage>,
|
|
412
|
+
): Promise<void> => {
|
|
413
|
+
await handleTelegramAssistantMessagePreviewStart(event.message, deps);
|
|
414
|
+
},
|
|
415
|
+
onMessageUpdate: async (
|
|
416
|
+
event: TelegramAssistantMessagePreviewHookEvent<TMessage>,
|
|
417
|
+
): Promise<void> => {
|
|
418
|
+
await handleTelegramAssistantMessagePreviewUpdate(event.message, deps);
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function handleTelegramAssistantMessagePreviewStart<TMessage>(
|
|
424
|
+
message: TMessage,
|
|
425
|
+
deps: TelegramAssistantMessagePreviewStartDeps<TMessage>,
|
|
426
|
+
): Promise<void> {
|
|
427
|
+
const turn = deps.getActiveTurn();
|
|
428
|
+
if (!turn || !deps.isAssistantMessage(message)) return;
|
|
429
|
+
const state = deps.getState();
|
|
430
|
+
if (
|
|
431
|
+
state &&
|
|
432
|
+
(state.pendingText.trim().length > 0 ||
|
|
433
|
+
state.lastSentText.trim().length > 0)
|
|
434
|
+
) {
|
|
435
|
+
const previousText = state.pendingText.trim();
|
|
436
|
+
if (previousText.length > 0) {
|
|
437
|
+
await deps.finalizeMarkdownPreview(turn.chatId, previousText);
|
|
438
|
+
} else {
|
|
439
|
+
await deps.finalizePreview(turn.chatId);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
deps.setState(deps.createPreviewState());
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function handleTelegramAssistantMessagePreviewUpdate<TMessage>(
|
|
446
|
+
message: TMessage,
|
|
447
|
+
deps: TelegramAssistantMessagePreviewUpdateDeps<TMessage>,
|
|
448
|
+
): Promise<void> {
|
|
449
|
+
const turn = deps.getActiveTurn();
|
|
450
|
+
if (!turn || !deps.isAssistantMessage(message)) return;
|
|
451
|
+
let state = deps.getState();
|
|
452
|
+
if (!state) {
|
|
453
|
+
state = deps.createPreviewState();
|
|
454
|
+
deps.setState(state);
|
|
455
|
+
}
|
|
456
|
+
state.pendingText = deps.getMessageText(message);
|
|
457
|
+
deps.schedulePreviewFlush(turn.chatId);
|
|
458
|
+
}
|
|
459
|
+
|
|
68
460
|
export function buildTelegramPreviewFinalText(
|
|
69
|
-
state:
|
|
461
|
+
state: TelegramPreviewState,
|
|
70
462
|
): string | undefined {
|
|
71
463
|
const finalText = state.pendingText.trim();
|
|
72
464
|
if (finalText) return finalText;
|
|
@@ -79,8 +471,25 @@ export function buildTelegramPreviewFinalText(
|
|
|
79
471
|
return state.lastSentText.trim() || undefined;
|
|
80
472
|
}
|
|
81
473
|
|
|
474
|
+
export function createTelegramPreviewRuntimeState(
|
|
475
|
+
draftSupport: TelegramDraftSupport,
|
|
476
|
+
): TelegramPreviewRuntimeState {
|
|
477
|
+
return {
|
|
478
|
+
mode: draftSupport === "unsupported" ? "message" : "draft",
|
|
479
|
+
pendingText: "",
|
|
480
|
+
lastSentText: "",
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function allocateTelegramDraftId(
|
|
485
|
+
currentDraftId: number,
|
|
486
|
+
maxDraftId: number,
|
|
487
|
+
): number {
|
|
488
|
+
return currentDraftId >= maxDraftId ? 1 : currentDraftId + 1;
|
|
489
|
+
}
|
|
490
|
+
|
|
82
491
|
export function shouldUseTelegramDraftPreview(options: {
|
|
83
|
-
draftSupport:
|
|
492
|
+
draftSupport: TelegramDraftSupport;
|
|
84
493
|
snapshot?: TelegramPreviewSnapshot;
|
|
85
494
|
}): boolean {
|
|
86
495
|
return (
|
|
@@ -93,25 +502,18 @@ export async function clearTelegramPreview(
|
|
|
93
502
|
chatId: number,
|
|
94
503
|
deps: TelegramPreviewRuntimeDeps,
|
|
95
504
|
): Promise<void> {
|
|
505
|
+
void chatId;
|
|
96
506
|
const state = deps.getState();
|
|
97
507
|
if (!state) return;
|
|
98
508
|
deps.clearScheduledFlush(state);
|
|
99
509
|
deps.setState(undefined);
|
|
100
|
-
if (state.mode !== "draft" || state.draftId === undefined) return;
|
|
101
|
-
try {
|
|
102
|
-
await deps.sendDraft(chatId, state.draftId, "");
|
|
103
|
-
} catch {
|
|
104
|
-
// ignore
|
|
105
|
-
}
|
|
106
510
|
}
|
|
107
511
|
|
|
108
|
-
|
|
512
|
+
async function performTelegramPreviewFlush(
|
|
109
513
|
chatId: number,
|
|
514
|
+
state: TelegramPreviewRuntimeState,
|
|
110
515
|
deps: TelegramPreviewRuntimeDeps,
|
|
111
516
|
): Promise<void> {
|
|
112
|
-
const state = deps.getState();
|
|
113
|
-
if (!state) return;
|
|
114
|
-
state.flushTimer = undefined;
|
|
115
517
|
const snapshot = buildTelegramPreviewSnapshot({
|
|
116
518
|
state,
|
|
117
519
|
maxMessageLength: deps.maxMessageLength,
|
|
@@ -139,13 +541,6 @@ export async function flushTelegramPreview(
|
|
|
139
541
|
deps.setDraftSupport("unsupported");
|
|
140
542
|
}
|
|
141
543
|
}
|
|
142
|
-
if (state.mode === "draft" && state.draftId !== undefined) {
|
|
143
|
-
try {
|
|
144
|
-
await deps.sendDraft(chatId, state.draftId, "");
|
|
145
|
-
} catch {
|
|
146
|
-
// ignore
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
544
|
if (state.messageId === undefined) {
|
|
150
545
|
const sent = await deps.sendMessage(chatId, snapshot.text, {
|
|
151
546
|
parseMode: snapshot.parseMode,
|
|
@@ -166,6 +561,33 @@ export async function flushTelegramPreview(
|
|
|
166
561
|
state.lastSentStrategy = snapshot.strategy;
|
|
167
562
|
}
|
|
168
563
|
|
|
564
|
+
export async function flushTelegramPreview(
|
|
565
|
+
chatId: number,
|
|
566
|
+
deps: TelegramPreviewRuntimeDeps,
|
|
567
|
+
): Promise<void> {
|
|
568
|
+
const state = deps.getState();
|
|
569
|
+
if (!state) return;
|
|
570
|
+
if (state.flushPromise) {
|
|
571
|
+
state.flushRequested = true;
|
|
572
|
+
await state.flushPromise;
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
state.flushTimer = undefined;
|
|
576
|
+
state.flushPromise = (async () => {
|
|
577
|
+
do {
|
|
578
|
+
state.flushRequested = false;
|
|
579
|
+
await performTelegramPreviewFlush(chatId, state, deps);
|
|
580
|
+
} while (deps.getState() === state && state.flushRequested);
|
|
581
|
+
})();
|
|
582
|
+
try {
|
|
583
|
+
await state.flushPromise;
|
|
584
|
+
} finally {
|
|
585
|
+
if (deps.getState() === state) {
|
|
586
|
+
state.flushPromise = undefined;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
169
591
|
export async function finalizeTelegramPreview(
|
|
170
592
|
chatId: number,
|
|
171
593
|
deps: TelegramPreviewRuntimeDeps,
|