/** * DeepSeek Peak Pricing Hours Extension * * Visual indicator for DeepSeek API peak pricing (Beijing Time UTC+8). * Shows a dot + bar in the footer, preserving the original footer info. * * Peak Hours: 9:00–12:00 & 14:00–18:00 Beijing Time (UTC+8) * Weekends (Saturday & Sunday, Beijing Time): the peak/off-peak division * doesn't apply — all calls bill uniformly at the off-peak rate. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; // ── Peak window definitions (Beijing Time UTC+8) ────────────────────────── interface PeakWindow { startHour: number; // inclusive endHour: number; // exclusive } const PEAK_WINDOWS: PeakWindow[] = [ { startHour: 9, endHour: 12 }, { startHour: 14, endHour: 18 }, ]; // ── Time helpers ────────────────────────────────────────────────────────── const BJ_OFFSET_MS = 8 * 60 * 60 * 1000; const WEEKDAY_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const; /** Beijing wall clock as a shifted UTC date — read fields with getters. */ function beijingShifted(t: number): Date { return new Date(t + BJ_OFFSET_MS); } /** Beijing weekday of an instant (0 = Sunday … 6 = Saturday). */ function beijingWeekdayAt(t: number): number { return beijingShifted(t).getUTCDay(); } /** Get current day/hour/minute in Beijing time (UTC+8) */ function getBeijingNow(): { day: number; hour: number; minute: number } { const bj = beijingShifted(Date.now()); return { day: bj.getUTCDay(), hour: bj.getUTCHours(), minute: bj.getUTCMinutes() }; } /** Whether the instant is a Beijing weekend — flat off-peak all day. */ function isBeijingWeekend(t: number = Date.now()): boolean { const day = beijingWeekdayAt(t); return day === 0 || day === 6; } /** Whether a Beijing wall hour falls inside a peak window (weekday terms). */ function isPeakHour(hour: number): boolean { return PEAK_WINDOWS.some((w) => hour >= w.startHour && hour < w.endHour); } /** * Given a Beijing hour, return a local Date for that time today * (or tomorrow if the hour has already passed in Beijing). */ function beijingHourToLocal(hour: number, minute?: number): Date { const now = new Date(); const bjNow = getBeijingNow(); // Which UTC date the target Beijing hour falls on const utcHour = (hour - 8 + 24) % 24; let targetDate = new Date(Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), utcHour, minute ?? 0, )); // If already past in Beijing time, push to tomorrow const bjTargetHour = (targetDate.getUTCHours() + 8) % 24; const bjTargetMin = targetDate.getUTCMinutes(); if ( bjTargetHour < bjNow.hour || (bjTargetHour === bjNow.hour && bjTargetMin <= bjNow.minute) ) { targetDate = new Date(targetDate.getTime() + 24 * 60 * 60 * 1000); } return targetDate; } /** * The next local `Date` at which peak billing begins: the earliest peak-window * start on a Beijing weekday. Weekends never qualify, so e.g. a Friday * evening rolls forward to Monday 09:00 Beijing. */ function nextPeakStart(): Date { const now = Date.now(); const base = new Date(now); for (let addDays = 0; addDays < 8; addDays++) { const dayStart = Date.UTC( base.getUTCFullYear(), base.getUTCMonth(), base.getUTCDate() + addDays, 0, 0, 0, 0, ); const bjDay = beijingWeekdayAt(dayStart); if (bjDay === 0 || bjDay === 6) continue; // weekend: flat off-peak all day for (const w of PEAK_WINDOWS) { const utcOffsetMs = ((w.startHour - 8 + 24) % 24) * 3_600_000; const t = dayStart + utcOffsetMs; if (t > now) return new Date(t); } } return new Date(now + 7 * 86_400_000); // unreachable within an 8-day scan } /** Format a Date as a short local time string like "09:00" or "14:00" */ function formatLocalTime(d: Date): string { const h = String(d.getHours()).padStart(2, "0"); const m = String(d.getMinutes()).padStart(2, "0"); return `${h}:${m}`; } interface PeakInfo { isPeak: boolean; currentHour: number; currentMinute: number; /** True when today (Beijing) is a weekend: flat off-peak, windows ignored. */ weekend: boolean; label: string; nextTransition: string; // in local time } function getPeakInfo(): PeakInfo { const { day, hour, minute } = getBeijingNow(); const weekend = day === 0 || day === 6; const isPeak = !weekend && isPeakHour(hour); let transitionLabel: string; if (isPeak) { const window = PEAK_WINDOWS.find((w) => hour >= w.startHour && hour < w.endHour)!; transitionLabel = `until ${formatLocalTime(beijingHourToLocal(window.endHour))}`; } else { const next = nextPeakStart(); const aheadMs = next.getTime() - Date.now(); // Tag the weekday whenever the transition is a day or more away, so a // Friday night / weekend reads "next Mon 09:00" instead of implying today. const dayTag = aheadMs >= 24 * 3_600_000 ? `${WEEKDAY_SHORT[beijingWeekdayAt(next.getTime())]} ` : ""; transitionLabel = `next ${dayTag}${formatLocalTime(next)}`; } return { isPeak, currentHour: hour, currentMinute: minute, weekend, label: isPeak ? "PEAK" : "Off-Peak", nextTransition: transitionLabel, }; } // ── Visual bar renderer ─────────────────────────────────────────────────── // // 24-hour bar in Beijing time. Each column = 1 hour. // Peak █ (warning), Off-peak ░ (dim), current peak ◆ (bright/bold), // current off-peak ◇ (accent). // On Beijing weekends every column renders off-peak. // Collapses 2h-per-char when width < 26. const BLOCK_FULL = "█"; const BLOCK_OFF = "░"; const BLOCK_CURRENT_PEAK = "◆"; const BLOCK_CURRENT_OFF = "◇"; function renderBar(peakInfo: PeakInfo, barWidth: number): string { const cols: { isPeak: boolean; isCurrent: boolean }[] = []; for (let h = 0; h < 24; h++) { cols.push({ isPeak: !peakInfo.weekend && isPeakHour(h), isCurrent: h === peakInfo.currentHour, }); } if (barWidth >= 26) { const chars = cols.map((c) => { if (c.isCurrent) return c.isPeak ? BLOCK_CURRENT_PEAK : BLOCK_CURRENT_OFF; return c.isPeak ? BLOCK_FULL : BLOCK_OFF; }); return `[${chars.join("")}]`; } else if (barWidth >= 14) { const merged: string[] = []; for (let i = 0; i < 24; i += 2) { const a = cols[i]!; const b = cols[i + 1]!; if (a.isCurrent || b.isCurrent) { const current = a.isCurrent ? a : b; merged.push(current.isPeak ? BLOCK_CURRENT_PEAK : BLOCK_CURRENT_OFF); } else if (a.isPeak || b.isPeak) { merged.push(BLOCK_FULL); } else { merged.push(BLOCK_OFF); } } return `[${merged.join("")}]`; } return ""; } // ── Extension ───────────────────────────────────────────────────────────── export default function (pi: ExtensionAPI) { let refreshTimer: ReturnType | null = null; let widgetActive = false; function stopTimer() { if (refreshTimer !== null) { clearInterval(refreshTimer); refreshTimer = null; } } function startTimer(tui: any) { stopTimer(); refreshTimer = setInterval(() => { tui.requestRender(); }, 30_000); } // ── Visual bar widget (below the editor) ────────────────────────── function showWidget(ctx: { ui: { setWidget: (k: string, v: any, opts?: any) => void; theme: any } }) { widgetActive = true; ctx.ui.setWidget( "deepseek-bar", (_tui: any, theme: any) => { startTimer(_tui); return { invalidate() {}, render() { const info = getPeakInfo(); // Status marker + transition const dotColor = info.isPeak ? "error" : "success"; const statusGlyph = info.isPeak ? BLOCK_CURRENT_PEAK : BLOCK_CURRENT_OFF; const dot = theme.fg( dotColor, info.isPeak ? theme.bold(statusGlyph) : statusGlyph, ); const label = theme.fg(dotColor, theme.bold(` DeepSeek ${info.label} `)); // Build the bar (estimate ~50 cols available for the widget area) const rawBar = renderBar(info, 48); const barColored = rawBar .split("") .map((ch: string) => { if (ch === BLOCK_CURRENT_PEAK) return theme.fg("error", theme.bold(ch)); if (ch === BLOCK_CURRENT_OFF) return theme.fg("accent", ch); if (ch === BLOCK_FULL) return theme.fg("muted", ch); return theme.fg("dim", ch); }) .join(""); const transition = theme.fg("dim", ` ${info.nextTransition} local`); return [dot + label + barColored + transition]; }, }; }, { placement: "belowEditor" }, ); } function hideWidget(ctx: { ui: { setWidget: (k: string, v: undefined) => void } }) { widgetActive = false; ctx.ui.setWidget("deepseek-bar", undefined); stopTimer(); } // ── Command ─────────────────────────────────────────────────────── pi.registerCommand("ds-peak", { description: "Toggle DeepSeek peak pricing indicator", handler: async (_args, ctx) => { if (!ctx.hasUI) { const info = getPeakInfo(); console.log( `DeepSeek peak pricing: ${info.isPeak ? "PEAK NOW" : "Off-Peak"}${info.weekend ? " (weekend rate)" : ""} (${info.nextTransition} local)`, ); return; } if (widgetActive) { hideWidget(ctx); ctx.ui.notify("DeepSeek peak indicator hidden", "info"); } else { showWidget(ctx); ctx.ui.notify("DeepSeek peak indicator shown", "info"); } }, }); // ── Lifecycle ───────────────────────────────────────────────────── pi.on("session_start", async (_event, ctx) => { stopTimer(); if (ctx.hasUI) { showWidget(ctx); } }); pi.on("session_shutdown", () => { stopTimer(); }); }