@llblab/pi-telegram 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/docs/README.md +2 -0
- package/docs/architecture.md +42 -45
- package/docs/attachment-handlers.md +60 -0
- package/docs/command-templates.md +75 -0
- package/index.ts +9 -7
- package/lib/attachments.ts +70 -2
- package/lib/commands.ts +144 -58
- package/lib/handlers.ts +118 -192
- package/lib/lifecycle.ts +140 -0
- package/lib/locks.ts +43 -13
- package/lib/menu.ts +0 -4
- package/lib/prompts.ts +44 -0
- package/lib/queue.ts +21 -6
- package/lib/routing.ts +5 -2
- package/lib/runtime.ts +9 -6
- package/lib/setup.ts +5 -1
- package/lib/status.ts +29 -4
- package/lib/turns.ts +12 -7
- package/package.json +1 -1
- package/lib/registration.ts +0 -346
package/lib/handlers.ts
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram inbound attachment handler pipeline
|
|
3
|
-
* Owns MIME/type matching
|
|
3
|
+
* Owns MIME/type matching, command-template execution, fallback handling, and prompt injection before prompt enqueueing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFile } from "node:fs/promises";
|
|
7
6
|
import { homedir } from "node:os";
|
|
8
|
-
import { basename, isAbsolute,
|
|
7
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
9
8
|
|
|
10
9
|
const DEFAULT_ATTACHMENT_HANDLER_TIMEOUT_MS = 120_000;
|
|
11
10
|
|
|
12
|
-
function getDefaultAgentDir(): string {
|
|
13
|
-
return process.env.PI_CODING_AGENT_DIR
|
|
14
|
-
? resolve(process.env.PI_CODING_AGENT_DIR)
|
|
15
|
-
: join(homedir(), ".pi", "agent");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getDefaultAutoToolsPath(): string {
|
|
19
|
-
return join(getDefaultAgentDir(), "auto-tools.json");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
11
|
export interface TelegramAttachmentHandlerConfig {
|
|
23
12
|
match?: string | string[];
|
|
24
13
|
mime?: string | string[];
|
|
25
14
|
type?: string | string[];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
template?: string;
|
|
16
|
+
args?: string | string[];
|
|
17
|
+
defaults?: Record<string, unknown>;
|
|
29
18
|
timeoutMs?: number;
|
|
30
19
|
}
|
|
31
20
|
|
|
@@ -77,8 +66,6 @@ export interface TelegramAttachmentHandlerRuntimeDeps<TContext> {
|
|
|
77
66
|
options?: TelegramAttachmentHandlerExecOptions,
|
|
78
67
|
) => Promise<TelegramAttachmentHandlerExecResult>;
|
|
79
68
|
getCwd: (ctx: TContext) => string;
|
|
80
|
-
readTextFile?: (path: string) => Promise<string>;
|
|
81
|
-
autoToolsPath?: string;
|
|
82
69
|
recordRuntimeEvent?: (
|
|
83
70
|
category: string,
|
|
84
71
|
error: unknown,
|
|
@@ -99,15 +86,12 @@ interface AttachmentHandlerInvocation {
|
|
|
99
86
|
args: string[];
|
|
100
87
|
}
|
|
101
88
|
|
|
102
|
-
interface AutoToolConfig {
|
|
103
|
-
name: string;
|
|
104
|
-
script: string;
|
|
105
|
-
args: string[];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
89
|
function normalizeStringList(value: string | string[] | undefined): string[] {
|
|
109
90
|
if (Array.isArray(value)) {
|
|
110
|
-
return value
|
|
91
|
+
return value
|
|
92
|
+
.map(String)
|
|
93
|
+
.map((item) => item.trim())
|
|
94
|
+
.filter(Boolean);
|
|
111
95
|
}
|
|
112
96
|
if (typeof value === "string" && value.trim()) return [value.trim()];
|
|
113
97
|
return [];
|
|
@@ -124,7 +108,9 @@ function matchesWildcard(pattern: string, value: string | undefined): boolean {
|
|
|
124
108
|
return new RegExp(`^${escaped}$`).test(normalizedValue);
|
|
125
109
|
}
|
|
126
110
|
|
|
127
|
-
function handlerHasSelectors(
|
|
111
|
+
function handlerHasSelectors(
|
|
112
|
+
handler: TelegramAttachmentHandlerConfig,
|
|
113
|
+
): boolean {
|
|
128
114
|
return (
|
|
129
115
|
normalizeStringList(handler.match).length > 0 ||
|
|
130
116
|
normalizeStringList(handler.mime).length > 0 ||
|
|
@@ -132,7 +118,10 @@ function handlerHasSelectors(handler: TelegramAttachmentHandlerConfig): boolean
|
|
|
132
118
|
);
|
|
133
119
|
}
|
|
134
120
|
|
|
135
|
-
function matchesAnyPattern(
|
|
121
|
+
function matchesAnyPattern(
|
|
122
|
+
patterns: string[],
|
|
123
|
+
value: string | undefined,
|
|
124
|
+
): boolean {
|
|
136
125
|
return patterns.some((pattern) => matchesWildcard(pattern, value));
|
|
137
126
|
}
|
|
138
127
|
|
|
@@ -150,12 +139,12 @@ export function telegramAttachmentHandlerMatchesFile(
|
|
|
150
139
|
return matchesAnyPattern(matchPatterns, file.kind);
|
|
151
140
|
}
|
|
152
141
|
|
|
153
|
-
export function
|
|
142
|
+
export function findTelegramAttachmentHandlers(
|
|
154
143
|
handlers: TelegramAttachmentHandlerConfig[] | undefined,
|
|
155
144
|
file: TelegramAttachmentHandlerFile,
|
|
156
|
-
): TelegramAttachmentHandlerConfig
|
|
157
|
-
if (!Array.isArray(handlers)) return
|
|
158
|
-
return handlers.
|
|
145
|
+
): TelegramAttachmentHandlerConfig[] {
|
|
146
|
+
if (!Array.isArray(handlers)) return [];
|
|
147
|
+
return handlers.filter(
|
|
159
148
|
(handler) =>
|
|
160
149
|
!!handler &&
|
|
161
150
|
typeof handler === "object" &&
|
|
@@ -163,29 +152,64 @@ export function findTelegramAttachmentHandler(
|
|
|
163
152
|
);
|
|
164
153
|
}
|
|
165
154
|
|
|
166
|
-
function
|
|
167
|
-
|
|
155
|
+
export function findTelegramAttachmentHandler(
|
|
156
|
+
handlers: TelegramAttachmentHandlerConfig[] | undefined,
|
|
157
|
+
file: TelegramAttachmentHandlerFile,
|
|
158
|
+
): TelegramAttachmentHandlerConfig | undefined {
|
|
159
|
+
return findTelegramAttachmentHandlers(handlers, file)[0];
|
|
168
160
|
}
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
function hasAttachmentFilePlaceholder(value: string): boolean {
|
|
163
|
+
return /\{file\}/.test(value);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeTelegramAttachmentHandlerArgs(
|
|
167
|
+
value: string | string[] | undefined,
|
|
168
|
+
): string[] {
|
|
169
|
+
if (Array.isArray(value)) return value.map(String).map((item) => item.trim());
|
|
170
|
+
if (typeof value !== "string") return [];
|
|
171
|
+
return value.split(",").map((item) => item.trim());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getTelegramAttachmentHandlerArgDefaults(
|
|
175
|
+
handler: TelegramAttachmentHandlerConfig,
|
|
176
|
+
): Record<string, string> {
|
|
177
|
+
const defaults: Record<string, string> = {};
|
|
178
|
+
for (const item of normalizeTelegramAttachmentHandlerArgs(handler.args)) {
|
|
179
|
+
if (!item) continue;
|
|
180
|
+
const [name, ...defaultParts] = item.split("=");
|
|
181
|
+
if (!name || defaultParts.length === 0) continue;
|
|
182
|
+
defaults[name.trim()] = defaultParts.join("=").trim();
|
|
183
|
+
}
|
|
184
|
+
for (const [key, value] of Object.entries(handler.defaults ?? {})) {
|
|
185
|
+
defaults[key] = value === undefined || value === null ? "" : String(value);
|
|
186
|
+
}
|
|
187
|
+
return defaults;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getTelegramAttachmentHandlerTemplateValues(
|
|
191
|
+
handler: TelegramAttachmentHandlerConfig,
|
|
172
192
|
file: TelegramAttachmentHandlerFile,
|
|
173
|
-
): string {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"{type}": file.kind ?? "",
|
|
193
|
+
): Record<string, string> {
|
|
194
|
+
return {
|
|
195
|
+
...getTelegramAttachmentHandlerArgDefaults(handler),
|
|
196
|
+
file: file.path,
|
|
197
|
+
mime: file.mimeType ?? "",
|
|
198
|
+
type: file.kind ?? "",
|
|
180
199
|
};
|
|
181
|
-
let result = token;
|
|
182
|
-
for (const [key, value] of Object.entries(replacements)) {
|
|
183
|
-
result = result.split(key).join(value);
|
|
184
|
-
}
|
|
185
|
-
return result;
|
|
186
200
|
}
|
|
187
201
|
|
|
188
|
-
|
|
202
|
+
function substituteTelegramAttachmentHandlerTemplateToken(
|
|
203
|
+
token: string,
|
|
204
|
+
values: Record<string, string>,
|
|
205
|
+
): string {
|
|
206
|
+
return token.replace(/\{([A-Za-z_][A-Za-z0-9_-]*)\}/g, (_match, name) => {
|
|
207
|
+
if (Object.hasOwn(values, name)) return values[name] ?? "";
|
|
208
|
+
throw new Error(`Missing attachment handler template value: ${name}`);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function splitTelegramAttachmentHandlerTemplate(input: string): string[] {
|
|
189
213
|
const words: string[] = [];
|
|
190
214
|
let current = "";
|
|
191
215
|
let quote: "'" | '"' | undefined;
|
|
@@ -237,135 +261,38 @@ function expandExecutablePath(command: string, cwd: string): string {
|
|
|
237
261
|
return command;
|
|
238
262
|
}
|
|
239
263
|
|
|
240
|
-
|
|
241
|
-
|
|
264
|
+
function buildTelegramAttachmentTemplateInvocation(
|
|
265
|
+
template: string,
|
|
266
|
+
handler: TelegramAttachmentHandlerConfig,
|
|
242
267
|
file: TelegramAttachmentHandlerFile,
|
|
243
268
|
cwd: string,
|
|
244
269
|
): AttachmentHandlerInvocation {
|
|
245
|
-
const parts =
|
|
270
|
+
const parts = splitTelegramAttachmentHandlerTemplate(template);
|
|
246
271
|
const commandPart = parts[0];
|
|
247
|
-
if (!commandPart) throw new Error("Attachment handler
|
|
248
|
-
const
|
|
272
|
+
if (!commandPart) throw new Error("Attachment handler template is empty");
|
|
273
|
+
const values = getTelegramAttachmentHandlerTemplateValues(handler, file);
|
|
274
|
+
const hadFilePlaceholder = parts.some(hasAttachmentFilePlaceholder);
|
|
249
275
|
const command = expandExecutablePath(
|
|
250
|
-
|
|
276
|
+
substituteTelegramAttachmentHandlerTemplateToken(commandPart, values),
|
|
251
277
|
cwd,
|
|
252
278
|
);
|
|
253
279
|
const args = parts
|
|
254
280
|
.slice(1)
|
|
255
|
-
.map((part) =>
|
|
256
|
-
|
|
257
|
-
return { command, args };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function normalizeAutoToolName(name: string): string {
|
|
261
|
-
return name
|
|
262
|
-
.trim()
|
|
263
|
-
.toLowerCase()
|
|
264
|
-
.replace(/[^a-z0-9_]/g, "_")
|
|
265
|
-
.replace(/_+/g, "_")
|
|
266
|
-
.replace(/^_+|_+$/g, "");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function normalizeAutoToolArgs(value: unknown): string[] {
|
|
270
|
-
const source = Array.isArray(value)
|
|
271
|
-
? value
|
|
272
|
-
: typeof value === "string"
|
|
273
|
-
? value.split(",")
|
|
274
|
-
: [];
|
|
275
|
-
const args: string[] = [];
|
|
276
|
-
const seen = new Set<string>();
|
|
277
|
-
for (const item of source) {
|
|
278
|
-
const arg = normalizeAutoToolName(String(item));
|
|
279
|
-
if (!arg || seen.has(arg)) continue;
|
|
280
|
-
seen.add(arg);
|
|
281
|
-
args.push(arg);
|
|
282
|
-
}
|
|
283
|
-
return args;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export function parseTelegramAutoToolsRegistry(
|
|
287
|
-
content: string,
|
|
288
|
-
): Map<string, AutoToolConfig> {
|
|
289
|
-
const raw = JSON.parse(content) as unknown;
|
|
290
|
-
const entries = Array.isArray(raw)
|
|
291
|
-
? raw.map((value) => [undefined, value] as const)
|
|
292
|
-
: raw && typeof raw === "object"
|
|
293
|
-
? Object.entries(raw as Record<string, unknown>)
|
|
294
|
-
: [];
|
|
295
|
-
const tools = new Map<string, AutoToolConfig>();
|
|
296
|
-
for (const [key, value] of entries) {
|
|
297
|
-
if (!value || typeof value !== "object") continue;
|
|
298
|
-
const record = value as Record<string, unknown>;
|
|
299
|
-
const name = normalizeAutoToolName(
|
|
300
|
-
typeof record.name === "string" ? record.name : (key ?? ""),
|
|
281
|
+
.map((part) =>
|
|
282
|
+
substituteTelegramAttachmentHandlerTemplateToken(part, values),
|
|
301
283
|
);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
tools.set(name, { name, script, args: normalizeAutoToolArgs(record.args) });
|
|
305
|
-
}
|
|
306
|
-
return tools;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function readTelegramAutoToolsRegistry(
|
|
310
|
-
path: string,
|
|
311
|
-
readTextFile: (path: string) => Promise<string>,
|
|
312
|
-
): Promise<Map<string, AutoToolConfig>> {
|
|
313
|
-
try {
|
|
314
|
-
return parseTelegramAutoToolsRegistry(await readTextFile(path));
|
|
315
|
-
} catch {
|
|
316
|
-
return new Map();
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function getConfiguredToolArgValue(
|
|
321
|
-
value: unknown,
|
|
322
|
-
file: TelegramAttachmentHandlerFile,
|
|
323
|
-
): string | undefined {
|
|
324
|
-
if (value === undefined || value === null) return undefined;
|
|
325
|
-
return substituteTelegramAttachmentHandlerToken(String(value), file);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function getDefaultToolArgValue(
|
|
329
|
-
arg: string,
|
|
330
|
-
file: TelegramAttachmentHandlerFile,
|
|
331
|
-
): string {
|
|
332
|
-
if (["file", "filename", "path"].includes(arg)) return file.path;
|
|
333
|
-
if (["basename", "name"].includes(arg)) {
|
|
334
|
-
return file.fileName || basename(file.path);
|
|
335
|
-
}
|
|
336
|
-
if (["mime", "mime_type", "mimetype"].includes(arg)) return file.mimeType ?? "";
|
|
337
|
-
if (["type", "kind"].includes(arg)) return file.kind ?? "";
|
|
338
|
-
return "";
|
|
284
|
+
if (!hadFilePlaceholder) args.push(file.path);
|
|
285
|
+
return { command, args };
|
|
339
286
|
}
|
|
340
287
|
|
|
341
|
-
|
|
288
|
+
export function buildTelegramAttachmentHandlerInvocation(
|
|
342
289
|
handler: TelegramAttachmentHandlerConfig,
|
|
343
290
|
file: TelegramAttachmentHandlerFile,
|
|
344
291
|
cwd: string,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
): Promise<AttachmentHandlerInvocation> {
|
|
350
|
-
const toolName = normalizeAutoToolName(handler.tool ?? "");
|
|
351
|
-
if (!toolName) throw new Error("Attachment handler tool is empty");
|
|
352
|
-
const readRegistryFile =
|
|
353
|
-
deps.readTextFile ?? ((path: string) => readFile(path, "utf8"));
|
|
354
|
-
const registry = await readTelegramAutoToolsRegistry(
|
|
355
|
-
deps.autoToolsPath ?? getDefaultAutoToolsPath(),
|
|
356
|
-
readRegistryFile,
|
|
357
|
-
);
|
|
358
|
-
const tool = registry.get(toolName);
|
|
359
|
-
if (!tool) {
|
|
360
|
-
throw new Error(`Attachment handler tool not found in auto-tools: ${toolName}`);
|
|
361
|
-
}
|
|
362
|
-
const script = expandExecutablePath(tool.script, cwd);
|
|
363
|
-
const args = tool.args.map(
|
|
364
|
-
(arg) =>
|
|
365
|
-
getConfiguredToolArgValue(handler.args?.[arg], file) ??
|
|
366
|
-
getDefaultToolArgValue(arg, file),
|
|
367
|
-
);
|
|
368
|
-
return { command: script, args };
|
|
292
|
+
): AttachmentHandlerInvocation {
|
|
293
|
+
const { template } = handler;
|
|
294
|
+
if (!template) throw new Error("Attachment handler template is required");
|
|
295
|
+
return buildTelegramAttachmentTemplateInvocation(template, handler, file, cwd);
|
|
369
296
|
}
|
|
370
297
|
|
|
371
298
|
function getTelegramAttachmentHandlerTimeout(
|
|
@@ -378,9 +305,10 @@ function getTelegramAttachmentHandlerTimeout(
|
|
|
378
305
|
: DEFAULT_ATTACHMENT_HANDLER_TIMEOUT_MS;
|
|
379
306
|
}
|
|
380
307
|
|
|
381
|
-
function getTelegramAttachmentHandlerKind(
|
|
382
|
-
|
|
383
|
-
|
|
308
|
+
function getTelegramAttachmentHandlerKind(
|
|
309
|
+
handler: TelegramAttachmentHandlerConfig,
|
|
310
|
+
): string {
|
|
311
|
+
if (handler.template) return "template";
|
|
384
312
|
return "unknown";
|
|
385
313
|
}
|
|
386
314
|
|
|
@@ -399,19 +327,19 @@ async function executeTelegramAttachmentHandler(
|
|
|
399
327
|
handler: TelegramAttachmentHandlerConfig,
|
|
400
328
|
file: TelegramAttachmentHandlerFile,
|
|
401
329
|
cwd: string,
|
|
402
|
-
deps: Pick<
|
|
403
|
-
TelegramAttachmentHandlerRuntimeDeps<unknown>,
|
|
404
|
-
"execCommand" | "readTextFile" | "autoToolsPath"
|
|
405
|
-
>,
|
|
330
|
+
deps: Pick<TelegramAttachmentHandlerRuntimeDeps<unknown>, "execCommand">,
|
|
406
331
|
): Promise<string> {
|
|
407
|
-
const invocation =
|
|
408
|
-
|
|
409
|
-
|
|
332
|
+
const invocation = buildTelegramAttachmentHandlerInvocation(
|
|
333
|
+
handler,
|
|
334
|
+
file,
|
|
335
|
+
cwd,
|
|
336
|
+
);
|
|
410
337
|
const result = await deps.execCommand(invocation.command, invocation.args, {
|
|
411
338
|
cwd,
|
|
412
339
|
timeout: getTelegramAttachmentHandlerTimeout(handler),
|
|
413
340
|
});
|
|
414
|
-
if (result.code !== 0)
|
|
341
|
+
if (result.code !== 0)
|
|
342
|
+
throw new Error(formatTelegramAttachmentHandlerFailure(result));
|
|
415
343
|
return result.stdout.trim();
|
|
416
344
|
}
|
|
417
345
|
|
|
@@ -423,28 +351,28 @@ export async function processTelegramAttachmentHandlers<
|
|
|
423
351
|
handlers?: TelegramAttachmentHandlerConfig[];
|
|
424
352
|
cwd: string;
|
|
425
353
|
execCommand: TelegramAttachmentHandlerRuntimeDeps<unknown>["execCommand"];
|
|
426
|
-
readTextFile?: (path: string) => Promise<string>;
|
|
427
|
-
autoToolsPath?: string;
|
|
428
354
|
recordRuntimeEvent?: TelegramAttachmentHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
|
|
429
355
|
}): Promise<TelegramAttachmentHandlerProcessResult<TFile>> {
|
|
430
356
|
const promptFiles: TFile[] = [...options.files];
|
|
431
357
|
const outputs: TelegramAttachmentHandlerOutput[] = [];
|
|
432
358
|
for (const file of options.files) {
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
359
|
+
const handlers = findTelegramAttachmentHandlers(options.handlers, file);
|
|
360
|
+
for (const handler of handlers) {
|
|
361
|
+
try {
|
|
362
|
+
const output = await executeTelegramAttachmentHandler(
|
|
363
|
+
handler,
|
|
364
|
+
file,
|
|
365
|
+
options.cwd,
|
|
366
|
+
options,
|
|
367
|
+
);
|
|
368
|
+
if (output) outputs.push({ file, output, handler });
|
|
369
|
+
break;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
options.recordRuntimeEvent?.("attachment-handler", error, {
|
|
372
|
+
fileName: file.fileName || basename(file.path),
|
|
373
|
+
handler: getTelegramAttachmentHandlerKind(handler),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
448
376
|
}
|
|
449
377
|
}
|
|
450
378
|
return {
|
|
@@ -466,8 +394,6 @@ export function createTelegramAttachmentHandlerRuntime<TContext>(
|
|
|
466
394
|
handlers: deps.getHandlers(),
|
|
467
395
|
cwd: deps.getCwd(ctx),
|
|
468
396
|
execCommand: deps.execCommand,
|
|
469
|
-
readTextFile: deps.readTextFile,
|
|
470
|
-
autoToolsPath: deps.autoToolsPath,
|
|
471
397
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
472
398
|
}),
|
|
473
399
|
};
|
package/lib/lifecycle.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram lifecycle hook registration helpers
|
|
3
|
+
* Owns binding prepared Telegram lifecycle runtimes to pi extension lifecycle events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AgentEndEvent,
|
|
8
|
+
AgentStartEvent,
|
|
9
|
+
BeforeAgentStartEvent,
|
|
10
|
+
ExtensionAPI,
|
|
11
|
+
ExtensionContext,
|
|
12
|
+
SessionShutdownEvent,
|
|
13
|
+
SessionStartEvent,
|
|
14
|
+
} from "./pi.ts";
|
|
15
|
+
|
|
16
|
+
export interface TelegramBeforeAgentStartResult {
|
|
17
|
+
systemPrompt?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type TelegramBeforeAgentStartReturn =
|
|
21
|
+
| Promise<TelegramBeforeAgentStartResult | undefined>
|
|
22
|
+
| TelegramBeforeAgentStartResult
|
|
23
|
+
| undefined;
|
|
24
|
+
|
|
25
|
+
type TelegramLifecycleModel = ExtensionContext["model"];
|
|
26
|
+
type TelegramLifecycleMessage = AgentEndEvent["messages"][number];
|
|
27
|
+
|
|
28
|
+
export interface TelegramLifecycleRegistrationDeps {
|
|
29
|
+
onSessionStart: (
|
|
30
|
+
event: SessionStartEvent,
|
|
31
|
+
ctx: ExtensionContext,
|
|
32
|
+
) => Promise<void>;
|
|
33
|
+
onSessionShutdown: (
|
|
34
|
+
event: SessionShutdownEvent,
|
|
35
|
+
ctx: ExtensionContext,
|
|
36
|
+
) => Promise<void>;
|
|
37
|
+
onBeforeAgentStart: (
|
|
38
|
+
event: BeforeAgentStartEvent,
|
|
39
|
+
ctx: ExtensionContext,
|
|
40
|
+
) => TelegramBeforeAgentStartReturn;
|
|
41
|
+
onModelSelect: (
|
|
42
|
+
event: { model: TelegramLifecycleModel },
|
|
43
|
+
ctx: ExtensionContext,
|
|
44
|
+
) => Promise<void> | void;
|
|
45
|
+
onAgentStart: (
|
|
46
|
+
event: AgentStartEvent,
|
|
47
|
+
ctx: ExtensionContext,
|
|
48
|
+
) => Promise<void>;
|
|
49
|
+
onToolExecutionStart: (
|
|
50
|
+
event: unknown,
|
|
51
|
+
ctx: ExtensionContext,
|
|
52
|
+
) => Promise<void> | void;
|
|
53
|
+
onToolExecutionEnd: (
|
|
54
|
+
event: unknown,
|
|
55
|
+
ctx: ExtensionContext,
|
|
56
|
+
) => Promise<void> | void;
|
|
57
|
+
onMessageStart: (
|
|
58
|
+
event: { message: TelegramLifecycleMessage },
|
|
59
|
+
ctx: ExtensionContext,
|
|
60
|
+
) => Promise<void>;
|
|
61
|
+
onMessageUpdate: (
|
|
62
|
+
event: { message: TelegramLifecycleMessage },
|
|
63
|
+
ctx: ExtensionContext,
|
|
64
|
+
) => Promise<void>;
|
|
65
|
+
onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TelegramSessionLifecycleHooks {
|
|
69
|
+
onSessionStart: (
|
|
70
|
+
event: SessionStartEvent,
|
|
71
|
+
ctx: ExtensionContext,
|
|
72
|
+
) => Promise<void>;
|
|
73
|
+
onSessionShutdown: (
|
|
74
|
+
event: SessionShutdownEvent,
|
|
75
|
+
ctx: ExtensionContext,
|
|
76
|
+
) => Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface TelegramExtraLifecycleHooks {
|
|
80
|
+
onSessionStart?: (
|
|
81
|
+
event: SessionStartEvent,
|
|
82
|
+
ctx: ExtensionContext,
|
|
83
|
+
) => Promise<void>;
|
|
84
|
+
onSessionShutdown?: (
|
|
85
|
+
event: SessionShutdownEvent,
|
|
86
|
+
ctx: ExtensionContext,
|
|
87
|
+
) => Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function appendTelegramLifecycleHooks(
|
|
91
|
+
base: TelegramSessionLifecycleHooks,
|
|
92
|
+
extra: TelegramExtraLifecycleHooks,
|
|
93
|
+
): TelegramSessionLifecycleHooks {
|
|
94
|
+
return {
|
|
95
|
+
onSessionStart: async (event, ctx) => {
|
|
96
|
+
await base.onSessionStart(event, ctx);
|
|
97
|
+
await extra.onSessionStart?.(event, ctx);
|
|
98
|
+
},
|
|
99
|
+
onSessionShutdown: async (event, ctx) => {
|
|
100
|
+
await base.onSessionShutdown(event, ctx);
|
|
101
|
+
await extra.onSessionShutdown?.(event, ctx);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function registerTelegramLifecycleHooks(
|
|
107
|
+
pi: ExtensionAPI,
|
|
108
|
+
deps: TelegramLifecycleRegistrationDeps,
|
|
109
|
+
): void {
|
|
110
|
+
pi.on("session_start", async (event, ctx) => {
|
|
111
|
+
await deps.onSessionStart(event, ctx);
|
|
112
|
+
});
|
|
113
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
114
|
+
await deps.onSessionShutdown(event, ctx);
|
|
115
|
+
});
|
|
116
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
117
|
+
return deps.onBeforeAgentStart(event, ctx);
|
|
118
|
+
});
|
|
119
|
+
pi.on("model_select", async (event, ctx) => {
|
|
120
|
+
await deps.onModelSelect(event, ctx);
|
|
121
|
+
});
|
|
122
|
+
pi.on("agent_start", async (event, ctx) => {
|
|
123
|
+
await deps.onAgentStart(event, ctx);
|
|
124
|
+
});
|
|
125
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
126
|
+
await deps.onToolExecutionStart(event, ctx);
|
|
127
|
+
});
|
|
128
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
129
|
+
await deps.onToolExecutionEnd(event, ctx);
|
|
130
|
+
});
|
|
131
|
+
pi.on("message_start", async (event, ctx) => {
|
|
132
|
+
await deps.onMessageStart(event, ctx);
|
|
133
|
+
});
|
|
134
|
+
pi.on("message_update", async (event, ctx) => {
|
|
135
|
+
await deps.onMessageUpdate(event, ctx);
|
|
136
|
+
});
|
|
137
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
138
|
+
await deps.onAgentEnd(event, ctx);
|
|
139
|
+
});
|
|
140
|
+
}
|