@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.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.
Files changed (194) hide show
  1. package/LICENSE +1 -2
  2. package/NOTICE +19 -0
  3. package/README.md +589 -2
  4. package/cordis.patch.yml +14 -0
  5. package/lib/access.d.ts +76 -0
  6. package/lib/access.d.ts.map +1 -0
  7. package/lib/access.js +137 -0
  8. package/lib/access.js.map +1 -0
  9. package/lib/client.js +835 -0
  10. package/lib/commands.d.ts +51 -0
  11. package/lib/commands.d.ts.map +1 -0
  12. package/lib/commands.js +83 -0
  13. package/lib/commands.js.map +1 -0
  14. package/lib/config.d.ts +125 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +97 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/diagnostics.d.ts +58 -0
  19. package/lib/diagnostics.d.ts.map +1 -0
  20. package/lib/diagnostics.js +59 -0
  21. package/lib/diagnostics.js.map +1 -0
  22. package/lib/failures.d.ts +71 -0
  23. package/lib/failures.d.ts.map +1 -0
  24. package/lib/failures.js +139 -0
  25. package/lib/failures.js.map +1 -0
  26. package/lib/harness/host.d.ts +106 -0
  27. package/lib/harness/host.d.ts.map +1 -0
  28. package/lib/harness/host.js +142 -0
  29. package/lib/harness/host.js.map +1 -0
  30. package/lib/harness/message.d.ts +45 -0
  31. package/lib/harness/message.d.ts.map +1 -0
  32. package/lib/harness/message.js +28 -0
  33. package/lib/harness/message.js.map +1 -0
  34. package/lib/harness/model-selection.d.ts +82 -0
  35. package/lib/harness/model-selection.d.ts.map +1 -0
  36. package/lib/harness/model-selection.js +103 -0
  37. package/lib/harness/model-selection.js.map +1 -0
  38. package/lib/harness/questions-seam.d.ts +34 -0
  39. package/lib/harness/questions-seam.d.ts.map +1 -0
  40. package/lib/harness/questions-seam.js +45 -0
  41. package/lib/harness/questions-seam.js.map +1 -0
  42. package/lib/harness/types.d.ts +108 -0
  43. package/lib/harness/types.d.ts.map +1 -0
  44. package/lib/harness/types.js +23 -0
  45. package/lib/harness/types.js.map +1 -0
  46. package/lib/index.d.ts +65 -0
  47. package/lib/index.d.ts.map +1 -0
  48. package/lib/index.js +977 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/interact/approvals.d.ts +59 -0
  51. package/lib/interact/approvals.d.ts.map +1 -0
  52. package/lib/interact/approvals.js +132 -0
  53. package/lib/interact/approvals.js.map +1 -0
  54. package/lib/interact/pending.d.ts +56 -0
  55. package/lib/interact/pending.d.ts.map +1 -0
  56. package/lib/interact/pending.js +89 -0
  57. package/lib/interact/pending.js.map +1 -0
  58. package/lib/interact/questions.d.ts +100 -0
  59. package/lib/interact/questions.d.ts.map +1 -0
  60. package/lib/interact/questions.js +255 -0
  61. package/lib/interact/questions.js.map +1 -0
  62. package/lib/interact/surface.d.ts +33 -0
  63. package/lib/interact/surface.d.ts.map +1 -0
  64. package/lib/interact/surface.js +39 -0
  65. package/lib/interact/surface.js.map +1 -0
  66. package/lib/interact/text-capture.d.ts +38 -0
  67. package/lib/interact/text-capture.d.ts.map +1 -0
  68. package/lib/interact/text-capture.js +75 -0
  69. package/lib/interact/text-capture.js.map +1 -0
  70. package/lib/media/collect.d.ts +157 -0
  71. package/lib/media/collect.d.ts.map +1 -0
  72. package/lib/media/collect.js +235 -0
  73. package/lib/media/collect.js.map +1 -0
  74. package/lib/media/extractor.d.ts +117 -0
  75. package/lib/media/extractor.d.ts.map +1 -0
  76. package/lib/media/extractor.js +207 -0
  77. package/lib/media/extractor.js.map +1 -0
  78. package/lib/media/intake.d.ts +57 -0
  79. package/lib/media/intake.d.ts.map +1 -0
  80. package/lib/media/intake.js +115 -0
  81. package/lib/media/intake.js.map +1 -0
  82. package/lib/media/limits.d.ts +69 -0
  83. package/lib/media/limits.d.ts.map +1 -0
  84. package/lib/media/limits.js +97 -0
  85. package/lib/media/limits.js.map +1 -0
  86. package/lib/media/ocr.d.ts +66 -0
  87. package/lib/media/ocr.d.ts.map +1 -0
  88. package/lib/media/ocr.js +131 -0
  89. package/lib/media/ocr.js.map +1 -0
  90. package/lib/media/screenshot.d.ts +57 -0
  91. package/lib/media/screenshot.d.ts.map +1 -0
  92. package/lib/media/screenshot.js +101 -0
  93. package/lib/media/screenshot.js.map +1 -0
  94. package/lib/media/vision.d.ts +60 -0
  95. package/lib/media/vision.d.ts.map +1 -0
  96. package/lib/media/vision.js +74 -0
  97. package/lib/media/vision.js.map +1 -0
  98. package/lib/render/clamp.d.ts +34 -0
  99. package/lib/render/clamp.d.ts.map +1 -0
  100. package/lib/render/clamp.js +65 -0
  101. package/lib/render/clamp.js.map +1 -0
  102. package/lib/render/escape.d.ts +15 -0
  103. package/lib/render/escape.d.ts.map +1 -0
  104. package/lib/render/escape.js +17 -0
  105. package/lib/render/escape.js.map +1 -0
  106. package/lib/reply/activity.d.ts +31 -0
  107. package/lib/reply/activity.d.ts.map +1 -0
  108. package/lib/reply/activity.js +88 -0
  109. package/lib/reply/activity.js.map +1 -0
  110. package/lib/reply/rich-stream.d.ts +173 -0
  111. package/lib/reply/rich-stream.d.ts.map +1 -0
  112. package/lib/reply/rich-stream.js +286 -0
  113. package/lib/reply/rich-stream.js.map +1 -0
  114. package/lib/reply/split-markdown.d.ts +21 -0
  115. package/lib/reply/split-markdown.d.ts.map +1 -0
  116. package/lib/reply/split-markdown.js +87 -0
  117. package/lib/reply/split-markdown.js.map +1 -0
  118. package/lib/reply/turn-bridge.d.ts +143 -0
  119. package/lib/reply/turn-bridge.d.ts.map +1 -0
  120. package/lib/reply/turn-bridge.js +182 -0
  121. package/lib/reply/turn-bridge.js.map +1 -0
  122. package/lib/router.d.ts +415 -0
  123. package/lib/router.d.ts.map +1 -0
  124. package/lib/router.js +663 -0
  125. package/lib/router.js.map +1 -0
  126. package/lib/secrets.d.ts +30 -0
  127. package/lib/secrets.d.ts.map +1 -0
  128. package/lib/secrets.js +51 -0
  129. package/lib/secrets.js.map +1 -0
  130. package/lib/session/bindings.d.ts +81 -0
  131. package/lib/session/bindings.d.ts.map +1 -0
  132. package/lib/session/bindings.js +170 -0
  133. package/lib/session/bindings.js.map +1 -0
  134. package/lib/session/history.d.ts +50 -0
  135. package/lib/session/history.d.ts.map +1 -0
  136. package/lib/session/history.js +127 -0
  137. package/lib/session/history.js.map +1 -0
  138. package/lib/session/models.d.ts +99 -0
  139. package/lib/session/models.d.ts.map +1 -0
  140. package/lib/session/models.js +123 -0
  141. package/lib/session/models.js.map +1 -0
  142. package/lib/session/permission.d.ts +83 -0
  143. package/lib/session/permission.d.ts.map +1 -0
  144. package/lib/session/permission.js +107 -0
  145. package/lib/session/permission.js.map +1 -0
  146. package/lib/session/picker.d.ts +49 -0
  147. package/lib/session/picker.d.ts.map +1 -0
  148. package/lib/session/picker.js +107 -0
  149. package/lib/session/picker.js.map +1 -0
  150. package/lib/session/preferences.d.ts +55 -0
  151. package/lib/session/preferences.d.ts.map +1 -0
  152. package/lib/session/preferences.js +102 -0
  153. package/lib/session/preferences.js.map +1 -0
  154. package/lib/session/recovery.d.ts +66 -0
  155. package/lib/session/recovery.d.ts.map +1 -0
  156. package/lib/session/recovery.js +112 -0
  157. package/lib/session/recovery.js.map +1 -0
  158. package/lib/session/runner.d.ts +223 -0
  159. package/lib/session/runner.d.ts.map +1 -0
  160. package/lib/session/runner.js +260 -0
  161. package/lib/session/runner.js.map +1 -0
  162. package/lib/session/workspaces.d.ts +28 -0
  163. package/lib/session/workspaces.d.ts.map +1 -0
  164. package/lib/session/workspaces.js +53 -0
  165. package/lib/session/workspaces.js.map +1 -0
  166. package/lib/telegram/addressing.d.ts +39 -0
  167. package/lib/telegram/addressing.d.ts.map +1 -0
  168. package/lib/telegram/addressing.js +81 -0
  169. package/lib/telegram/addressing.js.map +1 -0
  170. package/lib/telegram/albums.d.ts +46 -0
  171. package/lib/telegram/albums.d.ts.map +1 -0
  172. package/lib/telegram/albums.js +104 -0
  173. package/lib/telegram/albums.js.map +1 -0
  174. package/lib/telegram/api.d.ts +238 -0
  175. package/lib/telegram/api.d.ts.map +1 -0
  176. package/lib/telegram/api.js +518 -0
  177. package/lib/telegram/api.js.map +1 -0
  178. package/lib/telegram/poller.d.ts +59 -0
  179. package/lib/telegram/poller.d.ts.map +1 -0
  180. package/lib/telegram/poller.js +94 -0
  181. package/lib/telegram/poller.js.map +1 -0
  182. package/lib/telegram/types.d.ts +130 -0
  183. package/lib/telegram/types.d.ts.map +1 -0
  184. package/lib/telegram/types.js +9 -0
  185. package/lib/telegram/types.js.map +1 -0
  186. package/lib/telegram/typing.d.ts +47 -0
  187. package/lib/telegram/typing.d.ts.map +1 -0
  188. package/lib/telegram/typing.js +91 -0
  189. package/lib/telegram/typing.js.map +1 -0
  190. package/lib/versions.d.ts +63 -0
  191. package/lib/versions.d.ts.map +1 -0
  192. package/lib/versions.js +135 -0
  193. package/lib/versions.js.map +1 -0
  194. package/package.json +94 -10
@@ -0,0 +1,518 @@
1
+ /**
2
+ * Telegram Bot API client.
3
+ *
4
+ * Speaks the HTTP protocol directly — no SDK — because the surface this plugin
5
+ * needs is a dozen methods wide and an SDK would only add a dependency and a
6
+ * translation layer between us and the error messages that matter.
7
+ *
8
+ * Three behaviours are deliberate and are the reason this client exists rather
9
+ * than a thin fetch wrapper:
10
+ *
11
+ * - **Replies are sent as rich markdown.** Since Bot API 10.1 Telegram parses
12
+ * markdown itself, so the agent's tables, headings and lists arrive as real
13
+ * elements. The plugin's own prompts stay on plain HTML: they are short,
14
+ * built from escaped text, and carry the inline keyboards.
15
+ * - **Rate limits are waited out, not thrown.** A 429 carries `retry_after`;
16
+ * the client sleeps that long and retries, so a burst of streaming edits
17
+ * degrades into slower edits rather than a failed turn.
18
+ * - **The token never reaches a log.** It lives in the request path, so every
19
+ * error message is redacted before it escapes.
20
+ */
21
+ /** Attempts for a request that fails with a retryable status. */
22
+ const MAX_RETRIES = 3;
23
+ /** Backoff for retryable failures that carry no `retry_after`. */
24
+ const BACKOFF_MS = [500, 1500, 4000];
25
+ /** Statuses worth retrying: rate limit, and Telegram's own transient faults. */
26
+ const RETRYABLE = new Set([429, 500, 502, 503, 504]);
27
+ /**
28
+ * Methods safe to repeat after a transport failure.
29
+ *
30
+ * A dropped connection leaves no way to know whether the request arrived, so
31
+ * repeating a send could post the same message twice. These are all reads or
32
+ * idempotent, so repeating one costs nothing and rescues the call.
33
+ *
34
+ * This matters most for `getFile`: it runs while the long poll holds its own
35
+ * connection open, so it is the call most likely to need a fresh one, and a
36
+ * single flaky connect was turning a sent screenshot into an error.
37
+ */
38
+ const IDEMPOTENT = new Set(['getMe', 'getFile', 'getUpdates', 'deleteWebhook', 'downloadFile']);
39
+ /** Deadline for one file download; larger than an API call's, for large files. */
40
+ const DEFAULT_DOWNLOAD_TIMEOUT_MS = 60_000;
41
+ /** A Bot API call that failed, with the token stripped from every field. */
42
+ export class TelegramApiError extends Error {
43
+ code;
44
+ description;
45
+ retryAfterSeconds;
46
+ constructor(message, code, description,
47
+ /** Seconds Telegram asked us to wait, from a 429's `parameters.retry_after`. */
48
+ retryAfterSeconds) {
49
+ super(message);
50
+ this.code = code;
51
+ this.description = description;
52
+ this.retryAfterSeconds = retryAfterSeconds;
53
+ this.name = 'TelegramApiError';
54
+ }
55
+ /** Whether the failure means the bot token itself is not valid. */
56
+ get isAuthFailure() {
57
+ return this.code === 401;
58
+ }
59
+ }
60
+ export class TelegramApi {
61
+ options;
62
+ fetchImpl;
63
+ sleep;
64
+ downloadTimeoutMs;
65
+ constructor(options) {
66
+ this.options = options;
67
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
68
+ this.sleep = options.sleep ?? defaultSleep;
69
+ this.downloadTimeoutMs = options.downloadTimeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS;
70
+ }
71
+ /** Verify the token and read the bot's own identity. */
72
+ getMe() {
73
+ return this.call('getMe', {});
74
+ }
75
+ /** Drop any webhook so long polling is the only delivery path. */
76
+ async deleteWebhook() {
77
+ await this.call('deleteWebhook', { drop_pending_updates: false });
78
+ }
79
+ /**
80
+ * Long-poll for updates.
81
+ *
82
+ * `allowed_updates` includes `callback_query`, which is what lets a button
83
+ * press reach the agent at all.
84
+ */
85
+ getUpdates(offset, timeoutSeconds, signal) {
86
+ return this.call('getUpdates', {
87
+ offset,
88
+ timeout: timeoutSeconds,
89
+ allowed_updates: ['message', 'callback_query'],
90
+ }, { ...(signal ? { signal } : {}), timeoutMs: timeoutSeconds * 1000 + 5000 });
91
+ }
92
+ /**
93
+ * Send one HTML message, falling back to plain text if Telegram rejects the
94
+ * markup rather than letting the content vanish.
95
+ */
96
+ async sendMessage(options) {
97
+ const body = {
98
+ chat_id: options.chatId,
99
+ text: options.html,
100
+ parse_mode: 'HTML',
101
+ link_preview_options: { is_disabled: true },
102
+ ...(options.keyboard ? { reply_markup: toReplyMarkup(options.keyboard) } : {}),
103
+ ...(options.replyToMessageId
104
+ ? { reply_parameters: { message_id: options.replyToMessageId } }
105
+ : {}),
106
+ ...(options.threadId !== undefined ? { message_thread_id: options.threadId } : {}),
107
+ };
108
+ const sent = await this.call('sendMessage', body, {
109
+ ...(options.signal ? { signal: options.signal } : {}),
110
+ });
111
+ return { messageId: sent.message_id };
112
+ }
113
+ /**
114
+ * Edit one message in place — the mechanism behind streamed replies.
115
+ *
116
+ * Telegram answers 400 when the new text equals the old, which during
117
+ * streaming is an ordinary and frequent event, so it is reported rather than
118
+ * thrown.
119
+ */
120
+ async editMessageText(options) {
121
+ const body = {
122
+ chat_id: options.chatId,
123
+ message_id: options.messageId,
124
+ text: options.html,
125
+ parse_mode: 'HTML',
126
+ link_preview_options: { is_disabled: true },
127
+ ...(options.keyboard ? { reply_markup: toReplyMarkup(options.keyboard) } : {}),
128
+ };
129
+ try {
130
+ await this.call('editMessageText', body, {
131
+ ...(options.signal ? { signal: options.signal } : {}),
132
+ });
133
+ return 'edited';
134
+ }
135
+ catch (error) {
136
+ if (isNotModified(error))
137
+ return 'unchanged';
138
+ throw error;
139
+ }
140
+ }
141
+ /**
142
+ * Send one rich message.
143
+ *
144
+ * Telegram parses the Rich Markdown itself, so tables, headings, lists and
145
+ * task lists arrive as real elements rather than as an approximation of
146
+ * them — and the cap is 32768 characters rather than 4096.
147
+ */
148
+ async sendRichMessage(options) {
149
+ const sent = await this.call('sendRichMessage', {
150
+ chat_id: options.chatId,
151
+ rich_message: { markdown: options.markdown },
152
+ ...(options.keyboard ? { reply_markup: toReplyMarkup(options.keyboard) } : {}),
153
+ ...(options.replyToMessageId
154
+ ? { reply_parameters: { message_id: options.replyToMessageId } }
155
+ : {}),
156
+ ...(options.threadId !== undefined ? { message_thread_id: options.threadId } : {}),
157
+ }, { ...(options.signal ? { signal: options.signal } : {}) });
158
+ return { messageId: sent.message_id };
159
+ }
160
+ /**
161
+ * Stream one frame of a partial reply.
162
+ *
163
+ * The draft is a 30-second ephemeral preview, and Telegram animates changes
164
+ * carrying the same `draftId` — so a turn keeps one id throughout and the
165
+ * text grows in place. It must still be persisted with a real send when the
166
+ * turn ends, and it works in private chats only.
167
+ */
168
+ async sendRichMessageDraft(options) {
169
+ await this.call('sendRichMessageDraft', {
170
+ chat_id: options.chatId,
171
+ draft_id: options.draftId,
172
+ rich_message: { markdown: options.markdown },
173
+ ...(options.threadId !== undefined ? { message_thread_id: options.threadId } : {}),
174
+ }, { ...(options.signal ? { signal: options.signal } : {}) });
175
+ }
176
+ /** Replace a message's content with rich markdown. */
177
+ async editRichMessage(options) {
178
+ try {
179
+ await this.call('editMessageText', {
180
+ chat_id: options.chatId,
181
+ message_id: options.messageId,
182
+ rich_message: { markdown: options.markdown },
183
+ ...(options.keyboard ? { reply_markup: toReplyMarkup(options.keyboard) } : {}),
184
+ });
185
+ return 'edited';
186
+ }
187
+ catch (error) {
188
+ if (isNotModified(error))
189
+ return 'unchanged';
190
+ throw error;
191
+ }
192
+ }
193
+ /** Replace or clear a message's inline keyboard, leaving its text alone. */
194
+ async editMessageReplyMarkup(options) {
195
+ try {
196
+ await this.call('editMessageReplyMarkup', {
197
+ chat_id: options.chatId,
198
+ message_id: options.messageId,
199
+ reply_markup: toReplyMarkup(options.keyboard),
200
+ });
201
+ }
202
+ catch (error) {
203
+ if (!isNotModified(error))
204
+ throw error;
205
+ }
206
+ }
207
+ /**
208
+ * Acknowledge a button press. Telegram shows a spinner on the button until
209
+ * this lands, so it is sent before any slow work the press triggers.
210
+ */
211
+ async answerCallbackQuery(id, text) {
212
+ try {
213
+ await this.call('answerCallbackQuery', {
214
+ callback_query_id: id,
215
+ ...(text ? { text } : {}),
216
+ });
217
+ }
218
+ catch {
219
+ // An expired callback id is normal (the user pressed twice, or waited);
220
+ // failing to acknowledge must never abort the work the press started.
221
+ }
222
+ }
223
+ /**
224
+ * Upload one file to a chat.
225
+ *
226
+ * Multipart rather than the JSON path every other call uses, because
227
+ * Telegram takes file bytes no other way. Node builds the body: `FormData`
228
+ * and `Blob` are standard here, so there is no boundary to get wrong.
229
+ *
230
+ * Not retried. A retry would re-upload the whole file, and the failures that
231
+ * matter for one — too large, wrong type — do not pass on a second attempt.
232
+ *
233
+ * @param method - `sendPhoto` or `sendDocument`.
234
+ * @param field - the form field Telegram expects the bytes under.
235
+ * @param options - the chat, the bytes, and what to call them.
236
+ */
237
+ async uploadFile(method, field, options) {
238
+ const form = new FormData();
239
+ form.set('chat_id', options.chatId);
240
+ if (options.caption !== undefined)
241
+ form.set('caption', options.caption);
242
+ if (options.caption !== undefined)
243
+ form.set('parse_mode', 'HTML');
244
+ if (options.threadId !== undefined)
245
+ form.set('message_thread_id', String(options.threadId));
246
+ form.set(field,
247
+ // Copied into a plain ArrayBuffer: a Uint8Array view may sit on a larger
248
+ // pooled buffer, and Blob would take the whole thing.
249
+ new Blob([options.data.slice().buffer], { type: options.contentType }), options.filename);
250
+ const controller = new AbortController();
251
+ const timer = setTimeout(() => controller.abort(), this.downloadTimeoutMs);
252
+ try {
253
+ const response = await this.fetchImpl(`${this.base()}/bot${this.options.token}/${method}`, {
254
+ method: 'POST',
255
+ body: form,
256
+ signal: controller.signal,
257
+ });
258
+ const envelope = parseEnvelope(await response.text(), method);
259
+ if (!envelope.ok) {
260
+ throw new TelegramApiError(this.redact(`telegram ${method} failed: ${envelope.description ?? `http ${response.status}`}`), envelope.error_code ?? response.status, envelope.description === undefined ? undefined : this.redact(envelope.description));
261
+ }
262
+ return { messageId: envelope.result.message_id };
263
+ }
264
+ catch (error) {
265
+ throw this.normalize(error, method);
266
+ }
267
+ finally {
268
+ clearTimeout(timer);
269
+ }
270
+ }
271
+ /**
272
+ * Publish the command menu Telegram offers when someone types `/`.
273
+ *
274
+ * Without this the bot answers every command correctly and advertises none
275
+ * of them, so they are discoverable only by reading the README. Best-effort:
276
+ * a bot that cannot publish its menu still works, it is only harder to find
277
+ * your way around.
278
+ *
279
+ * @param commands - name and one-line description, in menu order.
280
+ */
281
+ async setMyCommands(commands) {
282
+ try {
283
+ await this.call('setMyCommands', { commands });
284
+ }
285
+ catch {
286
+ // Cosmetic only.
287
+ }
288
+ }
289
+ /** Show a progress indicator in the chat. Best-effort by design. */
290
+ async sendChatAction(chatId, action, threadId) {
291
+ try {
292
+ await this.call('sendChatAction', {
293
+ chat_id: chatId,
294
+ action,
295
+ ...(threadId === undefined ? {} : { message_thread_id: threadId }),
296
+ });
297
+ }
298
+ catch {
299
+ // Cosmetic only.
300
+ }
301
+ }
302
+ /** Resolve a file id to downloadable metadata. */
303
+ getFile(fileId) {
304
+ return this.call('getFile', { file_id: fileId });
305
+ }
306
+ /**
307
+ * Download a resolved file's bytes.
308
+ *
309
+ * The download URL carries the token in its path, and a transport failure
310
+ * quotes the URL it was given — so this fetch is wrapped like every other,
311
+ * or a dropped connection would hand the token to the caller's log.
312
+ */
313
+ async downloadFile(filePath, signal) {
314
+ const url = `${this.base()}/file/bot${this.options.token}/${filePath.replace(/^\/+/, '')}`;
315
+ return this.withRetries('downloadFile', signal, async () => {
316
+ // Its own deadline, and a generous one: without a timeout this waited on
317
+ // the operating system's TCP timeout, which is minutes, and a file may
318
+ // legitimately be 20 MB over a slow link.
319
+ const controller = new AbortController();
320
+ const timer = setTimeout(() => controller.abort(), this.downloadTimeoutMs);
321
+ const onAbort = () => controller.abort();
322
+ signal?.addEventListener('abort', onAbort, { once: true });
323
+ try {
324
+ const response = await this.fetchImpl(url, { signal: controller.signal });
325
+ if (!response.ok) {
326
+ throw new TelegramApiError(`telegram file download failed with http ${response.status}`, response.status, undefined);
327
+ }
328
+ return new Uint8Array(await response.arrayBuffer());
329
+ }
330
+ catch (error) {
331
+ throw this.normalize(error, 'downloadFile');
332
+ }
333
+ finally {
334
+ clearTimeout(timer);
335
+ signal?.removeEventListener('abort', onAbort);
336
+ }
337
+ });
338
+ }
339
+ /** Base url without a trailing slash. */
340
+ base() {
341
+ return this.options.baseUrl.replace(/\/+$/, '');
342
+ }
343
+ /**
344
+ * One Bot API call, with retries for transient failures.
345
+ *
346
+ * @throws {TelegramApiError} with the token redacted from every field.
347
+ */
348
+ call(method, body, options = {}) {
349
+ return this.withRetries(method, options.signal, () => this.request(method, body, options));
350
+ }
351
+ /**
352
+ * Run one operation, backing off and repeating what is safe to repeat.
353
+ *
354
+ * Shared with the download path rather than living inside `call`: a download
355
+ * is the most retry-worthy request this client makes — a pure read of bytes
356
+ * — and leaving it outside the retry loop is what turned an intermittent
357
+ * connection into a screenshot the agent never saw.
358
+ */
359
+ async withRetries(method, signal, run) {
360
+ let lastError;
361
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {
362
+ try {
363
+ return await run();
364
+ }
365
+ catch (error) {
366
+ lastError = error;
367
+ const delay = retryDelay(error, attempt, IDEMPOTENT.has(method));
368
+ if (delay === undefined)
369
+ throw error;
370
+ await this.sleep(delay, signal);
371
+ }
372
+ }
373
+ throw lastError;
374
+ }
375
+ /** One attempt: build, send, validate, unwrap. */
376
+ async request(method, body, options) {
377
+ const controller = new AbortController();
378
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? this.options.timeoutMs);
379
+ const onAbort = () => controller.abort();
380
+ options.signal?.addEventListener('abort', onAbort, { once: true });
381
+ try {
382
+ const response = await this.fetchImpl(`${this.base()}/bot${this.options.token}/${method}`, {
383
+ method: 'POST',
384
+ headers: { 'content-type': 'application/json' },
385
+ body: JSON.stringify(prune(body)),
386
+ signal: controller.signal,
387
+ });
388
+ const envelope = parseEnvelope(await response.text(), method);
389
+ if (!envelope.ok) {
390
+ throw new TelegramApiError(this.redact(`telegram ${method} failed: ${envelope.description ?? `http ${response.status}`}`), envelope.error_code ?? response.status,
391
+ // Redacted too, though Telegram's own descriptions do not quote the
392
+ // request. `description` is read straight into a log line on an auth
393
+ // failure, and a field that skips this is a field that only has to be
394
+ // wrong once.
395
+ envelope.description === undefined ? undefined : this.redact(envelope.description), envelope.parameters?.retry_after);
396
+ }
397
+ return envelope.result;
398
+ }
399
+ catch (error) {
400
+ throw this.normalize(error, method);
401
+ }
402
+ finally {
403
+ clearTimeout(timer);
404
+ options.signal?.removeEventListener('abort', onAbort);
405
+ }
406
+ }
407
+ /** Wrap any thrown value as a redacted TelegramApiError. */
408
+ normalize(error, method) {
409
+ if (error instanceof TelegramApiError)
410
+ return error;
411
+ return new TelegramApiError(`telegram ${method} failed: ${this.redact(describeCause(error))}`, undefined, undefined);
412
+ }
413
+ /** Remove the bot token from any string bound for a log or an error. */
414
+ redact(text) {
415
+ return text.split(this.options.token).join('<redacted>');
416
+ }
417
+ }
418
+ /** Parse a response body, treating unparseable output as a failed call. */
419
+ function parseEnvelope(text, method) {
420
+ if (text === '')
421
+ return { ok: true, result: undefined };
422
+ try {
423
+ return JSON.parse(text);
424
+ }
425
+ catch {
426
+ throw new TelegramApiError(`telegram ${method} returned a non-JSON response`, undefined, undefined);
427
+ }
428
+ }
429
+ /**
430
+ * How long to wait before retrying, or undefined when the failure is final.
431
+ * A 429 names its own delay; everything else uses fixed backoff.
432
+ *
433
+ * @param repeatable - whether repeating this method is safe when the transport
434
+ * failed and the request's fate is unknown.
435
+ */
436
+ function retryDelay(error, attempt, repeatable) {
437
+ if (!(error instanceof TelegramApiError))
438
+ return undefined;
439
+ if (attempt >= MAX_RETRIES - 1)
440
+ return undefined;
441
+ // No status means the request never got an answer: a refused connection, a
442
+ // dropped socket, a name that would not resolve. The poll loop already
443
+ // survives those; an individual call did not, so one flaky connect surfaced
444
+ // to the user as a failure.
445
+ if (error.code === undefined) {
446
+ return repeatable ? (BACKOFF_MS[attempt] ?? BACKOFF_MS[BACKOFF_MS.length - 1]) : undefined;
447
+ }
448
+ if (!RETRYABLE.has(error.code))
449
+ return undefined;
450
+ if (error.retryAfterSeconds !== undefined)
451
+ return error.retryAfterSeconds * 1000;
452
+ const stated = /retry after (\d+)/i.exec(error.description ?? '')?.[1];
453
+ if (stated)
454
+ return Number(stated) * 1000;
455
+ if (error.code === 429)
456
+ return 1000;
457
+ return BACKOFF_MS[attempt] ?? BACKOFF_MS[BACKOFF_MS.length - 1];
458
+ }
459
+ /**
460
+ * One readable line from an error and everything underneath it.
461
+ *
462
+ * Node's fetch reports every transport failure as the same three words —
463
+ * `fetch failed` — and puts what actually happened in `cause`: a refused
464
+ * connection, an unresolved name, an expired certificate, a timeout. Reporting
465
+ * only the top layer turns four distinct problems into one unactionable
466
+ * sentence, which is exactly how far a diagnosis gets on it.
467
+ *
468
+ * @param error - any thrown value.
469
+ * @returns the message chain, outermost first.
470
+ */
471
+ export function describeCause(error) {
472
+ const chain = [];
473
+ let current = error;
474
+ // Bounded: a cause chain is normally two or three deep, and a cyclic one
475
+ // must not become an infinite loop inside an error handler.
476
+ for (let depth = 0; depth < 5 && current !== undefined && current !== null; depth += 1) {
477
+ const message = current instanceof Error ? current.message : String(current);
478
+ const code = current.code;
479
+ const labelled = typeof code === 'string' && !message.includes(code) ? `${message} (${code})` : message;
480
+ if (labelled !== '' && !chain.includes(labelled))
481
+ chain.push(labelled);
482
+ current = current instanceof Error ? current.cause : undefined;
483
+ }
484
+ return chain.length > 0 ? chain.join(': ') : String(error);
485
+ }
486
+ /** Whether a failure means "the edit changed nothing", which is not a failure. */
487
+ function isNotModified(error) {
488
+ if (!(error instanceof TelegramApiError))
489
+ return false;
490
+ return /message is not modified/i.test(error.description ?? '');
491
+ }
492
+ /** Convert our keyboard shape into the Bot API's snake-cased markup. */
493
+ function toReplyMarkup(keyboard) {
494
+ return {
495
+ inline_keyboard: keyboard.map((row) => row.map((button) => ({ text: button.text, callback_data: button.callbackData }))),
496
+ };
497
+ }
498
+ /** Drop undefined values so `parse_mode: undefined` really omits the field. */
499
+ function prune(body) {
500
+ return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined));
501
+ }
502
+ /** Cancellable sleep used by the retry path. */
503
+ function defaultSleep(ms, signal) {
504
+ return new Promise((resolve) => {
505
+ if (signal?.aborted)
506
+ return resolve();
507
+ const timer = setTimeout(() => {
508
+ signal?.removeEventListener('abort', onAbort);
509
+ resolve();
510
+ }, ms);
511
+ const onAbort = () => {
512
+ clearTimeout(timer);
513
+ resolve();
514
+ };
515
+ signal?.addEventListener('abort', onAbort, { once: true });
516
+ });
517
+ }
518
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/telegram/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The long-poll receive loop.
3
+ *
4
+ * Telegram's `getUpdates` offset is the delivery guarantee: an update is
5
+ * redelivered until an offset past it is acknowledged. So the offset advances
6
+ * only after the handler for that update returns. A handler that throws leaves
7
+ * the offset where it is and the update comes back on the next poll — which is
8
+ * why the router above it never throws for ordinary failures, and only a real
9
+ * fault gets a retry.
10
+ *
11
+ * Reconnection is the loop's other job. A laptop that sleeps, a network that
12
+ * drops, a Telegram hiccup — each surfaces as a rejected poll, and the bot has
13
+ * to come back on its own rather than going quietly silent.
14
+ */
15
+ import type { Logger } from '../harness/types.js';
16
+ import type { TelegramUpdate } from './types.js';
17
+ /** Where updates come from — narrowed so tests need no Bot API client. */
18
+ export interface UpdateSource {
19
+ getUpdates(offset: number, timeoutSeconds: number, signal?: AbortSignal): Promise<TelegramUpdate[]>;
20
+ }
21
+ /** Construction options. */
22
+ export interface PollerOptions {
23
+ readonly source: UpdateSource;
24
+ /** Handles one update; may throw, which leaves the update unacknowledged. */
25
+ readonly onUpdate: (update: TelegramUpdate) => Promise<void>;
26
+ /** Seconds Telegram holds an empty poll open. */
27
+ readonly longPollSeconds?: number;
28
+ /** First reconnect delay; doubles up to `maxDelayMs`. */
29
+ readonly baseDelayMs?: number;
30
+ readonly maxDelayMs?: number;
31
+ /** Called the first time a poll succeeds after a failure. */
32
+ readonly onConnected?: () => void;
33
+ readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
34
+ readonly logger?: Logger;
35
+ }
36
+ export declare class UpdatePoller {
37
+ private readonly options;
38
+ private readonly logger;
39
+ private readonly sleep;
40
+ private offset;
41
+ private connected;
42
+ constructor(options: PollerOptions);
43
+ /** The next offset that will be acknowledged; exposed for tests and status. */
44
+ get acknowledged(): number;
45
+ /**
46
+ * Poll until the signal aborts.
47
+ *
48
+ * @param signal - stops the loop; an in-flight poll is aborted with it.
49
+ */
50
+ run(signal: AbortSignal): Promise<void>;
51
+ /**
52
+ * Hand each update to the handler, acknowledging only after it returns.
53
+ * A throw stops the batch so the failed update is redelivered.
54
+ */
55
+ private dispatch;
56
+ /** Exponential backoff, capped so a long outage still retries regularly. */
57
+ private backoff;
58
+ }
59
+ //# sourceMappingURL=poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poller.d.ts","sourceRoot":"","sources":["../../src/telegram/poller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;CACpG;AAED,4BAA4B;AAC5B,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,iDAAiD;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,qBAAa,YAAY;IAMX,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqD;IAC3E,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,SAAS,CAAQ;gBAEI,OAAO,EAAE,aAAa;IAKnD,+EAA+E;IAC/E,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;;OAIG;IACG,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B7C;;;OAGG;YACW,QAAQ;IAQtB,4EAA4E;IAC5E,OAAO,CAAC,OAAO;CAKhB"}