/** * Fonte KIMI: quota real via GET https://api.kimi.com/coding/v1/usages. * * A chave chega EXCLUSIVAMENTE por parâmetro (vinda de process.env.KIMI_CODE_API). * Nunca logamos/imprimimos a chave — mensagens de erro passam por `sanitize()`, * que remove qualquer ocorrência acidental da chave no corpo do erro. */ import { formatResetTime, formatWindowDuration, progressBar, toNumber } from "./format.ts"; export const KIMI_USAGES_URL = "https://api.kimi.com/coding/v1/usages"; const FETCH_TIMEOUT_MS = 15_000; export type KimiResult = | { available: true; data: Record } | { available: false; reason: string }; /** Remove a chave de qualquer texto (defesa em profundidade). */ export function sanitize(text: string, apiKey: string): string { if (!apiKey) return text; return text.split(apiKey).join("[redacted]"); } export async function fetchKimiUsage(apiKey: string): Promise { let res: Response; try { res = await fetch(KIMI_USAGES_URL, { method: "GET", headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" }, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); return { available: false, reason: sanitize(`indisponível: ${msg}`, apiKey) }; } if (!res.ok) { let detail = ""; try { detail = (await res.text()).slice(0, 200); } catch { /* ignora */ } const reason = `indisponível: HTTP ${res.status}${detail ? ` — ${detail}` : ""}`; return { available: false, reason: sanitize(reason, apiKey) }; } try { const data = (await res.json()) as Record; return { available: true, data }; } catch { return { available: false, reason: "indisponível: resposta JSON inválida" }; } } function asRecord(value: unknown): Record | undefined { return typeof value === "object" && value !== null ? (value as Record) : undefined; } function str(value: unknown, fallback = "N/A"): string { return typeof value === "string" && value ? value : fallback; } function stripPrefix(value: string, prefix: string): string { return value.startsWith(prefix) ? value.slice(prefix.length) : value; } function centsToDollars(record: Record | undefined): string | null { if (!record) return null; const cents = toNumber(record.priceInCents); const currency = str(record.currency, "USD"); return `$${(cents / 100).toFixed(2)} ${currency}`; } /** Renderiza a seção KIMI como linhas de texto puro (tema aplicado fora). */ export function renderKimiSection(result: KimiResult, timeZone: string, nowMs: number): string[] { const lines: string[] = []; lines.push("KIMI (quota real — api.kimi.com/coding)"); if (!result.available) { lines.push(` ${result.reason}`); return lines; } const data = result.data; const user = asRecord(data.user); const membership = asRecord(user?.membership); const level = stripPrefix(str(membership?.level), "LEVEL_"); const parallel = asRecord(data.parallel); const parallelLimit = str(String(parallel?.limit ?? ""), "N/A"); lines.push(` Plano: ${level} · Paralelismo: ${parallelLimit}`); const usage = asRecord(data.usage); if (usage) { const limit = toNumber(usage.limit); const used = toNumber(usage.used); const remaining = toNumber(usage.remaining); lines.push( ` ${progressBar(used, limit, 20)} · Restante: ${remaining} · Reseta: ${formatResetTime(usage.resetTime, timeZone, nowMs)}`, ); } const limits = Array.isArray(data.limits) ? data.limits : []; if (limits.length > 1) lines.push(" Janelas:"); limits.forEach((raw, i) => { const lim = asRecord(raw); if (!lim) return; const window = asRecord(lim.window); const detail = asRecord(lim.detail); const duration = toNumber(window?.duration); const unit = stripPrefix(str(window?.timeUnit, "TIME_UNIT_MINUTE"), "TIME_UNIT_").toLowerCase(); const label = limits.length > 1 ? `Janela ${i + 1} (${formatWindowDuration(duration, unit)})` : `Janela ${formatWindowDuration(duration, unit)}`; lines.push( ` ${label}: ${progressBar(toNumber(detail?.used), toNumber(detail?.limit), 14)} · Restante: ${toNumber(detail?.remaining)} · Reseta: ${formatResetTime(detail?.resetTime, timeZone, nowMs)}`, ); }); const booster = asRecord(data.boosterWallet); if (booster && Object.keys(booster).length > 0) { const status = booster.status === "STATUS_ENABLED" ? "ATIVO" : "DESATIVADO"; const monthly = centsToDollars(asRecord(booster.monthlyChargeLimit)); const monthlyUsed = centsToDollars(asRecord(booster.monthlyUsed)); const tail = monthly ? ` · Limite ${monthly} · Usado ${monthlyUsed ?? "N/A"}` : ""; lines.push(` Booster: ${status} (recarga: ${booster.allowTopup ? "sim" : "não"})${tail}`); } return lines; }