@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/queue.ts
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
* Owns queue items, queue mutations, dispatch and lifecycle planning, session resets, and queue-adjacent runtime helpers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ImageContent, Model, TextContent } from "@mariozechner/pi-ai";
|
|
7
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
8
|
-
|
|
9
6
|
// --- Queue Items ---
|
|
10
7
|
|
|
11
8
|
export interface QueuedAttachment {
|
|
@@ -13,8 +10,56 @@ export interface QueuedAttachment {
|
|
|
13
10
|
fileName: string;
|
|
14
11
|
}
|
|
15
12
|
|
|
13
|
+
export interface TelegramPromptTextContent {
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TelegramPromptImageContent {
|
|
19
|
+
type: "image";
|
|
20
|
+
data: string;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TelegramPromptContent =
|
|
25
|
+
| TelegramPromptTextContent
|
|
26
|
+
| TelegramPromptImageContent;
|
|
27
|
+
|
|
16
28
|
export type TelegramQueueItemKind = "prompt" | "control";
|
|
17
29
|
export type TelegramQueueLane = "control" | "priority" | "default";
|
|
30
|
+
export type TelegramQueueAdmissionMode =
|
|
31
|
+
| "control-queue"
|
|
32
|
+
| "priority-queue"
|
|
33
|
+
| "default-queue";
|
|
34
|
+
|
|
35
|
+
export interface TelegramQueueLaneContract {
|
|
36
|
+
lane: TelegramQueueLane;
|
|
37
|
+
admissionMode: TelegramQueueAdmissionMode;
|
|
38
|
+
dispatchRank: number;
|
|
39
|
+
allowedKinds: readonly TelegramQueueItemKind[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const TELEGRAM_QUEUE_LANE_CONTRACTS: readonly TelegramQueueLaneContract[] =
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
lane: "control",
|
|
46
|
+
admissionMode: "control-queue",
|
|
47
|
+
dispatchRank: 0,
|
|
48
|
+
allowedKinds: ["control", "prompt"],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
lane: "priority",
|
|
52
|
+
admissionMode: "priority-queue",
|
|
53
|
+
dispatchRank: 1,
|
|
54
|
+
allowedKinds: ["prompt"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
lane: "default",
|
|
58
|
+
admissionMode: "default-queue",
|
|
59
|
+
dispatchRank: 2,
|
|
60
|
+
allowedKinds: ["prompt"],
|
|
61
|
+
},
|
|
62
|
+
] as const;
|
|
18
63
|
|
|
19
64
|
export interface TelegramQueueItemBase {
|
|
20
65
|
kind: TelegramQueueItemKind;
|
|
@@ -30,19 +75,44 @@ export interface PendingTelegramTurn extends TelegramQueueItemBase {
|
|
|
30
75
|
kind: "prompt";
|
|
31
76
|
sourceMessageIds: number[];
|
|
32
77
|
queuedAttachments: QueuedAttachment[];
|
|
33
|
-
content:
|
|
78
|
+
content: TelegramPromptContent[];
|
|
34
79
|
historyText: string;
|
|
35
80
|
}
|
|
36
81
|
|
|
37
|
-
export interface PendingTelegramControlItem
|
|
82
|
+
export interface PendingTelegramControlItem<
|
|
83
|
+
TContext = unknown,
|
|
84
|
+
> extends TelegramQueueItemBase {
|
|
38
85
|
kind: "control";
|
|
39
86
|
controlType: "status" | "model";
|
|
40
|
-
execute: (ctx:
|
|
87
|
+
execute: (ctx: TContext) => Promise<void>;
|
|
41
88
|
}
|
|
42
89
|
|
|
43
|
-
export type TelegramQueueItem =
|
|
90
|
+
export type TelegramQueueItem<TContext = unknown> =
|
|
44
91
|
| PendingTelegramTurn
|
|
45
|
-
| PendingTelegramControlItem
|
|
92
|
+
| PendingTelegramControlItem<TContext>;
|
|
93
|
+
|
|
94
|
+
export interface TelegramQueueStore<TContext = unknown> {
|
|
95
|
+
getQueuedItems: () => TelegramQueueItem<TContext>[];
|
|
96
|
+
setQueuedItems: (items: TelegramQueueItem<TContext>[]) => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface TelegramQueueStateStore<
|
|
100
|
+
TContext = unknown,
|
|
101
|
+
> extends TelegramQueueStore<TContext> {
|
|
102
|
+
hasQueuedItems: () => boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface TelegramActiveTurnStore<
|
|
106
|
+
TTurn extends PendingTelegramTurn = PendingTelegramTurn,
|
|
107
|
+
> {
|
|
108
|
+
get: () => TTurn | undefined;
|
|
109
|
+
has: () => boolean;
|
|
110
|
+
set: (turn: TTurn) => void;
|
|
111
|
+
clear: () => void;
|
|
112
|
+
getChatId: () => number | undefined;
|
|
113
|
+
getReplyToMessageId: () => number | undefined;
|
|
114
|
+
getSourceMessageIds: () => number[] | undefined;
|
|
115
|
+
}
|
|
46
116
|
|
|
47
117
|
export interface TelegramDispatchGuardState {
|
|
48
118
|
compactionInProgress: boolean;
|
|
@@ -52,39 +122,91 @@ export interface TelegramDispatchGuardState {
|
|
|
52
122
|
hasPendingMessages: boolean;
|
|
53
123
|
}
|
|
54
124
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
125
|
+
export function getTelegramQueueLaneContract(
|
|
126
|
+
lane: TelegramQueueLane,
|
|
127
|
+
): TelegramQueueLaneContract {
|
|
128
|
+
const contract = TELEGRAM_QUEUE_LANE_CONTRACTS.find(
|
|
129
|
+
(entry) => entry.lane === lane,
|
|
130
|
+
);
|
|
131
|
+
if (!contract) throw new Error(`Unknown Telegram queue lane: ${lane}`);
|
|
132
|
+
return contract;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getTelegramQueueItemAdmissionMode(
|
|
136
|
+
item: Pick<TelegramQueueItem, "queueLane">,
|
|
137
|
+
): TelegramQueueAdmissionMode {
|
|
138
|
+
return getTelegramQueueLaneContract(item.queueLane).admissionMode;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isTelegramQueueItemAdmissionValid(
|
|
142
|
+
item: Pick<TelegramQueueItem, "kind" | "queueLane">,
|
|
143
|
+
): boolean {
|
|
144
|
+
return getTelegramQueueLaneContract(item.queueLane).allowedKinds.includes(
|
|
145
|
+
item.kind,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function assertTelegramQueueItemAdmissionValid(
|
|
150
|
+
item: Pick<TelegramQueueItem, "kind" | "queueLane">,
|
|
151
|
+
): void {
|
|
152
|
+
if (isTelegramQueueItemAdmissionValid(item)) return;
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Invalid Telegram queue admission: ${item.kind} item cannot use ${item.queueLane} lane`,
|
|
155
|
+
);
|
|
59
156
|
}
|
|
60
157
|
|
|
61
158
|
function getTelegramQueueLaneRank(lane: TelegramQueueLane): number {
|
|
62
|
-
|
|
63
|
-
case "control":
|
|
64
|
-
return 0;
|
|
65
|
-
case "priority":
|
|
66
|
-
return 1;
|
|
67
|
-
default:
|
|
68
|
-
return 2;
|
|
69
|
-
}
|
|
159
|
+
return getTelegramQueueLaneContract(lane).dispatchRank;
|
|
70
160
|
}
|
|
71
161
|
|
|
72
|
-
export function isPendingTelegramTurn(
|
|
73
|
-
item: TelegramQueueItem
|
|
162
|
+
export function isPendingTelegramTurn<TContext = unknown>(
|
|
163
|
+
item: TelegramQueueItem<TContext>,
|
|
74
164
|
): item is PendingTelegramTurn {
|
|
75
165
|
return item.kind === "prompt";
|
|
76
166
|
}
|
|
77
167
|
|
|
168
|
+
export function createTelegramQueueStore<TContext = unknown>(
|
|
169
|
+
initialItems: TelegramQueueItem<TContext>[] = [],
|
|
170
|
+
): TelegramQueueStateStore<TContext> {
|
|
171
|
+
let queuedItems = initialItems;
|
|
172
|
+
return {
|
|
173
|
+
getQueuedItems: () => queuedItems,
|
|
174
|
+
setQueuedItems: (items) => {
|
|
175
|
+
queuedItems = items;
|
|
176
|
+
},
|
|
177
|
+
hasQueuedItems: () => queuedItems.length > 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function createTelegramActiveTurnStore<
|
|
182
|
+
TTurn extends PendingTelegramTurn = PendingTelegramTurn,
|
|
183
|
+
>(): TelegramActiveTurnStore<TTurn> {
|
|
184
|
+
let activeTurn: TTurn | undefined;
|
|
185
|
+
return {
|
|
186
|
+
get: () => activeTurn,
|
|
187
|
+
has: () => !!activeTurn,
|
|
188
|
+
set: (turn) => {
|
|
189
|
+
activeTurn = { ...turn };
|
|
190
|
+
},
|
|
191
|
+
clear: () => {
|
|
192
|
+
activeTurn = undefined;
|
|
193
|
+
},
|
|
194
|
+
getChatId: () => activeTurn?.chatId,
|
|
195
|
+
getReplyToMessageId: () => activeTurn?.replyToMessageId,
|
|
196
|
+
getSourceMessageIds: () => activeTurn?.sourceMessageIds,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
78
200
|
// --- Queue Mutations ---
|
|
79
201
|
|
|
80
|
-
export function partitionTelegramQueueItemsForHistory(
|
|
81
|
-
items: TelegramQueueItem[],
|
|
202
|
+
export function partitionTelegramQueueItemsForHistory<TContext = unknown>(
|
|
203
|
+
items: TelegramQueueItem<TContext>[],
|
|
82
204
|
): {
|
|
83
205
|
historyTurns: PendingTelegramTurn[];
|
|
84
|
-
remainingItems: TelegramQueueItem[];
|
|
206
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
85
207
|
} {
|
|
86
208
|
const historyTurns: PendingTelegramTurn[] = [];
|
|
87
|
-
const remainingItems: TelegramQueueItem[] = [];
|
|
209
|
+
const remainingItems: TelegramQueueItem<TContext>[] = [];
|
|
88
210
|
for (const item of items) {
|
|
89
211
|
if (isPendingTelegramTurn(item)) {
|
|
90
212
|
historyTurns.push(item);
|
|
@@ -95,10 +217,36 @@ export function partitionTelegramQueueItemsForHistory(
|
|
|
95
217
|
return { historyTurns, remainingItems };
|
|
96
218
|
}
|
|
97
219
|
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
|
|
220
|
+
export function planTelegramPromptEnqueue<TContext = unknown>(
|
|
221
|
+
items: TelegramQueueItem<TContext>[],
|
|
222
|
+
preserveQueuedTurnsAsHistory: boolean,
|
|
223
|
+
): {
|
|
224
|
+
historyTurns: PendingTelegramTurn[];
|
|
225
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
226
|
+
} {
|
|
227
|
+
if (!preserveQueuedTurnsAsHistory) {
|
|
228
|
+
return { historyTurns: [], remainingItems: items };
|
|
229
|
+
}
|
|
230
|
+
return partitionTelegramQueueItemsForHistory(items);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function appendTelegramQueueItem<
|
|
234
|
+
TContext = unknown,
|
|
235
|
+
TItem extends TelegramQueueItem<TContext> = TelegramQueueItem<TContext>,
|
|
236
|
+
>(
|
|
237
|
+
items: TelegramQueueItem<TContext>[],
|
|
238
|
+
item: TItem,
|
|
239
|
+
): TelegramQueueItem<TContext>[] {
|
|
240
|
+
assertTelegramQueueItemAdmissionValid(item);
|
|
241
|
+
return [...items, item];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function compareTelegramQueueItems<TContext = unknown>(
|
|
245
|
+
left: TelegramQueueItem<TContext>,
|
|
246
|
+
right: TelegramQueueItem<TContext>,
|
|
101
247
|
): number {
|
|
248
|
+
assertTelegramQueueItemAdmissionValid(left);
|
|
249
|
+
assertTelegramQueueItemAdmissionValid(right);
|
|
102
250
|
const laneRankDelta =
|
|
103
251
|
getTelegramQueueLaneRank(left.queueLane) -
|
|
104
252
|
getTelegramQueueLaneRank(right.queueLane);
|
|
@@ -109,10 +257,10 @@ export function compareTelegramQueueItems(
|
|
|
109
257
|
return left.queueOrder - right.queueOrder;
|
|
110
258
|
}
|
|
111
259
|
|
|
112
|
-
export function removeTelegramQueueItemsByMessageIds(
|
|
113
|
-
items: TelegramQueueItem[],
|
|
260
|
+
export function removeTelegramQueueItemsByMessageIds<TContext = unknown>(
|
|
261
|
+
items: TelegramQueueItem<TContext>[],
|
|
114
262
|
messageIds: number[],
|
|
115
|
-
): { items: TelegramQueueItem[]; removedCount: number } {
|
|
263
|
+
): { items: TelegramQueueItem<TContext>[]; removedCount: number } {
|
|
116
264
|
if (messageIds.length === 0 || items.length === 0) {
|
|
117
265
|
return { items, removedCount: 0 };
|
|
118
266
|
}
|
|
@@ -129,10 +277,10 @@ export function removeTelegramQueueItemsByMessageIds(
|
|
|
129
277
|
};
|
|
130
278
|
}
|
|
131
279
|
|
|
132
|
-
export function clearTelegramQueuePromptPriority(
|
|
133
|
-
items: TelegramQueueItem[],
|
|
280
|
+
export function clearTelegramQueuePromptPriority<TContext = unknown>(
|
|
281
|
+
items: TelegramQueueItem<TContext>[],
|
|
134
282
|
messageId: number,
|
|
135
|
-
): { items: TelegramQueueItem[]; changed: boolean } {
|
|
283
|
+
): { items: TelegramQueueItem<TContext>[]; changed: boolean } {
|
|
136
284
|
let changed = false;
|
|
137
285
|
const nextItems = items.map((item) => {
|
|
138
286
|
if (
|
|
@@ -152,11 +300,11 @@ export function clearTelegramQueuePromptPriority(
|
|
|
152
300
|
return { items: nextItems, changed };
|
|
153
301
|
}
|
|
154
302
|
|
|
155
|
-
export function prioritizeTelegramQueuePrompt(
|
|
156
|
-
items: TelegramQueueItem[],
|
|
303
|
+
export function prioritizeTelegramQueuePrompt<TContext = unknown>(
|
|
304
|
+
items: TelegramQueueItem<TContext>[],
|
|
157
305
|
messageId: number,
|
|
158
306
|
laneOrder: number,
|
|
159
|
-
): { items: TelegramQueueItem[]; changed: boolean } {
|
|
307
|
+
): { items: TelegramQueueItem<TContext>[]; changed: boolean } {
|
|
160
308
|
let changed = false;
|
|
161
309
|
const nextItems = items.map((item) => {
|
|
162
310
|
if (
|
|
@@ -175,10 +323,13 @@ export function prioritizeTelegramQueuePrompt(
|
|
|
175
323
|
return { items: nextItems, changed };
|
|
176
324
|
}
|
|
177
325
|
|
|
178
|
-
export function consumeDispatchedTelegramPrompt(
|
|
179
|
-
items: TelegramQueueItem[],
|
|
326
|
+
export function consumeDispatchedTelegramPrompt<TContext = unknown>(
|
|
327
|
+
items: TelegramQueueItem<TContext>[],
|
|
180
328
|
hasPendingDispatch: boolean,
|
|
181
|
-
): {
|
|
329
|
+
): {
|
|
330
|
+
activeTurn?: PendingTelegramTurn;
|
|
331
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
332
|
+
} {
|
|
182
333
|
if (!hasPendingDispatch) {
|
|
183
334
|
return { activeTurn: undefined, remainingItems: items };
|
|
184
335
|
}
|
|
@@ -189,15 +340,17 @@ export function consumeDispatchedTelegramPrompt(
|
|
|
189
340
|
return { activeTurn: nextItem, remainingItems: items.slice(1) };
|
|
190
341
|
}
|
|
191
342
|
|
|
192
|
-
function formatTelegramQueueItemStatusSummary
|
|
343
|
+
function formatTelegramQueueItemStatusSummary<TContext = unknown>(
|
|
344
|
+
item: TelegramQueueItem<TContext>,
|
|
345
|
+
): string {
|
|
193
346
|
if (item.queueLane === "priority") {
|
|
194
347
|
return `⬆ ${item.statusSummary}`;
|
|
195
348
|
}
|
|
196
349
|
return item.statusSummary;
|
|
197
350
|
}
|
|
198
351
|
|
|
199
|
-
export function formatQueuedTelegramItemsStatus(
|
|
200
|
-
items: TelegramQueueItem[],
|
|
352
|
+
export function formatQueuedTelegramItemsStatus<TContext = unknown>(
|
|
353
|
+
items: TelegramQueueItem<TContext>[],
|
|
201
354
|
): string {
|
|
202
355
|
if (items.length === 0) return "";
|
|
203
356
|
const previewCount = 4;
|
|
@@ -222,45 +375,90 @@ export function canDispatchTelegramTurnState(
|
|
|
222
375
|
);
|
|
223
376
|
}
|
|
224
377
|
|
|
225
|
-
export
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
378
|
+
export interface TelegramDispatchReadinessDeps<TContext> {
|
|
379
|
+
isCompactionInProgress: () => boolean;
|
|
380
|
+
hasActiveTurn: () => boolean;
|
|
381
|
+
hasDispatchPending: () => boolean;
|
|
382
|
+
isIdle: (ctx: TContext) => boolean;
|
|
383
|
+
hasPendingMessages: (ctx: TContext) => boolean;
|
|
229
384
|
}
|
|
230
385
|
|
|
231
|
-
export function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
386
|
+
export function createTelegramDispatchReadinessChecker<TContext>(
|
|
387
|
+
deps: TelegramDispatchReadinessDeps<TContext>,
|
|
388
|
+
): (ctx: TContext) => boolean {
|
|
389
|
+
return (ctx) =>
|
|
390
|
+
canDispatchTelegramTurnState({
|
|
391
|
+
compactionInProgress: deps.isCompactionInProgress(),
|
|
392
|
+
hasActiveTelegramTurn: deps.hasActiveTurn(),
|
|
393
|
+
hasPendingTelegramDispatch: deps.hasDispatchPending(),
|
|
394
|
+
isIdle: deps.isIdle(ctx),
|
|
395
|
+
hasPendingMessages: deps.hasPendingMessages(ctx),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function buildPendingTelegramControlItem<TContext = unknown>(options: {
|
|
400
|
+
chatId: number;
|
|
401
|
+
replyToMessageId: number;
|
|
402
|
+
controlType: PendingTelegramControlItem<TContext>["controlType"];
|
|
403
|
+
queueOrder: number;
|
|
404
|
+
laneOrder: number;
|
|
405
|
+
statusSummary: string;
|
|
406
|
+
execute: PendingTelegramControlItem<TContext>["execute"];
|
|
407
|
+
}): PendingTelegramControlItem<TContext> {
|
|
408
|
+
return {
|
|
409
|
+
kind: "control",
|
|
410
|
+
controlType: options.controlType,
|
|
411
|
+
chatId: options.chatId,
|
|
412
|
+
replyToMessageId: options.replyToMessageId,
|
|
413
|
+
queueOrder: options.queueOrder,
|
|
414
|
+
queueLane: "control",
|
|
415
|
+
laneOrder: options.laneOrder,
|
|
416
|
+
statusSummary: options.statusSummary,
|
|
417
|
+
execute: options.execute,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface TelegramControlItemBuilderDeps {
|
|
422
|
+
allocateItemOrder: () => number;
|
|
423
|
+
allocateControlOrder: () => number;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function createTelegramControlItemBuilder<TContext = unknown>(
|
|
427
|
+
deps: TelegramControlItemBuilderDeps,
|
|
428
|
+
): (options: {
|
|
429
|
+
chatId: number;
|
|
430
|
+
replyToMessageId: number;
|
|
431
|
+
controlType: PendingTelegramControlItem<TContext>["controlType"];
|
|
432
|
+
statusSummary: string;
|
|
433
|
+
execute: PendingTelegramControlItem<TContext>["execute"];
|
|
434
|
+
}) => PendingTelegramControlItem<TContext> {
|
|
435
|
+
return (options) =>
|
|
436
|
+
buildPendingTelegramControlItem<TContext>({
|
|
437
|
+
...options,
|
|
438
|
+
queueOrder: deps.allocateItemOrder(),
|
|
439
|
+
laneOrder: deps.allocateControlOrder(),
|
|
440
|
+
});
|
|
243
441
|
}
|
|
244
442
|
|
|
245
443
|
// --- Dispatch Planning ---
|
|
246
444
|
|
|
247
|
-
export type TelegramQueueDispatchAction =
|
|
248
|
-
| { kind: "none"; remainingItems: TelegramQueueItem[] }
|
|
445
|
+
export type TelegramQueueDispatchAction<TContext = unknown> =
|
|
446
|
+
| { kind: "none"; remainingItems: TelegramQueueItem<TContext>[] }
|
|
249
447
|
| {
|
|
250
448
|
kind: "control";
|
|
251
|
-
item: PendingTelegramControlItem
|
|
252
|
-
remainingItems: TelegramQueueItem[];
|
|
449
|
+
item: PendingTelegramControlItem<TContext>;
|
|
450
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
253
451
|
}
|
|
254
452
|
| {
|
|
255
453
|
kind: "prompt";
|
|
256
454
|
item: PendingTelegramTurn;
|
|
257
|
-
remainingItems: TelegramQueueItem[];
|
|
455
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
258
456
|
};
|
|
259
457
|
|
|
260
|
-
export function planNextTelegramQueueAction(
|
|
261
|
-
items: TelegramQueueItem[],
|
|
458
|
+
export function planNextTelegramQueueAction<TContext = unknown>(
|
|
459
|
+
items: TelegramQueueItem<TContext>[],
|
|
262
460
|
canDispatch: boolean,
|
|
263
|
-
): TelegramQueueDispatchAction {
|
|
461
|
+
): TelegramQueueDispatchAction<TContext> {
|
|
264
462
|
if (!canDispatch || items.length === 0) {
|
|
265
463
|
return { kind: "none", remainingItems: items };
|
|
266
464
|
}
|
|
@@ -268,6 +466,7 @@ export function planNextTelegramQueueAction(
|
|
|
268
466
|
if (!firstItem) {
|
|
269
467
|
return { kind: "none", remainingItems: items };
|
|
270
468
|
}
|
|
469
|
+
assertTelegramQueueItemAdmissionValid(firstItem);
|
|
271
470
|
if (isPendingTelegramTurn(firstItem)) {
|
|
272
471
|
return { kind: "prompt", item: firstItem, remainingItems: items };
|
|
273
472
|
}
|
|
@@ -288,19 +487,74 @@ export function shouldDispatchAfterTelegramAgentEnd(options: {
|
|
|
288
487
|
|
|
289
488
|
// --- Agent Runtime ---
|
|
290
489
|
|
|
291
|
-
export interface TelegramAgentStartPlan {
|
|
490
|
+
export interface TelegramAgentStartPlan<TContext = unknown> {
|
|
292
491
|
activeTurn?: PendingTelegramTurn;
|
|
293
|
-
remainingItems: TelegramQueueItem[];
|
|
492
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
294
493
|
shouldResetPendingModelSwitch: boolean;
|
|
295
494
|
shouldResetToolExecutions: boolean;
|
|
296
495
|
shouldClearDispatchPending: boolean;
|
|
297
496
|
}
|
|
298
497
|
|
|
299
|
-
export
|
|
300
|
-
|
|
498
|
+
export interface TelegramAgentStartRuntimeDeps<
|
|
499
|
+
TTurn extends PendingTelegramTurn,
|
|
500
|
+
TContext = unknown,
|
|
501
|
+
> {
|
|
502
|
+
queuedItems: TelegramQueueItem<TContext>[];
|
|
301
503
|
hasPendingDispatch: boolean;
|
|
302
504
|
hasActiveTurn: boolean;
|
|
303
|
-
|
|
505
|
+
resetToolExecutions: () => void;
|
|
506
|
+
resetPendingModelSwitch: () => void;
|
|
507
|
+
setQueuedItems: (items: TelegramQueueItem<TContext>[]) => void;
|
|
508
|
+
clearDispatchPending: () => void;
|
|
509
|
+
setActiveTurn: (turn: TTurn) => void;
|
|
510
|
+
createPreviewState: () => void;
|
|
511
|
+
startTypingLoop: () => void;
|
|
512
|
+
updateStatus: () => void;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export interface TelegramAgentStartHookRuntimeDeps<
|
|
516
|
+
TTurn extends PendingTelegramTurn,
|
|
517
|
+
TContext = unknown,
|
|
518
|
+
> {
|
|
519
|
+
setAbortHandler: (ctx: TContext) => void;
|
|
520
|
+
getQueuedItems: () => TelegramQueueItem<TContext>[];
|
|
521
|
+
hasPendingDispatch: () => boolean;
|
|
522
|
+
hasActiveTurn: () => boolean;
|
|
523
|
+
resetToolExecutions: () => void;
|
|
524
|
+
resetPendingModelSwitch: () => void;
|
|
525
|
+
setQueuedItems: (items: TelegramQueueItem<TContext>[]) => void;
|
|
526
|
+
clearDispatchPending: () => void;
|
|
527
|
+
setActiveTurn: (turn: TTurn) => void;
|
|
528
|
+
createPreviewState: () => void;
|
|
529
|
+
startTypingLoop: (ctx: TContext) => void;
|
|
530
|
+
updateStatus: (ctx: TContext) => void;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export type TelegramAgentStartHookEvent = unknown;
|
|
534
|
+
|
|
535
|
+
export interface TelegramToolExecutionRuntimeDeps {
|
|
536
|
+
hasActiveTurn: () => boolean;
|
|
537
|
+
getActiveToolExecutions: () => number;
|
|
538
|
+
setActiveToolExecutions: (count: number) => void;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export interface TelegramToolExecutionEndRuntimeDeps extends TelegramToolExecutionRuntimeDeps {
|
|
542
|
+
triggerPendingModelSwitchAbort: () => void;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export interface TelegramToolExecutionHookRuntimeDeps<
|
|
546
|
+
TContext,
|
|
547
|
+
> extends TelegramToolExecutionRuntimeDeps {
|
|
548
|
+
triggerPendingModelSwitchAbort: (ctx: TContext) => unknown;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export type TelegramToolExecutionHookEvent = unknown;
|
|
552
|
+
|
|
553
|
+
export function buildTelegramAgentStartPlan<TContext = unknown>(options: {
|
|
554
|
+
queuedItems: TelegramQueueItem<TContext>[];
|
|
555
|
+
hasPendingDispatch: boolean;
|
|
556
|
+
hasActiveTurn: boolean;
|
|
557
|
+
}): TelegramAgentStartPlan<TContext> {
|
|
304
558
|
if (options.hasActiveTurn || !options.hasPendingDispatch) {
|
|
305
559
|
return {
|
|
306
560
|
activeTurn: undefined,
|
|
@@ -323,6 +577,52 @@ export function buildTelegramAgentStartPlan(options: {
|
|
|
323
577
|
};
|
|
324
578
|
}
|
|
325
579
|
|
|
580
|
+
export function handleTelegramAgentStartRuntime<
|
|
581
|
+
TTurn extends PendingTelegramTurn,
|
|
582
|
+
TContext = unknown,
|
|
583
|
+
>(deps: TelegramAgentStartRuntimeDeps<TTurn, TContext>): void {
|
|
584
|
+
const startPlan = buildTelegramAgentStartPlan({
|
|
585
|
+
queuedItems: deps.queuedItems,
|
|
586
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
587
|
+
hasActiveTurn: deps.hasActiveTurn,
|
|
588
|
+
});
|
|
589
|
+
if (startPlan.shouldResetToolExecutions) deps.resetToolExecutions();
|
|
590
|
+
if (startPlan.shouldResetPendingModelSwitch) deps.resetPendingModelSwitch();
|
|
591
|
+
deps.setQueuedItems(startPlan.remainingItems);
|
|
592
|
+
if (startPlan.shouldClearDispatchPending) deps.clearDispatchPending();
|
|
593
|
+
if (startPlan.activeTurn) {
|
|
594
|
+
deps.setActiveTurn(startPlan.activeTurn as TTurn);
|
|
595
|
+
deps.createPreviewState();
|
|
596
|
+
deps.startTypingLoop();
|
|
597
|
+
}
|
|
598
|
+
deps.updateStatus();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function createTelegramAgentStartHook<
|
|
602
|
+
TTurn extends PendingTelegramTurn,
|
|
603
|
+
TContext = unknown,
|
|
604
|
+
>(deps: TelegramAgentStartHookRuntimeDeps<TTurn, TContext>) {
|
|
605
|
+
return async function onAgentStart(
|
|
606
|
+
_event: TelegramAgentStartHookEvent,
|
|
607
|
+
ctx: TContext,
|
|
608
|
+
): Promise<void> {
|
|
609
|
+
deps.setAbortHandler(ctx);
|
|
610
|
+
handleTelegramAgentStartRuntime<TTurn, TContext>({
|
|
611
|
+
queuedItems: deps.getQueuedItems(),
|
|
612
|
+
hasPendingDispatch: deps.hasPendingDispatch(),
|
|
613
|
+
hasActiveTurn: deps.hasActiveTurn(),
|
|
614
|
+
resetToolExecutions: deps.resetToolExecutions,
|
|
615
|
+
resetPendingModelSwitch: deps.resetPendingModelSwitch,
|
|
616
|
+
setQueuedItems: deps.setQueuedItems,
|
|
617
|
+
clearDispatchPending: deps.clearDispatchPending,
|
|
618
|
+
setActiveTurn: deps.setActiveTurn,
|
|
619
|
+
createPreviewState: deps.createPreviewState,
|
|
620
|
+
startTypingLoop: () => deps.startTypingLoop(ctx),
|
|
621
|
+
updateStatus: () => deps.updateStatus(ctx),
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
326
626
|
export function getNextTelegramToolExecutionCount(options: {
|
|
327
627
|
hasActiveTurn: boolean;
|
|
328
628
|
currentCount: number;
|
|
@@ -335,6 +635,75 @@ export function getNextTelegramToolExecutionCount(options: {
|
|
|
335
635
|
return Math.max(0, options.currentCount - 1);
|
|
336
636
|
}
|
|
337
637
|
|
|
638
|
+
export function handleTelegramToolExecutionStartRuntime(
|
|
639
|
+
deps: TelegramToolExecutionRuntimeDeps,
|
|
640
|
+
): void {
|
|
641
|
+
deps.setActiveToolExecutions(
|
|
642
|
+
getNextTelegramToolExecutionCount({
|
|
643
|
+
hasActiveTurn: deps.hasActiveTurn(),
|
|
644
|
+
currentCount: deps.getActiveToolExecutions(),
|
|
645
|
+
event: "start",
|
|
646
|
+
}),
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export function handleTelegramToolExecutionEndRuntime(
|
|
651
|
+
deps: TelegramToolExecutionEndRuntimeDeps,
|
|
652
|
+
): void {
|
|
653
|
+
const hasActiveTurn = deps.hasActiveTurn();
|
|
654
|
+
deps.setActiveToolExecutions(
|
|
655
|
+
getNextTelegramToolExecutionCount({
|
|
656
|
+
hasActiveTurn,
|
|
657
|
+
currentCount: deps.getActiveToolExecutions(),
|
|
658
|
+
event: "end",
|
|
659
|
+
}),
|
|
660
|
+
);
|
|
661
|
+
if (hasActiveTurn) deps.triggerPendingModelSwitchAbort();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export type TelegramAgentLifecycleHooksRuntimeDeps<
|
|
665
|
+
TTurn extends PendingTelegramTurn,
|
|
666
|
+
TContext,
|
|
667
|
+
TMessage,
|
|
668
|
+
> = TelegramAgentStartHookRuntimeDeps<TTurn, TContext> &
|
|
669
|
+
TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage> &
|
|
670
|
+
TelegramToolExecutionHookRuntimeDeps<TContext>;
|
|
671
|
+
|
|
672
|
+
export function createTelegramAgentLifecycleHooks<
|
|
673
|
+
TTurn extends PendingTelegramTurn,
|
|
674
|
+
TContext,
|
|
675
|
+
TMessage,
|
|
676
|
+
>(deps: TelegramAgentLifecycleHooksRuntimeDeps<TTurn, TContext, TMessage>) {
|
|
677
|
+
return {
|
|
678
|
+
onAgentStart: createTelegramAgentStartHook<TTurn, TContext>(deps),
|
|
679
|
+
onAgentEnd: createTelegramAgentEndHook<TTurn, TContext, TMessage>(deps),
|
|
680
|
+
...createTelegramToolExecutionHooks<TContext>(deps),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function createTelegramToolExecutionHooks<TContext>(
|
|
685
|
+
deps: TelegramToolExecutionHookRuntimeDeps<TContext>,
|
|
686
|
+
) {
|
|
687
|
+
return {
|
|
688
|
+
onToolExecutionStart: (): void => {
|
|
689
|
+
handleTelegramToolExecutionStartRuntime(deps);
|
|
690
|
+
},
|
|
691
|
+
onToolExecutionEnd: (
|
|
692
|
+
_event: TelegramToolExecutionHookEvent,
|
|
693
|
+
ctx: TContext,
|
|
694
|
+
): void => {
|
|
695
|
+
handleTelegramToolExecutionEndRuntime({
|
|
696
|
+
hasActiveTurn: deps.hasActiveTurn,
|
|
697
|
+
getActiveToolExecutions: deps.getActiveToolExecutions,
|
|
698
|
+
setActiveToolExecutions: deps.setActiveToolExecutions,
|
|
699
|
+
triggerPendingModelSwitchAbort: () => {
|
|
700
|
+
deps.triggerPendingModelSwitchAbort(ctx);
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
},
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
338
707
|
// --- Agent End Lifecycle ---
|
|
339
708
|
|
|
340
709
|
export interface TelegramAgentEndPlan {
|
|
@@ -345,6 +714,66 @@ export interface TelegramAgentEndPlan {
|
|
|
345
714
|
shouldSendAttachmentNotice: boolean;
|
|
346
715
|
}
|
|
347
716
|
|
|
717
|
+
export interface TelegramAgentEndAssistantResult {
|
|
718
|
+
text?: string;
|
|
719
|
+
stopReason?: string;
|
|
720
|
+
errorMessage?: string;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export interface TelegramAgentEndRuntimeDeps<
|
|
724
|
+
TTurn extends PendingTelegramTurn,
|
|
725
|
+
> {
|
|
726
|
+
turn: TTurn | undefined;
|
|
727
|
+
assistant: TelegramAgentEndAssistantResult;
|
|
728
|
+
preserveQueuedTurnsAsHistory: boolean;
|
|
729
|
+
resetRuntimeState: () => void;
|
|
730
|
+
updateStatus: () => void;
|
|
731
|
+
dispatchNextQueuedTelegramTurn: () => void;
|
|
732
|
+
clearPreview: (chatId: number) => Promise<void>;
|
|
733
|
+
setPreviewPendingText: (text: string) => void;
|
|
734
|
+
finalizeMarkdownPreview: (
|
|
735
|
+
chatId: number,
|
|
736
|
+
markdown: string,
|
|
737
|
+
replyToMessageId: number,
|
|
738
|
+
) => Promise<boolean>;
|
|
739
|
+
sendMarkdownReply: (
|
|
740
|
+
chatId: number,
|
|
741
|
+
replyToMessageId: number,
|
|
742
|
+
markdown: string,
|
|
743
|
+
) => Promise<unknown>;
|
|
744
|
+
sendTextReply: (
|
|
745
|
+
chatId: number,
|
|
746
|
+
replyToMessageId: number,
|
|
747
|
+
text: string,
|
|
748
|
+
) => Promise<unknown>;
|
|
749
|
+
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export interface TelegramAgentEndHookRuntimeDeps<
|
|
753
|
+
TTurn extends PendingTelegramTurn,
|
|
754
|
+
TContext,
|
|
755
|
+
TMessage,
|
|
756
|
+
> {
|
|
757
|
+
getActiveTurn: () => TTurn | undefined;
|
|
758
|
+
extractAssistant: (
|
|
759
|
+
messages: readonly TMessage[],
|
|
760
|
+
) => TelegramAgentEndAssistantResult;
|
|
761
|
+
getPreserveQueuedTurnsAsHistory: () => boolean;
|
|
762
|
+
resetRuntimeState: () => void;
|
|
763
|
+
updateStatus: (ctx: TContext) => void;
|
|
764
|
+
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
765
|
+
clearPreview: (chatId: number) => Promise<void>;
|
|
766
|
+
setPreviewPendingText: (text: string) => void;
|
|
767
|
+
finalizeMarkdownPreview: TelegramAgentEndRuntimeDeps<TTurn>["finalizeMarkdownPreview"];
|
|
768
|
+
sendMarkdownReply: TelegramAgentEndRuntimeDeps<TTurn>["sendMarkdownReply"];
|
|
769
|
+
sendTextReply: TelegramAgentEndRuntimeDeps<TTurn>["sendTextReply"];
|
|
770
|
+
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export interface TelegramAgentEndHookEvent<TMessage> {
|
|
774
|
+
messages: readonly TMessage[];
|
|
775
|
+
}
|
|
776
|
+
|
|
348
777
|
export function buildTelegramAgentEndPlan(options: {
|
|
349
778
|
hasTurn: boolean;
|
|
350
779
|
stopReason?: string;
|
|
@@ -411,42 +840,105 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
411
840
|
};
|
|
412
841
|
}
|
|
413
842
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
843
|
+
export function createTelegramAgentEndHook<
|
|
844
|
+
TTurn extends PendingTelegramTurn,
|
|
845
|
+
TContext,
|
|
846
|
+
TMessage,
|
|
847
|
+
>(deps: TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage>) {
|
|
848
|
+
return async function onAgentEnd(
|
|
849
|
+
event: TelegramAgentEndHookEvent<TMessage>,
|
|
850
|
+
ctx: TContext,
|
|
851
|
+
): Promise<void> {
|
|
852
|
+
const turn = deps.getActiveTurn();
|
|
853
|
+
await handleTelegramAgentEndRuntime({
|
|
854
|
+
turn,
|
|
855
|
+
assistant: turn ? deps.extractAssistant(event.messages) : {},
|
|
856
|
+
preserveQueuedTurnsAsHistory: deps.getPreserveQueuedTurnsAsHistory(),
|
|
857
|
+
resetRuntimeState: deps.resetRuntimeState,
|
|
858
|
+
updateStatus: () => deps.updateStatus(ctx),
|
|
859
|
+
dispatchNextQueuedTelegramTurn: () =>
|
|
860
|
+
deps.dispatchNextQueuedTelegramTurn(ctx),
|
|
861
|
+
clearPreview: deps.clearPreview,
|
|
862
|
+
setPreviewPendingText: deps.setPreviewPendingText,
|
|
863
|
+
finalizeMarkdownPreview: deps.finalizeMarkdownPreview,
|
|
864
|
+
sendMarkdownReply: deps.sendMarkdownReply,
|
|
865
|
+
sendTextReply: deps.sendTextReply,
|
|
866
|
+
sendQueuedAttachments: deps.sendQueuedAttachments,
|
|
867
|
+
});
|
|
868
|
+
};
|
|
419
869
|
}
|
|
420
870
|
|
|
421
|
-
export function
|
|
422
|
-
|
|
423
|
-
):
|
|
424
|
-
|
|
871
|
+
export async function handleTelegramAgentEndRuntime<
|
|
872
|
+
TTurn extends PendingTelegramTurn,
|
|
873
|
+
>(deps: TelegramAgentEndRuntimeDeps<TTurn>): Promise<void> {
|
|
874
|
+
const { turn, assistant } = deps;
|
|
875
|
+
const finalText = assistant.text;
|
|
876
|
+
deps.resetRuntimeState();
|
|
877
|
+
deps.updateStatus();
|
|
878
|
+
const endPlan = buildTelegramAgentEndPlan({
|
|
879
|
+
hasTurn: !!turn,
|
|
880
|
+
stopReason: assistant.stopReason,
|
|
881
|
+
hasFinalText: !!finalText,
|
|
882
|
+
hasQueuedAttachments: (turn?.queuedAttachments.length ?? 0) > 0,
|
|
883
|
+
preserveQueuedTurnsAsHistory: deps.preserveQueuedTurnsAsHistory,
|
|
884
|
+
});
|
|
885
|
+
if (!turn) {
|
|
886
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (endPlan.shouldClearPreview) {
|
|
890
|
+
await deps.clearPreview(turn.chatId);
|
|
891
|
+
}
|
|
892
|
+
if (endPlan.shouldSendErrorMessage) {
|
|
893
|
+
await deps.sendTextReply(
|
|
894
|
+
turn.chatId,
|
|
895
|
+
turn.replyToMessageId,
|
|
896
|
+
assistant.errorMessage ||
|
|
897
|
+
"Telegram bridge: pi failed while processing the request.",
|
|
898
|
+
);
|
|
899
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (finalText) deps.setPreviewPendingText(finalText);
|
|
903
|
+
if (endPlan.kind === "text" && finalText) {
|
|
904
|
+
const finalized = await deps.finalizeMarkdownPreview(
|
|
905
|
+
turn.chatId,
|
|
906
|
+
finalText,
|
|
907
|
+
turn.replyToMessageId,
|
|
908
|
+
);
|
|
909
|
+
if (!finalized) {
|
|
910
|
+
await deps.clearPreview(turn.chatId);
|
|
911
|
+
await deps.sendMarkdownReply(
|
|
912
|
+
turn.chatId,
|
|
913
|
+
turn.replyToMessageId,
|
|
914
|
+
finalText,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (endPlan.shouldSendAttachmentNotice) {
|
|
919
|
+
await deps.sendTextReply(
|
|
920
|
+
turn.chatId,
|
|
921
|
+
turn.replyToMessageId,
|
|
922
|
+
"Attached requested file(s).",
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
await deps.sendQueuedAttachments(turn);
|
|
926
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
425
927
|
}
|
|
426
928
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
currentTelegramModel:
|
|
929
|
+
// --- Session Runtime ---
|
|
930
|
+
|
|
931
|
+
export interface TelegramSessionStartState<TModel = unknown> {
|
|
932
|
+
currentTelegramModel: TModel | undefined;
|
|
431
933
|
activeTelegramToolExecutions: number;
|
|
432
934
|
pendingTelegramModelSwitch: undefined;
|
|
433
935
|
nextQueuedTelegramItemOrder: number;
|
|
434
936
|
nextQueuedTelegramControlOrder: number;
|
|
435
937
|
telegramTurnDispatchPending: boolean;
|
|
436
938
|
compactionInProgress: boolean;
|
|
437
|
-
} {
|
|
438
|
-
return {
|
|
439
|
-
currentTelegramModel: currentModel,
|
|
440
|
-
activeTelegramToolExecutions: 0,
|
|
441
|
-
pendingTelegramModelSwitch: undefined,
|
|
442
|
-
nextQueuedTelegramItemOrder: 0,
|
|
443
|
-
nextQueuedTelegramControlOrder: 0,
|
|
444
|
-
telegramTurnDispatchPending: false,
|
|
445
|
-
compactionInProgress: false,
|
|
446
|
-
};
|
|
447
939
|
}
|
|
448
940
|
|
|
449
|
-
export
|
|
941
|
+
export interface TelegramSessionShutdownState<TQueueItem> {
|
|
450
942
|
queuedTelegramItems: TQueueItem[];
|
|
451
943
|
nextQueuedTelegramItemOrder: number;
|
|
452
944
|
nextQueuedTelegramControlOrder: number;
|
|
@@ -457,7 +949,183 @@ export function buildTelegramSessionShutdownState<TQueueItem>(): {
|
|
|
457
949
|
telegramTurnDispatchPending: boolean;
|
|
458
950
|
compactionInProgress: boolean;
|
|
459
951
|
preserveQueuedTurnsAsHistory: boolean;
|
|
460
|
-
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
export interface TelegramSessionRuntimeCounterState {
|
|
955
|
+
nextQueuedTelegramItemOrder?: number;
|
|
956
|
+
nextQueuedTelegramControlOrder?: number;
|
|
957
|
+
nextPriorityReactionOrder?: number;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export interface TelegramSessionRuntimeFlagState {
|
|
961
|
+
activeTelegramToolExecutions?: number;
|
|
962
|
+
telegramTurnDispatchPending?: boolean;
|
|
963
|
+
compactionInProgress?: boolean;
|
|
964
|
+
preserveQueuedTurnsAsHistory?: boolean;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
export interface TelegramSessionStateApplierDeps<TQueueItem, TModel> {
|
|
968
|
+
setQueuedItems: (items: TQueueItem[]) => void;
|
|
969
|
+
setCurrentModel: (model: TModel | undefined) => void;
|
|
970
|
+
setPendingModelSwitch: (selection: undefined) => void;
|
|
971
|
+
syncCounters: (state: TelegramSessionRuntimeCounterState) => void;
|
|
972
|
+
syncFlags: (state: TelegramSessionRuntimeFlagState) => void;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export interface TelegramSessionStateApplier<TQueueItem, TModel> {
|
|
976
|
+
applyStartState: (state: TelegramSessionStartState<TModel>) => void;
|
|
977
|
+
applyShutdownState: (state: TelegramSessionShutdownState<TQueueItem>) => void;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export interface TelegramSessionStartRuntimeDeps<TModel = unknown> {
|
|
981
|
+
currentModel: TModel | undefined;
|
|
982
|
+
loadConfig: () => Promise<void>;
|
|
983
|
+
applyState: (state: TelegramSessionStartState<TModel>) => void;
|
|
984
|
+
prepareTempDir: () => Promise<unknown>;
|
|
985
|
+
updateStatus: () => void;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
export interface TelegramSessionShutdownRuntimeDeps<TQueueItem> {
|
|
989
|
+
applyState: (state: TelegramSessionShutdownState<TQueueItem>) => void;
|
|
990
|
+
clearPendingMediaGroups: () => void;
|
|
991
|
+
clearModelMenuState: () => void;
|
|
992
|
+
getActiveTurnChatId: () => number | undefined;
|
|
993
|
+
clearPreview: (chatId: number) => Promise<void>;
|
|
994
|
+
clearActiveTurn: () => void;
|
|
995
|
+
clearAbort: () => void;
|
|
996
|
+
stopPolling: () => Promise<void>;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export interface TelegramSessionLifecycleHookRuntimeDeps<
|
|
1000
|
+
TContext,
|
|
1001
|
+
TQueueItem,
|
|
1002
|
+
TModel = unknown,
|
|
1003
|
+
> extends TelegramRuntimeEventRecorderPort {
|
|
1004
|
+
getCurrentModel: (ctx: TContext) => TModel | undefined;
|
|
1005
|
+
loadConfig: () => Promise<void>;
|
|
1006
|
+
applySessionStartState: (state: TelegramSessionStartState<TModel>) => void;
|
|
1007
|
+
prepareTempDir: () => Promise<unknown>;
|
|
1008
|
+
updateStatus: (ctx: TContext) => void;
|
|
1009
|
+
applySessionShutdownState: (
|
|
1010
|
+
state: TelegramSessionShutdownState<TQueueItem>,
|
|
1011
|
+
) => void;
|
|
1012
|
+
clearPendingMediaGroups: () => void;
|
|
1013
|
+
clearModelMenuState: () => void;
|
|
1014
|
+
getActiveTurnChatId: () => number | undefined;
|
|
1015
|
+
clearPreview: (chatId: number) => Promise<void>;
|
|
1016
|
+
clearActiveTurn: () => void;
|
|
1017
|
+
clearAbort: () => void;
|
|
1018
|
+
stopPolling: () => Promise<void>;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
export type TelegramSessionLifecycleHookEvent = unknown;
|
|
1022
|
+
|
|
1023
|
+
export function createTelegramSessionStateApplier<TQueueItem, TModel>(
|
|
1024
|
+
deps: TelegramSessionStateApplierDeps<TQueueItem, TModel>,
|
|
1025
|
+
): TelegramSessionStateApplier<TQueueItem, TModel> {
|
|
1026
|
+
return {
|
|
1027
|
+
applyStartState: (state) => {
|
|
1028
|
+
deps.setCurrentModel(state.currentTelegramModel);
|
|
1029
|
+
deps.setPendingModelSwitch(state.pendingTelegramModelSwitch);
|
|
1030
|
+
deps.syncCounters(state);
|
|
1031
|
+
deps.syncFlags(state);
|
|
1032
|
+
},
|
|
1033
|
+
applyShutdownState: (state) => {
|
|
1034
|
+
deps.setQueuedItems(state.queuedTelegramItems);
|
|
1035
|
+
deps.syncCounters(state);
|
|
1036
|
+
deps.syncFlags(state);
|
|
1037
|
+
deps.setCurrentModel(state.currentTelegramModel);
|
|
1038
|
+
deps.setPendingModelSwitch(state.pendingTelegramModelSwitch);
|
|
1039
|
+
},
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export interface TelegramQueueMutationRuntimeDeps<
|
|
1044
|
+
TContext,
|
|
1045
|
+
> extends TelegramQueueStore<TContext> {
|
|
1046
|
+
ctx: TContext;
|
|
1047
|
+
getNextPriorityReactionOrder?: () => number;
|
|
1048
|
+
incrementNextPriorityReactionOrder?: () => void;
|
|
1049
|
+
updateStatus: (ctx: TContext) => void;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
export interface TelegramQueueMutationControllerDeps<
|
|
1053
|
+
TContext,
|
|
1054
|
+
> extends TelegramQueueStore<TContext> {
|
|
1055
|
+
getNextPriorityReactionOrder?: () => number;
|
|
1056
|
+
incrementNextPriorityReactionOrder?: () => void;
|
|
1057
|
+
updateStatus: (ctx: TContext) => void;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
export interface TelegramQueueMutationController<TContext> {
|
|
1061
|
+
append: (item: TelegramQueueItem<TContext>, ctx: TContext) => void;
|
|
1062
|
+
reorder: (ctx: TContext) => void;
|
|
1063
|
+
removeByMessageIds: (messageIds: number[], ctx: TContext) => number;
|
|
1064
|
+
clearPriorityByMessageId: (messageId: number, ctx: TContext) => boolean;
|
|
1065
|
+
prioritizeByMessageId: (messageId: number, ctx: TContext) => boolean;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export interface TelegramControlQueueControllerDeps<TContext> {
|
|
1069
|
+
appendControlItem: (
|
|
1070
|
+
item: PendingTelegramControlItem<TContext>,
|
|
1071
|
+
ctx: TContext,
|
|
1072
|
+
) => void;
|
|
1073
|
+
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
export interface TelegramControlQueueController<TContext> {
|
|
1077
|
+
enqueue: (item: PendingTelegramControlItem<TContext>, ctx: TContext) => void;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export interface TelegramPromptEnqueueRuntimeDeps<
|
|
1081
|
+
TMessage,
|
|
1082
|
+
TContext = unknown,
|
|
1083
|
+
> extends TelegramQueueStore<TContext> {
|
|
1084
|
+
getPreserveQueuedTurnsAsHistory: () => boolean;
|
|
1085
|
+
setPreserveQueuedTurnsAsHistory: (preserve: boolean) => void;
|
|
1086
|
+
createTurn: (
|
|
1087
|
+
messages: TMessage[],
|
|
1088
|
+
historyTurns: PendingTelegramTurn[],
|
|
1089
|
+
) => Promise<PendingTelegramTurn>;
|
|
1090
|
+
updateStatus: () => void;
|
|
1091
|
+
dispatchNextQueuedTelegramTurn: () => void;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export interface TelegramPromptEnqueueControllerDeps<
|
|
1095
|
+
TMessage,
|
|
1096
|
+
TContext = unknown,
|
|
1097
|
+
> extends TelegramQueueStore<TContext> {
|
|
1098
|
+
getPreserveQueuedTurnsAsHistory: () => boolean;
|
|
1099
|
+
setPreserveQueuedTurnsAsHistory: (preserve: boolean) => void;
|
|
1100
|
+
createTurn: (
|
|
1101
|
+
messages: TMessage[],
|
|
1102
|
+
historyTurns: PendingTelegramTurn[],
|
|
1103
|
+
) => Promise<PendingTelegramTurn>;
|
|
1104
|
+
updateStatus: (ctx: TContext) => void;
|
|
1105
|
+
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export interface TelegramPromptEnqueueController<TMessage, TContext = unknown> {
|
|
1109
|
+
enqueue: (messages: TMessage[], ctx: TContext) => Promise<void>;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
export function buildTelegramSessionStartState<TModel = unknown>(
|
|
1113
|
+
currentModel: TModel | undefined,
|
|
1114
|
+
): TelegramSessionStartState<TModel> {
|
|
1115
|
+
return {
|
|
1116
|
+
currentTelegramModel: currentModel,
|
|
1117
|
+
activeTelegramToolExecutions: 0,
|
|
1118
|
+
pendingTelegramModelSwitch: undefined,
|
|
1119
|
+
nextQueuedTelegramItemOrder: 0,
|
|
1120
|
+
nextQueuedTelegramControlOrder: 0,
|
|
1121
|
+
telegramTurnDispatchPending: false,
|
|
1122
|
+
compactionInProgress: false,
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
export function buildTelegramSessionShutdownState<
|
|
1127
|
+
TQueueItem,
|
|
1128
|
+
>(): TelegramSessionShutdownState<TQueueItem> {
|
|
461
1129
|
return {
|
|
462
1130
|
queuedTelegramItems: [],
|
|
463
1131
|
nextQueuedTelegramItemOrder: 0,
|
|
@@ -472,10 +1140,266 @@ export function buildTelegramSessionShutdownState<TQueueItem>(): {
|
|
|
472
1140
|
};
|
|
473
1141
|
}
|
|
474
1142
|
|
|
1143
|
+
export async function startTelegramSessionRuntime<TModel = unknown>(
|
|
1144
|
+
deps: TelegramSessionStartRuntimeDeps<TModel>,
|
|
1145
|
+
): Promise<void> {
|
|
1146
|
+
await deps.loadConfig();
|
|
1147
|
+
deps.applyState(buildTelegramSessionStartState(deps.currentModel));
|
|
1148
|
+
await deps.prepareTempDir();
|
|
1149
|
+
deps.updateStatus();
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
1153
|
+
deps: TelegramSessionShutdownRuntimeDeps<TQueueItem>,
|
|
1154
|
+
): Promise<void> {
|
|
1155
|
+
deps.applyState(buildTelegramSessionShutdownState<TQueueItem>());
|
|
1156
|
+
deps.clearPendingMediaGroups();
|
|
1157
|
+
deps.clearModelMenuState();
|
|
1158
|
+
const activeTurnChatId = deps.getActiveTurnChatId();
|
|
1159
|
+
if (activeTurnChatId !== undefined) {
|
|
1160
|
+
await deps.clearPreview(activeTurnChatId);
|
|
1161
|
+
}
|
|
1162
|
+
deps.clearActiveTurn();
|
|
1163
|
+
deps.clearAbort();
|
|
1164
|
+
await deps.stopPolling();
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export type TelegramSessionLifecycleRuntimeDeps<
|
|
1168
|
+
TContext,
|
|
1169
|
+
TQueueItem,
|
|
1170
|
+
TModel = unknown,
|
|
1171
|
+
> = Omit<
|
|
1172
|
+
TelegramSessionLifecycleHookRuntimeDeps<TContext, TQueueItem, TModel>,
|
|
1173
|
+
"applySessionStartState" | "applySessionShutdownState"
|
|
1174
|
+
> &
|
|
1175
|
+
TelegramSessionStateApplierDeps<TQueueItem, TModel>;
|
|
1176
|
+
|
|
1177
|
+
export function createTelegramSessionLifecycleRuntime<
|
|
1178
|
+
TContext,
|
|
1179
|
+
TQueueItem,
|
|
1180
|
+
TModel = unknown,
|
|
1181
|
+
>(deps: TelegramSessionLifecycleRuntimeDeps<TContext, TQueueItem, TModel>) {
|
|
1182
|
+
const stateApplier = createTelegramSessionStateApplier({
|
|
1183
|
+
setQueuedItems: deps.setQueuedItems,
|
|
1184
|
+
setCurrentModel: deps.setCurrentModel,
|
|
1185
|
+
setPendingModelSwitch: deps.setPendingModelSwitch,
|
|
1186
|
+
syncCounters: deps.syncCounters,
|
|
1187
|
+
syncFlags: deps.syncFlags,
|
|
1188
|
+
});
|
|
1189
|
+
return createTelegramSessionLifecycleHooks({
|
|
1190
|
+
getCurrentModel: deps.getCurrentModel,
|
|
1191
|
+
loadConfig: deps.loadConfig,
|
|
1192
|
+
applySessionStartState: stateApplier.applyStartState,
|
|
1193
|
+
prepareTempDir: deps.prepareTempDir,
|
|
1194
|
+
updateStatus: deps.updateStatus,
|
|
1195
|
+
applySessionShutdownState: stateApplier.applyShutdownState,
|
|
1196
|
+
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
1197
|
+
clearModelMenuState: deps.clearModelMenuState,
|
|
1198
|
+
getActiveTurnChatId: deps.getActiveTurnChatId,
|
|
1199
|
+
clearPreview: deps.clearPreview,
|
|
1200
|
+
clearActiveTurn: deps.clearActiveTurn,
|
|
1201
|
+
clearAbort: deps.clearAbort,
|
|
1202
|
+
stopPolling: deps.stopPolling,
|
|
1203
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
export function createTelegramSessionLifecycleHooks<
|
|
1208
|
+
TContext,
|
|
1209
|
+
TQueueItem,
|
|
1210
|
+
TModel = unknown,
|
|
1211
|
+
>(deps: TelegramSessionLifecycleHookRuntimeDeps<TContext, TQueueItem, TModel>) {
|
|
1212
|
+
return {
|
|
1213
|
+
onSessionStart: async (
|
|
1214
|
+
_event: TelegramSessionLifecycleHookEvent,
|
|
1215
|
+
ctx: TContext,
|
|
1216
|
+
): Promise<void> => {
|
|
1217
|
+
try {
|
|
1218
|
+
await startTelegramSessionRuntime({
|
|
1219
|
+
currentModel: deps.getCurrentModel(ctx),
|
|
1220
|
+
loadConfig: deps.loadConfig,
|
|
1221
|
+
applyState: deps.applySessionStartState,
|
|
1222
|
+
prepareTempDir: deps.prepareTempDir,
|
|
1223
|
+
updateStatus: () => deps.updateStatus(ctx),
|
|
1224
|
+
});
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
deps.recordRuntimeEvent?.("session", error, { phase: "start" });
|
|
1227
|
+
throw error;
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
onSessionShutdown: async (): Promise<void> => {
|
|
1231
|
+
try {
|
|
1232
|
+
await shutdownTelegramSessionRuntime<TQueueItem>({
|
|
1233
|
+
applyState: deps.applySessionShutdownState,
|
|
1234
|
+
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
1235
|
+
clearModelMenuState: deps.clearModelMenuState,
|
|
1236
|
+
getActiveTurnChatId: deps.getActiveTurnChatId,
|
|
1237
|
+
clearPreview: deps.clearPreview,
|
|
1238
|
+
clearActiveTurn: deps.clearActiveTurn,
|
|
1239
|
+
clearAbort: deps.clearAbort,
|
|
1240
|
+
stopPolling: deps.stopPolling,
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
deps.recordRuntimeEvent?.("session", error, { phase: "shutdown" });
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
export function createTelegramQueueMutationController<TContext>(
|
|
1251
|
+
deps: TelegramQueueMutationControllerDeps<TContext>,
|
|
1252
|
+
): TelegramQueueMutationController<TContext> {
|
|
1253
|
+
const buildRuntimeDeps = (
|
|
1254
|
+
ctx: TContext,
|
|
1255
|
+
): TelegramQueueMutationRuntimeDeps<TContext> => ({
|
|
1256
|
+
...deps,
|
|
1257
|
+
ctx,
|
|
1258
|
+
});
|
|
1259
|
+
return {
|
|
1260
|
+
append: (item, ctx) =>
|
|
1261
|
+
appendTelegramQueueItemRuntime(item, buildRuntimeDeps(ctx)),
|
|
1262
|
+
reorder: (ctx) => reorderTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
|
|
1263
|
+
removeByMessageIds: (messageIds, ctx) =>
|
|
1264
|
+
removeTelegramQueueItemsByMessageIdsRuntime(
|
|
1265
|
+
messageIds,
|
|
1266
|
+
buildRuntimeDeps(ctx),
|
|
1267
|
+
),
|
|
1268
|
+
clearPriorityByMessageId: (messageId, ctx) =>
|
|
1269
|
+
clearTelegramQueuePromptPriorityRuntime(messageId, buildRuntimeDeps(ctx)),
|
|
1270
|
+
prioritizeByMessageId: (messageId, ctx) =>
|
|
1271
|
+
prioritizeTelegramQueuePromptRuntime(messageId, buildRuntimeDeps(ctx)),
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function appendTelegramQueueItemRuntime<TContext>(
|
|
1276
|
+
item: TelegramQueueItem<TContext>,
|
|
1277
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1278
|
+
): void {
|
|
1279
|
+
deps.setQueuedItems(appendTelegramQueueItem(deps.getQueuedItems(), item));
|
|
1280
|
+
reorderTelegramQueueItemsRuntime(deps);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
export function reorderTelegramQueueItemsRuntime<TContext>(
|
|
1284
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1285
|
+
): void {
|
|
1286
|
+
deps.setQueuedItems(
|
|
1287
|
+
[...deps.getQueuedItems()].sort(compareTelegramQueueItems),
|
|
1288
|
+
);
|
|
1289
|
+
deps.updateStatus(deps.ctx);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
|
|
1293
|
+
messageIds: number[],
|
|
1294
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1295
|
+
): number {
|
|
1296
|
+
const { items, removedCount } = removeTelegramQueueItemsByMessageIds(
|
|
1297
|
+
deps.getQueuedItems(),
|
|
1298
|
+
messageIds,
|
|
1299
|
+
);
|
|
1300
|
+
if (removedCount === 0) return 0;
|
|
1301
|
+
deps.setQueuedItems(items);
|
|
1302
|
+
deps.updateStatus(deps.ctx);
|
|
1303
|
+
return removedCount;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export function clearTelegramQueuePromptPriorityRuntime<TContext>(
|
|
1307
|
+
messageId: number,
|
|
1308
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1309
|
+
): boolean {
|
|
1310
|
+
const { changed, items } = clearTelegramQueuePromptPriority(
|
|
1311
|
+
deps.getQueuedItems(),
|
|
1312
|
+
messageId,
|
|
1313
|
+
);
|
|
1314
|
+
if (!changed) return false;
|
|
1315
|
+
deps.setQueuedItems(items);
|
|
1316
|
+
reorderTelegramQueueItemsRuntime(deps);
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
export function prioritizeTelegramQueuePromptRuntime<TContext>(
|
|
1321
|
+
messageId: number,
|
|
1322
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1323
|
+
): boolean {
|
|
1324
|
+
const nextPriorityReactionOrder = deps.getNextPriorityReactionOrder?.();
|
|
1325
|
+
if (nextPriorityReactionOrder === undefined) return false;
|
|
1326
|
+
const { changed, items } = prioritizeTelegramQueuePrompt(
|
|
1327
|
+
deps.getQueuedItems(),
|
|
1328
|
+
messageId,
|
|
1329
|
+
nextPriorityReactionOrder,
|
|
1330
|
+
);
|
|
1331
|
+
if (!changed) return false;
|
|
1332
|
+
deps.setQueuedItems(items);
|
|
1333
|
+
deps.incrementNextPriorityReactionOrder?.();
|
|
1334
|
+
reorderTelegramQueueItemsRuntime(deps);
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
export async function enqueueTelegramPromptTurnRuntime<
|
|
1339
|
+
TMessage,
|
|
1340
|
+
TContext = unknown,
|
|
1341
|
+
>(
|
|
1342
|
+
messages: TMessage[],
|
|
1343
|
+
deps: TelegramPromptEnqueueRuntimeDeps<TMessage, TContext>,
|
|
1344
|
+
): Promise<void> {
|
|
1345
|
+
const enqueuePlan = planTelegramPromptEnqueue(
|
|
1346
|
+
deps.getQueuedItems(),
|
|
1347
|
+
deps.getPreserveQueuedTurnsAsHistory(),
|
|
1348
|
+
);
|
|
1349
|
+
deps.setPreserveQueuedTurnsAsHistory(false);
|
|
1350
|
+
const turn = await deps.createTurn(messages, enqueuePlan.historyTurns);
|
|
1351
|
+
deps.setQueuedItems(
|
|
1352
|
+
appendTelegramQueueItem(enqueuePlan.remainingItems, turn),
|
|
1353
|
+
);
|
|
1354
|
+
deps.updateStatus();
|
|
1355
|
+
deps.dispatchNextQueuedTelegramTurn();
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
export function createTelegramPromptEnqueueController<
|
|
1359
|
+
TMessage,
|
|
1360
|
+
TContext = unknown,
|
|
1361
|
+
>(
|
|
1362
|
+
deps: TelegramPromptEnqueueControllerDeps<TMessage, TContext>,
|
|
1363
|
+
): TelegramPromptEnqueueController<TMessage, TContext> {
|
|
1364
|
+
return {
|
|
1365
|
+
enqueue: (messages, ctx) =>
|
|
1366
|
+
enqueueTelegramPromptTurnRuntime(messages, {
|
|
1367
|
+
...deps,
|
|
1368
|
+
updateStatus: () => deps.updateStatus(ctx),
|
|
1369
|
+
dispatchNextQueuedTelegramTurn: () =>
|
|
1370
|
+
deps.dispatchNextQueuedTelegramTurn(ctx),
|
|
1371
|
+
}),
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
export function createTelegramControlQueueController<TContext>(
|
|
1376
|
+
deps: TelegramControlQueueControllerDeps<TContext>,
|
|
1377
|
+
): TelegramControlQueueController<TContext> {
|
|
1378
|
+
return {
|
|
1379
|
+
enqueue: (item, ctx) => {
|
|
1380
|
+
deps.appendControlItem(item, ctx);
|
|
1381
|
+
deps.dispatchNextQueuedTelegramTurn(ctx);
|
|
1382
|
+
},
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
|
|
475
1386
|
// --- Control Runtime ---
|
|
476
1387
|
|
|
477
|
-
|
|
478
|
-
|
|
1388
|
+
function getTelegramQueueErrorMessage(error: unknown): string {
|
|
1389
|
+
return error instanceof Error ? error.message : String(error);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
export interface TelegramRuntimeEventRecorderPort {
|
|
1393
|
+
recordRuntimeEvent?: (
|
|
1394
|
+
category: string,
|
|
1395
|
+
error: unknown,
|
|
1396
|
+
details?: Record<string, unknown>,
|
|
1397
|
+
) => void;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
export interface TelegramControlRuntimeDeps<TContext>
|
|
1401
|
+
extends TelegramRuntimeEventRecorderPort {
|
|
1402
|
+
ctx: TContext;
|
|
479
1403
|
sendTextReply: (
|
|
480
1404
|
chatId: number,
|
|
481
1405
|
replyToMessageId: number,
|
|
@@ -484,14 +1408,19 @@ export interface TelegramControlRuntimeDeps {
|
|
|
484
1408
|
onSettled: () => void;
|
|
485
1409
|
}
|
|
486
1410
|
|
|
487
|
-
export async function executeTelegramControlItemRuntime(
|
|
488
|
-
item: PendingTelegramControlItem
|
|
489
|
-
deps: TelegramControlRuntimeDeps
|
|
1411
|
+
export async function executeTelegramControlItemRuntime<TContext>(
|
|
1412
|
+
item: PendingTelegramControlItem<TContext>,
|
|
1413
|
+
deps: TelegramControlRuntimeDeps<TContext>,
|
|
490
1414
|
): Promise<void> {
|
|
491
1415
|
try {
|
|
492
1416
|
await item.execute(deps.ctx);
|
|
493
1417
|
} catch (error) {
|
|
494
|
-
const message =
|
|
1418
|
+
const message = getTelegramQueueErrorMessage(error);
|
|
1419
|
+
deps.recordRuntimeEvent?.("control", error, {
|
|
1420
|
+
controlType: item.controlType,
|
|
1421
|
+
chatId: item.chatId,
|
|
1422
|
+
replyToMessageId: item.replyToMessageId,
|
|
1423
|
+
});
|
|
495
1424
|
await deps.sendTextReply(
|
|
496
1425
|
item.chatId,
|
|
497
1426
|
item.replyToMessageId,
|
|
@@ -504,9 +1433,12 @@ export async function executeTelegramControlItemRuntime(
|
|
|
504
1433
|
|
|
505
1434
|
// --- Dispatch Runtime ---
|
|
506
1435
|
|
|
507
|
-
export interface TelegramDispatchRuntimeDeps {
|
|
1436
|
+
export interface TelegramDispatchRuntimeDeps<TContext = unknown> {
|
|
508
1437
|
executeControlItem: (
|
|
509
|
-
item: Extract<
|
|
1438
|
+
item: Extract<
|
|
1439
|
+
TelegramQueueDispatchAction<TContext>,
|
|
1440
|
+
{ kind: "control" }
|
|
1441
|
+
>["item"],
|
|
510
1442
|
) => void;
|
|
511
1443
|
onPromptDispatchStart: (chatId: number) => void;
|
|
512
1444
|
sendUserMessage: (
|
|
@@ -519,9 +1451,25 @@ export interface TelegramDispatchRuntimeDeps {
|
|
|
519
1451
|
onIdle: () => void;
|
|
520
1452
|
}
|
|
521
1453
|
|
|
522
|
-
export
|
|
523
|
-
|
|
524
|
-
|
|
1454
|
+
export interface TelegramQueueDispatchControllerDeps<TContext = unknown>
|
|
1455
|
+
extends TelegramRuntimeEventRecorderPort {
|
|
1456
|
+
getQueuedItems: () => TelegramQueueItem<TContext>[];
|
|
1457
|
+
setQueuedItems: (items: TelegramQueueItem<TContext>[]) => void;
|
|
1458
|
+
canDispatch: (ctx: TContext) => boolean;
|
|
1459
|
+
updateStatus: (ctx: TContext, error?: string) => void;
|
|
1460
|
+
sendTextReply: TelegramControlRuntimeDeps<TContext>["sendTextReply"];
|
|
1461
|
+
onPromptDispatchStart: (ctx: TContext, chatId: number) => void;
|
|
1462
|
+
sendUserMessage: TelegramDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
1463
|
+
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
export interface TelegramQueueDispatchController<TContext = unknown> {
|
|
1467
|
+
dispatchNext: (ctx: TContext) => void;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
export function executeTelegramQueueDispatchPlan<TContext = unknown>(
|
|
1471
|
+
plan: TelegramQueueDispatchAction<TContext>,
|
|
1472
|
+
deps: TelegramDispatchRuntimeDeps<TContext>,
|
|
525
1473
|
): void {
|
|
526
1474
|
if (plan.kind === "none") {
|
|
527
1475
|
deps.onIdle();
|
|
@@ -535,7 +1483,83 @@ export function executeTelegramQueueDispatchPlan(
|
|
|
535
1483
|
try {
|
|
536
1484
|
deps.sendUserMessage(plan.item.content);
|
|
537
1485
|
} catch (error) {
|
|
538
|
-
const message =
|
|
1486
|
+
const message = getTelegramQueueErrorMessage(error);
|
|
539
1487
|
deps.onPromptDispatchFailure(message);
|
|
540
1488
|
}
|
|
541
1489
|
}
|
|
1490
|
+
|
|
1491
|
+
export type TelegramQueueDispatchRuntimeDeps<TContext = unknown> = Omit<
|
|
1492
|
+
TelegramQueueDispatchControllerDeps<TContext>,
|
|
1493
|
+
"canDispatch"
|
|
1494
|
+
> &
|
|
1495
|
+
TelegramDispatchReadinessDeps<TContext>;
|
|
1496
|
+
|
|
1497
|
+
export function createTelegramQueueDispatchRuntime<TContext = unknown>(
|
|
1498
|
+
deps: TelegramQueueDispatchRuntimeDeps<TContext>,
|
|
1499
|
+
): TelegramQueueDispatchController<TContext> {
|
|
1500
|
+
return createTelegramQueueDispatchController({
|
|
1501
|
+
getQueuedItems: deps.getQueuedItems,
|
|
1502
|
+
setQueuedItems: deps.setQueuedItems,
|
|
1503
|
+
canDispatch: createTelegramDispatchReadinessChecker({
|
|
1504
|
+
isCompactionInProgress: deps.isCompactionInProgress,
|
|
1505
|
+
hasActiveTurn: deps.hasActiveTurn,
|
|
1506
|
+
hasDispatchPending: deps.hasDispatchPending,
|
|
1507
|
+
isIdle: deps.isIdle,
|
|
1508
|
+
hasPendingMessages: deps.hasPendingMessages,
|
|
1509
|
+
}),
|
|
1510
|
+
updateStatus: deps.updateStatus,
|
|
1511
|
+
sendTextReply: deps.sendTextReply,
|
|
1512
|
+
onPromptDispatchStart: deps.onPromptDispatchStart,
|
|
1513
|
+
sendUserMessage: deps.sendUserMessage,
|
|
1514
|
+
onPromptDispatchFailure: deps.onPromptDispatchFailure,
|
|
1515
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
1520
|
+
deps: TelegramQueueDispatchControllerDeps<TContext>,
|
|
1521
|
+
): TelegramQueueDispatchController<TContext> {
|
|
1522
|
+
let controlDispatchPending = false;
|
|
1523
|
+
const controller: TelegramQueueDispatchController<TContext> = {
|
|
1524
|
+
dispatchNext: (ctx) => {
|
|
1525
|
+
if (controlDispatchPending) {
|
|
1526
|
+
deps.updateStatus(ctx);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const dispatchPlan = planNextTelegramQueueAction(
|
|
1530
|
+
deps.getQueuedItems(),
|
|
1531
|
+
deps.canDispatch(ctx),
|
|
1532
|
+
);
|
|
1533
|
+
if (dispatchPlan.kind !== "none") {
|
|
1534
|
+
deps.setQueuedItems(dispatchPlan.remainingItems);
|
|
1535
|
+
}
|
|
1536
|
+
executeTelegramQueueDispatchPlan(dispatchPlan, {
|
|
1537
|
+
executeControlItem: (item) => {
|
|
1538
|
+
controlDispatchPending = true;
|
|
1539
|
+
deps.updateStatus(ctx);
|
|
1540
|
+
void executeTelegramControlItemRuntime(item, {
|
|
1541
|
+
ctx,
|
|
1542
|
+
sendTextReply: deps.sendTextReply,
|
|
1543
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1544
|
+
onSettled: () => {
|
|
1545
|
+
controlDispatchPending = false;
|
|
1546
|
+
deps.updateStatus(ctx);
|
|
1547
|
+
controller.dispatchNext(ctx);
|
|
1548
|
+
},
|
|
1549
|
+
});
|
|
1550
|
+
},
|
|
1551
|
+
onPromptDispatchStart: (chatId) => {
|
|
1552
|
+
deps.onPromptDispatchStart(ctx, chatId);
|
|
1553
|
+
},
|
|
1554
|
+
sendUserMessage: deps.sendUserMessage,
|
|
1555
|
+
onPromptDispatchFailure: (message) => {
|
|
1556
|
+
deps.onPromptDispatchFailure(ctx, message);
|
|
1557
|
+
},
|
|
1558
|
+
onIdle: () => {
|
|
1559
|
+
deps.updateStatus(ctx);
|
|
1560
|
+
},
|
|
1561
|
+
});
|
|
1562
|
+
},
|
|
1563
|
+
};
|
|
1564
|
+
return controller;
|
|
1565
|
+
}
|