Files
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

186 lines
5.4 KiB
TypeScript

/**
* Telegram thinking menu UI helpers
* Zones: telegram ui, thinking controls, menu composition
* Owns thinking-menu text, reply markup, callback handling, and thinking-menu message rendering
*/
import type {
TelegramMenuMessageRuntimeDeps,
TelegramMenuRenderPayload,
TelegramModelMenuState,
TelegramReplyMarkup,
} from "./menu-model.ts";
import {
isThinkingLevel,
type MenuModel,
THINKING_LEVELS,
type ThinkingLevel,
} from "./model.ts";
export interface TelegramThinkingMenuCallbackDeps {
setThinkingLevel: (level: ThinkingLevel) => void;
getCurrentThinkingLevel: () => ThinkingLevel;
updateStatusMessage: () => Promise<void>;
answerCallbackQuery: (
callbackQueryId: string,
text?: string,
) => Promise<void>;
isVoiceReplyActive?: () => boolean;
}
export interface TelegramThinkingMenuOpenDeps<
TModel extends MenuModel = MenuModel,
> extends TelegramMenuMessageRuntimeDeps {
getModelMenuState: () => Promise<TelegramModelMenuState<TModel>>;
getActiveModel: () => TModel | undefined;
getThinkingLevel: () => ThinkingLevel;
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
isVoiceReplyActive?: () => boolean;
}
function parseTelegramThinkingMenuCallbackAction(
data: string | undefined,
): { kind: "thinking:set"; level: string } | undefined {
if (!data?.startsWith("thinking:set:")) return undefined;
return { kind: "thinking:set", level: data.slice("thinking:set:".length) };
}
function applyTelegramMenuRenderPayload(
state: TelegramModelMenuState,
payload: TelegramMenuRenderPayload,
): TelegramMenuRenderPayload {
state.mode = payload.nextMode;
return payload;
}
async function editTelegramMenuMessage(
state: TelegramModelMenuState,
payload: TelegramMenuRenderPayload,
deps: TelegramMenuMessageRuntimeDeps,
): Promise<void> {
const appliedPayload = applyTelegramMenuRenderPayload(state, payload);
await deps.editInteractiveMessage(
state.chatId,
state.messageId,
appliedPayload.text,
appliedPayload.mode,
appliedPayload.replyMarkup,
);
}
function sendTelegramMenuMessage(
state: TelegramModelMenuState,
payload: TelegramMenuRenderPayload,
deps: TelegramMenuMessageRuntimeDeps,
): Promise<number | undefined> {
const appliedPayload = applyTelegramMenuRenderPayload(state, payload);
return deps.sendInteractiveMessage(
state.chatId,
appliedPayload.text,
appliedPayload.mode,
appliedPayload.replyMarkup,
state.threadId !== undefined
? { target: { chatId: state.chatId, threadId: state.threadId } }
: undefined,
);
}
export async function handleTelegramThinkingMenuCallbackAction(
callbackQueryId: string,
data: string | undefined,
activeModel: MenuModel | undefined,
deps: TelegramThinkingMenuCallbackDeps,
): Promise<boolean> {
const action = parseTelegramThinkingMenuCallbackAction(data);
if (!action) return false;
if (!isThinkingLevel(action.level)) {
await deps.answerCallbackQuery(callbackQueryId, "Invalid thinking level.");
return true;
}
if (deps.isVoiceReplyActive?.()) {
await deps.answerCallbackQuery(
callbackQueryId,
"Thinking controls are disabled during voice replies.",
);
return true;
}
if (!activeModel?.reasoning) {
await deps.answerCallbackQuery(
callbackQueryId,
"This model has no reasoning controls.",
);
return true;
}
deps.setThinkingLevel(action.level);
await deps.updateStatusMessage();
await deps.answerCallbackQuery(
callbackQueryId,
`Thinking: ${deps.getCurrentThinkingLevel()}`,
);
return true;
}
export function buildThinkingMenuText(): string {
return "<b>🧠 Choose a thinking level:</b>";
}
export function buildThinkingMenuReplyMarkup(
currentThinkingLevel: ThinkingLevel,
): TelegramReplyMarkup {
const rows = [[{ text: "⬆️ Main menu", callback_data: "menu:back" }]];
rows.push(
...THINKING_LEVELS.map((level) => [
{
text: level === currentThinkingLevel ? `🟢 ${level}` : level,
callback_data: `thinking:set:${level}`,
},
]),
);
return { inline_keyboard: rows };
}
export function buildTelegramThinkingMenuRenderPayload(
_activeModel: MenuModel | undefined,
currentThinkingLevel: ThinkingLevel,
): TelegramMenuRenderPayload {
return {
nextMode: "thinking",
text: buildThinkingMenuText(),
mode: "html",
replyMarkup: buildThinkingMenuReplyMarkup(currentThinkingLevel),
};
}
export async function openTelegramThinkingMenu<
TModel extends MenuModel = MenuModel,
>(deps: TelegramThinkingMenuOpenDeps<TModel>): Promise<void> {
if (deps.isVoiceReplyActive?.()) return;
const state = await deps.getModelMenuState();
const messageId = await sendTelegramMenuMessage(
state,
buildTelegramThinkingMenuRenderPayload(
deps.getActiveModel(),
deps.getThinkingLevel(),
),
deps,
);
if (messageId === undefined) return;
state.messageId = messageId;
state.mode = "thinking";
deps.storeModelMenuState(state);
}
export async function updateTelegramThinkingMenuMessage(
state: TelegramModelMenuState,
activeModel: MenuModel | undefined,
currentThinkingLevel: ThinkingLevel,
deps: TelegramMenuMessageRuntimeDeps & { isVoiceReplyActive?: () => boolean },
): Promise<void> {
if (deps.isVoiceReplyActive?.()) return;
await editTelegramMenuMessage(
state,
buildTelegramThinkingMenuRenderPayload(activeModel, currentThinkingLevel),
deps,
);
}