@llblab/pi-telegram 0.2.10 → 0.4.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 +52 -19
- package/docs/README.md +2 -3
- package/docs/architecture.md +62 -31
- package/docs/locks.md +136 -0
- package/index.ts +323 -1880
- package/lib/api.ts +396 -60
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +648 -14
- package/lib/config.ts +169 -0
- package/lib/handlers.ts +474 -0
- package/lib/locks.ts +306 -0
- package/lib/media.ts +196 -46
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +90 -0
- package/lib/polling.ts +240 -14
- package/lib/preview.ts +420 -25
- package/lib/queue.ts +1137 -110
- package/lib/registration.ts +214 -31
- package/lib/rendering.ts +560 -366
- package/lib/replies.ts +198 -8
- package/lib/routing.ts +217 -0
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +143 -1
- package/lib/status.ts +432 -13
- package/lib/turns.ts +217 -36
- package/lib/updates.ts +340 -109
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -34
- package/lib/model-switch.ts +0 -62
- package/lib/types.ts +0 -137
- package/tests/api.test.ts +0 -331
- package/tests/attachments.test.ts +0 -132
- package/tests/commands.test.ts +0 -85
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -166
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -202
- package/tests/preview.test.ts +0 -480
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -526
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -247
- package/tests/updates.test.ts +0 -416
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,15 +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>;
|
|
26
41
|
flushPromise?: Promise<void>;
|
|
27
42
|
flushRequested?: boolean;
|
|
28
43
|
}
|
|
29
44
|
|
|
30
|
-
export
|
|
31
|
-
message_id: number;
|
|
32
|
-
}
|
|
45
|
+
export type TelegramSentPreviewMessage = TelegramSentMessage;
|
|
33
46
|
|
|
34
47
|
export interface TelegramPreviewRuntimeDeps {
|
|
35
48
|
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
@@ -37,21 +50,25 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
37
50
|
clearScheduledFlush: (state: TelegramPreviewRuntimeState) => void;
|
|
38
51
|
maxMessageLength: number;
|
|
39
52
|
renderPreviewText: (markdown: string) => string;
|
|
40
|
-
getDraftSupport: () =>
|
|
41
|
-
setDraftSupport: (support:
|
|
53
|
+
getDraftSupport: () => TelegramDraftSupport;
|
|
54
|
+
setDraftSupport: (support: TelegramDraftSupport) => void;
|
|
42
55
|
allocateDraftId: () => number;
|
|
43
|
-
sendDraft: (
|
|
56
|
+
sendDraft: (
|
|
57
|
+
chatId: number,
|
|
58
|
+
draftId: number,
|
|
59
|
+
text: string,
|
|
60
|
+
) => Promise<unknown>;
|
|
44
61
|
sendMessage: (
|
|
45
62
|
chatId: number,
|
|
46
63
|
text: string,
|
|
47
64
|
options?: { parseMode?: "HTML" },
|
|
48
|
-
) => Promise<
|
|
65
|
+
) => Promise<TelegramSentPreviewMessage>;
|
|
49
66
|
editMessageText: (
|
|
50
67
|
chatId: number,
|
|
51
68
|
messageId: number,
|
|
52
69
|
text: string,
|
|
53
70
|
options?: { parseMode?: "HTML" },
|
|
54
|
-
) => Promise<
|
|
71
|
+
) => Promise<unknown>;
|
|
55
72
|
renderTelegramMessage: (
|
|
56
73
|
text: string,
|
|
57
74
|
options?: { mode?: TelegramRenderMode },
|
|
@@ -67,8 +84,381 @@ export interface TelegramPreviewRuntimeDeps {
|
|
|
67
84
|
) => Promise<number | undefined>;
|
|
68
85
|
}
|
|
69
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
|
+
|
|
70
460
|
export function buildTelegramPreviewFinalText(
|
|
71
|
-
state:
|
|
461
|
+
state: TelegramPreviewState,
|
|
72
462
|
): string | undefined {
|
|
73
463
|
const finalText = state.pendingText.trim();
|
|
74
464
|
if (finalText) return finalText;
|
|
@@ -81,8 +471,25 @@ export function buildTelegramPreviewFinalText(
|
|
|
81
471
|
return state.lastSentText.trim() || undefined;
|
|
82
472
|
}
|
|
83
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
|
+
|
|
84
491
|
export function shouldUseTelegramDraftPreview(options: {
|
|
85
|
-
draftSupport:
|
|
492
|
+
draftSupport: TelegramDraftSupport;
|
|
86
493
|
snapshot?: TelegramPreviewSnapshot;
|
|
87
494
|
}): boolean {
|
|
88
495
|
return (
|
|
@@ -95,16 +502,11 @@ export async function clearTelegramPreview(
|
|
|
95
502
|
chatId: number,
|
|
96
503
|
deps: TelegramPreviewRuntimeDeps,
|
|
97
504
|
): Promise<void> {
|
|
505
|
+
void chatId;
|
|
98
506
|
const state = deps.getState();
|
|
99
507
|
if (!state) return;
|
|
100
508
|
deps.clearScheduledFlush(state);
|
|
101
509
|
deps.setState(undefined);
|
|
102
|
-
if (state.mode !== "draft" || state.draftId === undefined) return;
|
|
103
|
-
try {
|
|
104
|
-
await deps.sendDraft(chatId, state.draftId, "");
|
|
105
|
-
} catch {
|
|
106
|
-
// ignore
|
|
107
|
-
}
|
|
108
510
|
}
|
|
109
511
|
|
|
110
512
|
async function performTelegramPreviewFlush(
|
|
@@ -139,13 +541,6 @@ async function performTelegramPreviewFlush(
|
|
|
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,
|