Files
pikasTech-HWLAB/tools/src/hwlab-cli/trace-renderer.ts
T

731 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface TraceEventRow {
rowId: string;
seq: number | null;
sequenceAuthority: TraceSequenceAuthority;
tone: "ok" | "blocked" | "warn" | "source";
header: string;
body: string | null;
preview?: string | null;
terminal?: boolean;
bodyFormat?: "markdown" | "text";
}
type TraceEvent = Record<string, unknown>;
type TraceClockFormatter = (value: string) => string;
export type TraceSequenceAuthority = "projected" | "source";
export type TraceFinalResponsePlacement = "trace" | "outer";
export interface TraceDisplayRowsOptions {
formatClock?: TraceClockFormatter;
finalResponsePlacement?: TraceFinalResponsePlacement;
outerFinalResponseText?: string | null;
}
interface ResolvedTraceDisplayRowsOptions extends TraceDisplayRowsOptions {
sequenceAuthority: TraceSequenceAuthority;
finalResponsePlacement: TraceFinalResponsePlacement;
}
export function traceDisplayRows(trace: Record<string, unknown> = {}, events: TraceEvent[] = [], options: TraceDisplayRowsOptions = {}): TraceEventRow[] {
const displayOptions: ResolvedTraceDisplayRowsOptions = {
...options,
sequenceAuthority: traceSequenceAuthority(trace),
finalResponsePlacement: options.finalResponsePlacement ?? "trace"
};
const orderedEvents = traceEventsForDisplay(
events.filter((event) => hasTraceSequence(event, displayOptions.sequenceAuthority)),
displayOptions.sequenceAuthority
);
const effectiveTrace = traceWithInferredStart(trace, orderedEvents);
const finalResponseText = nonEmptyString(options.outerFinalResponseText) ?? traceFinalResponseText(effectiveTrace);
const finalResponseFingerprint = traceMessageFingerprint(finalResponseText);
const terminalAssistantFingerprints = new Set(orderedEvents
.filter(isTerminalAssistantTraceEvent)
.map((event) => traceAssistantEventFingerprint(effectiveTrace, event))
.filter((value): value is string => Boolean(value)));
if (finalResponseFingerprint) terminalAssistantFingerprints.add(finalResponseFingerprint);
const hasCompletionEvent = orderedEvents.some(isCompletionTraceEvent);
const rows: TraceEventRow[] = [];
let completionEvent: TraceEvent | null = null;
const renderedSourceEvents = new Set<string>();
const renderedToolIdentities = new Set<string>();
for (let index = 0; index < orderedEvents.length; index += 1) {
const event = orderedEvents[index];
const sourceEventKey = traceSourceEventKey(event);
if (sourceEventKey) {
if (renderedSourceEvents.has(sourceEventKey)) continue;
renderedSourceEvents.add(sourceEventKey);
}
if (!event || isNoisyTraceEvent(event)) continue;
if (isSupersededTraceItemLifecycleEvent(event, orderedEvents, index)) continue;
if (isToolTraceEvent(event)) {
const identity = toolIdentity(event);
if (identity) {
if (renderedToolIdentities.has(identity)) continue;
renderedToolIdentities.add(identity);
}
rows.push(traceToolCallRow(effectiveTrace, event, displayOptions));
continue;
}
if (isRequestTraceEvent(event)) {
continue;
}
if (isSetupTraceEvent(event)) {
continue;
}
if (isTerminalAssistantTraceEvent(event)) {
if (isOuterFinalResponseTraceEvent(effectiveTrace, event, displayOptions, terminalAssistantFingerprints)) continue;
if (hasCompletionEvent) continue;
if (finalResponseText) {
rows.push(traceFinalResponseRow(event, finalResponseText, displayOptions));
} else {
rows.push(traceAssistantMessageRow(effectiveTrace, event, { terminal: true }, displayOptions));
}
continue;
}
if (isAssistantTraceEvent(event)) {
const fingerprint = traceAssistantEventFingerprint(effectiveTrace, event);
if (isOuterFinalResponseTraceEvent(effectiveTrace, event, displayOptions, terminalAssistantFingerprints)) continue;
if (finalResponseFingerprint && fingerprint === finalResponseFingerprint) continue;
rows.push(traceAssistantMessageRow(effectiveTrace, event, { terminal: false }, displayOptions));
continue;
}
if (isCompletionTraceEvent(event)) {
completionEvent ??= event;
continue;
}
rows.push(traceDisplayRow(effectiveTrace, event, displayOptions));
}
if (completionEvent) rows.push(traceCompletionSummaryRow(effectiveTrace, completionEvent, displayOptions));
const progressRow = traceBackendProgressSummaryRow(effectiveTrace, rows, orderedEvents, displayOptions);
if (progressRow) rows.push(progressRow);
if (rows.length > 0) return rows;
if (orderedEvents.length === 0) return [];
return orderedEvents
.filter((event, index) => !isSupersededTraceItemLifecycleEvent(event, orderedEvents, index))
.filter((event) => !isSuppressedTraceEvent(event))
.filter((event) => !isOuterFinalResponseTraceEvent(effectiveTrace, event, displayOptions, terminalAssistantFingerprints))
.map((event) => traceDisplayRow(effectiveTrace, event, displayOptions));
}
function isOuterFinalResponseTraceEvent(
trace: Record<string, unknown>,
event: TraceEvent,
options: ResolvedTraceDisplayRowsOptions,
terminalAssistantFingerprints: Set<string>
): boolean {
if (options.finalResponsePlacement !== "outer") return false;
if (isTerminalAssistantTraceEvent(event)) return true;
if (!isAssistantTraceEvent(event)) return false;
const fingerprint = traceAssistantEventFingerprint(trace, event);
return Boolean(fingerprint && terminalAssistantFingerprints.has(fingerprint));
}
function traceSequenceAuthority(trace: Record<string, unknown>): TraceSequenceAuthority {
return nonEmptyString(trace.eventSource) === "hwlab-kafka-sse" ? "source" : "projected";
}
function hasTraceSequence(event: TraceEvent | null | undefined, authority: TraceSequenceAuthority): boolean {
return traceEventDisplaySeq(event, authority) !== null;
}
export function renderTraceRowsMarkdown(rows: TraceEventRow[] = []): string {
if (!Array.isArray(rows) || rows.length === 0) return `_No rendered trace rows were returned._`;
return rows.map((row, index) => renderTraceRowMarkdown(row, index + 1)).join("\n\n");
}
export function renderTraceRowMarkdown(row: TraceEventRow, index = 1): string {
if (!isToolTraceRow(row)) return renderTraceMessageRowMarkdown(row);
const body = renderTraceToolDetailBody(row) || row.header || `trace row ${index}`;
return [
`- <details>`,
` <summary>${escapeHtml(`已运行 ${traceToolSummary(row)}`)}</summary>`,
``,
indentTraceMarkdownBlock(traceMarkdownFence(row.bodyFormat === "markdown" ? "markdown" : "text", body)),
``,
` </details>`
].join("\n");
}
function renderTraceMessageRowMarkdown(row: TraceEventRow): string {
const body = row.body?.trimEnd();
if (body) return traceBoldMarkdown(body);
return traceBoldMarkdown(row.header || `_No readable trace row body._`);
}
function traceEventsForDisplay(events: TraceEvent[] = [], authority: TraceSequenceAuthority): TraceEvent[] {
if (authority === "source") return [...events];
return [...events].sort((left, right) => {
const leftSeq = traceEventDisplaySeq(left, authority);
const rightSeq = traceEventDisplaySeq(right, authority);
if (leftSeq !== null || rightSeq !== null) {
if (leftSeq === null) return 1;
if (rightSeq === null) return -1;
if (leftSeq !== rightSeq) return leftSeq - rightSeq;
}
const leftTime = traceEventTimestampMs(left);
const rightTime = traceEventTimestampMs(right);
if (leftTime !== null || rightTime !== null) {
if (leftTime === null) return 1;
if (rightTime === null) return -1;
if (leftTime !== rightTime) return leftTime - rightTime;
}
return 0;
});
}
function traceEventDisplaySeq(event: TraceEvent | null | undefined, authority: TraceSequenceAuthority): number | null {
return numberOrNull(authority === "source" ? event?.sourceSeq : event?.projectedSeq);
}
function traceEventIdentityToken(event: TraceEvent, authority: TraceSequenceAuthority): string | number | null {
if (authority === "source") {
const sourceEventId = nonEmptyString(event.sourceEventId);
if (sourceEventId) return sourceEventId;
const runId = nonEmptyString(event.runId);
const sourceSeq = traceEventDisplaySeq(event, authority);
if (runId && sourceSeq !== null) return `${runId}:${sourceSeq}`;
return sourceSeq;
}
const seq = traceEventDisplaySeq(event, authority);
return seq ?? nonEmptyString(event.projectedSeq);
}
function isToolTraceRow(row: TraceEventRow): boolean {
return row.rowId.startsWith("tool:") || /commandExecution/u.test(row.header);
}
function traceToolSummary(row: TraceEventRow): string {
if (row.preview) return compactTraceOneLine(row.preview, 90);
const firstLine = traceMarkdownPreviewLines(row.body).find((line) => !/^(stdout|stderr|exitCode|rowId|terminal):/iu.test(line.trim()));
return compactTraceOneLine(stripTraceToolOutputFromSummary(firstLine || row.header || "tool call"), 90);
}
function stripTraceToolOutputFromSummary(value: string): string {
return value.replace(/\s+(stdout:|stderr:|exitCode=).*$/isu, "").trim();
}
function traceToolCallRow(trace: Record<string, unknown>, event: TraceEvent, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const command = cleanShellCommand(event.command);
const outputPreview = traceEventText(event, ["outputSummary", "stdoutSummary", "stderrSummary", "output", "outputText"]);
const status = traceStatusToken(event);
const toolName = traceToolName(event, command);
const body = traceToolCallBody(event, command);
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
const duration = numberOrNull(event.durationMs);
const exitCode = numberOrNull(event.exitCode);
return {
rowId: `tool:${event.itemId ?? eventKey ?? `${event.label ?? event.type ?? "tool"}:${event.createdAt ?? "unknown"}`}`,
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: traceEventTone(event),
header: [
traceEventClock(event, options),
`total=${formatTraceDuration(traceRelativeMs(trace, event))}`,
status,
toolName,
duration !== null ? `duration=${formatTraceDuration(duration)}` : null,
exitCode !== null ? `exit=${exitCode}` : null
].filter(Boolean).join(" "),
body,
preview: traceToolPreview(command, outputPreview),
bodyFormat: "text"
};
}
function traceToolPreview(command: string | null, output: string | null): string | null {
const parts = [
command ? compactTraceOneLine(command, 92) : null,
output ? `output: ${compactTraceOneLine(output, 92)}` : null
].filter((value): value is string => Boolean(value));
return parts.length > 0 ? parts.join(" | ") : null;
}
function traceToolName(event: TraceEvent, command: string | null): string {
const direct = nonEmptyString(event.toolName);
if (direct && !isProtocolToolLifecycleName(direct) && !isGenericToolName(direct)) return direct;
const label = readableTraceLabel(event).replace(/^agentrun:tool:/u, "").replace(/^tool:/u, "").replace(/^item\//u, "");
if (label && !isProtocolToolLifecycleName(label) && !isGenericToolName(label)) return label;
const commandName = traceToolNameFromCommand(command);
if (commandName) return commandName;
if (direct && !isProtocolToolLifecycleName(direct)) return direct;
const previewName = traceToolNameFromPreview(event);
if (previewName) return previewName;
return "tool call";
}
function isGenericToolName(value: unknown): boolean {
return /^(commandExecution|tool|shell|command|unknown)$/iu.test(String(value ?? "").trim());
}
function isProtocolToolLifecycleName(value: unknown): boolean {
return /^(?:item\/)?(?:started|completed)$/iu.test(String(value ?? "").trim()) || /^item\/(?:started|completed)$/iu.test(String(value ?? "").trim());
}
function traceToolNameFromCommand(command: string | null): string | null {
const text = cleanTraceText(command);
const segments = text.split(/\s*(?:&&|\|\||;)\s*/u).map((value) => value.trim()).filter(Boolean);
const candidate = segments.find((value) => !/^(?:cd|export|set|source|umask)\b/u.test(value)) ?? segments[0] ?? text;
const normalized = candidate
.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=[^\s]+\s+)+/u, "")
.replace(/^(?:sudo\s+|env\s+|command\s+)+/u, "")
.replace(/^timeout\s+\S+\s+/u, "");
const match = normalized.match(/^([A-Za-z./][A-Za-z0-9_./:-]{0,80})(?:\s|$)/u);
return match?.[1]?.split("/").filter(Boolean).at(-1) ?? null;
}
function traceToolNameFromPreview(event: TraceEvent): string | null {
const preview = traceEventText(event, ["output", "outputText", "outputSummary", "stdoutSummary", "message"]);
return jsonPreviewStringField(preview, "toolName") ?? jsonPreviewStringField(preview, "type") ?? jsonPreviewStringField(preview, "name");
}
function jsonPreviewStringField(text: string | null, key: string): string | null {
const body = String(text ?? "");
if (!body || !/^[A-Za-z][A-Za-z0-9_]*$/u.test(key)) return null;
const match = body.match(new RegExp(`"${key}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`, "u"));
if (!match) return null;
try {
const parsed = JSON.parse(`"${match[1]}"`);
return typeof parsed === "string" && parsed.trim() ? parsed.trim() : null;
} catch {
return match[1]?.trim() || null;
}
}
function traceToolCallBody(event: TraceEvent, command: string | null): string | null {
const stdout = traceEventText(event, ["stdout", "stdoutText", "stdoutSummary"]);
const stderr = traceEventText(event, ["stderr", "stderrText", "stderrSummary"]);
const output = traceEventText(event, ["output", "outputText", "outputSummary"]);
const distinctOutput = output && output !== stdout && output !== stderr ? output : null;
const outputBytes = numberOrNull(event.outputBytes);
const lines = [
command,
traceDetailBlock("stdout", stdout),
traceDetailBlock("stderr", stderr),
traceDetailBlock("output", distinctOutput),
event.commandTruncated === true ? "commandTruncated=true" : null,
outputBytes !== null ? `outputBytes=${outputBytes}` : null,
event.outputTruncated === true ? "outputTruncated=true" : null,
event.exitCode !== undefined ? `exitCode=${event.exitCode}` : null
].map(cleanTraceDetailText).filter(Boolean);
return lines.length > 0 ? lines.join("\n") : null;
}
export function traceNoiseEventCount(events: TraceEvent[] = []): number {
return Array.isArray(events) ? events.filter((event) => isNoisyTraceEvent(event)).length : 0;
}
function traceAssistantMessageRow(trace: Record<string, unknown>, event: TraceEvent, { terminal }: { terminal: boolean }, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const text = traceAssistantEventText(trace, event);
const index = Number.isInteger(event.messageIndex) ? Number(event.messageIndex) : null;
const count = Number.isInteger(event.messageCount) ? Number(event.messageCount) : null;
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
return {
rowId: `event:${eventKey ?? `${event.label ?? event.type ?? "assistant"}:${event.createdAt ?? "unknown"}`}`,
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: "ok",
header: `${traceEventClock(event, options)} ${terminal ? "助手最终消息" : "助手消息"}${index ? ` ${index}${count ? `/${count}` : ""}` : ""}`,
terminal: terminal ? true : undefined,
body: text || null,
bodyFormat: "markdown"
};
}
function traceAssistantEventText(trace: Record<string, unknown>, event: TraceEvent): string {
return cleanTraceDetailText(event.message ?? event.outputSummary ?? assistantStreamText(trace, event) ?? "");
}
function traceAssistantEventFingerprint(trace: Record<string, unknown>, event: TraceEvent): string | null {
return traceMessageFingerprint(traceAssistantEventText(trace, event));
}
function traceMessageFingerprint(value: unknown): string | null {
const text = cleanTraceDetailText(value ?? "").replace(/\s+/gu, " ").trim();
return text || null;
}
function traceFinalResponseRow(event: TraceEvent, finalText: string, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
return {
rowId: `trace-final-response:${eventKey ?? "completed"}`,
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: "ok",
header: `${traceEventClock(event, options)} 助手最终消息`,
terminal: true,
body: finalText,
bodyFormat: "markdown"
};
}
function traceFinalResponseText(trace: Record<string, unknown>): string | null {
const response = trace.finalResponse && typeof trace.finalResponse === "object" ? trace.finalResponse as Record<string, unknown> : null;
return nonEmptyString(response?.text ?? response?.content ?? response?.message);
}
function traceCompletionSummaryRow(trace: Record<string, unknown>, event: TraceEvent, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
return {
rowId: `trace-completion:${eventKey ?? "turn"}`,
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: traceEventTone(event),
header: `${traceEventClock(event, options)} 轮次完成(总耗时 ${formatTraceDuration(traceRelativeMs(trace, event))}`,
body: null
};
}
function traceNoiseSummaryRow(trace: Record<string, unknown>, events: TraceEvent[], options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const lastEvent = events.at(-1);
const status = nonEmptyString(trace.status ?? trace.traceStatus) ?? "running";
const lastLabel = lastEvent ? readableTraceLabel(lastEvent) : "未观测";
return {
rowId: "trace-noise-summary",
seq: traceEventDisplaySeq(lastEvent, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: lastEvent ? traceEventTone(lastEvent) : "source",
header: `Trace ${status},等待可读事件`,
body: `已隐藏 ${events.length} 条 AgentRun backend 状态事件。最新原始事件:${lastLabel}。`,
bodyFormat: "text"
};
}
function traceBackendProgressSummaryRow(trace: Record<string, unknown>, rows: TraceEventRow[], events: TraceEvent[], options: ResolvedTraceDisplayRowsOptions): TraceEventRow | null {
if (!Array.isArray(events) || events.length === 0) return null;
if (rows.some((row) => row.rowId.startsWith("tool:") || /助手|轮次完成/u.test(row.header))) return null;
const lastRenderedSeq = Math.max(0, ...rows.map((row) => row.seq ?? 0).filter(Number.isFinite));
const hiddenProgress = events.filter((event) => {
const seq = traceEventDisplaySeq(event, options.sequenceAuthority) ?? 0;
return seq > lastRenderedSeq && (isRequestTraceEvent(event) || isSetupTraceEvent(event) || isNoisyTraceEvent(event));
});
return hiddenProgress.length > 0 ? traceNoiseSummaryRow(trace, hiddenProgress, options) : null;
}
function traceDisplayRow(trace: Record<string, unknown>, event: TraceEvent, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
const label = readableTraceLabel(event);
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
return {
rowId: `event:${eventKey ?? `${event.label ?? event.type ?? "event"}:${event.createdAt ?? "unknown"}`}`,
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
sequenceAuthority: options.sequenceAuthority,
tone: traceEventTone(event),
header: `${traceEventClock(event, options)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${traceStatusToken(event)} ${label}`.trim(),
body: traceDisplayBody(event)
};
}
function assistantStreamText(trace: Record<string, unknown>, event: TraceEvent): string {
const streams = Array.isArray(trace.assistantStreams) ? trace.assistantStreams.filter(isRecord) : [];
const itemId = nonEmptyString(event.itemId ?? event.messageId ?? event.id);
const matched = itemId ? streams.find((stream) => [stream.itemId, stream.messageId, stream.id].map(nonEmptyString).includes(itemId)) : null;
return nonEmptyString(matched?.text ?? streams.at(-1)?.text) ?? "";
}
function isRequestTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
return label === "agentrun:request:accepted" || label === "request:accepted-short-connection" || label === "request:accepted" || label === "request:received";
}
function isSetupTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
return /^session:|^stdio:|^prompt:|^thread:|^turn:started|^turn:start|turn\/start|codex turn|tool:codex-app-server/iu.test(label) ||
/^agentrun:backend:(codex-app-server-starting|initialize:completed|thread\/start|thread\/resume|thread\/started|turn\/start|turn\/started)/iu.test(label) ||
/^agentrun:run:(reuse-skipped|reused)$/iu.test(label) ||
/^agentrun:command:created$/iu.test(label) ||
/^agentrun:runner-job:(created|ensured|reused)$/iu.test(label) ||
/^agentrun:session-storage-recover-warning$/iu.test(label);
}
function isCompletionTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
return label.startsWith("turn:completed") || label.startsWith("result:completed") || label === "agentrun:backend:turn/completed" || label === "agentrun:terminal:completed" || label === "agentrun:result:completed";
}
function isAssistantTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
return label === "agentrun:assistant:message" || label === "assistant:message" || event.type === "assistant_message" || event.type === "assistant";
}
function isTerminalAssistantTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
if (label === "assistant:completed") return true;
if (label === "agentrun:assistant:message") return event.replyAuthority === true || event.final === true;
return event.type === "assistant_message" && (event.status === "completed" || event.final === true || event.terminal === true);
}
function isToolTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
const type = String(event.type ?? "");
const toolName = String(event.toolName ?? "");
return type === "tool_call" || toolName === "commandExecution" || /commandExecution|^agentrun:tool:/u.test(label);
}
function isToolStartTraceEvent(event: TraceEvent): boolean {
const status = String(event.status ?? "").toLowerCase();
const label = String(event.label ?? "");
return ["started", "running", "inprogress", "in-progress"].includes(status) || /:started$|\/started$/u.test(label);
}
function isToolCompleteTraceEvent(event: TraceEvent): boolean {
const status = String(event.status ?? "").toLowerCase();
const label = String(event.label ?? "");
return ["completed", "succeeded", "failed", "blocked", "timeout", "canceled", "cancelled"].includes(status) || /:completed$|\/completed$/u.test(label);
}
function toolIdentity(event: TraceEvent): string | null {
return nonEmptyString(event.itemId ?? event.id ?? event.command);
}
function traceSourceEventKey(event: TraceEvent | null | undefined): string | null {
if (!event || typeof event !== "object") return null;
const sourceEventId = nonEmptyString(event.sourceEventId);
if (sourceEventId) return `source-event:${sourceEventId}`;
const runId = nonEmptyString(event.runId);
const sourceSeq = nonEmptyString(event.sourceSeq);
if (runId && sourceSeq) return `run-source:${runId}:${sourceSeq}`;
const source = nonEmptyString(event.source);
if (!source || !sourceSeq) return null;
return `${source}:${sourceSeq}:${nonEmptyString(event.label ?? event.type) ?? "event"}`;
}
function isSupersededTraceItemLifecycleEvent(event: TraceEvent, events: TraceEvent[], index: number): boolean {
return isSupersededToolStart(event, events, index) || isLateAssistantProgressAfterCompletion(event, events, index);
}
function isSupersededToolStart(event: TraceEvent, events: TraceEvent[], index: number): boolean {
if (!isToolStartTraceEvent(event)) return false;
const identity = toolIdentity(event);
if (!identity) return false;
return events.slice(index + 1).some((candidate) => candidate && isToolTraceEvent(candidate) && isToolCompleteTraceEvent(candidate) && toolIdentity(candidate) === identity);
}
function isLateAssistantProgressAfterCompletion(event: TraceEvent, events: TraceEvent[], index: number): boolean {
if (!isAssistantTraceEvent(event) || nonEmptyString(event.assistantSource) !== "agent-message-delta-progress") return false;
const identity = nonEmptyString(event.itemId);
if (!identity) return false;
return events.slice(0, index).some((candidate) => candidate
&& isAssistantTraceEvent(candidate)
&& nonEmptyString(candidate.itemId) === identity
&& nonEmptyString(candidate.assistantSource) === "completed-agent-message");
}
function isNoisyTraceEvent(event: TraceEvent): boolean {
const label = String(event.label ?? "");
if (isRequestTraceEvent(event) || isSetupTraceEvent(event) || isCompletionTraceEvent(event) || isTerminalAssistantTraceEvent(event) || isAssistantTraceEvent(event)) return false;
if (/^agentrun:backend:/u.test(label) && !event.errorCode) return true;
if (/token_count|outputDelta:chunk/iu.test(label)) return true;
if (/^agentrun:output:(stdout|stderr)$/u.test(label)) return true;
if (/^agentrun:backend:(run-created|command-created|runner-job-created|thread\/status\/changed|thread\/tokenUsage\/updated|account\/rateLimits\/updated|remoteControl\/status\/changed|configWarning|codex-app-server-closed|codex-app-server-notifications-suppressed|session-updated|command-terminal|backend-turn-finished|item\/agentMessage:(started|completed)|thread\/goal\/cleared)$/u.test(label)) return true;
return event.type === "event" && !event.outputSummary && !event.message && !event.errorCode;
}
function isSuppressedTraceEvent(event: TraceEvent): boolean {
return isNoisyTraceEvent(event) || isRequestTraceEvent(event) || isSetupTraceEvent(event);
}
function readableTraceLabel(event: TraceEvent): string {
const label = String(event.label ?? `${event.type ?? "event"}:${event.status ?? "observed"}`);
if (label === "request:accepted-short-connection") return "submit short-connection";
if (label === "request:accepted") return "request accepted";
if (label.startsWith("turn:completed")) return "turn completed";
if (label.startsWith("result:completed")) return "result ready";
return label.replace(/^tool:/u, "").replace(/^assistant:/u, "assistant ").replace(/:completed$/u, " completed").replace(/:started$/u, " started");
}
function traceDisplayBody(event: TraceEvent): string | null {
const command = cleanShellCommand(event.command);
const lines = [
command,
traceDetailBlock("stdout", traceEventText(event, ["stdout", "stdoutText", "stdoutSummary"])),
traceDetailBlock("stderr", traceEventText(event, ["stderr", "stderrText", "stderrSummary"])),
traceEventText(event, ["output", "outputText", "outputSummary"]),
traceEventText(event, ["prompt", "promptText", "promptSummary"]),
traceEventText(event, ["message"]),
traceEventText(event, ["chunk"])
].map(cleanTraceDetailText).filter(Boolean);
return lines.length > 0 ? lines.join("\n") : null;
}
function traceMarkdownPreviewLines(body: string | null | undefined): string[] {
return String(body ?? "")
.replace(/\r\n|\r/gu, "\n")
.trim()
.split("\n")
.map((line) => line.trimEnd())
.filter((line) => Boolean(line.trim()));
}
function renderTraceRowDetailBody(row: TraceEventRow): string {
return [
row.body?.trimEnd() ?? "",
row.rowId ? `rowId: ${row.rowId}` : "",
row.terminal === true ? "terminal: true" : ""
].filter(Boolean).join("\n");
}
function renderTraceToolDetailBody(row: TraceEventRow): string {
return normalizeTraceToolDetailBody(renderTraceRowDetailBody(row));
}
function normalizeTraceToolDetailBody(value: string): string {
return String(value ?? "")
.replace(/\r\n|\r/gu, "\n")
.replace(/[^\S\n]+(stdout:)[^\S\n]*/giu, "\n$1\n")
.replace(/[^\S\n]+(stderr:)[^\S\n]*/giu, "\n$1\n")
.replace(/[^\S\n]+(exitCode=)/giu, "\n$1")
.replace(/\n{3,}/gu, "\n\n")
.trimEnd();
}
function traceBoldMarkdown(value: string): string {
const body = String(value ?? "").trimEnd();
if (!body) return "";
return body.split("\n").map((line) => {
if (!line.trim()) return "";
return `**${escapeMarkdownStrongText(line)}**`;
}).join("\n");
}
function escapeMarkdownStrongText(value: string): string {
return escapeHtml(value).replace(/\\/gu, "\\\\").replace(/\*/gu, "\\*");
}
function indentTraceMarkdownBlock(value: string): string {
return String(value).split("\n").map((line) => ` ${line}`).join("\n");
}
function escapeHtml(value: string): string {
return String(value).replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;");
}
function traceMarkdownFence(language: string, body: string): string {
const fence = body.includes("```") ? "````" : "```";
return `${fence}${language}\n${body.trimEnd()}\n${fence}`;
}
function traceStatusToken(event: TraceEvent): string {
const rawStatus = String(event.status ?? "");
const status = rawStatus.toLowerCase();
if (status === "completed" || status === "succeeded") return "ok";
if (["failed", "blocked", "error", "timeout", "canceled"].includes(status) || event.errorCode) return "fail";
if (["started", "running", "accepted", "inprogress", "in-progress"].includes(status)) return "run";
return rawStatus || "event";
}
function traceEventTone(event: TraceEvent): TraceEventRow["tone"] {
const status = traceStatusToken(event);
if (status === "ok") return "ok";
if (status === "fail") return "blocked";
if (status === "run") return "warn";
return "source";
}
function traceClock(value: unknown, options: TraceDisplayRowsOptions): string {
const timestamp = nonEmptyString(value);
if (timestamp && options.formatClock) return options.formatClock(timestamp);
const date = new Date(String(value ?? ""));
return Number.isNaN(date.getTime()) ? "--:--:--" : date.toISOString().slice(11, 19);
}
function traceEventClock(event: TraceEvent | null | undefined, options: TraceDisplayRowsOptions): string {
return traceClock(traceEventTimestamp(event), options);
}
function traceWithInferredStart(trace: Record<string, unknown>, events: TraceEvent[]): Record<string, unknown> {
if (nonEmptyString(trace.startedAt ?? trace.createdAt)) return trace;
const firstTimestamp = events.map((event) => traceEventTimestamp(event)).find(Boolean);
return firstTimestamp ? { ...trace, startedAt: firstTimestamp } : trace;
}
function traceEventTimestamp(event: TraceEvent | null | undefined): string | null {
if (!event) return null;
return nonEmptyString(event.createdAt ?? event.ts ?? event.timestamp ?? event.observedAt);
}
function traceEventTimestampMs(event: TraceEvent | null | undefined): number | null {
const timestamp = traceEventTimestamp(event);
if (!timestamp) return null;
const parsed = Date.parse(timestamp);
return Number.isFinite(parsed) ? parsed : null;
}
function traceRelativeMs(trace: Record<string, unknown>, event: TraceEvent): number {
if (typeof event.elapsedMs === "number") return event.elapsedMs;
const start = Date.parse(String(trace.startedAt ?? trace.createdAt ?? ""));
const current = Date.parse(String(traceEventTimestamp(event) ?? ""));
return Number.isNaN(start) || Number.isNaN(current) ? 0 : Math.max(0, current - start);
}
function formatTraceDuration(ms: number): string {
const seconds = Math.floor(Math.max(0, Number(ms) || 0) / 1000);
return [Math.floor(seconds / 3600), Math.floor((seconds % 3600) / 60), seconds % 60].map((part) => String(part).padStart(2, "0")).join(":");
}
function cleanTraceText(value: unknown): string {
return String(value ?? "").replace(/\u0000/gu, "").replace(/\r\n|\r/gu, "\n").replace(/[ \t]{2,}/gu, " ").replace(/\n{3,}/gu, "\n\n").trim();
}
function cleanTraceDetailText(value: unknown): string {
return String(value ?? "").replace(/\u0000/gu, "").replace(/\r\n|\r/gu, "\n").replace(/\n{4,}/gu, "\n\n\n").trim();
}
function traceEventText(event: TraceEvent, keys: string[]): string | null {
for (const key of keys) {
const value = event[key];
if (value === undefined || value === null) continue;
const body = cleanTraceDetailText(value);
if (body) return body;
}
return null;
}
function traceDetailBlock(label: string, value: string | null): string | null {
return value ? `${label}:\n${value}` : null;
}
function cleanShellCommand(value: unknown): string | null {
const raw = cleanTraceDetailText(value);
if (!raw) return null;
const stripped = stripShellWrapper(raw);
const unescaped = stripped.replace(/\\(["`$\\])/gu, "$1");
return cleanTraceDetailText(unescaped);
}
function stripShellWrapper(value: string): string {
const match = value.match(/^\s*(?:\/usr\/bin\/env\s+)?(?:\/bin\/sh|sh|bash|\/bin\/bash)\s+-lc\s+([\s\S]*)$/u);
if (!match) return value;
return unwrapShellArgument(match[1] ?? "");
}
function unwrapShellArgument(value: string): string {
const text = value.trim();
if (text.length >= 2) {
const quote = text[0];
if ((quote === "'" || quote === '"') && text[text.length - 1] === quote) {
return text.slice(1, -1);
}
}
return text;
}
function compactTraceOneLine(value: unknown, limit = 220): string {
const text = cleanTraceText(value).replace(/\s+/gu, " ");
return text.length <= limit ? text : `${text.slice(0, Math.max(0, limit - 3))}...`;
}
function numberOrNull(value: unknown): number | null {
if (value === null || value === undefined || value === "") return null;
const number = Number(value);
return Number.isInteger(number) ? number : null;
}
function nonEmptyString(value: unknown): string | null {
const text = String(value ?? "").trim();
return text ? text : null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object");
}