feat(lens): format review span previews as readable messages

This commit is contained in:
Ishaan Jaff 2026-10-03 17:00:29 -07:00
parent 616c59c8d0
commit af1c2f1a95
No known key found for this signature in database
2 changed files with 116 additions and 0 deletions

View file

@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import { spanPreviewLines } from "./spanPreview";
describe("span previews", () => {
it("shows the last chat message of an agent span as role: content, not raw JSON", () => {
const preview =
'Input: [{"role": "system", "content": "You are a research assistant."}, {"role": "user", "content": "Find the median latency of the EU region."}]\nOutput: [{"role": "assistant", "content": "I could not find it."}]';
expect(spanPreviewLines(preview)).toEqual([
{ label: "in", text: "user: Find the median latency of the EU region.", error: false },
{ label: "out", text: "assistant: I could not find it.", error: false },
]);
});
it("recovers the last message from truncated JSON with the omitted marker", () => {
const preview =
'Input: [{"role": "system", "content": "You are a bill\n[... preview omitted; read this span for evidence ...]\nn"}, {"role": "user", "content": "Cancel C2473\'s subscript';
expect(spanPreviewLines(preview)).toEqual([
{ label: "in", text: "user: Cancel C2473's subscript", error: false },
]);
});
it("keeps tool payloads compact and drops an unset status", () => {
const preview = 'Input: {"query":"latency"}\nOutput: {"results": []}\nStatus: STATUS_CODE_UNSET ';
expect(spanPreviewLines(preview)).toEqual([
{ label: "in", text: '{"query":"latency"}', error: false },
{ label: "out", text: '{"results":[]}', error: false },
]);
});
it("marks a failed span's output and shows its error", () => {
const preview =
'Input: {"query":"q"}\nOutput: {"error": "500 search index unavailable"}\nStatus: STATUS_CODE_ERROR 500 search index unavailable';
const lines = spanPreviewLines(preview);
expect(lines.at(-1)).toEqual({ label: "error", text: "500 search index unavailable", error: true });
expect(lines.find((l) => l.label === "out")?.error).toBe(true);
});
it("keeps multi-line payloads inside their section and collapses whitespace", () => {
const preview = 'Output: {"content": "def total(items):\\n return 1"}\nmore text\nStatus: STATUS_CODE_OK';
expect(spanPreviewLines(preview)).toEqual([
{ label: "out", text: '{"content": "def total(items):\\n return 1"} more text', error: false },
]);
});
it("passes through plain text without sections", () => {
expect(spanPreviewLines("500 Internal Server Error")).toEqual([
{ label: "", text: "500 Internal Server Error", error: false },
]);
expect(spanPreviewLines("")).toEqual([]);
});
});

View file

@ -0,0 +1,65 @@
import { parseJson, parseMessages, previewText } from "@/components/view_logs/TraceView/traceUtils";
export interface PreviewLine {
label: string;
text: string;
error: boolean;
}
const SECTION = /^(Input|Output|Status): ?/;
const OMITTED = /\n?\[\.\.\. preview omitted; read this span for evidence \.\.\.\]\n?/g;
const OK_STATUS = /^STATUS_CODE_(UNSET|OK)\b/;
const ROLE_CONTENT = /"role":\s*"(\w+)",\s*"content":\s*"((?:[^"\\]|\\.)*)/g;
function sections(preview: string): Readonly<Record<string, string>> {
const lines = preview.split("\n");
const starts = lines.flatMap((line, index) => (SECTION.test(line) ? [index] : []));
if (!starts.length) return { Body: preview };
return Object.fromEntries(
starts.map((start, n) => {
const body = lines.slice(start, starts[n + 1] ?? lines.length).join("\n");
const name = SECTION.exec(body)?.[1] ?? "Body";
return [name, body.replace(SECTION, "").trim()];
}),
);
}
function decode(escaped: string): string {
const parsed = parseJson(`"${escaped.replace(/\\u[0-9a-fA-F]{0,3}$|\\$/, "")}"`);
return typeof parsed === "string" ? parsed : escaped;
}
function lastMessage(value: string): string | null {
const messages = parseMessages(value);
if (messages?.length) {
const last = messages.at(-1);
return last ? `${last.role}: ${last.content}` : null;
}
const matches = [...value.matchAll(ROLE_CONTENT)];
const match = matches.at(-1);
return match ? `${match[1]}: ${decode(match[2])}` : null;
}
function compactJson(value: string): string {
const parsed = parseJson(value);
if (parsed === null || typeof parsed !== "object") return value;
return JSON.stringify(parsed);
}
function readable(value: string): string {
const joined = value.replace(OMITTED, " … ");
return (lastMessage(joined) ?? previewText(compactJson(joined))).replace(/\s+/g, " ").trim();
}
export function spanPreviewLines(preview: string): PreviewLine[] {
const parts = sections(preview);
const status = parts.Status ?? "";
const failed = !!status && !OK_STATUS.test(status);
const content = [
...(parts.Body ? [{ label: "", text: readable(parts.Body), error: false }] : []),
...(parts.Input ? [{ label: "in", text: readable(parts.Input), error: false }] : []),
...(parts.Output ? [{ label: "out", text: readable(parts.Output), error: failed }] : []),
...(failed ? [{ label: "error", text: status.replace(/^STATUS_CODE_ERROR\s*/, ""), error: true }] : []),
];
return content.filter((line) => line.text);
}