feat(telegram): add deleteMessage action

Add ability to delete messages in Telegram chats via the message tool.

Changes:
- Add deleteMessageTelegram function in send.ts
- Add deleteMessage action handler in telegram-actions.ts
- Add delete action support in telegram message plugin adapter
- Add deleteMessage to TelegramActionConfig type
- Update message tool description to mention delete action

Usage:
- Via message tool: action="delete", chatId, messageId
- Can be disabled via channels.telegram.actions.deleteMessage=false

Limitations (Telegram API):
- Bot can delete its own messages in any chat
- Bot can delete others' messages only if admin with "Delete Messages"
- Messages older than 48h in groups may fail to delete
This commit is contained in:
sleontenko
2026-01-14 22:20:17 +02:00
committed by Peter Steinberger
parent 9b7df414e6
commit 83a25d26fc
5 changed files with 86 additions and 2 deletions

View File

@@ -148,7 +148,7 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
label: "Message",
name: "message",
description:
"Send messages and channel actions (polls, reactions, pins, threads, etc.) via configured channel plugins.",
"Send, delete, and manage messages via channel plugins. Supports actions: send, delete, react, poll, pin, threads, and more.",
parameters: schema,
execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>;

View File

@@ -1,7 +1,11 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { reactMessageTelegram, sendMessageTelegram } from "../../telegram/send.js";
import {
deleteMessageTelegram,
reactMessageTelegram,
sendMessageTelegram,
} from "../../telegram/send.js";
import { resolveTelegramToken } from "../../telegram/token.js";
import {
createActionGate,
@@ -149,5 +153,29 @@ export async function handleTelegramAction(
});
}
if (action === "deleteMessage") {
if (!isActionEnabled("deleteMessage")) {
throw new Error("Telegram deleteMessage is disabled.");
}
const chatId = readStringOrNumberParam(params, "chatId", {
required: true,
});
const messageId = readNumberParam(params, "messageId", {
required: true,
integer: true,
});
const token = resolveTelegramToken(cfg, { accountId }).token;
if (!token) {
throw new Error(
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
);
}
await deleteMessageTelegram(chatId ?? "", messageId ?? 0, {
token,
accountId: accountId ?? undefined,
});
return jsonResult({ ok: true, deleted: true });
}
throw new Error(`Unsupported Telegram action: ${action}`);
}

View File

@@ -35,6 +35,7 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
const gate = createActionGate(cfg.channels?.telegram?.actions);
const actions = new Set<ChannelMessageActionName>(["send"]);
if (gate("reactions")) actions.add("react");
if (gate("deleteMessage")) actions.add("delete");
return Array.from(actions);
},
supportsButtons: ({ cfg }) => hasTelegramInlineButtons(cfg),
@@ -92,6 +93,22 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
);
}
if (action === "delete") {
const chatId = readStringParam(params, "chatId", { required: true });
const messageId = readStringParam(params, "messageId", {
required: true,
});
return await handleTelegramAction(
{
action: "deleteMessage",
chatId,
messageId: Number(messageId),
accountId: accountId ?? undefined,
},
cfg,
);
}
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
},
};

View File

@@ -11,6 +11,7 @@ import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
export type TelegramActionConfig = {
reactions?: boolean;
sendMessage?: boolean;
deleteMessage?: boolean;
};
export type TelegramAccountConfig = {

View File

@@ -396,6 +396,44 @@ export async function reactMessageTelegram(
return { ok: true };
}
type TelegramDeleteOpts = {
token?: string;
accountId?: string;
verbose?: boolean;
api?: Bot["api"];
retry?: RetryConfig;
};
export async function deleteMessageTelegram(
chatIdInput: string | number,
messageIdInput: string | number,
opts: TelegramDeleteOpts = {},
): Promise<{ ok: true }> {
const cfg = loadConfig();
const account = resolveTelegramAccount({
cfg,
accountId: opts.accountId,
});
const token = resolveToken(opts.token, account);
const chatId = normalizeChatId(String(chatIdInput));
const messageId = normalizeMessageId(messageIdInput);
const fetchImpl = resolveTelegramFetch();
const client: ApiClientOptions | undefined = fetchImpl
? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] }
: undefined;
const api = opts.api ?? new Bot(token, client ? { client } : undefined).api;
const request = createTelegramRetryRunner({
retry: opts.retry,
configRetry: account.config.retry,
verbose: opts.verbose,
});
await request(() => api.deleteMessage(chatId, messageId), "deleteMessage");
logVerbose(
`[telegram] Deleted message ${messageId} from chat ${chatId}`,
);
return { ok: true };
}
function inferFilename(kind: ReturnType<typeof mediaKindFromMime>) {
switch (kind) {
case "image":