import { randomUUID } from "node:crypto"; const DEFAULT_MAX_TRACES = 256; const DEFAULT_MAX_EVENTS = 600; const TEXT_LIMIT = 1200; const SECRET_PATTERN = /\b(?:sk-[A-Za-z0-9_-]+|OPENAI_API_KEY|DATABASE_URL|secretRef:[^\s,;]+|secret|token|password|passwd|credential|private[_ -]?key|kubeconfig|BEGIN [A-Z ]*PRIVATE KEY)\b/giu; export function createCodeAgentTraceStore(options = {}) { const maxTraces = positiveInteger(options.maxTraces, DEFAULT_MAX_TRACES); const maxEvents = positiveInteger(options.maxEvents, DEFAULT_MAX_EVENTS); const traces = new Map(); function ensure(traceId, meta = {}) { const id = cleanTraceId(traceId) || `trc_${randomUUID()}`; let trace = traces.get(id); if (!trace) { const timestamp = timestampFor(meta.now); trace = { traceId: id, status: "running", createdAt: timestamp, updatedAt: timestamp, startedAt: timestamp, finishedAt: null, nextSeq: 1, events: [], listeners: new Set(), meta: { runnerKind: meta.runnerKind ?? null, workspace: meta.workspace ?? null, sandbox: meta.sandbox ?? null, sessionMode: meta.sessionMode ?? null, implementationType: meta.implementationType ?? null } }; traces.set(id, trace); prune(); } else { trace.meta = { ...trace.meta, ...dropEmpty({ runnerKind: meta.runnerKind, workspace: meta.workspace, sandbox: meta.sandbox, sessionMode: meta.sessionMode, implementationType: meta.implementationType }) }; } return trace; } function append(traceId, event = {}, meta = {}) { const trace = ensure(traceId, meta); const normalized = normalizeTraceEvent(event, { traceId: trace.traceId, seq: trace.nextSeq, now: meta.now, fallbackRunnerKind: trace.meta.runnerKind }); trace.nextSeq += 1; trace.events.push(normalized); if (trace.events.length > maxEvents) { trace.events.splice(0, trace.events.length - maxEvents); } trace.updatedAt = normalized.createdAt; if (normalized.terminal === true) { trace.status = normalized.status === "completed" ? "completed" : normalized.status === "canceled" ? "canceled" : normalized.type; trace.finishedAt = normalized.createdAt; } else if (normalized.status === "failed" || normalized.type === "error" || normalized.type === "timeout") { trace.status = normalized.type; trace.finishedAt = normalized.createdAt; } notify(trace, normalized); return normalized; } function snapshot(traceId, extra = {}) { const trace = traces.get(cleanTraceId(traceId)); if (!trace) return emptySnapshot(traceId, extra); const events = trace.events.map((event) => ({ ...event })); const lastEvent = events.at(-1) ?? null; return { traceId: trace.traceId, status: trace.status, createdAt: trace.createdAt, updatedAt: trace.updatedAt, startedAt: trace.startedAt, finishedAt: trace.finishedAt, eventCount: events.length, events, eventLabels: events.map((event) => event.label).filter(Boolean), lastEvent, elapsedMs: elapsedMs(trace.startedAt, trace.finishedAt ?? trace.updatedAt), waitingFor: lastWaitingFor(events), runnerKind: extra.runnerKind ?? trace.meta.runnerKind ?? null, workspace: extra.workspace ?? trace.meta.workspace ?? null, sandbox: extra.sandbox ?? trace.meta.sandbox ?? null, sessionMode: extra.sessionMode ?? trace.meta.sessionMode ?? null, implementationType: extra.implementationType ?? trace.meta.implementationType ?? null, sessionId: extra.sessionId ?? lastEvent?.sessionId ?? null, sessionStatus: extra.sessionStatus ?? lastEvent?.sessionStatus ?? null, turn: extra.turn ?? lastEvent?.turn ?? null, outputTruncated: events.some((event) => event.outputTruncated === true), valuesPrinted: false }; } function subscribe(traceId, listener, meta = {}) { const trace = ensure(traceId, meta); trace.listeners.add(listener); return () => { trace.listeners.delete(listener); }; } function clear() { traces.clear(); } function prune() { if (traces.size <= maxTraces) return; const stale = [...traces.values()] .sort((left, right) => String(left.updatedAt).localeCompare(String(right.updatedAt))) .slice(0, traces.size - maxTraces); for (const trace of stale) { traces.delete(trace.traceId); } } return { ensure, append, snapshot, subscribe, clear }; } export const defaultCodeAgentTraceStore = createCodeAgentTraceStore(); export function createCodeAgentTraceRecorder({ traceStore = defaultCodeAgentTraceStore, traceId, now, runnerKind = null, workspace = null, sandbox = null, sessionMode = null, implementationType = null } = {}) { const startedAt = timestampFor(now); const startedEpochMs = Date.now(); const baseMeta = { now, runnerKind, workspace, sandbox, sessionMode, implementationType }; traceStore.ensure(traceId, baseMeta); function append(event = {}) { return traceStore.append(traceId, { elapsedMs: Date.now() - startedEpochMs, ...event }, baseMeta); } function snapshot(extra = {}) { return traceStore.snapshot(traceId, { runnerKind, workspace, sandbox, sessionMode, implementationType, ...extra }); } function runnerTrace(extra = {}) { const current = snapshot(extra); return { traceId: current.traceId, runnerKind: extra.runnerKind ?? current.runnerKind, workspace: extra.workspace ?? current.workspace, sandbox: extra.sandbox ?? current.sandbox, sessionMode: extra.sessionMode ?? current.sessionMode, sessionId: extra.sessionId ?? current.sessionId, sessionStatus: extra.sessionStatus ?? current.sessionStatus, idleTimeoutMs: extra.idleTimeoutMs ?? null, lastTraceId: extra.lastTraceId ?? current.traceId, turn: extra.turn ?? current.turn, sessionReused: extra.sessionReused ?? false, implementationType: extra.implementationType ?? current.implementationType, limitations: Array.isArray(extra.limitations) ? extra.limitations : [], startedAt: extra.startedAt ?? startedAt, finishedAt: extra.finishedAt ?? current.finishedAt ?? timestampFor(now), updatedAt: current.updatedAt, events: current.events, eventLabels: current.eventLabels, lastEvent: current.lastEvent, elapsedMs: current.elapsedMs, waitingFor: current.waitingFor, outputTruncated: current.outputTruncated || extra.outputTruncated === true, valuesPrinted: false, note: extra.note ?? "Real-time runnerTrace is appended as Codex stdio/session events are captured." }; } return { traceId, append, snapshot, runnerTrace }; } export function runnerTraceFromSnapshot(snapshot = {}, extra = {}) { return { traceId: snapshot.traceId ?? extra.traceId ?? null, runnerKind: extra.runnerKind ?? snapshot.runnerKind ?? null, workspace: extra.workspace ?? snapshot.workspace ?? null, sandbox: extra.sandbox ?? snapshot.sandbox ?? null, sessionMode: extra.sessionMode ?? snapshot.sessionMode ?? null, sessionId: extra.sessionId ?? snapshot.sessionId ?? null, sessionStatus: extra.sessionStatus ?? snapshot.sessionStatus ?? null, idleTimeoutMs: extra.idleTimeoutMs ?? null, lastTraceId: extra.lastTraceId ?? snapshot.traceId ?? null, turn: extra.turn ?? snapshot.turn ?? null, sessionReused: extra.sessionReused ?? false, implementationType: extra.implementationType ?? snapshot.implementationType ?? null, limitations: Array.isArray(extra.limitations) ? extra.limitations : [], startedAt: extra.startedAt ?? snapshot.startedAt ?? null, finishedAt: extra.finishedAt ?? snapshot.finishedAt ?? snapshot.updatedAt ?? null, updatedAt: snapshot.updatedAt ?? null, events: Array.isArray(snapshot.events) ? snapshot.events : [], eventLabels: Array.isArray(snapshot.eventLabels) ? snapshot.eventLabels : [], lastEvent: snapshot.lastEvent ?? null, elapsedMs: snapshot.elapsedMs ?? null, waitingFor: snapshot.waitingFor ?? null, outputTruncated: snapshot.outputTruncated === true || extra.outputTruncated === true, valuesPrinted: false, note: extra.note ?? "runnerTrace snapshot" }; } function normalizeTraceEvent(event, { traceId, seq, now, fallbackRunnerKind } = {}) { const type = safeToken(event.type ?? event.kind ?? "event"); const status = safeToken(event.status ?? "observed"); const toolName = safeText(event.toolName ?? event.name, 120); const label = safeText(event.label, 180) || labelFor({ type, status, toolName }); return dropUndefined({ seq, traceId, type, stage: safeToken(event.stage ?? type), status, label, createdAt: timestampFor(now), elapsedMs: typeof event.elapsedMs === "number" ? Math.max(0, Math.trunc(event.elapsedMs)) : null, runnerKind: safeText(event.runnerKind ?? fallbackRunnerKind, 140), sessionId: safeText(event.sessionId, 180), sessionStatus: safeText(event.sessionStatus, 80), sessionReused: typeof event.sessionReused === "boolean" ? event.sessionReused : undefined, turn: typeof event.turn === "number" ? event.turn : undefined, toolName, promptSummary: safeText(event.promptSummary, 240), outputSummary: safeText(event.outputSummary, 400), chunk: safeText(event.chunk, 400), message: safeText(event.message, 500), errorCode: safeText(event.errorCode, 120), waitingFor: safeText(event.waitingFor, 220), timeoutMs: typeof event.timeoutMs === "number" ? Math.trunc(event.timeoutMs) : undefined, hardTimeoutMs: typeof event.hardTimeoutMs === "number" ? Math.trunc(event.hardTimeoutMs) : undefined, lastActivityAt: safeText(event.lastActivityAt, 80), idleMs: typeof event.idleMs === "number" ? Math.max(0, Math.trunc(event.idleMs)) : undefined, outputTruncated: event.outputTruncated === true, terminal: event.terminal === true, valuesPrinted: false }); } function labelFor({ type, status, toolName }) { if (type === "session") return `session:${status}`; if (type === "prompt") return `prompt:${status}`; if (type === "tool_call") return `tool:${toolName || "codex"}:${status}`; if (type === "assistant_message") return `assistant:${status}`; if (["timeout", "cancel", "error"].includes(type)) return type; if (type === "request") return `request:${status}`; if (type === "stdio") return `stdio:${status}`; return `${type}:${status}`; } function notify(trace, event) { const snapshot = { ...emptySnapshot(trace.traceId), ...{ traceId: trace.traceId, status: trace.status, createdAt: trace.createdAt, updatedAt: trace.updatedAt, startedAt: trace.startedAt, finishedAt: trace.finishedAt, eventCount: trace.events.length, events: trace.events.map((item) => ({ ...item })), eventLabels: trace.events.map((item) => item.label).filter(Boolean), lastEvent: event, elapsedMs: elapsedMs(trace.startedAt, trace.finishedAt ?? trace.updatedAt), waitingFor: lastWaitingFor(trace.events), runnerKind: trace.meta.runnerKind, workspace: trace.meta.workspace, sandbox: trace.meta.sandbox, sessionMode: trace.meta.sessionMode, implementationType: trace.meta.implementationType, outputTruncated: trace.events.some((item) => item.outputTruncated === true), valuesPrinted: false } }; for (const listener of trace.listeners) { try { listener(event, snapshot); } catch { trace.listeners.delete(listener); } } } function emptySnapshot(traceId, extra = {}) { const id = cleanTraceId(traceId); return { traceId: id, status: "missing", createdAt: null, updatedAt: null, startedAt: null, finishedAt: null, eventCount: 0, events: [], eventLabels: [], lastEvent: null, elapsedMs: null, waitingFor: null, runnerKind: extra.runnerKind ?? null, workspace: extra.workspace ?? null, sandbox: extra.sandbox ?? null, sessionMode: extra.sessionMode ?? null, implementationType: extra.implementationType ?? null, sessionId: null, sessionStatus: null, turn: null, outputTruncated: false, valuesPrinted: false }; } function lastWaitingFor(events) { for (const event of [...events].reverse()) { if (event.waitingFor) return event.waitingFor; if (event.type === "tool_call" && ["started", "output_chunk"].includes(event.status)) { return `tool:${event.toolName ?? "codex"}`; } if (event.type === "prompt" && event.status === "sent") return "assistant-message"; } return null; } function elapsedMs(start, end) { const startMs = Date.parse(start ?? ""); const endMs = Date.parse(end ?? ""); if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return null; return Math.max(0, endMs - startMs); } function safeText(value, limit = TEXT_LIMIT) { if (value === undefined || value === null || value === "") return undefined; const redacted = String(value).replace(SECRET_PATTERN, "[redacted]").replace(/\s+/gu, " ").trim(); if (!redacted) return undefined; return redacted.length > limit ? `${redacted.slice(0, limit - 3)}...` : redacted; } function safeToken(value) { return String(value ?? "") .trim() .replace(/[^A-Za-z0-9_.:-]/gu, "_") .slice(0, 80) || "event"; } function cleanTraceId(value) { const id = String(value ?? "").trim(); return /^trc_[A-Za-z0-9_.:-]+$/u.test(id) ? id : null; } function timestampFor(now) { const value = typeof now === "function" ? now() : now; const date = value ? new Date(value) : new Date(); return Number.isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString(); } function positiveInteger(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } function dropEmpty(value) { return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== "")); } function dropUndefined(value) { return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)); }