@intentface/latch-channels 0.9.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/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/ask.d.ts +69 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/ask.js +94 -0
- package/dist/ask.js.map +1 -0
- package/dist/commands.d.ts +41 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +54 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +207 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +883 -0
- package/dist/kernel.js.map +1 -0
- package/dist/render.d.ts +43 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +192 -0
- package/dist/render.js.map +1 -0
- package/dist/slack/api.d.ts +207 -0
- package/dist/slack/api.d.ts.map +1 -0
- package/dist/slack/api.js +249 -0
- package/dist/slack/api.js.map +1 -0
- package/dist/slack/blocks.d.ts +36 -0
- package/dist/slack/blocks.d.ts.map +1 -0
- package/dist/slack/blocks.js +161 -0
- package/dist/slack/blocks.js.map +1 -0
- package/dist/slack/ids.d.ts +40 -0
- package/dist/slack/ids.d.ts.map +1 -0
- package/dist/slack/ids.js +47 -0
- package/dist/slack/ids.js.map +1 -0
- package/dist/slack/index.d.ts +24 -0
- package/dist/slack/index.d.ts.map +1 -0
- package/dist/slack/index.js +18 -0
- package/dist/slack/index.js.map +1 -0
- package/dist/slack/manifest.d.ts +23 -0
- package/dist/slack/manifest.d.ts.map +1 -0
- package/dist/slack/manifest.js +102 -0
- package/dist/slack/manifest.js.map +1 -0
- package/dist/slack/normalize.d.ts +35 -0
- package/dist/slack/normalize.d.ts.map +1 -0
- package/dist/slack/normalize.js +63 -0
- package/dist/slack/normalize.js.map +1 -0
- package/dist/slack/payloads.d.ts +36 -0
- package/dist/slack/payloads.d.ts.map +1 -0
- package/dist/slack/payloads.js +80 -0
- package/dist/slack/payloads.js.map +1 -0
- package/dist/slack/slackChannel.d.ts +152 -0
- package/dist/slack/slackChannel.d.ts.map +1 -0
- package/dist/slack/slackChannel.js +650 -0
- package/dist/slack/slackChannel.js.map +1 -0
- package/dist/slack/stream.d.ts +88 -0
- package/dist/slack/stream.d.ts.map +1 -0
- package/dist/slack/stream.js +418 -0
- package/dist/slack/stream.js.map +1 -0
- package/dist/slack/verify.d.ts +20 -0
- package/dist/slack/verify.d.ts.map +1 -0
- package/dist/slack/verify.js +32 -0
- package/dist/slack/verify.js.map +1 -0
- package/dist/telegram/api.d.ts +110 -0
- package/dist/telegram/api.d.ts.map +1 -0
- package/dist/telegram/api.js +170 -0
- package/dist/telegram/api.js.map +1 -0
- package/dist/telegram/format.d.ts +26 -0
- package/dist/telegram/format.d.ts.map +1 -0
- package/dist/telegram/format.js +56 -0
- package/dist/telegram/format.js.map +1 -0
- package/dist/telegram/html.d.ts +11 -0
- package/dist/telegram/html.d.ts.map +1 -0
- package/dist/telegram/html.js +35 -0
- package/dist/telegram/html.js.map +1 -0
- package/dist/telegram/index.d.ts +15 -0
- package/dist/telegram/index.d.ts.map +1 -0
- package/dist/telegram/index.js +15 -0
- package/dist/telegram/index.js.map +1 -0
- package/dist/telegram/stream.d.ts +59 -0
- package/dist/telegram/stream.d.ts.map +1 -0
- package/dist/telegram/stream.js +185 -0
- package/dist/telegram/stream.js.map +1 -0
- package/dist/telegram/telegramChannel.d.ts +136 -0
- package/dist/telegram/telegramChannel.d.ts.map +1 -0
- package/dist/telegram/telegramChannel.js +277 -0
- package/dist/telegram/telegramChannel.js.map +1 -0
- package/dist/telegram/verify.d.ts +7 -0
- package/dist/telegram/verify.d.ts.map +1 -0
- package/dist/telegram/verify.js +14 -0
- package/dist/telegram/verify.js.map +1 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/whatsapp/api.d.ts +59 -0
- package/dist/whatsapp/api.d.ts.map +1 -0
- package/dist/whatsapp/api.js +154 -0
- package/dist/whatsapp/api.js.map +1 -0
- package/dist/whatsapp/index.d.ts +13 -0
- package/dist/whatsapp/index.d.ts.map +1 -0
- package/dist/whatsapp/index.js +13 -0
- package/dist/whatsapp/index.js.map +1 -0
- package/dist/whatsapp/verify.d.ts +17 -0
- package/dist/whatsapp/verify.d.ts.map +1 -0
- package/dist/whatsapp/verify.js +32 -0
- package/dist/whatsapp/verify.js.map +1 -0
- package/dist/whatsapp/whatsappChannel.d.ts +104 -0
- package/dist/whatsapp/whatsappChannel.d.ts.map +1 -0
- package/dist/whatsapp/whatsappChannel.js +217 -0
- package/dist/whatsapp/whatsappChannel.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { TelegramApiError } from "./api.js";
|
|
2
|
+
import { escapeHtml, gfmToTelegramHtml } from "./html.js";
|
|
3
|
+
/**
|
|
4
|
+
* The streaming-edit engine: Telegram (unlike Slack) can edit a sent message,
|
|
5
|
+
* so the answer streams as progressive `editMessageText` calls on one message.
|
|
6
|
+
*
|
|
7
|
+
* sendChatAction("typing") on start.
|
|
8
|
+
* First text-delta → sendMessage; keep the message_id.
|
|
9
|
+
* Buffer deltas; flush when ≥`minEditIntervalMs` since the last edit OR
|
|
10
|
+
* ≥`minEditChars` new chars. On 429, honor `retry_after` and resume.
|
|
11
|
+
* tool-start folds a status line into the draft; tool-end clears it.
|
|
12
|
+
* At `maxChars` stop growing, append the truncation notice (one message,
|
|
13
|
+
* never a spill-over second one), and keep draining events without editing.
|
|
14
|
+
* finish/error → final flush (status line removed, error appended).
|
|
15
|
+
*
|
|
16
|
+
* Renders HTML (`parse_mode: "HTML"`); if Telegram rejects the entity parse
|
|
17
|
+
* (400), the same content is retried as plain text and the message stays
|
|
18
|
+
* plain for the rest of the turn.
|
|
19
|
+
*/
|
|
20
|
+
export const MAX_TELEGRAM_TEXT_CHARS = 4000;
|
|
21
|
+
export const TELEGRAM_TEXT_TRUNCATION_NOTICE = "\n\n_Response truncated for Telegram. The full turn is available in Latch._";
|
|
22
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23
|
+
export async function renderTelegramStream(events, opts) {
|
|
24
|
+
const maxChars = opts.maxChars ?? MAX_TELEGRAM_TEXT_CHARS;
|
|
25
|
+
const notice = opts.truncationNotice ?? TELEGRAM_TEXT_TRUNCATION_NOTICE;
|
|
26
|
+
const minInterval = opts.minEditIntervalMs ?? 1500;
|
|
27
|
+
const minChars = opts.minEditChars ?? 500;
|
|
28
|
+
const now = opts.now ?? Date.now;
|
|
29
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
30
|
+
let text = "";
|
|
31
|
+
let truncated = false;
|
|
32
|
+
let statusLine = "";
|
|
33
|
+
let errorMessage;
|
|
34
|
+
let messageId;
|
|
35
|
+
let lastSent = "";
|
|
36
|
+
let lastFlushAt = 0;
|
|
37
|
+
let charsSinceFlush = 0;
|
|
38
|
+
let plainFallback = false;
|
|
39
|
+
function compose(final) {
|
|
40
|
+
let body = truncated ? text.slice(0, maxChars) + notice : text;
|
|
41
|
+
if (!final && statusLine)
|
|
42
|
+
body += `\n\n⏳ ${statusLine}`;
|
|
43
|
+
if (final && errorMessage)
|
|
44
|
+
body += `${body ? "\n\n" : ""}⚠️ ${errorMessage.slice(0, 500)}`;
|
|
45
|
+
return { html: gfmToTelegramHtml(body), plain: body };
|
|
46
|
+
}
|
|
47
|
+
async function send(content) {
|
|
48
|
+
const attempt = async () => {
|
|
49
|
+
const input = plainFallback
|
|
50
|
+
? { chat_id: opts.chatId, text: content.plain }
|
|
51
|
+
: {
|
|
52
|
+
chat_id: opts.chatId,
|
|
53
|
+
text: content.html,
|
|
54
|
+
parse_mode: "HTML",
|
|
55
|
+
link_preview_options: { is_disabled: true },
|
|
56
|
+
};
|
|
57
|
+
if (messageId === undefined) {
|
|
58
|
+
messageId = (await opts.api.sendMessage(input)).messageId;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await opts.api.editMessageText({ ...input, message_id: messageId });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
await attempt();
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (e instanceof TelegramApiError && e.status === 429) {
|
|
69
|
+
await sleep((e.retryAfter ?? 3) * 1000);
|
|
70
|
+
await attempt();
|
|
71
|
+
}
|
|
72
|
+
else if (e instanceof TelegramApiError && e.status === 400 && !plainFallback) {
|
|
73
|
+
// Almost always an entity-parse failure on partial markdown → go plain.
|
|
74
|
+
plainFallback = true;
|
|
75
|
+
await attempt();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function flush(final) {
|
|
83
|
+
const content = compose(final);
|
|
84
|
+
const key = plainFallback ? content.plain : content.html;
|
|
85
|
+
if (!key.trim() || key === lastSent)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
await send(content);
|
|
89
|
+
lastSent = plainFallback ? content.plain : content.html;
|
|
90
|
+
lastFlushAt = now();
|
|
91
|
+
charsSinceFlush = 0;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
if (final)
|
|
95
|
+
throw e;
|
|
96
|
+
console.error("[telegram] stream flush failed (will retry on next flush):", e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await opts.api.sendChatAction(opts.chatId, "typing");
|
|
100
|
+
for await (const ev of events) {
|
|
101
|
+
switch (ev.type) {
|
|
102
|
+
case "text-delta":
|
|
103
|
+
if (!truncated) {
|
|
104
|
+
text += ev.text;
|
|
105
|
+
charsSinceFlush += ev.text.length;
|
|
106
|
+
if (text.length > maxChars)
|
|
107
|
+
truncated = true;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
text += ev.text; // keep accumulating for the caller; stop editing
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case "tool-start":
|
|
115
|
+
statusLine = `Running ${ev.toolName}…`;
|
|
116
|
+
charsSinceFlush += minChars; // status changes flush on the next check
|
|
117
|
+
break;
|
|
118
|
+
case "tool-end":
|
|
119
|
+
statusLine = "";
|
|
120
|
+
charsSinceFlush += minChars;
|
|
121
|
+
break;
|
|
122
|
+
case "error":
|
|
123
|
+
errorMessage = ev.message;
|
|
124
|
+
continue;
|
|
125
|
+
default:
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const due = now() - lastFlushAt >= minInterval || charsSinceFlush >= minChars;
|
|
129
|
+
// Create the message promptly on first content; then throttle edits.
|
|
130
|
+
if ((messageId === undefined && text.trim()) || (messageId !== undefined && due)) {
|
|
131
|
+
await flush(false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
statusLine = "";
|
|
135
|
+
await flush(true);
|
|
136
|
+
const result = { text };
|
|
137
|
+
if (errorMessage !== undefined)
|
|
138
|
+
result.errorMessage = errorMessage;
|
|
139
|
+
if (messageId !== undefined)
|
|
140
|
+
result.messageId = messageId;
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/** Compose the approval-prompt text (HTML) for a tool awaiting approval. */
|
|
144
|
+
export function approvalPromptHtml(toolName, input) {
|
|
145
|
+
let json;
|
|
146
|
+
try {
|
|
147
|
+
json = JSON.stringify(input, null, 2) ?? String(input);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
json = String(input);
|
|
151
|
+
}
|
|
152
|
+
if (json.length > 2500)
|
|
153
|
+
json = `${json.slice(0, 2500)}\n… (truncated)`;
|
|
154
|
+
return `🔒 Approve <b>${escapeHtml(toolName)}</b>?\n\n<pre>${escapeHtml(json)}</pre>`;
|
|
155
|
+
}
|
|
156
|
+
/** Longest option label Telegram renders comfortably on a button. */
|
|
157
|
+
const MAX_BUTTON_LABEL = 40;
|
|
158
|
+
/** Button caption for an option — ticked marker for multi-select, length-capped. */
|
|
159
|
+
export function optionButtonText(label, ticked) {
|
|
160
|
+
const short = label.length > MAX_BUTTON_LABEL ? `${label.slice(0, MAX_BUTTON_LABEL - 1)}…` : label;
|
|
161
|
+
return ticked === undefined ? short : `${ticked ? "✓ " : " "}${short}`;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Compose one question of a questionnaire (HTML). Option *labels* go on the
|
|
165
|
+
* buttons; the descriptions only fit in the body, numbered so a typed "2" is an
|
|
166
|
+
* unambiguous answer.
|
|
167
|
+
*/
|
|
168
|
+
export function questionPromptHtml(question, index, total) {
|
|
169
|
+
const head = total > 1 ? `<b>Question ${index + 1} of ${total}</b>\n` : "";
|
|
170
|
+
const title = question.header
|
|
171
|
+
? `<i>${escapeHtml(question.header)}</i>\n${escapeHtml(question.question)}`
|
|
172
|
+
: escapeHtml(question.question);
|
|
173
|
+
const opts = question.options
|
|
174
|
+
.map((o, i) => {
|
|
175
|
+
const desc = o.description ? ` — ${escapeHtml(o.description)}` : "";
|
|
176
|
+
return `${i + 1}. <b>${escapeHtml(o.label)}</b>${desc}`;
|
|
177
|
+
})
|
|
178
|
+
.join("\n");
|
|
179
|
+
return `${head}${title}\n\n${opts}`;
|
|
180
|
+
}
|
|
181
|
+
/** Collapse an answered question card to a one-line receipt. */
|
|
182
|
+
export function answeredQuestionHtml(question, answer) {
|
|
183
|
+
return `✓ ${escapeHtml(question)}\n<b>${escapeHtml(answer)}</b>`;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/telegram/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { AskStateStore, AuthResult, ChannelRunOps, ChannelThreadStore, TranscribeAudio } from "../types.js";
|
|
2
|
+
import { decodeApprovalData, decodeQuestionData, encodeApprovalData, type InboundMedia, type Surface } from "../kernel.js";
|
|
3
|
+
import { type InlineKeyboardButton, type SendMessageInput, type TelegramApiOptions, type TelegramClient, type TelegramToken } from "./api.js";
|
|
4
|
+
export interface TelegramUser {
|
|
5
|
+
telegramUserId: string;
|
|
6
|
+
username?: string;
|
|
7
|
+
/** In a private chat Telegram's chat_id IS the user id, but carry it as delivered. */
|
|
8
|
+
telegramChatId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TelegramChannelConfig<P> {
|
|
11
|
+
/** The agent this bot serves. */
|
|
12
|
+
agent: string;
|
|
13
|
+
/**
|
|
14
|
+
* Stable host-side bot id (the bot row's id). Used in chat ids
|
|
15
|
+
* (`telegram_<botRef>_<userId>`) and delivery keys — NOT the Telegram bot id,
|
|
16
|
+
* so rotating the actual bot token/account keeps conversations intact.
|
|
17
|
+
*/
|
|
18
|
+
botRef: string;
|
|
19
|
+
/** Vault-backed in practice: `() => vault.get(principal, "telegram_bot_token:<id>")`. */
|
|
20
|
+
botToken: TelegramToken;
|
|
21
|
+
/** The `secret_token` registered with setWebhook (lazy-resolvable like the token). */
|
|
22
|
+
webhookSecret: string | (() => string | Promise<string>);
|
|
23
|
+
runtime: ChannelRunOps<P>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the inbound Telegram user to a platform principal. Return
|
|
26
|
+
* `{ allowed: false, linkUrl }` to refuse and DM a signed setup link; the
|
|
27
|
+
* kernel stashes the message (see `stashPending`) to replay after linking.
|
|
28
|
+
*/
|
|
29
|
+
authorizeUser: (user: TelegramUser) => Promise<AuthResult<P>>;
|
|
30
|
+
/** Persistent dedupe on `<botRef>:<chatId>:<userId>:<messageId>` (and `<botRef>:cb:<id>` for taps). */
|
|
31
|
+
seen?: (deliveryKey: string) => Promise<boolean>;
|
|
32
|
+
/** Host-backed one-turn-per-chat lease (see the kernel). */
|
|
33
|
+
claimTurn?: (chatId: string) => Promise<string | null>;
|
|
34
|
+
releaseTurn?: (chatId: string, token: string) => Promise<void>;
|
|
35
|
+
/** Stash the message that triggered a link prompt, to replay after linking. */
|
|
36
|
+
stashPending?: (input: TelegramUser & {
|
|
37
|
+
text: string;
|
|
38
|
+
media?: InboundMedia<TgFileRef>[];
|
|
39
|
+
}) => Promise<void>;
|
|
40
|
+
/** Build the platform authorize URL for a `connect_<name>` OAuth approval. */
|
|
41
|
+
authorizeUrl?: (connectionName: string, ctx: {
|
|
42
|
+
chatId: string;
|
|
43
|
+
telegramUserId: string;
|
|
44
|
+
approvalId: string;
|
|
45
|
+
}) => string;
|
|
46
|
+
/** Host speech-to-text for voice notes (see the kernel). */
|
|
47
|
+
transcribe?: TranscribeAudio;
|
|
48
|
+
/** Host storage for in-flight `askUser` questionnaires (see the kernel). */
|
|
49
|
+
askState?: AskStateStore;
|
|
50
|
+
/** Host storage for the thread epoch — what makes `/new` possible. */
|
|
51
|
+
thread?: ChannelThreadStore;
|
|
52
|
+
/** Keep a serverless function alive after the fast ACK (Vercel: `waitUntil`). */
|
|
53
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
54
|
+
/** Test seam: base URL / fetch for the Bot API client. */
|
|
55
|
+
apiOptions?: TelegramApiOptions;
|
|
56
|
+
}
|
|
57
|
+
export interface TgFileRef {
|
|
58
|
+
file_id: string;
|
|
59
|
+
file_size?: number;
|
|
60
|
+
mime_type?: string;
|
|
61
|
+
file_name?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Button payloads are the kernel's shared `lta:` / `ltq:` formats. */
|
|
64
|
+
export declare const encodeApprovalCallback: typeof encodeApprovalData;
|
|
65
|
+
export declare const decodeApprovalCallback: typeof decodeApprovalData;
|
|
66
|
+
export declare const decodeQuestionCallback: typeof decodeQuestionData;
|
|
67
|
+
/** Build the chat id for a (bot, Telegram user) at a thread epoch. */
|
|
68
|
+
export declare function telegramChatId(botRef: string, telegramUserId: string, epoch?: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* The inverse of `telegramChatId`, or null when `id` isn't one of ours. Parsed
|
|
71
|
+
* right-to-left because only the trailing parts have a fixed shape (numeric
|
|
72
|
+
* user id, optional `_t<digits>`) — a botRef is host-chosen and may contain
|
|
73
|
+
* anything but a newline.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseTelegramChatId(id: string): {
|
|
76
|
+
botRef: string;
|
|
77
|
+
telegramUserId: string;
|
|
78
|
+
epoch: number;
|
|
79
|
+
} | null;
|
|
80
|
+
export declare function telegramSurface(api: TelegramClient, botRef: string): Surface<TelegramUser, TgFileRef>;
|
|
81
|
+
export declare function telegramChannel<P>(config: TelegramChannelConfig<P>): {
|
|
82
|
+
handleRequest: (request: Request) => Promise<Response>;
|
|
83
|
+
/** Run one already-gated, already-leased message (scheduled delivery). */
|
|
84
|
+
runMessage: (ctx: {
|
|
85
|
+
telegramUserId: string;
|
|
86
|
+
telegramChatId: string;
|
|
87
|
+
text: string;
|
|
88
|
+
media?: InboundMedia<TgFileRef>[];
|
|
89
|
+
principal: P;
|
|
90
|
+
} & {
|
|
91
|
+
chatId?: string;
|
|
92
|
+
}) => Promise<void>;
|
|
93
|
+
/** Replay a stashed message under the turn lease (the post-link resume). */
|
|
94
|
+
replayMessage: (ctx: {
|
|
95
|
+
telegramUserId: string;
|
|
96
|
+
telegramChatId: string;
|
|
97
|
+
text: string;
|
|
98
|
+
media?: InboundMedia<TgFileRef>[];
|
|
99
|
+
principal: P;
|
|
100
|
+
}) => Promise<void>;
|
|
101
|
+
api: {
|
|
102
|
+
getMe(): Promise<import("./api.js").TelegramBotInfo>;
|
|
103
|
+
setWebhook(input: {
|
|
104
|
+
url: string;
|
|
105
|
+
secretToken: string;
|
|
106
|
+
dropPendingUpdates?: boolean;
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
deleteWebhook(): Promise<void>;
|
|
109
|
+
setMyCommands(commands: {
|
|
110
|
+
command: string;
|
|
111
|
+
description: string;
|
|
112
|
+
}[]): Promise<void>;
|
|
113
|
+
getWebhookInfo(): Promise<import("./api.js").TelegramWebhookInfo>;
|
|
114
|
+
sendMessage(input: SendMessageInput): Promise<{
|
|
115
|
+
messageId: number;
|
|
116
|
+
}>;
|
|
117
|
+
editMessageText(input: SendMessageInput & {
|
|
118
|
+
message_id: number;
|
|
119
|
+
}): Promise<void>;
|
|
120
|
+
editMessageReplyMarkup(input: {
|
|
121
|
+
chat_id: number | string;
|
|
122
|
+
message_id: number;
|
|
123
|
+
reply_markup?: {
|
|
124
|
+
inline_keyboard: InlineKeyboardButton[][];
|
|
125
|
+
};
|
|
126
|
+
}): Promise<void>;
|
|
127
|
+
sendChatAction(chatId: number | string, action?: string): Promise<void>;
|
|
128
|
+
answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;
|
|
129
|
+
getFile(fileId: string): Promise<{
|
|
130
|
+
filePath?: string;
|
|
131
|
+
fileSize?: number;
|
|
132
|
+
}>;
|
|
133
|
+
downloadFile(filePath: string): Promise<Uint8Array>;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=telegramChannel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegramChannel.d.ts","sourceRoot":"","sources":["../../src/telegram/telegramChannel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createTurnKernel, decodeApprovalData, decodeQuestionData, encodeApprovalData, } from "../kernel.js";
|
|
2
|
+
import { assertCallbackData, telegramApi, TelegramApiError, } from "./api.js";
|
|
3
|
+
import { verifyTelegramSecretToken } from "./verify.js";
|
|
4
|
+
import { answeredQuestionHtml, approvalPromptHtml, htmlToPlain, optionButtonText, questionPromptHtml, } from "./format.js";
|
|
5
|
+
import { escapeHtml, gfmToTelegramHtml } from "./html.js";
|
|
6
|
+
/**
|
|
7
|
+
* One agent's Telegram bot. The host wires `handleRequest` to the bot's per-bot
|
|
8
|
+
* webhook route; we verify the secret token, ACK fast, and hand the parsed
|
|
9
|
+
* update to the turn kernel in the background. The answer posts once at
|
|
10
|
+
* finish (as HTML, plain on an entity-parse failure); "typing…" is the only
|
|
11
|
+
* progress signal.
|
|
12
|
+
*
|
|
13
|
+
* v1 scope: private chats (DMs) only — group messages are ignored by design.
|
|
14
|
+
*
|
|
15
|
+
* Everything below the parser is a {@link Surface}: how to send text, cards
|
|
16
|
+
* and links, how to edit or retire a card, how to download a file. The
|
|
17
|
+
* admission, questionnaire and command semantics live in the kernel.
|
|
18
|
+
*/
|
|
19
|
+
/** Telegram caps messages at 4096 chars; leave room for the split's trimming. */
|
|
20
|
+
const MAX_TEXT_CHARS = 4000;
|
|
21
|
+
/** Button payloads are the kernel's shared `lta:` / `ltq:` formats. */
|
|
22
|
+
export const encodeApprovalCallback = encodeApprovalData;
|
|
23
|
+
export const decodeApprovalCallback = decodeApprovalData;
|
|
24
|
+
export const decodeQuestionCallback = decodeQuestionData;
|
|
25
|
+
const ok = () => new Response(null, { status: 200 });
|
|
26
|
+
/** The typed media attachments present on a message, largest photo size only. */
|
|
27
|
+
function mediaRefs(msg) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const photo = msg.photo?.[msg.photo.length - 1];
|
|
30
|
+
if (photo)
|
|
31
|
+
out.push({ ref: photo, mediaType: "image/jpeg", name: "photo.jpg" });
|
|
32
|
+
if (msg.document)
|
|
33
|
+
out.push({
|
|
34
|
+
ref: msg.document,
|
|
35
|
+
mediaType: msg.document.mime_type ?? "application/octet-stream",
|
|
36
|
+
name: msg.document.file_name ?? "document",
|
|
37
|
+
});
|
|
38
|
+
if (msg.voice)
|
|
39
|
+
out.push({ ref: msg.voice, mediaType: "audio/ogg", name: "voice.ogg" });
|
|
40
|
+
if (msg.audio)
|
|
41
|
+
out.push({ ref: msg.audio, mediaType: msg.audio.mime_type ?? "audio/mpeg", name: "audio" });
|
|
42
|
+
if (msg.video)
|
|
43
|
+
out.push({ ref: msg.video, mediaType: msg.video.mime_type ?? "video/mp4", name: "video.mp4" });
|
|
44
|
+
if (msg.video_note)
|
|
45
|
+
out.push({ ref: msg.video_note, mediaType: "video/mp4", name: "video-note.mp4" });
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const userOf = (from, chatId) => ({
|
|
49
|
+
telegramUserId: String(from.id),
|
|
50
|
+
...(from.username ? { username: from.username } : {}),
|
|
51
|
+
telegramChatId: String(chatId),
|
|
52
|
+
});
|
|
53
|
+
// ── The Telegram chat-id format ─────────────────────────────────────────────
|
|
54
|
+
// `telegram_<botRef>_<telegramUserId>` for the original thread, `…_t<epoch>`
|
|
55
|
+
// after `/new` bumps the epoch. Defined ONCE here and read back by
|
|
56
|
+
// `parseTelegramChatId`, so nothing outside this file has to know the shape —
|
|
57
|
+
// including the hosts that need to go the other way (a scheduled run resolving
|
|
58
|
+
// which DM it belongs to). `_t<n>` can't collide with a numeric Telegram user
|
|
59
|
+
// id and stays URL-safe: chat ids show up in `/chat/<chatId>` links.
|
|
60
|
+
/** Build the chat id for a (bot, Telegram user) at a thread epoch. */
|
|
61
|
+
export function telegramChatId(botRef, telegramUserId, epoch = 0) {
|
|
62
|
+
const base = `telegram_${botRef}_${telegramUserId}`;
|
|
63
|
+
return epoch > 0 ? `${base}_t${epoch}` : base;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The inverse of `telegramChatId`, or null when `id` isn't one of ours. Parsed
|
|
67
|
+
* right-to-left because only the trailing parts have a fixed shape (numeric
|
|
68
|
+
* user id, optional `_t<digits>`) — a botRef is host-chosen and may contain
|
|
69
|
+
* anything but a newline.
|
|
70
|
+
*/
|
|
71
|
+
export function parseTelegramChatId(id) {
|
|
72
|
+
const m = /^telegram_(.+)_(\d+)(?:_t(\d+))?$/.exec(id);
|
|
73
|
+
if (!m)
|
|
74
|
+
return null;
|
|
75
|
+
return { botRef: m[1], telegramUserId: m[2], epoch: m[3] ? Number(m[3]) : 0 };
|
|
76
|
+
}
|
|
77
|
+
// ── The surface ─────────────────────────────────────────────────────────────
|
|
78
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
79
|
+
/** The inline keyboard for one question: one row per option, Done for multi. */
|
|
80
|
+
function questionKeyboard(session, data) {
|
|
81
|
+
const q = session.questions[session.cursor];
|
|
82
|
+
const rows = q.options.map((o, i) => [
|
|
83
|
+
{
|
|
84
|
+
text: optionButtonText(o.label, q.multiSelect ? session.selected.includes(i) : undefined),
|
|
85
|
+
callback_data: assertCallbackData(data.pick(i)),
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
if (q.multiSelect)
|
|
89
|
+
rows.push([{ text: "✅ Done", callback_data: assertCallbackData(data.done) }]);
|
|
90
|
+
return rows;
|
|
91
|
+
}
|
|
92
|
+
export function telegramSurface(api, botRef) {
|
|
93
|
+
const chat = (u) => u.telegramChatId;
|
|
94
|
+
const ref = (r) => (r.messageId ? String(r.messageId) : undefined);
|
|
95
|
+
/**
|
|
96
|
+
* Send HTML; on 429 honour `retry_after` once; on 400 (almost always an
|
|
97
|
+
* entity-parse failure on model markdown) resend the plain twin.
|
|
98
|
+
*/
|
|
99
|
+
async function sendHtml(u, html, plain, reply_markup) {
|
|
100
|
+
const base = {
|
|
101
|
+
chat_id: chat(u),
|
|
102
|
+
link_preview_options: { is_disabled: true },
|
|
103
|
+
...(reply_markup ? { reply_markup } : {}),
|
|
104
|
+
};
|
|
105
|
+
const rich = { ...base, text: html, parse_mode: "HTML" };
|
|
106
|
+
try {
|
|
107
|
+
return ref(await api.sendMessage(rich));
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
if (e instanceof TelegramApiError && e.status === 429) {
|
|
111
|
+
await sleep((e.retryAfter ?? 3) * 1000);
|
|
112
|
+
return ref(await api.sendMessage(rich));
|
|
113
|
+
}
|
|
114
|
+
if (e instanceof TelegramApiError && e.status === 400) {
|
|
115
|
+
return ref(await api.sendMessage({ ...base, text: plain }));
|
|
116
|
+
}
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
kind: "telegram",
|
|
122
|
+
maxTextChars: MAX_TEXT_CHARS,
|
|
123
|
+
chatId: (u, epoch) => telegramChatId(botRef, u.telegramUserId, epoch),
|
|
124
|
+
sendText: async (u, text, opts) => opts?.rich
|
|
125
|
+
? sendHtml(u, gfmToTelegramHtml(text), text)
|
|
126
|
+
: ref(await api.sendMessage({ chat_id: chat(u), text })),
|
|
127
|
+
sendLink: async (u, body, label, url) => {
|
|
128
|
+
await sendHtml(u, escapeHtml(body), body, { inline_keyboard: [[{ text: label, url }]] });
|
|
129
|
+
},
|
|
130
|
+
sendApproval: (u, pa, data) => {
|
|
131
|
+
const html = approvalPromptHtml(pa.toolName, pa.input);
|
|
132
|
+
return sendHtml(u, html, htmlToPlain(html), {
|
|
133
|
+
inline_keyboard: [
|
|
134
|
+
[
|
|
135
|
+
{ text: "✅ Approve", callback_data: assertCallbackData(data.approve) },
|
|
136
|
+
{ text: "🚫 Deny", callback_data: assertCallbackData(data.deny) },
|
|
137
|
+
],
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
sendQuestion: (u, session, data) => {
|
|
142
|
+
const q = session.questions[session.cursor];
|
|
143
|
+
if (!q)
|
|
144
|
+
return Promise.resolve(undefined);
|
|
145
|
+
const html = questionPromptHtml(q, session.cursor, session.questions.length);
|
|
146
|
+
return sendHtml(u, html, htmlToPlain(html), data ? { inline_keyboard: questionKeyboard(session, data) } : undefined);
|
|
147
|
+
},
|
|
148
|
+
updateQuestion: (u, card, session, data) => api.editMessageReplyMarkup({
|
|
149
|
+
chat_id: chat(u),
|
|
150
|
+
message_id: Number(card),
|
|
151
|
+
reply_markup: { inline_keyboard: questionKeyboard(session, data) },
|
|
152
|
+
}),
|
|
153
|
+
answered: async (u, card, a) => {
|
|
154
|
+
if (card === undefined)
|
|
155
|
+
return;
|
|
156
|
+
await api.editMessageText({
|
|
157
|
+
chat_id: chat(u),
|
|
158
|
+
message_id: Number(card),
|
|
159
|
+
text: answeredQuestionHtml(a.question, a.answer),
|
|
160
|
+
parse_mode: "HTML",
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
settleCard: (u, card, text, opts) => api.editMessageText({
|
|
164
|
+
chat_id: chat(u),
|
|
165
|
+
message_id: Number(card),
|
|
166
|
+
text: opts?.rich ? gfmToTelegramHtml(text) : text,
|
|
167
|
+
...(opts?.rich ? { parse_mode: "HTML" } : {}),
|
|
168
|
+
}),
|
|
169
|
+
retireCard: (u, card) => api.editMessageReplyMarkup({ chat_id: chat(u), message_id: Number(card) }),
|
|
170
|
+
typing: (u) => api.sendChatAction(chat(u)),
|
|
171
|
+
ack: (id, text) => api.answerCallbackQuery(id, text),
|
|
172
|
+
fetchMedia: async (m, maxBytes) => {
|
|
173
|
+
if ((m.ref.file_size ?? 0) > maxBytes)
|
|
174
|
+
return null;
|
|
175
|
+
const { filePath, fileSize } = await api.getFile(m.ref.file_id);
|
|
176
|
+
if (!filePath || (fileSize ?? 0) > maxBytes)
|
|
177
|
+
return null;
|
|
178
|
+
return { bytes: await api.downloadFile(filePath), mediaType: m.mediaType };
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// ── The channel ─────────────────────────────────────────────────────────────
|
|
183
|
+
export function telegramChannel(config) {
|
|
184
|
+
const api = telegramApi(config.botToken, config.apiOptions);
|
|
185
|
+
const defer = (p) => (config.waitUntil ? config.waitUntil(p) : p);
|
|
186
|
+
const kernel = createTurnKernel({
|
|
187
|
+
agent: config.agent,
|
|
188
|
+
botRef: config.botRef,
|
|
189
|
+
runtime: config.runtime,
|
|
190
|
+
authorizeUser: config.authorizeUser,
|
|
191
|
+
...(config.seen ? { seen: config.seen } : {}),
|
|
192
|
+
...(config.claimTurn ? { claimTurn: config.claimTurn } : {}),
|
|
193
|
+
...(config.releaseTurn ? { releaseTurn: config.releaseTurn } : {}),
|
|
194
|
+
...(config.transcribe ? { transcribe: config.transcribe } : {}),
|
|
195
|
+
...(config.askState ? { askState: config.askState } : {}),
|
|
196
|
+
...(config.thread ? { thread: config.thread } : {}),
|
|
197
|
+
...(config.stashPending
|
|
198
|
+
? {
|
|
199
|
+
stashPending: (i) => config.stashPending({ ...i.user, text: i.text, ...(i.media ? { media: i.media } : {}) }),
|
|
200
|
+
}
|
|
201
|
+
: {}),
|
|
202
|
+
...(config.authorizeUrl
|
|
203
|
+
? {
|
|
204
|
+
authorizeUrl: (name, ctx) => config.authorizeUrl(name, {
|
|
205
|
+
chatId: ctx.chatId,
|
|
206
|
+
telegramUserId: ctx.user.telegramUserId,
|
|
207
|
+
approvalId: ctx.approvalId,
|
|
208
|
+
}),
|
|
209
|
+
}
|
|
210
|
+
: {}),
|
|
211
|
+
}, telegramSurface(api, config.botRef));
|
|
212
|
+
async function resolveWebhookSecret() {
|
|
213
|
+
return typeof config.webhookSecret === "function" ? config.webhookSecret() : config.webhookSecret;
|
|
214
|
+
}
|
|
215
|
+
/** A Telegram update → the kernel's inbound event, or null for updates we ignore. */
|
|
216
|
+
function parse(update) {
|
|
217
|
+
const cb = update.callback_query;
|
|
218
|
+
if (cb) {
|
|
219
|
+
// Same DM-only rule as messages: a tap on a card that somehow lives in a
|
|
220
|
+
// group would otherwise route every reply to that group's chat id.
|
|
221
|
+
if (cb.message && cb.message.chat.type !== "private")
|
|
222
|
+
return null;
|
|
223
|
+
return {
|
|
224
|
+
kind: "action",
|
|
225
|
+
user: userOf(cb.from, cb.message?.chat.id ?? cb.from.id),
|
|
226
|
+
data: cb.data ?? "",
|
|
227
|
+
deliveryKey: `${config.botRef}:cb:${cb.id}`,
|
|
228
|
+
...(cb.message ? { card: String(cb.message.message_id) } : {}),
|
|
229
|
+
ackId: cb.id,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const msg = update.message;
|
|
233
|
+
if (!msg?.from || msg.from.is_bot)
|
|
234
|
+
return null;
|
|
235
|
+
if (msg.chat.type !== "private")
|
|
236
|
+
return null; // v1: DMs only, by design
|
|
237
|
+
const user = userOf(msg.from, msg.chat.id);
|
|
238
|
+
return {
|
|
239
|
+
kind: "message",
|
|
240
|
+
user,
|
|
241
|
+
text: (msg.text ?? msg.caption ?? "").trim(),
|
|
242
|
+
media: mediaRefs(msg),
|
|
243
|
+
deliveryKey: `${config.botRef}:${user.telegramChatId}:${user.telegramUserId}:${msg.message_id}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// ── The per-bot webhook entrypoint ──────────────────────────────────────────
|
|
247
|
+
async function handleRequest(request) {
|
|
248
|
+
const header = request.headers.get("x-telegram-bot-api-secret-token");
|
|
249
|
+
if (!verifyTelegramSecretToken(header, await resolveWebhookSecret())) {
|
|
250
|
+
return new Response("invalid secret token", { status: 401 });
|
|
251
|
+
}
|
|
252
|
+
let update;
|
|
253
|
+
try {
|
|
254
|
+
update = (await request.json());
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return ok(); // malformed body — nothing to retry
|
|
258
|
+
}
|
|
259
|
+
const ev = parse(update);
|
|
260
|
+
if (ev)
|
|
261
|
+
defer(kernel.handleInbound(ev));
|
|
262
|
+
return ok();
|
|
263
|
+
}
|
|
264
|
+
const kernelCtx = ({ telegramUserId, telegramChatId, ...rest }) => ({
|
|
265
|
+
user: { telegramUserId, telegramChatId },
|
|
266
|
+
...rest,
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
handleRequest,
|
|
270
|
+
/** Run one already-gated, already-leased message (scheduled delivery). */
|
|
271
|
+
runMessage: (ctx) => kernel.runMessage(kernelCtx(ctx)),
|
|
272
|
+
/** Replay a stashed message under the turn lease (the post-link resume). */
|
|
273
|
+
replayMessage: (ctx) => kernel.replayMessage(kernelCtx(ctx)),
|
|
274
|
+
api,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=telegramChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegramChannel.js","sourceRoot":"","sources":["../../src/telegram/telegramChannel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Telegram's webhook secret: the `x-telegram-bot-api-secret-token`
|
|
3
|
+
* header must equal the `secret_token` we registered with `setWebhook`.
|
|
4
|
+
* Constant-time compare (length check first — `timingSafeEqual` requires it).
|
|
5
|
+
*/
|
|
6
|
+
export declare function verifyTelegramSecretToken(header: string | null, expected: string): boolean;
|
|
7
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/telegram/verify.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAKT"}
|