import * as fs from "fs"; import * as path from "path"; const REPO_ROOT = process.env.REPO_ROOT || path.resolve(process.cwd(), ".."); const USAGE_LOG = path.join(REPO_ROOT, "code/apps/palacemonitor", "usage.jsonl"); const PALACERING_URL = process.env.PALACERING_URL || "http://localhost:6572"; export interface UsageEntry { channel: string; model?: string; domain?: string; duration_ms: number; total_cost_usd: number; input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cache_creation_input_tokens: number; num_turns: number; } export function logUsage(entry: UsageEntry): void { const now = new Date(); const date = now.toLocaleDateString("en-CA", { timeZone: "America/Los_Angeles" }); const time = now.toLocaleTimeString("en-US", { timeZone: "America/Los_Angeles", hour12: false, hour: "2-digit", minute: "2-digit", }); const row: Record = { date, time, palace: process.env.PALACE_NAME ?? "manglasabang", user: process.env.PALACE_USER ?? "junwon", butler: process.env.PALACE_BUTLER ?? "ace", user_id: process.env.PALACE_USER_ID ? parseInt(process.env.PALACE_USER_ID) : undefined, channel: entry.channel, model: entry.model || "opus", duration_min: Math.round(entry.duration_ms / 60000), duration_ms: entry.duration_ms, tokens_in: entry.input_tokens + entry.cache_read_input_tokens + entry.cache_creation_input_tokens, tokens_out: entry.output_tokens, cost_usd: entry.total_cost_usd, num_turns: entry.num_turns, }; if (entry.domain) row.domain = entry.domain; fetch(`${PALACERING_URL}/api/usage/log`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(row), }).catch(() => { try { fs.appendFileSync(USAGE_LOG, JSON.stringify(row) + "\n"); } catch (err) { console.error("[usage] failed to log:", err); } }); } export function extractUsageFromResult(msg: any): Omit { return { duration_ms: msg.duration_ms || 0, total_cost_usd: msg.total_cost_usd || 0, input_tokens: msg.usage?.input_tokens || 0, output_tokens: msg.usage?.output_tokens || 0, cache_read_input_tokens: msg.usage?.cache_read_input_tokens || 0, cache_creation_input_tokens: msg.usage?.cache_creation_input_tokens || 0, num_turns: msg.num_turns || 0, }; }