fix: expose agent trace result summary

This commit is contained in:
lyon
2026-06-15 15:34:36 +08:00
parent 8633ab77a7
commit 2c6574e625
+336 -3
View File
@@ -999,14 +999,25 @@ async function agentCommand(context: any) {
const traceId = requiredTraceId(context.rest[1] ?? context.parsed.traceId);
const pathName = `/v1/agent/chat/result/${encodeURIComponent(traceId)}`;
const response = await requestJson({ ...context, method: "GET", path: pathName });
return responsePayload("client.agent.result", response, context, { route: route("GET", pathName), traceId, body: responseBodyForCli(response.body, context.parsed) });
return responsePayload("client.agent.result", response, context, {
route: route("GET", pathName),
traceId,
body: responseBodyForCli(response.body, context.parsed),
traceResultSummary: traceResultSummaryForCli(response.body, { traceId, command: "result" })
});
}
if (subcommand === "trace") {
const traceId = requiredTraceId(context.rest[1] ?? context.parsed.traceId);
const pathName = `/v1/agent/chat/trace/${encodeURIComponent(traceId)}`;
const response = await requestJson({ ...context, method: "GET", path: pathName });
const traceBody = traceBodyForCli(response.body, context.parsed);
return responsePayload("client.agent.trace", response, context, { route: route("GET", pathName), traceId, body: traceBody, ...traceResponseAliases(traceBody, context.parsed) });
return responsePayload("client.agent.trace", response, context, {
route: route("GET", pathName),
traceId,
body: traceBody,
traceResultSummary: traceResultSummaryForCli(response.body, { traceId, command: "trace", renderedTrace: traceBody }),
...traceResponseAliases(traceBody, context.parsed)
});
}
if (subcommand === "inspect") {
return agentInspect(context);
@@ -1023,7 +1034,329 @@ async function agentCommand(context: any) {
async function agentInspect(context: any) {
const pathName = agentInspectPath(context);
const response = await requestJson({ ...context, method: "GET", path: pathName });
return responsePayload("client.agent.inspect", response, context, { route: route("GET", pathName), body: responseBodyForCli(response.body, context.parsed) });
const traceId = text(context.parsed.traceId);
return responsePayload("client.agent.inspect", response, context, {
route: route("GET", pathName),
body: responseBodyForCli(response.body, context.parsed),
traceResultSummary: traceResultSummaryForCli(response.body, { traceId, command: "inspect" })
});
}
function traceResultSummaryForCli(source: any, options: { traceId?: string; command?: string; renderedTrace?: any } = {}) {
const rendered = options.renderedTrace && typeof options.renderedTrace === "object" ? options.renderedTrace : null;
const prompt = tracePromptForCli(source);
const toolCalls = traceToolCallsForCli(source, rendered);
const ids = completeTraceResultIdsFromToolCalls(traceResultIdsForCli(source, rendered, options.traceId), toolCalls);
const finalResponse = traceFinalResponseForCli(source, rendered, ids.traceId);
const agentMessages = traceAgentMessagesForCli(source, rendered, finalResponse);
const diagnostics = traceDiagnosticsForCli(source, rendered);
const counts = traceCountsForCli(source, rendered, toolCalls.length);
const upstreamGaps = traceResultUpstreamGaps({ ids, prompt, finalResponse, toolCalls, agentMessages, diagnostics });
return pruneUndefined({
command: text(options.command) || undefined,
ids,
prompt,
diagnostics,
counts,
toolCalls,
toolCallCount: toolCalls.length,
agentMessages,
finalResponse,
upstreamGaps: upstreamGaps.length > 0 ? upstreamGaps : undefined,
valuesPrinted: false,
fullBodyAvailable: true
});
}
function completeTraceResultIdsFromToolCalls(ids: any, toolCalls: any[]) {
const source = toolCalls.find((call) => call?.runId || call?.commandId || call?.runnerId) ?? null;
if (!source) return ids;
return pruneUndefined({
...ids,
runId: ids.runId ?? (text(source.runId) || undefined),
commandId: ids.commandId ?? (text(source.commandId) || undefined),
runnerId: ids.runnerId ?? (text(source.runnerId) || undefined)
});
}
function traceResultIdsForCli(source: any, rendered: any, traceId: unknown) {
const agentRun = traceAgentRunForCli(source, rendered);
const terminal = source?.terminalEvidence && typeof source.terminalEvidence === "object" ? source.terminalEvidence : null;
const session = source?.session && typeof source.session === "object" ? source.session : null;
const sessionReuse = source?.sessionReuse && typeof source.sessionReuse === "object" ? source.sessionReuse : null;
return pruneUndefined({
promptId: text(source?.promptId ?? source?.prompt?.id) || undefined,
traceId: text(traceId ?? source?.traceId ?? rendered?.traceId ?? terminal?.traceId ?? source?.traceSummary?.traceId ?? rendered?.traceSummary?.traceId) || undefined,
conversationId: text(source?.conversationId ?? session?.conversationId ?? sessionReuse?.conversationId ?? terminal?.conversationId ?? source?.agentRun?.conversationId) || undefined,
sessionId: text(source?.sessionId ?? session?.sessionId ?? sessionReuse?.sessionId ?? terminal?.sessionId) || undefined,
threadId: text(source?.threadId ?? session?.threadId ?? sessionReuse?.threadId ?? terminal?.threadId ?? source?.agentRun?.threadId) || undefined,
runId: text(agentRun?.runId) || undefined,
commandId: text(agentRun?.commandId) || undefined,
runnerId: text(agentRun?.runnerId) || undefined,
messageId: text(source?.messageId ?? source?.finalResponse?.messageId ?? terminal?.finalResponse?.messageId) || undefined
});
}
function traceAgentRunForCli(source: any, rendered: any) {
const candidates = [
source?.agentRun,
source?.runner,
source?.traceSummary?.agentRun,
source?.terminalEvidence?.agentRun,
source?.terminalEvidence?.traceSummary?.agentRun,
rendered?.traceSummary?.agentRun,
rendered?.terminalEvidence?.agentRun
];
return candidates.find((candidate) => candidate && typeof candidate === "object") ?? null;
}
function tracePromptForCli(source: any) {
const events = traceEventsForCli(source);
const promptEvent = events.find((event: any) => text(event?.promptId ?? event?.promptSummary ?? event?.promptPreview ?? event?.prompt ?? event?.userPrompt ?? event?.inputPreview ?? event?.input));
const promptText = text(source?.promptSummary ?? source?.promptPreview ?? source?.prompt?.summary ?? source?.prompt?.text ?? promptEvent?.promptSummary ?? promptEvent?.promptPreview ?? promptEvent?.prompt ?? promptEvent?.userPrompt ?? promptEvent?.inputPreview ?? promptEvent?.input);
const promptId = text(source?.promptId ?? source?.prompt?.id ?? promptEvent?.promptId ?? promptEvent?.prompt?.id);
const promptSha256 = text(source?.promptSha256 ?? source?.prompt?.sha256 ?? promptEvent?.promptSha256);
if (!promptText && !promptId && !promptSha256) return undefined;
return pruneUndefined({
promptId: promptId || undefined,
promptSha256: promptSha256 || undefined,
textPreview: clippedText(promptText, 500),
textChars: promptText ? promptText.length : undefined,
sourceSeq: promptEvent?.seq ?? promptEvent?.sourceSeq,
valuesPrinted: false
});
}
function traceDiagnosticsForCli(source: any, rendered: any) {
const agentRun = traceAgentRunForCli(source, rendered);
const providerTrace = source?.providerTrace ?? source?.agentRun?.providerTrace ?? agentRun?.providerTrace;
return pruneUndefined({
status: text(source?.status ?? rendered?.status) || undefined,
traceStatus: text(source?.traceStatus ?? rendered?.traceStatus ?? rendered?.status) || undefined,
terminalStatus: text(source?.terminalStatus ?? source?.traceSummary?.terminalStatus ?? rendered?.traceSummary?.terminalStatus ?? agentRun?.terminalStatus ?? agentRun?.status) || undefined,
provider: text(source?.provider ?? source?.runner?.provider ?? source?.longLivedSessionGate?.provider ?? providerTrace?.backendProfile) || undefined,
providerProfile: text(source?.providerProfile ?? source?.profile ?? source?.agentRun?.backendProfile ?? agentRun?.backendProfile) || undefined,
adapter: text(agentRun?.adapter ?? providerTrace?.transport) || undefined,
namespace: text(agentRun?.namespace ?? providerTrace?.namespace ?? source?.runner?.namespace) || undefined,
lane: text(agentRun?.namespace ?? providerTrace?.namespace ?? source?.runner?.namespace) || undefined,
runnerJobName: text(agentRun?.jobName ?? source?.runner?.jobName) || undefined,
runStatus: text(agentRun?.runStatus) || undefined,
commandState: text(agentRun?.commandState) || undefined,
failureKind: text(agentRun?.failureKind ?? providerTrace?.failureKind) || undefined,
valuesPrinted: false
});
}
function traceCountsForCli(source: any, rendered: any, toolCallCount: number) {
const events = traceEventsForCli(source);
const sourceEventCount = source?.traceSummary?.sourceEventCount ?? rendered?.traceSummary?.sourceEventCount ?? source?.eventCount ?? events.length;
return pruneUndefined({
sourceEventCount: sourceEventCount || undefined,
renderedRowCount: rendered?.renderedRowCount,
returnedRowCount: rendered?.returnedRowCount,
noiseEventCount: rendered?.noiseEventCount,
omittedNoiseCount: rendered?.omittedNoiseCount,
toolCallCount
});
}
function traceFinalResponseForCli(source: any, rendered: any, traceId: unknown) {
const terminal = source?.terminalEvidence && typeof source.terminalEvidence === "object" ? source.terminalEvidence : null;
const final = firstObjectForCli(source?.finalResponse, terminal?.finalResponse, rendered?.finalResponse);
const finalText = text(final?.text ?? final?.textPreview ?? assistantText(source) ?? rendered?.assistantText);
if (!finalText && !final) return undefined;
return pruneUndefined({
role: text(final?.role) || "assistant",
status: text(final?.status ?? source?.status ?? rendered?.status) || undefined,
traceId: text(final?.traceId ?? traceId) || undefined,
messageId: text(final?.messageId ?? source?.messageId) || undefined,
text: clippedText(finalText, 4000),
textChars: finalText ? finalText.length : final?.textChars,
valuesPrinted: false
});
}
function traceAgentMessagesForCli(source: any, rendered: any, finalResponse: any) {
const messages = [];
const seen = new Set<string>();
const pushMessage = (value: any) => {
const body = text(value?.text ?? value?.body ?? value?.message ?? value?.content);
if (!body) return;
const key = `${value?.kind ?? value?.role ?? "message"}:${body}`;
if (seen.has(key)) return;
seen.add(key);
messages.push(pruneUndefined({
kind: text(value?.kind) || undefined,
role: text(value?.role) || "assistant",
status: text(value?.status) || undefined,
sourceSeq: value?.sourceSeq ?? value?.seq,
terminal: value?.terminal === true ? true : undefined,
text: clippedText(body, 1200),
textChars: body.length,
valuesPrinted: false
}));
};
if (Array.isArray(source?.assistantStreams)) {
for (const item of source.assistantStreams.slice(-3)) pushMessage({ ...item, kind: "assistant-stream", text: item?.text ?? item?.lastChunk });
}
for (const row of firstArray(rendered?.rows, source?.rows)) {
if (row?.terminal === true || /|assistant/iu.test(text(row?.header))) pushMessage({ ...row, kind: "trace-row", text: row?.body });
}
if (finalResponse) pushMessage({ ...finalResponse, kind: "final-response" });
return messages.slice(0, 8);
}
function traceToolCallsForCli(source: any, rendered: any) {
const calls = [];
for (const event of traceEventsForCli(source)) {
const call = traceToolCallFromEvent(event);
if (call) calls.push(call);
}
for (const row of firstArray(rendered?.rows, source?.rows)) {
const call = traceToolCallFromRow(row);
if (call) calls.push(call);
}
if (calls.length === 0 && Array.isArray(source?.toolCalls)) {
for (const item of source.toolCalls) {
const call = traceToolCallFromObject(item);
if (call) calls.push(call);
}
}
return dedupeTraceToolCalls(calls).slice(0, 20);
}
function traceEventsForCli(source: any) {
return firstArray(source?.events, source?.runnerTrace?.events, source?.terminalEvidence?.events);
}
function traceToolCallFromObject(value: any) {
if (!value || typeof value !== "object") return null;
return pruneUndefined({
source: "toolCalls",
status: text(value.status) || undefined,
toolName: text(value.toolName ?? value.name) || undefined,
command: text(value.command) || undefined,
runId: text(value.runId) || undefined,
commandId: text(value.commandId) || undefined,
runnerId: text(value.runnerId) || undefined,
exitCode: value.exitCode,
durationMs: value.durationMs,
outputBytes: value.outputBytes,
outputTruncated: value.outputTruncated,
outputPreview: clippedText(text(value.outputPreview ?? value.stdoutSummary ?? value.stderrSummary ?? value.message), 500),
valuesPrinted: false
});
}
function traceToolCallFromEvent(event: any) {
if (!event || typeof event !== "object") return null;
const command = text(event.command);
const hasCommandOutput = command || /commandExecution/iu.test(text(event.message ?? event.stdoutSummary ?? event.stderrSummary));
if (!hasCommandOutput) return null;
const outputText = traceToolOutputText(event);
return pruneUndefined({
source: "runnerTrace.events",
sourceSeq: event.sourceSeq ?? event.seq,
status: traceToolStatusForCli(event.status, event.exitCode),
label: text(event.label) || undefined,
toolName: text(event.toolName ?? event.name) || undefined,
command: command || undefined,
runId: text(event.runId) || undefined,
commandId: text(event.commandId) || undefined,
runnerId: text(event.runnerId) || undefined,
exitCode: event.exitCode,
durationMs: event.durationMs,
outputBytes: event.outputBytes,
outputTruncated: event.outputTruncated,
outputPreview: outputText ? clippedText(outputText, 500) : undefined,
valuesPrinted: false
});
}
function traceToolCallFromRow(row: any) {
if (!row || typeof row !== "object") return null;
const rowId = text(row.rowId);
const body = text(row.body);
const header = text(row.header);
const isTool = rowId.startsWith("tool:") || /commandExecution|exitCode=/iu.test(`${header} ${body}`);
if (!isTool) return null;
const parsed = parseRenderedToolBody(body);
return pruneUndefined({
source: "trace-render-row",
rowId: rowId || undefined,
sourceSeq: row.seq,
status: traceToolStatusForCli(row.tone === "ok" ? "completed" : row.tone, parsed.exitCode),
label: header || undefined,
toolName: /commandExecution/u.test(header) ? "commandExecution" : undefined,
command: parsed.command,
exitCode: parsed.exitCode,
outputPreview: body ? clippedText(body, 500) : undefined,
valuesPrinted: false
});
}
function traceToolOutputText(event: any) {
const stdout = text(event.stdoutSummary ?? event.stdoutPreview ?? event.stdout);
const stderr = text(event.stderrSummary ?? event.stderrPreview ?? event.stderr);
const message = text(event.message);
const value = stdout || stderr || (/commandExecution inProgress/iu.test(message) ? "" : message);
return value;
}
function parseRenderedToolBody(value: string) {
const exitMatch = value.match(/\bexitCode=(-?\d+)\b/u);
const streamMatch = value.match(/^(.+?)\s+(?:stdout|stderr):\s+/u);
return pruneUndefined({
command: text(streamMatch?.[1]) || undefined,
exitCode: exitMatch ? Number.parseInt(exitMatch[1], 10) : undefined
});
}
function traceToolStatusForCli(status: unknown, exitCode: unknown) {
const code = typeof exitCode === "number" ? exitCode : Number.isFinite(Number(exitCode)) ? Number(exitCode) : null;
if (code !== null) return code === 0 ? "completed" : "failed";
const value = text(status);
if (value === "ok") return "completed";
return value || undefined;
}
function dedupeTraceToolCalls(calls: any[]) {
const byKey = new Map<string, any>();
for (const call of calls) {
const key = [call.commandId, call.command, call.sourceSeq, call.rowId].map((part) => text(part)).filter(Boolean).join(":") || JSON.stringify(call);
const existing = byKey.get(key);
if (!existing || traceToolCallScore(call) > traceToolCallScore(existing)) byKey.set(key, call);
}
return [...byKey.values()];
}
function traceToolCallScore(call: any) {
return [call.command, call.exitCode !== undefined, call.outputPreview, call.runId, call.commandId].filter(Boolean).length;
}
function traceResultUpstreamGaps({ ids, prompt, finalResponse, toolCalls, agentMessages, diagnostics }: any) {
const gaps = [];
if (!ids?.traceId) gaps.push("traceId_missing");
if (!ids?.conversationId) gaps.push("conversationId_missing");
if (!ids?.sessionId) gaps.push("sessionId_missing");
if (!ids?.runId) gaps.push("runId_missing");
if (!ids?.commandId) gaps.push("commandId_missing");
if (!prompt?.promptId && !prompt?.textPreview) gaps.push("prompt_not_returned_by_upstream");
if (!Array.isArray(toolCalls) || toolCalls.length === 0) gaps.push("tool_calls_missing");
if (!Array.isArray(agentMessages) || agentMessages.length === 0) gaps.push("agent_message_missing");
if (!finalResponse?.text) gaps.push("final_response_missing");
if (!diagnostics?.provider && !diagnostics?.namespace) gaps.push("provider_runner_lane_missing");
return gaps;
}
function firstObjectForCli(...values: any[]) {
return values.find((value) => value && typeof value === "object") ?? null;
}
function clippedText(value: unknown, max: number) {
const result = text(value);
if (!result) return undefined;
return result.length > max ? `${result.slice(0, max)}...` : result;
}
function agentHelp() {