UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

431 lines (427 loc) • 13.3 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { ROLE_TOKEN_BUDGETS, isExpertRole } from "./chunk-IKAOR4NY.js"; import { currentFeature } from "./chunk-5M55PDWL.js"; import { stampBundleRow } from "./chunk-RJMJYPHL.js"; import { CHIEF_ARCHITECT_AGENT_NAME, isHighTableAgent, stripPluginAgentNamespace } from "./chunk-NCYDV2N5.js"; import { appendStampedRowToUnit, readUnitFile } from "./chunk-Q353EDKY.js"; import { resolveHookSession, resolveSessionId } from "./chunk-SG6LXLDH.js"; import { featureChangeKey, featureFilePath } from "./chunk-AZLA53A6.js"; import { init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/cost/transcript-usage.ts init_esm_shims(); import { closeSync, openSync, readSync } from "fs"; var DEFAULT_CHUNK_BYTES = 1024 * 1024; var NEWLINE = 10; var UNKNOWN_MODEL = "unknown"; function emptyTokenUsage() { return { input: 0, output: 0, cache_read: 0, cache_write: 0, cache_write_5m: 0, cache_write_1h: 0, total: 0 }; } function count(value) { return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0; } function asRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null; } function withTotal(usage) { return { ...usage, total: usage.input + usage.output + usage.cache_read + usage.cache_write }; } function usageFrom(raw) { const breakdown = asRecord(raw.cache_creation); return withTotal({ input: count(raw.input_tokens), output: count(raw.output_tokens), cache_read: count(raw.cache_read_input_tokens), cache_write: count(raw.cache_creation_input_tokens), cache_write_5m: count(breakdown?.ephemeral_5m_input_tokens), cache_write_1h: count(breakdown?.ephemeral_1h_input_tokens), total: 0 }); } function maxTokenUsage(a, b) { return withTotal({ input: Math.max(a.input, b.input), output: Math.max(a.output, b.output), cache_read: Math.max(a.cache_read, b.cache_read), cache_write: Math.max(a.cache_write, b.cache_write), cache_write_5m: Math.max(a.cache_write_5m, b.cache_write_5m), cache_write_1h: Math.max(a.cache_write_1h, b.cache_write_1h), total: 0 }); } function addTokenUsage(a, b) { return withTotal({ input: a.input + b.input, output: a.output + b.output, cache_read: a.cache_read + b.cache_read, cache_write: a.cache_write + b.cache_write, cache_write_5m: a.cache_write_5m + b.cache_write_5m, cache_write_1h: a.cache_write_1h + b.cache_write_1h, total: 0 }); } function parseLine(text) { try { return JSON.parse(text); } catch { return void 0; } } function foldEvent(parsed, byId) { const event = asRecord(parsed); const message = asRecord(event?.message); const rawUsage = asRecord(message?.usage); const id = typeof message?.id === "string" && message.id.length > 0 ? message.id : null; if (event?.type !== "assistant" || !rawUsage || !id) return; const usage = usageFrom(rawUsage); const existing = byId.get(id); if (existing) { existing.usage = maxTokenUsage(existing.usage, usage); return; } byId.set(id, { id, model: typeof message?.model === "string" && message.model.length > 0 ? message.model : null, timestamp: typeof event.timestamp === "string" ? event.timestamp : null, usage }); } function parseBound(value) { if (value === void 0) return void 0; const parsed = Date.parse(value); return Number.isNaN(parsed) ? void 0 : parsed; } function inWindow(message, since, until) { if (since === void 0 && until === void 0) return true; const at = message.timestamp === null ? Number.NaN : Date.parse(message.timestamp); if (Number.isNaN(at)) return false; if (since !== void 0 && at < since) return false; return until === void 0 || at <= until; } function scanTranscriptLines(path, onEvent, options = {}) { const start = Math.max(0, Math.floor(options.fromOffset ?? 0)); const chunkBytes = Math.max(1, Math.floor(options.chunkBytes ?? DEFAULT_CHUNK_BYTES)); const result = { endOffset: start, badLines: 0 }; const fd = openSync(path, "r"); try { const buffer = Buffer.alloc(chunkBytes); let position = start; let pending = Buffer.alloc(0); for (; ; ) { const read = readSync(fd, buffer, 0, chunkBytes, position); if (read === 0) break; position += read; const data = Buffer.concat([pending, buffer.subarray(0, read)]); let lineStart = 0; let newline = data.indexOf(NEWLINE, lineStart); while (newline !== -1) { const text = data.subarray(lineStart, newline).toString("utf8").trim(); if (text.length > 0) { const parsed2 = parseLine(text); if (parsed2 === void 0) result.badLines += 1; else onEvent(parsed2); } lineStart = newline + 1; newline = data.indexOf(NEWLINE, lineStart); } result.endOffset = position - (data.length - lineStart); pending = Buffer.from(data.subarray(lineStart)); } const tail = pending.toString("utf8").trim(); const parsed = tail.length > 0 ? parseLine(tail) : void 0; if (parsed !== void 0) { onEvent(parsed); result.endOffset += pending.length; } } finally { closeSync(fd); } return result; } function readTranscriptUsage(path, options = {}) { const byId = /* @__PURE__ */ new Map(); let scan = { endOffset: Math.max(0, Math.floor(options.fromOffset ?? 0)), badLines: 0 }; try { scan = scanTranscriptLines(path, (event) => foldEvent(event, byId), options); } catch { } const since = parseBound(options.since); const until = parseBound(options.until); const messages = [...byId.values()].filter((message) => inWindow(message, since, until)); let totals = emptyTokenUsage(); const byModel = {}; for (const message of messages) { totals = addTokenUsage(totals, message.usage); const model = message.model ?? UNKNOWN_MODEL; byModel[model] = addTokenUsage(byModel[model] ?? emptyTokenUsage(), message.usage); } return { totals, by_model: byModel, messages, end_offset: scan.endOffset, bad_lines: scan.badLines }; } // src/spec-pipeline/high-table.ts init_esm_shims(); var HIGH_TABLE_DOC_TYPE = "paqad.high-table"; var HIGH_TABLE_SCHEMA_VERSION = 1; var CHIEF_ROLE = "chief-architect"; var CHIEF_BUDGET_TOKENS = ROLE_TOKEN_BUDGETS[CHIEF_ROLE]; function appendHighTableRow(projectRoot, dirName, row, options) { const stamped = stampBundleRow({ docType: HIGH_TABLE_DOC_TYPE, change: featureChangeKey(dirName), sessionId: options.sessionId ?? resolveSessionId(projectRoot, null), schemaVersion: HIGH_TABLE_SCHEMA_VERSION, row, now: options.now }); appendStampedRowToUnit(projectRoot, featureFilePath(dirName, "highTable"), stamped); return stamped; } function readHighTable(projectRoot, dirName) { return readUnitFile(projectRoot, featureFilePath(dirName, "highTable")); } function recordSeatedRows(projectRoot, dirName, seats, options) { if (seats.length === 0) return; for (const seat of seats) { appendHighTableRow(projectRoot, dirName, { event: "seated", ...seat }, options); } appendHighTableRow( projectRoot, dirName, { event: "seated", role: CHIEF_ROLE, reason: null, origin: null, budget_tokens: CHIEF_BUDGET_TOKENS, clamped: false }, options ); } function recordWritingRow(projectRoot, dirName, role, options) { appendHighTableRow(projectRoot, dirName, { event: "writing", role }, options); } function recordDoneRow(projectRoot, dirName, done, options) { appendHighTableRow( projectRoot, dirName, { event: "done", ...done, tokens_label: "estimate" }, options ); } var BRIEF_COMMAND = /\bexperts\s+brief\s+([a-z][a-z0-9-]*)/; function asRecord2(value) { return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null; } function toolCommands(event) { const message = asRecord2(asRecord2(event)?.message); const content = Array.isArray(message?.content) ? message.content : []; const commands = []; for (const block of content) { const record = asRecord2(block); const command = asRecord2(record?.input)?.command; if (record?.type === "tool_use" && typeof command === "string") commands.push(command); } return commands; } function roleFromSubagentTranscript(path) { let role = null; try { scanTranscriptLines(path, (event) => { if (role !== null) return; for (const command of toolCommands(event)) { const match = BRIEF_COMMAND.exec(command); if (match) { role = match[1]; return; } } }); } catch { return null; } return role !== null && isExpertRole(role) ? role : null; } function recordHighTableUsage(input) { const agentType = typeof input.payload.agent_type === "string" ? input.payload.agent_type : null; if (agentType === null || !isHighTableAgent(agentType)) return "not-high-table"; const path = input.payload.agent_transcript_path; if (typeof path !== "string" || path.trim() === "") return "no-transcript"; const usage = readTranscriptUsage(path); if (usage.messages.length === 0) return "no-usage"; const isChief = stripPluginAgentNamespace(agentType.trim()) === CHIEF_ARCHITECT_AGENT_NAME; const role = isChief ? CHIEF_ROLE : roleFromSubagentTranscript(path); if (role === null) return "no-role"; const sessionId = resolveHookSession(input.projectRoot, input.payload); const dirName = currentFeature(input.projectRoot, sessionId); if (!dirName) return "no-feature"; appendHighTableRow( input.projectRoot, dirName, { event: "usage", role, agent_id: typeof input.payload.agent_id === "string" ? input.payload.agent_id : null, agent_type: agentType, tokens: usage.totals, by_model: usage.by_model, messages: usage.messages.length, tokens_label: "exact" }, { sessionId, now: input.now } ); return "recorded"; } function str(value) { return typeof value === "string" && value.length > 0 ? value : null; } function num(value) { return typeof value === "number" && Number.isFinite(value) ? value : null; } function tokenUsageOf(value) { const record = asRecord2(value); if (!record) return null; const field = (key) => num(record[key]) ?? 0; return { input: field("input"), output: field("output"), cache_read: field("cache_read"), cache_write: field("cache_write"), cache_write_5m: field("cache_write_5m"), cache_write_1h: field("cache_write_1h"), total: field("total") }; } function sumUsage(a, b) { if (a === null) return b; return { input: a.input + b.input, output: a.output + b.output, cache_read: a.cache_read + b.cache_read, cache_write: a.cache_write + b.cache_write, cache_write_5m: a.cache_write_5m + b.cache_write_5m, cache_write_1h: a.cache_write_1h + b.cache_write_1h, total: a.total + b.total }; } function emptySeat(role) { return { role, reason: null, origin: null, state: null, seated_at: null, started_at: null, done_at: null, budget_tokens: null, clamped: null, findings: null, tokens_estimate: null, tokens: null }; } function applyRow(seat, row, at) { switch (row.event) { case "seated": return { ...emptySeat(seat.role), reason: str(row.reason), origin: str(row.origin), state: "seated", seated_at: at, budget_tokens: num(row.budget_tokens), clamped: typeof row.clamped === "boolean" ? row.clamped : null }; case "writing": return { ...seat, state: "writing", started_at: at, done_at: null }; case "done": return { ...seat, state: "done", done_at: at, findings: num(row.findings), tokens_estimate: num(row.tokens_estimate) }; case "usage": { const tokens = tokenUsageOf(row.tokens); return tokens === null ? seat : { ...seat, tokens: sumUsage(seat.tokens, tokens) }; } default: return seat; } } function foldHighTable(rows) { const seats = /* @__PURE__ */ new Map(); const usageAgents = /* @__PURE__ */ new Set(); for (const row of rows) { const role = str(row.role); if (role === null) continue; if (row.event === "usage") { const agent = str(row.agent_id); if (agent !== null && usageAgents.has(agent)) continue; if (agent !== null) usageAgents.add(agent); } const at = str(row.recorded_at) ?? str(row.ts); seats.set(role, applyRow(seats.get(role) ?? emptySeat(role), row, at)); } return seats; } function highTableDoneTimes(rows) { return rows.filter((row) => row.event === "done").map((row) => str(row.recorded_at) ?? str(row.ts)).filter((at) => at !== null); } export { emptyTokenUsage, maxTokenUsage, addTokenUsage, scanTranscriptLines, readTranscriptUsage, CHIEF_ROLE, readHighTable, recordSeatedRows, recordWritingRow, recordDoneRow, roleFromSubagentTranscript, recordHighTableUsage, foldHighTable, highTableDoneTimes };