fix(lens): copy supported trace and span content requests (#44902)

This commit is contained in:
moe-berri 2026-10-06 13:51:22 -07:00 • committed by GitHub
parent 2cecc7e33f
commit 8f041a8f06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 6 deletions

View file

@ -0,0 +1,31 @@
import { describe, expect, it, vi } from "vitest";
import { agentHandoffText } from "./api";
import { getProxyBaseUrl } from "../../networking";
vi.mock("../../networking", () => ({ getProxyBaseUrl: vi.fn(() => "https://proxy.test/root/") }));
describe("agent handoff", () => {
it("requests bounded trace pages and explains how to retrieve captured content", () => {
const text = agentHandoffText("trace-one", null, "source-one");
expect(text).toContain("'https://proxy.test/root/v1/traces/trace-one?trace_ref=source-one&page_size=200'");
expect(text).toContain("Follow next_cursor");
expect(text).toContain("/v1/traces/trace-one/spans/{span_id}");
expect(text).toContain("using the same trace_ref");
});
it("selects one span and keeps reserved characters inside their original parameter", () => {
const text = agentHandoffText("trace/one", "step?two", "source&cursor=other");
expect(text).toContain(
"'https://proxy.test/root/v1/traces/trace%2Fone/spans/step%3Ftwo?trace_ref=source%26cursor%3Dother'",
);
expect(text).toContain("this step's captured input, output, and attributes");
});
it("quotes the proxy URL as shell data", () => {
vi.mocked(getProxyBaseUrl).mockReturnValueOnce("https://proxy.test/a'b/$(printf injected)");
expect(agentHandoffText("trace-one", "span-one")).toContain(
"'https://proxy.test/a'\"'\"'b/$(printf injected)/v1/traces/trace-one/spans/span-one'",
);
});
});

View file

@ -11,6 +11,8 @@ import {
} from "../../networking";
import type {
SpanDetail,
SpanQuery,
TraceDetailQuery,
SpanErrorPage,
Trace,
TraceListQuery,
@ -48,9 +50,23 @@ export interface TracesApi {
/** A one-liner Claude Code / Codex can run to read the trace. */
export const agentHandoffText = (traceId: string, spanId?: string | null, traceRef?: string): string => {
const url = `${getProxyBaseUrl().replace(/\/$/, "")}/v1/traces/${traceId}?format=md${spanId ? `&span_id=${spanId}` : ""}${traceRef ? `&trace_ref=${traceRef}` : ""}`;
const base = `${getProxyBaseUrl().replace(/\/$/, "")}/v1/traces/${encodeURIComponent(traceId)}`;
const path = spanId ? `${base}/spans/${encodeURIComponent(spanId)}` : base;
const query = spanId
? ({ trace_ref: traceRef } satisfies SpanQuery)
: ({ trace_ref: traceRef, page_size: 200 } satisfies TraceDetailQuery);
const params = new URLSearchParams(
Object.entries(query)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => [key, String(value)]),
).toString();
const url = params ? `${path}?${params}` : path;
const quotedUrl = "'" + url.replaceAll("'", "'\"'\"'") + "'";
const what = spanId ? "this step of a LiteLLM agent trace" : "this LiteLLM agent trace";
return `Read ${what}, explain what happened, and investigate any issues:\ncurl -s -H "Authorization: Bearer $LITELLM_API_KEY" "${url}"`;
const guidance = spanId
? "The JSON response contains this step's captured input, output, and attributes. Report missing content and capture warnings explicitly."
: `The JSON response contains span summaries. Follow next_cursor by adding cursor to this URL until it is null, preserving trace_ref and page_size. Fetch captured content at ${base}/spans/{span_id}, using the same trace_ref. Report missing content and capture warnings explicitly.`;
return `Read ${what}, explain what happened, and investigate any issues:\ncurl --fail-with-body -sS -H "Authorization: Bearer $LITELLM_API_KEY" ${quotedUrl}\n${guidance}`;
};
export function liveTracesApi(accessToken: string): TracesApi {

View file

@ -492,8 +492,8 @@ describe("RunView", () => {
await user.click(await screen.findByRole("button", { name: /copy for agent/i }));
expect(copyToClipboard).toHaveBeenCalledWith(agentHandoffText(research.summary.trace_id), "Command copied");
expect(agentHandoffText("t1")).toContain('"http://proxy.test/v1/traces/t1?format=md"');
expect(agentHandoffText("t1", "s1")).toContain("&span_id=s1");
expect(agentHandoffText("t1")).toContain("http://proxy.test/v1/traces/t1?page_size=200");
expect(agentHandoffText("t1", "s1")).toContain("/v1/traces/t1/spans/s1");
});
it("copies a link that reopens just this run", async () => {

View file

@ -345,14 +345,14 @@ describe("DetailPane", () => {
expect(pane).toHaveTextContent("TimeoutError('slow')");
});
it("'Copy step' copies a curl for just this span as Markdown", async () => {
it("'Copy step' copies a curl for this span's captured content", async () => {
const user = userEvent.setup();
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true });
renderPane(spanRow(llm));
await user.click(screen.getByRole("button", { name: "Copy step" }));
await waitFor(() => expect(writeText).toHaveBeenCalled());
expect(writeText.mock.calls[0][0]).toContain("http://proxy.test/v1/traces/t1?format=md&span_id=llm1");
expect(writeText.mock.calls[0][0]).toContain("http://proxy.test/v1/traces/t1/spans/llm1");
});
it("renders the assistant tool call as a card and expands a long argument on click", async () => {