diff --git a/frontend/src/lib/components/AgentResultDisplay.svelte b/frontend/src/lib/components/AgentResultDisplay.svelte index 80df9d530a..935c32ebe9 100644 --- a/frontend/src/lib/components/AgentResultDisplay.svelte +++ b/frontend/src/lib/components/AgentResultDisplay.svelte @@ -2,13 +2,13 @@ import type { Snippet } from 'svelte' import { Badge } from '$lib/components/common' import GfmMarkdown from './GfmMarkdown.svelte' - import AgentActions from './AgentActions.svelte' + import AgentTrace from './AgentTrace.svelte' import { formatTokenCount, summarizeAgentResult, type AgentResult } from './aiAgentResult' interface Props { result: AgentResult /** The answer, or what the agent did to get there; JSON is the viewer's own toggle. */ - view: 'answer' | 'actions' + view: 'answer' | 'trace' workspaceId?: string /** * How to render an answer that is not text. An `output_schema` makes `output` @@ -27,8 +27,8 @@
- {#if view === 'actions'} - + {#if view === 'trace'} + {:else if textOutput !== undefined} {#if textOutput === ''} The agent returned no answer diff --git a/frontend/src/lib/components/AgentActions.svelte b/frontend/src/lib/components/AgentTrace.svelte similarity index 93% rename from frontend/src/lib/components/AgentActions.svelte rename to frontend/src/lib/components/AgentTrace.svelte index 3ff1be8ea2..4724f2b056 100644 --- a/frontend/src/lib/components/AgentActions.svelte +++ b/frontend/src/lib/components/AgentTrace.svelte @@ -8,7 +8,7 @@ import ToolContentDisplay from './copilot/chat/ToolContentDisplay.svelte' import WebSearchSourcesDisplay from './copilot/chat/WebSearchSourcesDisplay.svelte' import GfmMarkdown from './GfmMarkdown.svelte' - import { buildAgentActions, type AgentActionEntry } from './agentActions' + import { buildAgentTrace, type AgentTraceEntry } from './agentTrace' import type { AgentMessage } from './aiAgentResult' import { SvelteMap, SvelteSet } from 'svelte/reactivity' @@ -19,7 +19,7 @@ let { messages, workspaceId }: Props = $props() - const entries = $derived(buildAgentActions(messages)) + const entries = $derived(buildAgentTrace(messages)) let expanded = new SvelteSet() // A tool's own job holds what the envelope does not: its logs, how long it @@ -45,7 +45,7 @@ } } - function toggle(index: number, entry: AgentActionEntry) { + function toggle(index: number, entry: AgentTraceEntry) { if (expanded.delete(index)) { return } @@ -61,7 +61,7 @@ return typeof job === 'object' ? job : undefined } - function toolLabel(entry: Extract): string { + function toolLabel(entry: Extract): string { const job = jobOf(entry.jobId) const duration = job?.['duration_ms'] return duration === undefined diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index ed1bb9664d..229d1d442c 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -58,7 +58,7 @@ import type { MarkupTrust } from './apps/markupTrust' import AgentResultDisplay from './AgentResultDisplay.svelte' import AgentStreamDisplay from './AgentStreamDisplay.svelte' - import AgentActions from './AgentActions.svelte' + import AgentTrace from './AgentTrace.svelte' import { isAgentStream, parseAgentErrorMessages, parseAgentResult } from './aiAgentResult' const TABLE_MAX_SIZE = 5000000 @@ -165,7 +165,7 @@ }: Props = $props() let s3FileDisplayRawMode = $state(false) /** Which half of an agent result is showing; JSON is `forceJson`, as for any kind. */ - let agentView: 'answer' | 'actions' = $state('answer') + let agentView: 'answer' | 'trace' = $state('answer') /** The partial conversation a max-iterations failure carries, if this is one. */ let agentErrorMessages = $derived(parseAgentErrorMessages(result)) @@ -308,7 +308,7 @@ } // Classified before the size caps below: an agent's answer stays small - // however long its conversation grows, so a run with many actions + // however long its conversation grows, so a run with a long trace // must not fall back to the JSON tree that hides the answer inside it. // `largeObject` is still set honestly, so switching to JSON gets the // same too-big handling as any other oversized result. @@ -818,15 +818,15 @@ {#if !hideAsJson && !['json', 's3object'].includes(resultKind ?? '') && typeof result === 'object'} { forceJson = ev.detail === 'json' - if (ev.detail === 'actions' || ev.detail === 'pretty') { - agentView = ev.detail === 'actions' ? 'actions' : 'answer' + if (ev.detail === 'trace' || ev.detail === 'pretty') { + agentView = ev.detail === 'trace' ? 'trace' : 'answer' } }} > @@ -835,7 +835,7 @@ {:else if resultKind === 'aiagent'} - + {:else} {/if} @@ -1022,8 +1022,8 @@ whole reason to look at such a run, so it is added under the error rather than replacing it. -->
- Actions - + Trace +
{/if} {#if !isTest && language === 'bun'} diff --git a/frontend/src/lib/components/agentActions.test.ts b/frontend/src/lib/components/agentTrace.test.ts similarity index 91% rename from frontend/src/lib/components/agentActions.test.ts rename to frontend/src/lib/components/agentTrace.test.ts index 66eed1d91d..4d63666736 100644 --- a/frontend/src/lib/components/agentActions.test.ts +++ b/frontend/src/lib/components/agentTrace.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { buildAgentActions } from './agentActions' +import { buildAgentTrace } from './agentTrace' import type { AgentMessage } from './aiAgentResult' // The worker splits one tool call across two messages: the assistant message @@ -34,9 +34,9 @@ const messages: AgentMessage[] = [ { role: 'assistant', content: 'eu-central-1 is down.', agent_action: { type: 'message' } } ] -describe('buildAgentActions', () => { +describe('buildAgentTrace', () => { it('joins a tool call to the arguments on the message that requested it', () => { - expect(buildAgentActions(messages)).toEqual([ + expect(buildAgentTrace(messages)).toEqual([ { kind: 'tool', name: 'query_metrics', @@ -49,7 +49,7 @@ describe('buildAgentActions', () => { }) it('keeps an MCP call, whose arguments live on the action itself', () => { - const entries = buildAgentActions([ + const entries = buildAgentTrace([ { role: 'tool', content: 'sunny', @@ -74,7 +74,7 @@ describe('buildAgentActions', () => { }) it('carries web search citations onto the entry', () => { - const entries = buildAgentActions([ + const entries = buildAgentTrace([ { role: 'assistant', content: 'Postgres 17 changed the default.', @@ -94,9 +94,9 @@ describe('buildAgentActions', () => { // The prompt and the question are the step's inputs, shown as inputs. A replayed // turn comes back from memory without its tag, and crediting this run with an // answer a previous one gave would be a lie about what happened. - it('keeps only what this run did', () => { + it('traces only what this run did', () => { expect( - buildAgentActions([ + buildAgentTrace([ { role: 'system', content: 'You are an SRE assistant.' }, { role: 'user', content: 'Which region is broken?' }, { role: 'assistant', content: 'Answered in an earlier turn, replayed from memory.' }, diff --git a/frontend/src/lib/components/agentActions.ts b/frontend/src/lib/components/agentTrace.ts similarity index 84% rename from frontend/src/lib/components/agentActions.ts rename to frontend/src/lib/components/agentTrace.ts index 418db8520f..bf6194b1ce 100644 --- a/frontend/src/lib/components/agentActions.ts +++ b/frontend/src/lib/components/agentTrace.ts @@ -2,17 +2,16 @@ import type { WebSearchSource } from './copilot/chat/shared' import type { AgentMessage } from './aiAgentResult' /** - * One thing an agent did. Built from the envelope alone: the tool arguments come - * from the assistant message that asked for the call, and the result from the - * `tool` message that answered it, so the list renders without waiting on any - * request. A tool's child job is enrichment (logs, duration, whether it - * succeeded), not what makes the row. + * One entry in the trace of an agent run. Built from the envelope alone: the tool + * arguments come from the assistant message that asked for the call, and the + * result from the `tool` message that answered it, so the trace renders without + * waiting on any request. A tool's child job is enrichment (logs, duration, + * whether it succeeded), not what makes the row. * * The prompt and the user's question are deliberately absent. They are inputs to - * the step, shown as inputs, and a run is not a conversation the viewer is part - * of — it is a record of what the agent did with them. + * the step and are shown as inputs; the trace is what the agent did with them. */ -export type AgentActionEntry = +export type AgentTraceEntry = | { kind: 'assistant'; content: string; sources?: WebSearchSource[] } | { kind: 'search'; content: string; sources?: WebSearchSource[] } | { @@ -54,7 +53,7 @@ function sourcesOf(message: AgentMessage): WebSearchSource[] | undefined { return sources.length > 0 ? sources : undefined } -export function buildAgentActions(messages: AgentMessage[]): AgentActionEntry[] { +export function buildAgentTrace(messages: AgentMessage[]): AgentTraceEntry[] { // The arguments live on the assistant message that requested the call, while // the action tag and the result live on the `tool` message answering it, so // the two are joined by `tool_call_id`. @@ -67,7 +66,7 @@ export function buildAgentActions(messages: AgentMessage[]): AgentActionEntry[] } } - const entries: AgentActionEntry[] = [] + const entries: AgentTraceEntry[] = [] for (const message of messages) { const action = message.agent_action if (action?.type === 'tool_call') {