@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.
@@ -3,28 +3,62 @@
3
3
  * Owns tool, command, and lifecycle-hook registration so index.ts can stay focused on runtime orchestration state and side effects
4
4
  */
5
5
 
6
+ import { Type } from "@sinclair/typebox";
7
+
8
+ import {
9
+ queueTelegramAttachments,
10
+ TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
11
+ type TelegramAttachmentQueueTargetView,
12
+ } from "./attachments.ts";
6
13
  import type {
14
+ AgentEndEvent,
15
+ AgentStartEvent,
16
+ BeforeAgentStartEvent,
7
17
  ExtensionAPI,
8
18
  ExtensionCommandContext,
9
19
  ExtensionContext,
10
- } from "@mariozechner/pi-coding-agent";
11
- import { Type } from "@sinclair/typebox";
20
+ SessionShutdownEvent,
21
+ SessionStartEvent,
22
+ } from "./pi.ts";
23
+ import { TELEGRAM_PREFIX } from "./turns.ts";
12
24
 
13
- import { queueTelegramAttachments } from "./attachments.ts";
14
- import type { PendingTelegramTurn } from "./queue.ts";
25
+ const MAX_ATTACHMENTS_PER_TURN = 10;
26
+
27
+ const SYSTEM_PROMPT_SUFFIX = `
28
+
29
+ Telegram bridge extension is active.
30
+ - Messages forwarded from Telegram are prefixed with "[telegram]".
31
+ - [telegram] messages may include local temp file paths for Telegram attachments. Read those files as needed.
32
+ - Telegram is often read on narrow phone screens, so prefer narrow table columns when presenting tabular data; wide monospace tables can become unreadable.
33
+ - If a [telegram] user asked for a file or generated artifact, use the telegram_attach tool with the local file path so the extension can send it with your next final reply.
34
+ - Do not assume mentioning a local file path in plain text will send it to Telegram. Use telegram_attach.`;
15
35
 
16
36
  // --- Tool Registration ---
17
37
 
18
- export interface TelegramAttachmentToolRegistrationDeps {
19
- maxAttachmentsPerTurn: number;
20
- getActiveTurn: () => PendingTelegramTurn | undefined;
21
- statPath: (path: string) => Promise<{ isFile(): boolean }>;
38
+ export interface TelegramRuntimeEventRecorderPort {
39
+ recordRuntimeEvent?: (
40
+ category: string,
41
+ error: unknown,
42
+ details?: Record<string, unknown>,
43
+ ) => void;
44
+ }
45
+
46
+ export interface TelegramAttachmentToolRegistrationDeps
47
+ extends TelegramRuntimeEventRecorderPort {
48
+ maxAttachmentsPerTurn?: number;
49
+ maxAttachmentSizeBytes?: number;
50
+ getActiveTurn: () => TelegramAttachmentQueueTargetView | undefined;
51
+ statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
22
52
  }
23
53
 
24
54
  export function registerTelegramAttachmentTool(
25
55
  pi: ExtensionAPI,
26
56
  deps: TelegramAttachmentToolRegistrationDeps,
27
57
  ): void {
58
+ const maxAttachmentsPerTurn =
59
+ deps.maxAttachmentsPerTurn ?? MAX_ATTACHMENTS_PER_TURN;
60
+ const maxAttachmentSizeBytes =
61
+ deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES;
28
62
  pi.registerTool({
29
63
  name: "telegram_attach",
30
64
  label: "Telegram Attach",
@@ -37,16 +71,25 @@ export function registerTelegramAttachmentTool(
37
71
  parameters: Type.Object({
38
72
  paths: Type.Array(
39
73
  Type.String({ description: "Local file path to attach" }),
40
- { minItems: 1, maxItems: deps.maxAttachmentsPerTurn },
74
+ { minItems: 1, maxItems: maxAttachmentsPerTurn },
41
75
  ),
42
76
  }),
43
77
  async execute(_toolCallId, params) {
44
- return queueTelegramAttachments({
45
- activeTurn: deps.getActiveTurn(),
46
- paths: params.paths,
47
- maxAttachmentsPerTurn: deps.maxAttachmentsPerTurn,
48
- statPath: deps.statPath,
49
- });
78
+ try {
79
+ return await queueTelegramAttachments({
80
+ activeTurn: deps.getActiveTurn(),
81
+ paths: params.paths,
82
+ maxAttachmentsPerTurn,
83
+ maxAttachmentSizeBytes,
84
+ statPath: deps.statPath,
85
+ });
86
+ } catch (error) {
87
+ deps.recordRuntimeEvent?.("attachment", error, {
88
+ phase: "queue",
89
+ count: params.paths.length,
90
+ });
91
+ throw error;
92
+ }
50
93
  },
51
94
  });
52
95
  }
@@ -58,7 +101,7 @@ export interface TelegramCommandRegistrationDeps {
58
101
  getStatusLines: () => string[];
59
102
  reloadConfig: () => Promise<void>;
60
103
  hasBotToken: () => boolean;
61
- startPolling: (ctx: ExtensionCommandContext) => Promise<void>;
104
+ startPolling: (ctx: ExtensionCommandContext) => void | Promise<void>;
62
105
  stopPolling: () => Promise<void>;
63
106
  updateStatus: (ctx: ExtensionCommandContext) => void;
64
107
  }
@@ -76,7 +119,7 @@ export function registerTelegramCommands(
76
119
  pi.registerCommand("telegram-status", {
77
120
  description: "Show Telegram bridge status",
78
121
  handler: async (_args, ctx) => {
79
- ctx.ui.notify(deps.getStatusLines().join(" | "), "info");
122
+ ctx.ui.notify(deps.getStatusLines().join("\n"), "info");
80
123
  },
81
124
  });
82
125
  pi.registerCommand("telegram-connect", {
@@ -102,18 +145,67 @@ export function registerTelegramCommands(
102
145
 
103
146
  // --- Lifecycle Hook Registration ---
104
147
 
148
+ export function buildTelegramBridgeSystemPrompt(options: {
149
+ prompt: string;
150
+ systemPrompt: string;
151
+ telegramPrefix?: string;
152
+ systemPromptSuffix: string;
153
+ }): { systemPrompt: string } {
154
+ const telegramPrefix = options.telegramPrefix ?? TELEGRAM_PREFIX;
155
+ const suffix = options.prompt.trimStart().startsWith(telegramPrefix)
156
+ ? `${options.systemPromptSuffix}\n- The current user message came from Telegram.`
157
+ : options.systemPromptSuffix;
158
+ return { systemPrompt: options.systemPrompt + suffix };
159
+ }
160
+
161
+ export function createTelegramBeforeAgentStartHook(
162
+ options: {
163
+ telegramPrefix?: string;
164
+ systemPromptSuffix?: string;
165
+ } = {},
166
+ ): (event: BeforeAgentStartEvent) => { systemPrompt: string } {
167
+ return (event) =>
168
+ buildTelegramBridgeSystemPrompt({
169
+ prompt: event.prompt,
170
+ systemPrompt: event.systemPrompt,
171
+ telegramPrefix: options.telegramPrefix,
172
+ systemPromptSuffix: options.systemPromptSuffix ?? SYSTEM_PROMPT_SUFFIX,
173
+ });
174
+ }
175
+
176
+ export interface TelegramBeforeAgentStartResult {
177
+ systemPrompt?: string;
178
+ }
179
+
180
+ type TelegramBeforeAgentStartReturn =
181
+ | Promise<TelegramBeforeAgentStartResult | undefined>
182
+ | TelegramBeforeAgentStartResult
183
+ | undefined;
184
+
185
+ type TelegramLifecycleModel = ExtensionContext["model"];
186
+ type TelegramLifecycleMessage = AgentEndEvent["messages"][number];
187
+
105
188
  export interface TelegramLifecycleRegistrationDeps {
106
- onSessionStart: (event: unknown, ctx: ExtensionContext) => Promise<void>;
107
- onSessionShutdown: (event: unknown, ctx: ExtensionContext) => Promise<void>;
189
+ onSessionStart: (
190
+ event: SessionStartEvent,
191
+ ctx: ExtensionContext,
192
+ ) => Promise<void>;
193
+ onSessionShutdown: (
194
+ event: SessionShutdownEvent,
195
+ ctx: ExtensionContext,
196
+ ) => Promise<void>;
108
197
  onBeforeAgentStart: (
109
- event: unknown,
198
+ event: BeforeAgentStartEvent,
110
199
  ctx: ExtensionContext,
111
- ) => Promise<unknown> | unknown;
200
+ ) => TelegramBeforeAgentStartReturn;
112
201
  onModelSelect: (
113
- event: unknown,
202
+ event: { model: TelegramLifecycleModel },
114
203
  ctx: ExtensionContext,
115
204
  ) => Promise<void> | void;
116
- onAgentStart: (event: unknown, ctx: ExtensionContext) => Promise<void>;
205
+ onAgentStart: (
206
+ event: AgentStartEvent,
207
+ ctx: ExtensionContext,
208
+ ) => Promise<void>;
117
209
  onToolExecutionStart: (
118
210
  event: unknown,
119
211
  ctx: ExtensionContext,
@@ -122,9 +214,15 @@ export interface TelegramLifecycleRegistrationDeps {
122
214
  event: unknown,
123
215
  ctx: ExtensionContext,
124
216
  ) => Promise<void> | void;
125
- onMessageStart: (event: unknown, ctx: ExtensionContext) => Promise<void>;
126
- onMessageUpdate: (event: unknown, ctx: ExtensionContext) => Promise<void>;
127
- onAgentEnd: (event: unknown, ctx: ExtensionContext) => Promise<void>;
217
+ onMessageStart: (
218
+ event: { message: TelegramLifecycleMessage },
219
+ ctx: ExtensionContext,
220
+ ) => Promise<void>;
221
+ onMessageUpdate: (
222
+ event: { message: TelegramLifecycleMessage },
223
+ ctx: ExtensionContext,
224
+ ) => Promise<void>;
225
+ onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
128
226
  }
129
227
 
130
228
  export function registerTelegramLifecycleHooks(
@@ -137,8 +235,9 @@ export function registerTelegramLifecycleHooks(
137
235
  pi.on("session_shutdown", async (event, ctx) => {
138
236
  await deps.onSessionShutdown(event, ctx);
139
237
  });
140
- pi.on("before_agent_start", (async (event: unknown, ctx: ExtensionContext) =>
141
- deps.onBeforeAgentStart(event, ctx)) as never);
238
+ pi.on("before_agent_start", async (event, ctx) => {
239
+ return deps.onBeforeAgentStart(event, ctx);
240
+ });
142
241
  pi.on("model_select", async (event, ctx) => {
143
242
  await deps.onModelSelect(event, ctx);
144
243
  });