import { createServer } from "node:http"; import { createCanvas, joinSession } from "@github/copilot-sdk/extension"; const servers = new Map(); const characters = { mr: { name: "Mr. Pac-Man", color: "#ffd43b" }, mrs: { name: "Mrs. Pac-Man", color: "#ff78b7" }, }; const milestoneThresholds = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000]; function createState() { return { credits: 0, limit: 100, character: "mr", dead: false, achievements: [], fruit: null, fruitVersion: 0, entitlement: null, runVersion: 0, }; } function nextMilestone(credits) { return milestoneThresholds.find((threshold) => threshold > credits) || milestoneThresholds[milestoneThresholds.length - 1]; } function snapshot(entry) { const { state } = entry; return { ...state, percent: Math.min(100, Math.round((state.credits / state.limit) * 100)), character: characters[state.character], nextMilestone: nextMilestone(state.credits), }; } function broadcast(entry) { const message = `data: ${JSON.stringify(snapshot(entry))}\n\n`; for (const client of entry.clients) client.write(message); } function applyUsage(entry, totalCredits) { const { state } = entry; const safeTotal = Math.max(0, Number(totalCredits) || 0); const before = state.credits; state.credits = safeTotal; const awarded = []; for (const threshold of milestoneThresholds) { if (threshold <= state.credits && threshold > before) { awarded.push(threshold); } } for (const threshold of awarded) { state.fruit = ["πŸ’", "πŸ“", "🍊", "🍎", "πŸ‡"][Math.min(4, Math.floor((milestoneThresholds.indexOf(threshold)) / 2))]; state.fruitVersion += 1; state.achievements.unshift({ label: `${threshold.toLocaleString()} session credits munched`, fruit: state.fruit }); state.achievements = state.achievements.slice(0, 5); } state.dead = state.credits >= state.limit; broadcast(entry); } async function syncUsage(entry) { const metrics = await session.rpc.usage.getMetrics(); const nanoAiu = Number(metrics.totalNanoAiu) || entry.eventNanoAiu; const credits = nanoAiu > 0 ? nanoAiu / 1_000_000_000 : Number(metrics.totalPremiumRequestCost) || 0; applyUsage(entry, credits); } // Pull the authenticated user's real Copilot entitlement (premium request quota). async function syncQuota(entry) { try { const result = await session.connection.sendRequest("account.getQuota", {}); const snaps = result?.quotaSnapshots || {}; const snap = snaps.premium_interactions || snaps.chat || Object.values(snaps)[0]; if (!snap) return; entry.state.entitlement = { type: snaps.premium_interactions ? "Premium requests" : (snaps.chat ? "Chat requests" : "Requests"), unlimited: !!snap.isUnlimitedEntitlement, max: Number(snap.entitlementRequests), used: Number(snap.usedRequests) || 0, remainingPercentage: Number(snap.remainingPercentage), overage: Number(snap.overage) || 0, resetDate: snap.resetDate || null, }; broadcast(entry); } catch (err) { await session.log(`Token Pac-Man: quota lookup unavailable (${err?.message || err})`, { level: "warning", ephemeral: true }); } } function getOpenEntry(instanceId) { const entry = servers.get(instanceId); if (!entry) throw new Error("Token Pac-Man canvas is not open."); return entry; } function json(res, status, body) { res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" }); res.end(JSON.stringify(body)); } function renderHtml() { return ` Token Pac-Man
Live AI credit arcade

Token Pac-Man

Chomp this session's AI credits. Dodge the ghosts. Chase your next fruit.

🟒 Running
Session credits munched
0.00
Session credit limit
100.00
Next fruit
0%πŸ’Š ghost pressure
PlayerLimit
New run clears the visible fruit streak and starts a fresh chase without changing your live session credits.
Achievements
πŸ’ Ready for your first 1,000
`; } async function startServer(instanceId) { const entry = { server: null, url: null, state: createState(), clients: new Set(), eventNanoAiu: 0, quotaInterval: null, }; const server = createServer(async (req, res) => { if (req.url === "/events") { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }); res.write(`data: ${JSON.stringify(snapshot(entry))}\n\n`); entry.clients.add(res); req.on("close", () => entry.clients.delete(res)); return; } if (req.url === "/state") return json(res, 200, snapshot(entry)); if (req.method === "POST") { let body = ""; for await (const chunk of req) body += chunk; let input = {}; try { input = body ? JSON.parse(body) : {}; } catch { return json(res, 400, { error: "Invalid JSON request body." }); } const { state } = entry; if (req.url === "/choose" && characters[input.character]) state.character = input.character; else if (req.url === "/limit" && Number(input.limit) > 0) { state.limit = Number(input.limit); state.dead = state.credits >= state.limit; state.runVersion += 1; } else if (req.url === "/reset") { state.achievements = []; state.fruit = null; state.runVersion += 1; state.dead = state.credits >= state.limit; await syncUsage(entry); } broadcast(entry); return json(res, 200, snapshot(entry)); } res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(renderHtml()); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; entry.server = server; entry.url = `http://127.0.0.1:${port}/`; entry.quotaInterval = setInterval(() => { void syncQuota(entry); }, 60_000); return entry; } const session = await joinSession({ canvases: [createCanvas({ id: "token-pacman", displayName: "Token Pac-Man", description: "Live Pac-Man-style token consumption tracker with character choice, fruit achievements, and API limit tracking.", inputSchema: { type: "object", properties: {} }, actions: [ { name: "sync_usage", description: "Refresh the canvas from the active session's accumulated AI credit usage and the user's plan entitlement.", handler: async (ctx) => { const entry = getOpenEntry(ctx.instanceId); await syncUsage(entry); await syncQuota(entry); return snapshot(entry); } }, { name: "set_limit", description: "Set the AI credit limit that triggers game over. Resyncs the pellet board to the new limit.", inputSchema: { type: "object", properties: { limit: { type: "number", minimum: 0.01 } }, required: ["limit"] }, handler: async (ctx) => { const entry = getOpenEntry(ctx.instanceId); const { state } = entry; state.limit = Number(ctx.input.limit); state.dead = state.credits >= state.limit; state.runVersion += 1; broadcast(entry); return snapshot(entry); } }, { name: "reset_run", description: "Start a fresh visible run by clearing fruit streaks and resetting the board, without changing your live session credit total.", handler: async (ctx) => { const entry = getOpenEntry(ctx.instanceId); const { state } = entry; state.achievements = []; state.fruit = null; state.runVersion += 1; state.dead = state.credits >= state.limit; await syncUsage(entry); return snapshot(entry); } }, ], open: async (ctx) => { let entry = servers.get(ctx.instanceId); if (!entry) { entry = await startServer(ctx.instanceId); servers.set(ctx.instanceId, entry); } await syncUsage(entry); await syncQuota(entry); return { title: "Token Pac-Man", url: entry.url, status: entry.state.dead ? "Session limit busted" : `${entry.state.credits.toFixed(2)} session credits munched` }; }, onClose: async (ctx) => { const entry = servers.get(ctx.instanceId); if (entry) { servers.delete(ctx.instanceId); clearInterval(entry.quotaInterval); entry.clients.clear(); await new Promise((resolve) => entry.server.close(resolve)); } }, })], }); session.on("assistant.usage", (event) => { const nanoAiu = Number(event.data?.copilotUsage?.totalNanoAiu) || 0; for (const entry of servers.values()) { entry.eventNanoAiu += nanoAiu; void syncUsage(entry); void syncQuota(entry); } });