/** * Telegram status rendering helpers * Builds usage, cost, and context summaries for the interactive Telegram status view */ export type TelegramStatusQueueLane = "control" | "priority" | "default"; export interface TelegramUsageStats { totalInput: number; totalOutput: number; totalCacheRead: number; totalCacheWrite: number; totalCost: number; } interface TelegramUsageMessage { role: string; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: { total: number }; }; } interface TelegramStatusSessionEntry { type: string; message?: TelegramUsageMessage; } interface TelegramContextUsage { contextWindow?: number; percent: number | null; } export interface TelegramUsageLimits { session: number; weekly: number; sessionResetsIn?: string; weeklyResetsIn?: string; updatedAt: number; } export interface TelegramUsageLimitsUpdate { session?: unknown; weekly?: unknown; sessionResetsIn?: unknown; weeklyResetsIn?: unknown; } export interface TelegramUsageLimitsStore { update: (update: TelegramUsageLimitsUpdate) => void; get: () => TelegramUsageLimits | undefined; clear: () => void; } export interface TelegramStatusActiveModel { contextWindow?: number; } export interface TelegramStatusContext { sessionManager: { getEntries(): TelegramStatusSessionEntry[] }; getContextUsage(): TelegramContextUsage | undefined; modelRegistry: { isUsingOAuth(model: TelegramStatusActiveModel): boolean; }; } export type TelegramRuntimeEventDetailValue = string | number | boolean | null; const MAX_RECENT_TELEGRAM_RUNTIME_EVENTS = 10; export interface TelegramRuntimeEvent { at: number; category: string; message: string; details?: Record; } export interface TelegramRuntimeEventInput { category: string; error?: unknown; message?: string; details?: Record; } export interface TelegramRuntimeEventRecorder { record: ( category: string, error: unknown, details?: Record, ) => void; getEvents: () => TelegramRuntimeEvent[]; clear: () => void; } export interface TelegramRuntimeEventRecorderOptions { getBotToken: () => string | undefined; maxEvents?: number; now?: () => number; } export interface TelegramBridgeStatusLineState { botUsername?: string; allowedUserId?: number; lockState?: string; pollingActive: boolean; lastUpdateId?: number; activeSourceMessageIds?: number[]; pendingDispatch: boolean; compactionInProgress: boolean; activeToolExecutions: number; pendingModelSwitch: boolean; queuedItems: Array<{ queueLane: TelegramStatusQueueLane }>; recentRuntimeEvents: TelegramRuntimeEvent[]; } export interface TelegramStatusBarTheme { fg: ( token: "accent" | "error" | "muted" | "warning" | "success", text: string, ) => string; } export interface TelegramFooterTopicState { threadId?: number; topicName?: string; } export interface TelegramFooterLabelState { hasBotToken: boolean; connected: boolean; role: "router" | "target"; topic?: TelegramFooterTopicState; bindCode?: string; broadcastEnabled: boolean; queueCount: number; active: boolean; } export interface TelegramStatusBarState { hasBotToken: boolean; pollingActive: boolean; paired: boolean; compactionInProgress: boolean; processing: boolean; processingStatus?: string; queuedStatus: string; error?: string; footerState?: TelegramFooterLabelState; } export interface TelegramStatusRuntimeContext { ui: { theme: TelegramStatusBarTheme; setStatus: (key: string, text: string) => void; }; } export interface TelegramStatusRuntimeDeps< TContext extends TelegramStatusRuntimeContext, > { statusKey?: string; getStatusBarState: (ctx: TContext, error?: string) => TelegramStatusBarState; getBridgeStatusLineState: () => TelegramBridgeStatusLineState; } export interface TelegramBridgeStatusConfig { botToken?: string; botUsername?: string; allowedUserId?: number; lastUpdateId?: number; } export interface TelegramBridgeStatusRuntimeDeps< TQueueItem extends { queueLane: TelegramStatusQueueLane }, > { statusKey?: string; getConfig: () => TelegramBridgeStatusConfig; isPollingActive: () => boolean; getActiveSourceMessageIds: () => number[] | undefined; hasActiveTurn: () => boolean; hasDispatchPending: () => boolean; isCompactionInProgress: () => boolean; getActiveToolExecutions: () => number; hasPendingModelSwitch: () => boolean; getQueuedItems: () => TQueueItem[]; formatQueuedStatus: (items: TQueueItem[]) => string; getRecentRuntimeEvents: () => TelegramRuntimeEvent[]; getRuntimeLockState?: () => string; getFooterState?: () => TelegramFooterLabelState | undefined; } export interface TelegramStatusRuntime< TContext extends TelegramStatusRuntimeContext, > { updateStatus: (ctx: TContext, error?: string) => void; getStatusLines: () => string[]; } export function redactTelegramRuntimeMessage( message: string, botToken: string | undefined, ): string { if (!botToken) return message; return message.split(botToken).join(""); } function normalizeTelegramRuntimeEventDetails( details: Record | undefined, botToken: string | undefined, ): Record | undefined { if (!details) return undefined; const normalized: Record = {}; for (const [key, value] of Object.entries(details)) { if (value === undefined) continue; if (typeof value === "string") { normalized[key] = redactTelegramRuntimeMessage(value, botToken); continue; } if (typeof value === "number" || typeof value === "boolean") { normalized[key] = value; continue; } if (value === null) { normalized[key] = null; continue; } normalized[key] = redactTelegramRuntimeMessage(String(value), botToken); } return Object.keys(normalized).length > 0 ? normalized : undefined; } function getTelegramRuntimeEventMessage( input: TelegramRuntimeEventInput, ): string { if (input.message !== undefined) return input.message; if (input.error instanceof Error) return input.error.message; return String(input.error); } export function recordStructuredTelegramRuntimeEvent( events: TelegramRuntimeEvent[], input: TelegramRuntimeEventInput, options: { botToken?: string; maxEvents: number; now?: number }, ): void { const details = normalizeTelegramRuntimeEventDetails( input.details, options.botToken, ); events.push({ at: options.now ?? Date.now(), category: input.category, message: redactTelegramRuntimeMessage( getTelegramRuntimeEventMessage(input), options.botToken, ), ...(details ? { details } : {}), }); while (events.length > options.maxEvents) { events.shift(); } } export function recordTelegramRuntimeEvent( events: TelegramRuntimeEvent[], category: string, error: unknown, options: { botToken?: string; maxEvents: number; now?: number }, ): void { recordStructuredTelegramRuntimeEvent(events, { category, error }, options); } export function createTelegramRuntimeEventRecorder( options: TelegramRuntimeEventRecorderOptions, ): TelegramRuntimeEventRecorder { const events: TelegramRuntimeEvent[] = []; return { record: (category, error, details) => { recordStructuredTelegramRuntimeEvent( events, { category, error, details }, { botToken: options.getBotToken(), maxEvents: options.maxEvents ?? MAX_RECENT_TELEGRAM_RUNTIME_EVENTS, now: options.now?.(), }, ); }, getEvents: () => events, clear: () => { events.length = 0; }, }; } function formatTelegramRuntimeEventCategory( event: TelegramRuntimeEvent, ): string { const method = event.details?.method; return typeof method === "string" ? `${event.category}:${method}` : event.category; } function formatTelegramRuntimeEventDetails( event: TelegramRuntimeEvent, ): string { if (!event.details) return ""; const details = Object.entries(event.details) .filter(([key]) => key !== "method") .map(([key, value]) => `${key}=${JSON.stringify(value)}`); return details.length > 0 ? ` (${details.join(", ")})` : ""; } function formatTelegramRuntimeEventSummary( event: TelegramRuntimeEvent, ): string { return `${formatTelegramRuntimeEventCategory(event)}: ${event.message}${formatTelegramRuntimeEventDetails(event)}`; } function formatTelegramRuntimeEvent(event: TelegramRuntimeEvent): string { return `${new Date(event.at).toISOString()} ${formatTelegramRuntimeEventSummary(event)}`; } export function buildTelegramRuntimeEventLines( events: TelegramRuntimeEvent[], ): string[] { if (events.length === 0) return ["recent runtime events: none"]; return [ "recent runtime events:", ...events .slice() .reverse() .map((event) => `- ${formatTelegramRuntimeEvent(event)}`), ]; } export function createTelegramStatusHtmlBuilder(deps: { getActiveModel: (ctx: TContext) => TelegramStatusActiveModel | undefined; getUsageLimits?: () => TelegramUsageLimits | undefined; }): (ctx: TContext & TelegramStatusContext) => string { return (ctx) => buildStatusHtml(ctx, deps.getActiveModel(ctx), deps.getUsageLimits?.()); } export function createTelegramStatusRuntime< TContext extends TelegramStatusRuntimeContext, >(deps: TelegramStatusRuntimeDeps): TelegramStatusRuntime { const statusKey = deps.statusKey ?? "telegram"; return { updateStatus: (ctx, error) => { ctx.ui.setStatus( statusKey, buildTelegramStatusBarText( ctx.ui.theme, deps.getStatusBarState(ctx, error), ), ); }, getStatusLines: () => buildTelegramBridgeStatusLines(deps.getBridgeStatusLineState()), }; } export function createTelegramBridgeStatusRuntime< TContext extends TelegramStatusRuntimeContext, TQueueItem extends { queueLane: TelegramStatusQueueLane }, >( deps: TelegramBridgeStatusRuntimeDeps, ): TelegramStatusRuntime { return createTelegramStatusRuntime({ statusKey: deps.statusKey, getStatusBarState: (_ctx, error) => { const config = deps.getConfig(); const queuedItems = deps.getQueuedItems(); const hasActiveTurn = deps.hasActiveTurn(); const hasPendingDispatch = deps.hasDispatchPending(); const hasPendingModelSwitch = deps.hasPendingModelSwitch(); const activeToolExecutions = deps.getActiveToolExecutions(); const compactionInProgress = deps.isCompactionInProgress(); return { hasBotToken: !!config.botToken, pollingActive: deps.isPollingActive(), paired: !!config.allowedUserId, compactionInProgress, processing: hasActiveTurn || hasPendingDispatch || queuedItems.length > 0, processingStatus: getTelegramStatusBarProcessingStatus({ hasActiveTurn, hasPendingDispatch, hasPendingModelSwitch, activeToolExecutions, queuedItems: queuedItems.length, }), queuedStatus: deps.formatQueuedStatus(queuedItems), footerState: deps.getFooterState?.(), error, }; }, getBridgeStatusLineState: () => { const config = deps.getConfig(); return { botUsername: config.botUsername, allowedUserId: config.allowedUserId, lockState: deps.getRuntimeLockState?.(), pollingActive: deps.isPollingActive(), lastUpdateId: config.lastUpdateId, activeSourceMessageIds: deps.getActiveSourceMessageIds(), pendingDispatch: deps.hasDispatchPending(), compactionInProgress: deps.isCompactionInProgress(), activeToolExecutions: deps.getActiveToolExecutions(), pendingModelSwitch: deps.hasPendingModelSwitch(), queuedItems: deps.getQueuedItems(), recentRuntimeEvents: deps.getRecentRuntimeEvents(), }; }, }); } export function getTelegramStatusBarProcessingStatus(state: { hasActiveTurn: boolean; hasPendingDispatch: boolean; hasPendingModelSwitch: boolean; activeToolExecutions: number; queuedItems: number; }): string | undefined { if (state.hasPendingModelSwitch) return "model"; if (state.activeToolExecutions > 0) return "tool running"; if (state.hasActiveTurn) return "active"; if (state.hasPendingDispatch) return "dispatching"; if (state.queuedItems > 0) return "queued"; return undefined; } export function formatTelegramFooterLabel( state: TelegramFooterLabelState, _theme: TelegramStatusBarTheme, ): string { if (!state.hasBotToken) return "telegram setup needed"; const parts: string[] = ["tg:"]; parts.push(`poll ${state.connected ? "๐ŸŸข" : "๐ŸŸก"}`); parts.push(`bind ${state.topic ? "๐ŸŸข" : "๐ŸŸก"}`); if (state.bindCode) parts.push(`code ${state.bindCode}`); if (state.queueCount > 0) parts.push(`q${state.queueCount}`); if (state.active) parts.push("active"); parts.push(`brc ${state.broadcastEnabled ? "๐ŸŸข" : "๐ŸŸก"}`); return `${parts[0]} ${parts.slice(1).join(" ยท ")}`; } export function buildTelegramStatusBarText( theme: TelegramStatusBarTheme, state: TelegramStatusBarState, ): string { const label = theme.fg("accent", "telegram"); if (state.error) { return `${label} ${theme.fg("error", "error")} ${theme.fg("muted", state.error)}`; } if (state.footerState) return formatTelegramFooterLabel(state.footerState, theme); if (!state.hasBotToken) return `${label} ${theme.fg("muted", "not configured")}`; if (!state.pollingActive) return `${label} ${theme.fg("muted", "disconnected")}`; if (!state.paired) return `${label} ${theme.fg("warning", "awaiting pairing")}`; const queued = theme.fg("muted", state.queuedStatus); if (state.compactionInProgress) { return `${label} ${theme.fg("accent", "compacting")}${queued}`; } if (state.processing) { return `${label} ${theme.fg("accent", state.processingStatus ?? "processing")}${queued}`; } return `${label} ${theme.fg("success", "connected")}`; } export function buildTelegramBridgeStatusLines( state: TelegramBridgeStatusLineState, ): string[] { const controlQueueCount = state.queuedItems.filter( (item) => item.queueLane === "control", ).length; const priorityQueueCount = state.queuedItems.filter( (item) => item.queueLane === "priority", ).length; const defaultQueueCount = state.queuedItems.filter( (item) => item.queueLane === "default", ).length; return [ "connection:", `- bot: ${state.botUsername ? `@${state.botUsername}` : "not configured"}`, `- allowed user: ${state.allowedUserId ?? "not paired"}`, ...(state.lockState ? [`- owner: ${state.lockState}`] : []), "", "polling:", `- state: ${state.pollingActive ? "running" : "stopped"}`, `- last update id: ${state.lastUpdateId ?? "none"}`, "", "execution:", `- active turn: ${state.activeSourceMessageIds?.join(",") || "no"}`, `- pending dispatch: ${state.pendingDispatch ? "yes" : "no"}`, `- compaction: ${state.compactionInProgress ? "running" : "idle"}`, `- active tools: ${state.activeToolExecutions}`, `- pending model switch: ${state.pendingModelSwitch ? "yes" : "no"}`, "", "queue:", `- queued turns: ${state.queuedItems.length}`, `- lanes: control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount}`, "", ...buildTelegramRuntimeEventLines(state.recentRuntimeEvents), ]; } function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">"); } function formatTokens(count: number): string { if (count < 1000) return count.toString(); if (count < 10000) return `${(count / 1000).toFixed(1)}k`; if (count < 1000000) return `${Math.round(count / 1000)}k`; if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`; return `${Math.round(count / 1000000)}M`; } function collectUsageStats(ctx: TelegramStatusContext): TelegramUsageStats { const stats: TelegramUsageStats = { totalInput: 0, totalOutput: 0, totalCacheRead: 0, totalCacheWrite: 0, totalCost: 0, }; for (const entry of ctx.sessionManager.getEntries()) { const usage = entry.message?.usage; if ( entry.type !== "message" || entry.message?.role !== "assistant" || !usage ) { continue; } stats.totalInput += usage.input; stats.totalOutput += usage.output; stats.totalCacheRead += usage.cacheRead; stats.totalCacheWrite += usage.cacheWrite; stats.totalCost += usage.cost.total; } return stats; } function buildStatusRow(label: string, value: string): string { return `${escapeHtml(label)}: ${escapeHtml(value)}`; } function buildUsageSummary(stats: TelegramUsageStats): string | undefined { const tokenParts: string[] = []; if (stats.totalInput) tokenParts.push(`โ†‘${formatTokens(stats.totalInput)}`); if (stats.totalOutput) tokenParts.push(`โ†“${formatTokens(stats.totalOutput)}`); if (stats.totalCacheRead) tokenParts.push(`R${formatTokens(stats.totalCacheRead)}`); if (stats.totalCacheWrite) tokenParts.push(`W${formatTokens(stats.totalCacheWrite)}`); return tokenParts.length > 0 ? tokenParts.join(" ") : undefined; } function buildCostSummary( stats: TelegramUsageStats, usesSubscription: boolean, ): string | undefined { if (!stats.totalCost && !usesSubscription) return undefined; return `$${stats.totalCost.toFixed(3)}${usesSubscription ? " (sub)" : ""}`; } function asUsageLimitPercent(value: unknown): number | undefined { if (typeof value !== "number" || !Number.isFinite(value)) return undefined; return Math.max(0, Math.min(100, value)); } function asUsageLimitText(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed ? trimmed : undefined; } export function createTelegramUsageLimitsStore( now: () => number = () => Date.now(), ): TelegramUsageLimitsStore { let current: TelegramUsageLimits | undefined; return { update: (update) => { const session = asUsageLimitPercent(update.session); const weekly = asUsageLimitPercent(update.weekly); if (session === undefined || weekly === undefined) return; current = { session, weekly, sessionResetsIn: asUsageLimitText(update.sessionResetsIn), weeklyResetsIn: asUsageLimitText(update.weeklyResetsIn), updatedAt: now(), }; }, get: () => current, clear: () => { current = undefined; }, }; } function formatRemainingPercent(usedPercent: number): string { const remaining = Math.max(0, Math.min(100, 100 - usedPercent)); const rounded = Math.abs(remaining - Math.round(remaining)) < 0.05 ? String(Math.round(remaining)) : remaining.toFixed(1); return `${rounded}% left`; } function buildLimitsSummary(limits: TelegramUsageLimits): string { return `S ${formatRemainingPercent(limits.session)} W ${formatRemainingPercent(limits.weekly)}`; } function buildLimitsResetSummary(limits: TelegramUsageLimits): string | undefined { const parts: string[] = []; if (limits.sessionResetsIn) parts.push(`S ${limits.sessionResetsIn}`); if (limits.weeklyResetsIn) parts.push(`W ${limits.weeklyResetsIn}`); return parts.length > 0 ? parts.join(" ") : undefined; } function buildContextSummary( ctx: TelegramStatusContext, activeModel: TelegramStatusActiveModel | undefined, ): string { const usage = ctx.getContextUsage(); if (!usage) return "unknown"; const contextWindow = usage.contextWindow ?? activeModel?.contextWindow ?? 0; const percent = usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "?"; return `${percent}/${formatTokens(contextWindow)}`; } export function buildStatusHtml( ctx: TelegramStatusContext, activeModel: TelegramStatusActiveModel | undefined, usageLimits?: TelegramUsageLimits, ): string { const stats = collectUsageStats(ctx); const usesSubscription = activeModel ? ctx.modelRegistry.isUsingOAuth(activeModel) : false; const lines: string[] = []; if (usageLimits) { lines.push(buildStatusRow("Limits", buildLimitsSummary(usageLimits))); const resetSummary = buildLimitsResetSummary(usageLimits); if (resetSummary) lines.push(buildStatusRow("Reset", resetSummary)); } else { const usageSummary = buildUsageSummary(stats); const costSummary = buildCostSummary(stats, usesSubscription); if (usageSummary) { lines.push(buildStatusRow("Usage", usageSummary)); } if (costSummary) { lines.push(buildStatusRow("Cost", costSummary)); } } lines.push(buildStatusRow("Context", buildContextSummary(ctx, activeModel))); if (lines.length === 0) { lines.push(buildStatusRow("Status", "No usage data yet.")); } return lines.join("\n"); }