refactor(telegram): unify interactive keyboard rendering (#128728)

* refactor(telegram): unify interactive keyboard rendering

* chore(telegram): ratchet removed keyboard assertion
This commit is contained in:
Peter Steinberger
2026-08-24 06:24:36 -07:00
committed by GitHub
parent 898b1ce4b3
commit 15e9bea3cb
3 changed files with 60 additions and 37 deletions
+1 -1
View File
@@ -1279,7 +1279,7 @@ extensions/telegram/src/bot/body-helpers.ts 1
extensions/telegram/src/bot/delivery.replies.ts 1
extensions/telegram/src/bot/delivery.resolve-media.ts 1
extensions/telegram/src/bot/helpers.ts 2
extensions/telegram/src/button-types.ts 2
extensions/telegram/src/button-types.ts 1
extensions/telegram/src/callback-query-answer-state.ts 1
extensions/telegram/src/channel-actions.ts 1
extensions/telegram/src/channel.ts 1
@@ -53,6 +53,55 @@ describe("buildTelegramInteractiveButtons callback limits", () => {
});
});
describe("resolveTelegramInlineButtons precedence", () => {
it("returns explicit buttons without reading lower-priority payloads", () => {
const buttons = [[{ text: "Explicit", callback_data: "explicit" }]];
const params = {
buttons,
get interactive(): never {
throw new Error("unexpected interactive normalization");
},
get presentation(): never {
throw new Error("unexpected presentation normalization");
},
};
expect(resolveTelegramInlineButtons(params)).toBe(buttons);
});
it.each([
{
description: "the legacy payload contains only text",
interactive: { blocks: [{ type: "text", text: "Legacy heading" }] },
expectedDropped: [],
},
{
description: "every legacy callback exceeds the Telegram byte limit",
interactive: {
blocks: [{ type: "buttons", buttons: [{ label: "Oversized", value: "x".repeat(65) }] }],
},
expectedDropped: [
{ label: "Oversized", reason: "callback_data_too_long", callbackDataBytes: 65 },
],
},
])("falls back to presentation buttons when $description", ({ interactive, expectedDropped }) => {
const dropped: Array<{ label: string; reason: string; callbackDataBytes?: number }> = [];
expect(
resolveTelegramInlineButtons(
{
interactive,
presentation: {
blocks: [{ type: "buttons", buttons: [{ label: "Fallback", value: "fallback" }] }],
},
},
{ onDroppedControl: (control) => dropped.push(control) },
),
).toEqual([[{ text: "Fallback", callback_data: "fallback", style: undefined }]]);
expect(dropped).toEqual(expectedDropped);
});
});
describe("buildTelegramPresentationButtons", () => {
it("builds inline buttons from presentation blocks", () => {
expect(
+10 -36
View File
@@ -1,13 +1,12 @@
// Telegram plugin module implements button types behavior.
import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
import {
reduceLegacyInteractiveReply,
legacyInteractiveReplyToPresentation,
isMessagePresentationInteractiveBlock,
normalizeMessagePresentation,
normalizeLegacyInteractiveReply,
renderMessagePresentationFallbackText,
resolveMessagePresentationButtonAction,
type LegacyInteractiveReply,
type MessagePresentation,
type MessagePresentationButton,
} from "openclaw/plugin-sdk/interactive-runtime";
@@ -228,38 +227,6 @@ function chunkInteractiveButtons(
}
}
/**
* @deprecated Use buildTelegramPresentationButtons with MessagePresentation.
*/
function buildTelegramInteractiveButtons(
interactive?: LegacyInteractiveReply,
options?: TelegramButtonBuildOptions,
): TelegramInlineButtons | undefined {
const rows = reduceLegacyInteractiveReply(
interactive,
[] as TelegramInlineButton[][],
(state, block) => {
if (block.type === "buttons") {
chunkInteractiveButtons(block.buttons, state, options);
return state;
}
if (block.type === "select") {
chunkInteractiveButtons(
block.options.map((option) => ({
label: option.label,
action: option.action,
value: option.value,
})),
state,
options,
);
}
return state;
},
);
return rows.length > 0 ? rows : undefined;
}
/** Convert portable presentation controls to Telegram inline keyboard rows. */
export function buildTelegramPresentationButtons(
presentation?: MessagePresentation,
@@ -296,9 +263,16 @@ export function resolveTelegramInlineButtons(
},
options?: TelegramButtonBuildOptions,
): TelegramInlineButtons | undefined {
if (params.buttons) {
return params.buttons;
}
const interactive = normalizeLegacyInteractiveReply(params.interactive);
return (
params.buttons ??
buildTelegramInteractiveButtons(normalizeLegacyInteractiveReply(params.interactive), options) ??
buildTelegramPresentationButtons(
interactive ? legacyInteractiveReplyToPresentation(interactive) : undefined,
options,
) ??
buildTelegramPresentationButtons(normalizeMessagePresentation(params.presentation), options)
);
}