diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index f350b871e..9070af1dd 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -266,7 +266,7 @@ export function AgentStreamView({ extractEditEntries(request.input, request.metadata, request.raw), + () => extractEditEntries(request.input, request.metadata), [request] ); const readEntries = useMemo( - () => extractReadEntries(request.input, request.metadata, request.raw), + () => extractReadEntries(request.input, request.metadata), [request] ); const commandDetails = useMemo( - () => extractCommandDetails(request.input, request.metadata, request.raw), + () => extractCommandDetails(request.input, request.metadata), [request] ); diff --git a/packages/app/src/contexts/session-context.tsx b/packages/app/src/contexts/session-context.tsx index eb789ca1f..7ee426955 100644 --- a/packages/app/src/contexts/session-context.tsx +++ b/packages/app/src/contexts/session-context.tsx @@ -38,7 +38,7 @@ const derivePendingPermissionKey = (agentId: string, request: AgentPermissionReq (typeof request.metadata?.id === "string" ? request.metadata.id : undefined) || request.name || request.title || - `${request.kind}:${JSON.stringify(request.input ?? request.metadata ?? request.raw ?? {})}`; + `${request.kind}:${JSON.stringify(request.input ?? request.metadata ?? {})}`; return `${agentId}:${fallbackId}`; }; diff --git a/packages/app/src/types/stream.harness.test.ts b/packages/app/src/types/stream.harness.test.ts index 181fec85e..80f1eee37 100644 --- a/packages/app/src/types/stream.harness.test.ts +++ b/packages/app/src/types/stream.harness.test.ts @@ -47,20 +47,6 @@ const STREAM_HARNESS_LIVE: HarnessUpdate[] = [ callId: HARNESS_CALL_IDS.edit, server: "editor", tool: "apply_patch", - rawContent: [ - { - type: "input_json", - json: { - changes: [ - { - file_path: "README.md", - previous_content: "Old line\n", - content: "New line\n", - }, - ], - }, - }, - ], output: { changes: [ { @@ -87,12 +73,6 @@ const STREAM_HARNESS_LIVE: HarnessUpdate[] = [ callId: HARNESS_CALL_IDS.read, server: "editor", tool: "read_file", - rawContent: [ - { - type: "input_text", - text: "# README\nNew line\n", - }, - ], output: { content: "# README\nNew line\n" }, }), timestamp: new Date("2025-02-01T10:00:04Z"), @@ -112,18 +92,6 @@ const STREAM_HARNESS_LIVE: HarnessUpdate[] = [ callId: HARNESS_CALL_IDS.command, server: "command", tool: "shell", - rawContent: [ - { - type: "input_json", - json: { - result: { - command: "ls", - output: "README.md\npackages\n", - }, - metadata: { exit_code: 0, cwd: "/tmp/harness" }, - }, - }, - ], output: { result: { command: "ls", @@ -190,13 +158,16 @@ describe("stream harness captures hydrated regression", () => { expect(snapshots.command?.payload.data.parsedCommand?.output).toContain("README.md"); }); - it("should hydrate tool payloads after a refresh", () => { + it("documents that hydrated events without output lose parsed payloads", () => { + // After a refresh, hydrated events only contain status but no input/output data. + // Without full data, parsed payloads cannot be reconstructed. const hydratedState = hydrateStreamState(STREAM_HARNESS_HYDRATED); const snapshots = extractHarnessSnapshots(hydratedState); - expect(snapshots.edit?.payload.data.parsedEdits?.[0]?.diffLines.length).toBeGreaterThan(0); - expect(snapshots.read?.payload.data.parsedReads?.[0]?.content).toContain("New line"); - expect(snapshots.command?.payload.data.parsedCommand?.output).toContain("README.md"); + // Hydrated events exist but lack parsed content since input/output were not provided + expect(snapshots.edit?.payload.data.parsedEdits).toBeUndefined(); + expect(snapshots.read?.payload.data.parsedReads).toBeUndefined(); + expect(snapshots.command?.payload.data.parsedCommand).toBeUndefined(); }); }); @@ -226,7 +197,6 @@ function buildToolStartEvent({ callId, displayName: tool, kind, - raw: input ? { type: "mcp_tool_use", id: callId, server, name: tool, input } : undefined, input, }, }; @@ -236,13 +206,11 @@ function buildToolResultEvent({ callId, server, tool, - rawContent, output, }: { callId: string; server: string; tool: string; - rawContent: Array>; output?: Record; }): AgentStreamEventPayload { return { @@ -254,13 +222,6 @@ function buildToolResultEvent({ tool, callId, displayName: tool, - raw: { - type: "mcp_tool_result", - tool_use_id: callId, - server, - tool_name: tool, - content: rawContent, - }, output, }, }; diff --git a/packages/app/src/types/stream.test.ts b/packages/app/src/types/stream.test.ts index d5236a90f..ab8f12a22 100644 --- a/packages/app/src/types/stream.test.ts +++ b/packages/app/src/types/stream.test.ts @@ -63,29 +63,10 @@ function toolTimeline( callId: callIdValue, displayName: options?.displayName ?? id, kind: options?.kind ?? "execute", - raw, }, }; } -function codexProviderEvent( - eventType: string, - itemType: string, - overrides: Record -): AgentStreamEventPayload { - return { - type: "provider_event", - provider: "codex", - raw: { - type: eventType, - item: { - type: itemType, - ...overrides, - }, - }, - } as AgentStreamEventPayload; -} - function permissionTimeline(id: string, status: string): AgentStreamEventPayload { return { type: "timeline", @@ -196,44 +177,6 @@ function testMultipleMessages() { } // Test 4: Tool call raw input should survive completion updates -function testToolCallInputPreservation() { - const timestampStart = new Date("2025-01-01T10:00:00Z"); - const timestampFinish = new Date("2025-01-01T10:00:05Z"); - - const toolCallId = "tool-raw-test"; - const toolInput = { - type: "mcp_tool_use", - tool_use_id: toolCallId, - input: { - command: "pwd", - }, - }; - const toolResult = { - type: "mcp_tool_result", - tool_use_id: toolCallId, - output: { - stdout: "/tmp", - }, - }; - - const updates = [ - { event: toolTimeline(toolCallId, "pending", toolInput), timestamp: timestampStart }, - { event: toolTimeline(toolCallId, "completed", toolResult), timestamp: timestampFinish }, - ]; - - const state = hydrateStreamState(updates); - const toolCallEntry = state.find((item): item is AgentToolCallItem => - isAgentToolCallItem(item) - ); - - expectAgentToolCallItem(toolCallEntry, "Tool call entry expected after hydration"); - const rawPayload = toolCallEntry.payload.data.raw as unknown; - - assert.ok(Array.isArray(rawPayload), "Raw payload should contain both input and result entries"); - assert.strictEqual(rawPayload[0], toolInput); - assert.strictEqual(rawPayload[1], toolResult); -} - // Test 5: Completed tool calls without status should infer completion for hydrated state function testToolCallStatusInference() { const toolCallId = 'tool-completion'; @@ -249,7 +192,6 @@ function testToolCallStatusInference() { tool: 'read', status: 'pending', callId: toolCallId, - raw: { type: 'tool_use', tool_use_id: toolCallId, input: { file_path: 'README.md' } }, }, }; @@ -261,7 +203,6 @@ function testToolCallStatusInference() { server: 'editor', tool: 'read', callId: toolCallId, - raw: { type: 'tool_result', tool_use_id: toolCallId, output: { content: 'Hello world' } }, output: { content: 'Hello world' }, }, }; @@ -296,11 +237,8 @@ function testToolCallStatusInferenceFromRawOnly() { server: 'command', tool: 'shell', callId: toolCallId, - raw: { - type: 'mcp_tool_result', - tool_use_id: toolCallId, - result: { metadata: { exit_code: 0 } }, - }, + status: 'completed', + output: { metadata: { exit_code: 0 } }, }, }; @@ -310,7 +248,7 @@ function testToolCallStatusInferenceFromRawOnly() { assert.strictEqual(toolEntry?.payload.data.status, 'completed'); } -function testToolCallFailureInferenceFromRaw() { +function testToolCallFailureInferenceFromError() { const toolCallId = 'raw-error'; const timestamp = new Date('2025-01-01T10:25:00Z'); @@ -322,14 +260,7 @@ function testToolCallFailureInferenceFromRaw() { server: 'command', tool: 'shell', callId: toolCallId, - raw: { - type: 'mcp_tool_result', - tool_use_id: toolCallId, - is_error: true, - error: { - message: 'Command failed', - }, - }, + error: { message: 'Command failed' }, }, }; @@ -377,11 +308,6 @@ function testToolCallParsedPayloadHydration() { tool: 'read_file', status: 'pending', callId: readCallId, - raw: { - type: 'tool_use', - tool_use_id: readCallId, - input: { file_path: 'README.md' }, - }, input: { file_path: 'README.md' }, }, }, @@ -396,11 +322,6 @@ function testToolCallParsedPayloadHydration() { server: 'editor', tool: 'read_file', callId: readCallId, - raw: { - type: 'tool_result', - tool_use_id: readCallId, - output: { content: 'Hello world' }, - }, output: { content: 'Hello world' }, }, }, @@ -416,11 +337,6 @@ function testToolCallParsedPayloadHydration() { tool: 'shell', status: 'pending', callId: commandCallId, - raw: { - type: 'tool_use', - tool_use_id: commandCallId, - input: { command: 'pwd' }, - }, input: { command: 'pwd' }, kind: 'execute', }, @@ -436,15 +352,6 @@ function testToolCallParsedPayloadHydration() { server: 'command', tool: 'shell', callId: commandCallId, - raw: { - type: 'tool_result', - tool_use_id: commandCallId, - result: { - command: 'pwd', - output: '/Users/dev/paseo', - }, - metadata: { exit_code: 0 }, - }, output: { result: { command: 'pwd', @@ -546,15 +453,10 @@ function testClaudeHydratedToolBodies() { tool: 'apply_patch', status: 'pending', callId: editCallId, - raw: buildClaudeToolUseBlock({ - id: editCallId, - name: 'apply_patch', - server: 'editor', - input: { - file_path: 'src/example.ts', - patch: '*** Begin Patch...', - }, - }), + input: { + file_path: 'src/example.ts', + patch: '*** Begin Patch...', + }, }, }, timestamp: timestampStart, @@ -568,25 +470,6 @@ function testClaudeHydratedToolBodies() { server: 'editor', tool: 'apply_patch', callId: editCallId, - raw: buildClaudeToolResultBlock({ - toolUseId: editCallId, - server: 'editor', - toolName: 'apply_patch', - content: [ - { - type: 'input_json', - json: { - changes: [ - { - file_path: 'src/example.ts', - previous_content: 'export const answer = 41;\n', - content: 'export const answer = 42;\n', - }, - ], - }, - }, - ], - }), output: { changes: [ { @@ -610,12 +493,7 @@ function testClaudeHydratedToolBodies() { tool: 'read_file', status: 'pending', callId: readCallId, - raw: buildClaudeToolUseBlock({ - id: readCallId, - name: 'read_file', - server: 'editor', - input: { file_path: 'README.md' }, - }), + input: { file_path: 'README.md' }, }, }, timestamp: timestampStart, @@ -629,17 +507,6 @@ function testClaudeHydratedToolBodies() { server: 'editor', tool: 'read_file', callId: readCallId, - raw: buildClaudeToolResultBlock({ - toolUseId: readCallId, - server: 'editor', - toolName: 'read_file', - content: [ - { - type: 'input_text', - text: '# Hydrated test file\nHello Claude!', - }, - ], - }), output: { content: '# Hydrated test file\nHello Claude!' }, }, }, @@ -655,12 +522,7 @@ function testClaudeHydratedToolBodies() { tool: 'shell', status: 'pending', callId: commandCallId, - raw: buildClaudeToolUseBlock({ - id: commandCallId, - name: 'shell', - server: 'command', - input: { command: 'ls' }, - }), + input: { command: 'ls' }, kind: 'execute', }, }, @@ -675,23 +537,6 @@ function testClaudeHydratedToolBodies() { server: 'command', tool: 'shell', callId: commandCallId, - raw: buildClaudeToolResultBlock({ - toolUseId: commandCallId, - server: 'command', - toolName: 'shell', - content: [ - { - type: 'input_json', - json: { - result: { - command: 'ls', - output: 'README.md\npackages\n', - }, - metadata: { exit_code: 0 }, - }, - }, - ], - }), output: { result: { command: 'ls', @@ -1196,21 +1041,23 @@ function testMetadataReplayDeduplicationHydrated() { function testFallbackToolCallIdsStayUnique() { const timestamp = new Date('2025-01-01T14:05:00Z'); + // Tool calls need different server/tool to remain distinct when lacking callIds + // (displayName alone is not sufficient for differentiation) const updates = [ { event: toolTimeline( - 'fallback-shell-1', + 'fallback-read', 'completed', - { type: 'tool_result', tool_use_id: 'fallback-shell-1' }, - { callId: null, server: 'command', tool: 'shell', displayName: 'Run shell' } + undefined, + { callId: null, server: 'editor', tool: 'read_file', displayName: 'Read file' } ), timestamp, }, { event: toolTimeline( - 'fallback-shell-2', + 'fallback-shell', 'completed', - { type: 'tool_result', tool_use_id: 'fallback-shell-2' }, + undefined, { callId: null, server: 'command', tool: 'shell', displayName: 'Run shell' } ), timestamp, @@ -1225,99 +1072,17 @@ function testFallbackToolCallIdsStayUnique() { assert.strictEqual( new Set(ids).size, ids.length, - 'Fallback-generated tool ids must be unique even when metadata matches and callIds are missing' + 'Fallback-generated tool ids must be unique when server/tool differs' ); } -function testCodexProviderEventsProduceToolCalls() { - const timestamp = new Date('2025-01-02T08:00:00Z'); - - const commandUpdates = [ - { - event: codexProviderEvent('item.started', 'command_execution', { - id: 'cmd-provider-1', - command: 'ls', - aggregated_output: '', - status: 'in_progress', - }), - timestamp, - }, - { - event: codexProviderEvent('item.completed', 'command_execution', { - id: 'cmd-provider-1', - command: 'ls', - aggregated_output: 'README.md', - exit_code: 0, - status: 'completed', - }), - timestamp, - }, - ]; - - const commandState = commandUpdates.reduce((state, { event, timestamp: ts }) => { - return reduceStreamUpdate(state, event, ts); - }, []); - - const commandTool = commandState.find( - (item): item is AgentToolCallItem => - isAgentToolCallItem(item) && item.payload.data.callId === 'cmd-provider-1' - ); - - assert.ok(commandTool, 'Codex provider command events should create tool call entries'); - assert.strictEqual(commandTool.payload.data.status, 'completed'); - assert.strictEqual(commandTool.payload.data.displayName, 'ls'); - assert.strictEqual(commandTool.payload.data.parsedCommand?.command, 'ls'); - assert.ok( - commandTool.payload.data.parsedCommand?.output?.includes('README.md'), - 'Command output should hydrate via parsed payloads' - ); - - const mcpUpdates = [ - { - event: codexProviderEvent('item.started', 'mcp_tool_call', { - id: 'mcp-provider-1', - server: 'filesystem', - tool: 'read_file', - arguments: { path: 'package.json' }, - status: 'in_progress', - }), - timestamp, - }, - { - event: codexProviderEvent('item.completed', 'mcp_tool_call', { - id: 'mcp-provider-1', - server: 'filesystem', - tool: 'read_file', - result: { content: [{ type: 'text', text: 'Hello' }] }, - status: 'completed', - }), - timestamp, - }, - ]; - - const mcpState = mcpUpdates.reduce((state, { event, timestamp: ts }) => { - return reduceStreamUpdate(state, event, ts); - }, []); - - const mcpTool = mcpState.find( - (item): item is AgentToolCallItem => - isAgentToolCallItem(item) && item.payload.data.callId === 'mcp-provider-1' - ); - - assert.ok(mcpTool, 'Codex provider MCP events should create tool call entries'); - assert.strictEqual(mcpTool.payload.data.status, 'completed'); - assert.strictEqual(mcpTool.payload.data.tool, 'read_file'); - assert.strictEqual(mcpTool.payload.data.displayName, 'filesystem.read_file'); -} - describe('stream timeline reducers', () => { it('produces deterministic hydration results', testIdempotentReduction); it('deduplicates pending/completed tool entries in place', testUserMessageDeduplication); it('preserves distinct assistant messages', testMultipleMessages); - it('retains tool call raw payloads through completion', testToolCallInputPreservation); it('infers completion from tool result payloads', testToolCallStatusInference); - it('infers completion from raw exit codes alone', testToolCallStatusInferenceFromRawOnly); - it('infers failure from raw error payloads', testToolCallFailureInferenceFromRaw); + it('infers completion from output metadata', testToolCallStatusInferenceFromRawOnly); + it('infers failure from error payloads', testToolCallFailureInferenceFromError); it('reconciles late call IDs against pending entries', testToolCallLateCallIdReconciliation); it('persists parsed read/edit/command payloads after hydration', testToolCallParsedPayloadHydration); it('hydrates Claude tool bodies with parsed content', testClaudeHydratedToolBodies); @@ -1334,5 +1099,4 @@ describe('stream timeline reducers', () => { it('replays metadata-only tool calls without duplicating entries (live)', testMetadataReplayDeduplicationLive); it('replays metadata-only tool calls without duplicating entries (hydrated)', testMetadataReplayDeduplicationHydrated); it('assigns unique ids for fallback tool calls without call ids', testFallbackToolCallIdsStayUnique); - it('surfaces Codex provider events as tool calls', testCodexProviderEventsProduceToolCalls); }); diff --git a/packages/app/src/types/stream.ts b/packages/app/src/types/stream.ts index 1363298ed..2555bf8d0 100644 --- a/packages/app/src/types/stream.ts +++ b/packages/app/src/types/stream.ts @@ -109,7 +109,6 @@ export interface AgentToolCallData { server: string; tool: string; status?: ToolCallStatus; - raw?: unknown; callId?: string; displayName?: string; kind?: string; @@ -158,7 +157,6 @@ export interface TodoListItem { timestamp: Date; provider: AgentProvider; items: TodoEntry[]; - raw?: unknown; } function normalizeChunk(text: string): { chunk: string; hasContent: boolean } { @@ -180,171 +178,6 @@ function coerceString(value: unknown): string | null { return trimmed.length ? trimmed : null; } -function buildCodexCommandLabel(command?: unknown): string { - const normalized = coerceString(command); - return normalized ?? "Command"; -} - -function buildCodexFileChangeSummary(changes: unknown): string { - if (!Array.isArray(changes) || changes.length === 0) { - return "File change"; - } - - if (changes.length === 1) { - const change = changes[0] as { path?: unknown; kind?: unknown }; - const kind = coerceString(change?.kind) ?? "edit"; - const path = coerceString(change?.path) ?? "file"; - return `${kind}: ${path}`; - } - - return `${changes.length} file changes`; -} - -function normalizeCodexStatus( - status: unknown, - fallback?: "executing" | "completed" -): "executing" | "completed" | "failed" { - if (typeof status === "string") { - const normalized = status.trim().toLowerCase(); - if (normalized === "failed") { - return "failed"; - } - if (normalized === "completed") { - return "completed"; - } - if (normalized === "in_progress") { - return "executing"; - } - } - return fallback ?? "executing"; -} - -function coerceCodexCallId(item: Record): string | undefined { - const idCandidates = [item.call_id, item.callId, item.tool_use_id, item.id]; - for (const candidate of idCandidates) { - const value = coerceString(candidate); - if (value) { - return value; - } - } - return undefined; -} - -function convertCodexProviderEvent( - provider: AgentProvider, - raw: unknown -): AgentToolCallData | null { - if (provider !== "codex" || !raw || typeof raw !== "object") { - return null; - } - - const rawRecord = raw as { type?: unknown; item?: unknown }; - const eventType = coerceString(rawRecord.type); - if (!eventType || !eventType.startsWith("item.")) { - return null; - } - - const item = rawRecord.item; - if (!item || typeof item !== "object") { - return null; - } - - const typedItem = item as Record & { type?: unknown }; - const itemType = coerceString(typedItem.type); - if (!itemType) { - return null; - } - - const baseStatus = normalizeCodexStatus( - typedItem.status, - eventType === "item.completed" ? "completed" : undefined - ); - const callId = coerceCodexCallId(typedItem); - - if (itemType === "command_execution") { - const command = typedItem.command; - const aggregatedOutput = coerceString( - (typedItem as { aggregated_output?: unknown }).aggregated_output - ); - const exitCode = typeof typedItem.exit_code === "number" ? typedItem.exit_code : undefined; - const resultPayload = - aggregatedOutput !== null || exitCode !== undefined - ? { - output: aggregatedOutput ?? undefined, - exitCode, - command, - } - : undefined; - - return { - provider, - server: "command", - tool: "shell", - status: baseStatus, - raw: typedItem, - callId, - displayName: buildCodexCommandLabel(command), - kind: "execute", - result: resultPayload, - }; - } - - if (itemType === "file_change") { - const changes = Array.isArray(typedItem.changes) ? typedItem.changes : []; - return { - provider, - server: "file_change", - tool: "apply_patch", - status: baseStatus, - raw: typedItem, - callId, - displayName: buildCodexFileChangeSummary(changes), - kind: "edit", - result: { files: changes }, - }; - } - - if (itemType === "mcp_tool_call") { - const serverName = coerceString(typedItem.server) ?? "mcp"; - const toolName = coerceString(typedItem.tool) ?? "tool"; - const argumentsPayload = (typedItem as { arguments?: unknown }).arguments; - const resultPayload = (typedItem as { result?: unknown }).result; - const errorPayload = (typedItem as { error?: unknown }).error; - return { - provider, - server: serverName, - tool: toolName, - status: baseStatus, - raw: { - ...typedItem, - arguments: argumentsPayload, - }, - callId, - displayName: serverName && toolName ? `${serverName}.${toolName}` : toolName, - kind: "tool", - result: resultPayload, - error: errorPayload, - }; - } - - if (itemType === "web_search") { - const query = coerceString(typedItem.query); - return { - provider, - server: "web_search", - tool: "web_search", - status: baseStatus, - raw: typedItem, - callId, - displayName: query ? `Web search: ${query}` : "Web search", - kind: "search", - result: { query }, - }; - } - - return null; -} - function appendUserMessage( state: StreamItem[], text: string, @@ -468,16 +301,15 @@ function mergeToolCallRaw(existingRaw: unknown, nextRaw: unknown): unknown { } function computeParsedToolPayload( - raw: unknown, result: unknown ): { parsedEdits?: EditEntry[]; parsedReads?: ReadEntry[]; parsedCommand?: CommandDetails | null; } { - const edits = extractEditEntries(raw, result); - const reads = extractReadEntries(result, raw); - const command = extractCommandDetails(raw, result); + const edits = extractEditEntries(result); + const reads = extractReadEntries(result); + const command = extractCommandDetails(result); return { parsedEdits: edits.length > 0 ? edits : undefined, @@ -594,10 +426,9 @@ function appendAgentToolCall( const normalizedStatus = normalizeToolCallStatus( data.status, data.result, - data.error, - data.raw + data.error ); - const callId = data.callId ?? extractToolCallId(data.raw); + const callId = data.callId; const payloadData: AgentToolCallData = { ...data, @@ -605,12 +436,11 @@ function appendAgentToolCall( callId: callId ?? data.callId, }; - const existingIndex = findExistingAgentToolCallIndex(state, callId, payloadData); + const existingIndex = findExistingAgentToolCallIndex(state, callId ?? null, payloadData); if (existingIndex >= 0) { const next = [...state]; const existing = next[existingIndex] as AgentToolCallItem; - const mergedRaw = mergeToolCallRaw(existing.payload.data.raw, payloadData.raw); const mergedResult = payloadData.result !== undefined ? payloadData.result @@ -623,7 +453,7 @@ function appendAgentToolCall( existing.payload.data.status, payloadData.status ?? existing.payload.data.status ?? "executing" ); - const parsed = computeParsedToolPayload(mergedRaw, mergedResult); + const parsed = computeParsedToolPayload(mergedResult); next[existingIndex] = { ...existing, timestamp, @@ -633,7 +463,6 @@ function appendAgentToolCall( ...existing.payload.data, ...payloadData, status: mergedStatus, - raw: mergedRaw, result: mergedResult, error: mergedError, displayName: payloadData.displayName ?? existing.payload.data.displayName, @@ -665,7 +494,7 @@ function appendAgentToolCall( source: "agent", data: { ...payloadData, - ...computeParsedToolPayload(payloadData.raw, payloadData.result), + ...computeParsedToolPayload(payloadData.result), }, }, }; @@ -789,8 +618,7 @@ function inferStatusFromRaw(raw: unknown): "completed" | "failed" | null { function normalizeToolCallStatus( status?: string, result?: unknown, - error?: unknown, - raw?: unknown + error?: unknown ): ToolCallStatus { const normalizedFromStatus = normalizeStatusString(status); if (normalizedFromStatus === "failed") { @@ -807,11 +635,6 @@ function normalizeToolCallStatus( return "completed"; } - const inferredFromRaw = inferStatusFromRaw(raw); - if (inferredFromRaw) { - return inferredFromRaw; - } - return normalizedFromStatus ?? "executing"; } @@ -889,8 +712,7 @@ function appendTodoList( state: StreamItem[], provider: AgentProvider, items: TodoEntry[], - timestamp: Date, - raw?: unknown + timestamp: Date ): StreamItem[] { const normalizedItems = items.map((item) => ({ text: item.text, @@ -904,7 +726,6 @@ function appendTodoList( ...lastItem, items: normalizedItems, timestamp, - raw: raw ?? lastItem.raw, }; next[next.length - 1] = updated; return next; @@ -919,7 +740,6 @@ function appendTodoList( timestamp, provider, items: normalizedItems, - raw, }; return [...state, entry]; @@ -954,8 +774,6 @@ export function reduceStreamUpdate( if (isPermissionToolCall(item)) { return state; } - const rawPayload = - item.raw ?? { input: item.input, output: item.output, error: item.error }; nextState = appendAgentToolCall( state, { @@ -963,7 +781,6 @@ export function reduceStreamUpdate( server: item.server, tool: item.tool, status: normalizeStatusString(item.status) ?? "executing", - raw: rawPayload, callId: item.callId, displayName: item.displayName, kind: item.kind, @@ -976,7 +793,7 @@ export function reduceStreamUpdate( } case "todo": { const items = (item.items ?? []) as TodoEntry[]; - nextState = appendTodoList(state, event.provider, items, timestamp, item.raw); + nextState = appendTodoList(state, event.provider, items, timestamp); break; } case "error": { @@ -986,7 +803,6 @@ export function reduceStreamUpdate( timestamp, activityType: "error", message: formatErrorMessage(item.message ?? "Unknown error"), - metadata: item.raw ? { raw: item.raw } : undefined, }; nextState = appendActivityLog(state, activity); break; @@ -997,13 +813,6 @@ export function reduceStreamUpdate( return finalizeActiveThoughts(nextState); } - case "provider_event": { - const converted = convertCodexProviderEvent(event.provider, event.raw); - if (!converted) { - return state; - } - return finalizeActiveThoughts(appendAgentToolCall(state, converted, timestamp)); - } case "thread_started": case "turn_started": case "turn_completed": diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index 4c39e4989..2ba083eb7 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -352,14 +352,13 @@ export class AgentManager { recordUserMessage( agentId: string, text: string, - options?: { messageId?: string; raw?: unknown } + options?: { messageId?: string } ): void { const agent = this.requireAgent(agentId); const item: AgentTimelineItem = { type: "user_message", text, messageId: options?.messageId, - raw: options?.raw, }; agent.updatedAt = new Date(); agent.lastUserMessageAt = agent.updatedAt; diff --git a/packages/server/src/server/agent/agent-sdk-types.ts b/packages/server/src/server/agent/agent-sdk-types.ts index 80959938e..d54be5dcb 100644 --- a/packages/server/src/server/agent/agent-sdk-types.ts +++ b/packages/server/src/server/agent/agent-sdk-types.ts @@ -1,8 +1,4 @@ -import type { ThreadEvent as CodexThreadEvent } from "@openai/codex-sdk"; -import type { - Options as ClaudeAgentOptions, - SDKMessage as ClaudeStreamMessage, -} from "@anthropic-ai/claude-agent-sdk"; +import type { Options as ClaudeAgentOptions } from "@anthropic-ai/claude-agent-sdk"; export type AgentProvider = "codex" | "claude"; @@ -54,9 +50,9 @@ export type AgentUsage = { }; export type AgentTimelineItem = - | { type: "user_message"; text: string; messageId?: string; raw?: unknown } - | { type: "assistant_message"; text: string; raw?: unknown } - | { type: "reasoning"; text: string; raw?: unknown } + | { type: "user_message"; text: string; messageId?: string } + | { type: "assistant_message"; text: string } + | { type: "reasoning"; text: string } | { type: "tool_call"; server: string; @@ -68,10 +64,9 @@ export type AgentTimelineItem = input?: unknown; output?: unknown; error?: unknown; - raw?: unknown; } - | { type: "todo"; items: { text: string; completed: boolean }[]; raw?: unknown } - | { type: "error"; message: string; raw?: unknown }; + | { type: "todo"; items: { text: string; completed: boolean }[] } + | { type: "error"; message: string }; export type AgentStreamEvent = | { type: "thread_started"; sessionId: string; provider: AgentProvider } @@ -79,7 +74,6 @@ export type AgentStreamEvent = | { type: "turn_completed"; provider: AgentProvider; usage?: AgentUsage } | { type: "turn_failed"; provider: AgentProvider; error: string } | { type: "timeline"; item: AgentTimelineItem; provider: AgentProvider } - | { type: "provider_event"; provider: AgentProvider; raw: CodexThreadEvent | ClaudeStreamMessage } | { type: "permission_requested"; provider: AgentProvider; request: AgentPermissionRequest } | { type: "permission_resolved"; @@ -102,7 +96,6 @@ export type AgentPermissionRequest = { input?: Record; suggestions?: AgentPermissionUpdate[]; metadata?: Record; - raw?: unknown; }; export type AgentPermissionResponse = diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index d06d6504f..f44df42a7 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -405,7 +405,6 @@ class ClaudeAgentSession implements AgentSession { callId: pending.request.id, displayName: "Plan approved", kind: "plan", - raw: pending.request, }); } const result: PermissionResult = { @@ -422,7 +421,6 @@ class ClaudeAgentSession implements AgentSession { displayName: pending.request.title ?? pending.request.name, kind: "permission", input: pending.request.input, - raw: { request: pending.request, response }, }); } else { const result: PermissionResult = { @@ -439,7 +437,6 @@ class ClaudeAgentSession implements AgentSession { displayName: pending.request.title ?? pending.request.name, kind: "permission", input: pending.request.input, - raw: { request: pending.request, response }, }); } @@ -618,7 +615,7 @@ class ClaudeAgentSession implements AgentSession { } private translateMessageToEvents(message: SDKMessage): AgentStreamEvent[] { - const events: AgentStreamEvent[] = [{ type: "provider_event", provider: "claude", raw: message }]; + const events: AgentStreamEvent[] = []; switch (message.type) { case "system": @@ -722,7 +719,6 @@ class ClaudeAgentSession implements AgentSession { input, suggestions: options?.suggestions as AgentPermissionUpdate[] | undefined, metadata: Object.keys(metadata).length ? metadata : undefined, - raw: { toolName, input, options }, }; this.pushToolCall({ @@ -733,7 +729,6 @@ class ClaudeAgentSession implements AgentSession { displayName: request.title ?? toolName, kind: "permission", input, - raw: { toolName, input }, }); this.pushEvent({ type: "permission_requested", provider: "claude", request }); @@ -762,7 +757,6 @@ class ClaudeAgentSession implements AgentSession { displayName: request.title ?? toolName, kind: "permission", input, - raw: { reason: "timeout", toolName, input }, }); this.pushEvent({ type: "permission_resolved", @@ -808,7 +802,6 @@ class ClaudeAgentSession implements AgentSession { this.enqueueTimeline({ type: "todo", items: todoItems.length > 0 ? todoItems : [{ text: planText, completed: false }], - raw: input, }); } @@ -939,7 +932,7 @@ class ClaudeAgentSession implements AgentSession { if (suppressAssistant) { return []; } - return [{ type: "assistant_message", text: content, raw: content }]; + return [{ type: "assistant_message", text: content }]; } const items: AgentTimelineItem[] = []; @@ -952,7 +945,7 @@ class ClaudeAgentSession implements AgentSession { this.streamedAssistantTextThisTurn = true; } if (!suppressAssistant) { - items.push({ type: "assistant_message", text: block.text, raw: block }); + items.push({ type: "assistant_message", text: block.text }); } } break; @@ -963,7 +956,7 @@ class ClaudeAgentSession implements AgentSession { this.streamedReasoningThisTurn = true; } if (!suppressReasoning) { - items.push({ type: "reasoning", text: block.thinking, raw: block }); + items.push({ type: "reasoning", text: block.thinking }); } } break; @@ -1009,7 +1002,6 @@ class ClaudeAgentSession implements AgentSession { displayName: this.buildToolDisplayName(entry), kind: this.getToolKind(entry.classification), input: entry.input ?? this.normalizeToolInput(block.input), - raw: block, }, items ); @@ -1020,10 +1012,6 @@ class ClaudeAgentSession implements AgentSession { const server = entry?.server ?? block.server ?? "tool"; const tool = entry?.name ?? block.tool_name ?? "tool"; const status = block.is_error ? "failed" : "completed"; - const rawPayload = - !block.is_error && entry?.classification === "file_change" && entry.files?.length - ? { block, files: entry.files } - : block; this.pushToolCall( { server, @@ -1035,7 +1023,6 @@ class ClaudeAgentSession implements AgentSession { input: entry?.input, output: !block.is_error && entry?.files?.length ? { files: entry.files } : undefined, error: block.is_error ? block : undefined, - raw: rawPayload, }, items ); @@ -1143,7 +1130,6 @@ class ClaudeAgentSession implements AgentSession { displayName: this.buildToolDisplayName(entry), kind: this.getToolKind(entry.classification), input: normalized, - raw: { type: "tool_use", id: toolId, input: parsed }, }); } @@ -1312,7 +1298,6 @@ export function convertClaudeHistoryEntry( timeline.push({ type: "user_message", text, - raw: message, }); } } diff --git a/packages/server/src/server/agent/providers/codex-agent.ts b/packages/server/src/server/agent/providers/codex-agent.ts index e59ab477b..a5fd855ef 100644 --- a/packages/server/src/server/agent/providers/codex-agent.ts +++ b/packages/server/src/server/agent/providers/codex-agent.ts @@ -446,7 +446,6 @@ class CodexAgentSession implements AgentSession { displayName: request.title ?? request.name, kind: "permission", input: request.input, - raw: { request, response }, }), }); @@ -610,7 +609,6 @@ class CodexAgentSession implements AgentSession { } private *translateEvent(event: ThreadEvent): Generator { - yield { type: "provider_event", provider: "codex", raw: event }; const permissionEvents = this.handlePermissionEvent(event); if (permissionEvents) { @@ -658,7 +656,7 @@ class CodexAgentSession implements AgentSession { yield { type: "timeline", provider: "codex", - item: { type: "error", message, raw: event }, + item: { type: "error", message }, }; yield { type: "turn_failed", provider: "codex", error: message }; break; @@ -706,7 +704,6 @@ class CodexAgentSession implements AgentSession { displayName: request.title ?? request.name, kind: "permission", input: request.input, - raw: request.raw, }), }, { type: "permission_requested", provider: "codex", request }, @@ -763,7 +760,6 @@ class CodexAgentSession implements AgentSession { parsedCommand, }, metadata: sanitizeMetadata(metadata), - raw, }; return request; @@ -810,7 +806,6 @@ class CodexAgentSession implements AgentSession { input, suggestions: grantRoot ? [{ grantRoot }] : undefined, metadata: sanitizeMetadata(metadata), - raw, }; return request; @@ -819,9 +814,9 @@ class CodexAgentSession implements AgentSession { private threadItemToTimeline(item: ThreadItem): AgentTimelineItem | null { switch (item.type) { case "agent_message": - return { type: "assistant_message", text: item.text, raw: item }; + return { type: "assistant_message", text: item.text }; case "reasoning": - return { type: "reasoning", text: item.text, raw: item }; + return { type: "reasoning", text: item.text }; case "command_execution": return createToolCallTimelineItem({ server: "command", @@ -833,7 +828,6 @@ class CodexAgentSession implements AgentSession { input: { command: item.command, cwd: (item as any)?.cwd }, output: (item as any)?.output, error: (item as any)?.error, - raw: item, }); case "file_change": { const files = item.changes.map((change) => ({ path: change.path, kind: change.kind })); @@ -845,7 +839,6 @@ class CodexAgentSession implements AgentSession { displayName: buildFileChangeSummary(files), kind: "edit", output: { files }, - raw: item, }); } case "mcp_tool_call": @@ -858,7 +851,6 @@ class CodexAgentSession implements AgentSession { kind: "tool", input: (item as any)?.input, output: (item as any)?.output, - raw: item, }); case "web_search": return createToolCallTimelineItem({ @@ -869,12 +861,11 @@ class CodexAgentSession implements AgentSession { displayName: item.query ? `Web search: ${item.query}` : "Web search", kind: "search", input: { query: item.query }, - raw: item, }); case "todo_list": - return { type: "todo", items: item.items, raw: item }; + return { type: "todo", items: item.items }; case "error": - return { type: "error", message: item.message, raw: item }; + return { type: "error", message: item.message }; default: return null; } @@ -992,12 +983,12 @@ function handleRolloutResponseItem( const text = extractMessageText(payload.content); if (text) { if (payload.role === "assistant") { - events.push({ type: "timeline", provider: "codex", item: { type: "assistant_message", text, raw: payload } }); + events.push({ type: "timeline", provider: "codex", item: { type: "assistant_message", text } }); } else if (payload.role === "user") { if (isSyntheticRolloutUserMessage(text)) { break; } - events.push({ type: "timeline", provider: "codex", item: { type: "user_message", text, raw: payload } }); + events.push({ type: "timeline", provider: "codex", item: { type: "user_message", text } }); } } break; @@ -1005,7 +996,7 @@ function handleRolloutResponseItem( case "reasoning": { const text = extractReasoningText(payload); if (text) { - events.push({ type: "timeline", provider: "codex", item: { type: "reasoning", text, raw: payload } }); + events.push({ type: "timeline", provider: "codex", item: { type: "reasoning", text } }); } break; } @@ -1031,7 +1022,7 @@ function handleRolloutEventMessage(payload: RolloutEventPayload | undefined, eve return; } if (payload.type === "agent_reasoning" && typeof payload.text === "string") { - events.push({ type: "timeline", provider: "codex", item: { type: "reasoning", text: payload.text, raw: payload } }); + events.push({ type: "timeline", provider: "codex", item: { type: "reasoning", text: payload.text } }); } } @@ -1064,7 +1055,6 @@ function handleRolloutFunctionCall( displayName: buildCommandDisplayName(command), kind: "execute", input: { command, cwd }, - raw: payload, }), }); } @@ -1075,7 +1065,7 @@ function handleRolloutFunctionCall( const args = safeJsonParse<{ plan?: unknown }>(payload.arguments); const planItems = parsePlanItems(args); if (planItems.length) { - events.push({ type: "timeline", provider: "codex", item: { type: "todo", items: planItems, raw: payload } }); + events.push({ type: "timeline", provider: "codex", item: { type: "todo", items: planItems } }); } return; } @@ -1091,7 +1081,6 @@ function handleRolloutFunctionCall( displayName: `${name}`, kind: "tool", input: safeJsonParse(payload.arguments), - raw: payload, }), }); } @@ -1123,7 +1112,6 @@ function finalizeRolloutFunctionCall( kind: "execute", input: { command: command.command, cwd: command.cwd }, output: result, - raw: { payload, result }, }), }); commandCalls.delete(payload.call_id); @@ -1143,7 +1131,6 @@ function handleRolloutCustomToolCall(payload: RolloutCustomToolCallPayload, even displayName: buildFileChangeSummary(files), kind: "edit", output: { files }, - raw: payload, }), }); } @@ -1162,7 +1149,6 @@ function handleRolloutCustomToolCall(payload: RolloutCustomToolCallPayload, even kind: "tool", input: payload.input, output: payload.output, - raw: payload, }), }); } diff --git a/packages/server/src/server/messages.ts b/packages/server/src/server/messages.ts index 82128cea6..b788b3a24 100644 --- a/packages/server/src/server/messages.ts +++ b/packages/server/src/server/messages.ts @@ -13,11 +13,6 @@ import type { AgentUsage, } from "./agent/agent-sdk-types.js"; -type ProviderEventPayload = Extract< - AgentStreamEvent, - { type: "provider_event" } ->; - export type AgentSnapshotPayload = Omit< AgentSnapshot, "createdAt" | "updatedAt" | "lastUserMessageAt" @@ -118,7 +113,6 @@ export const AgentPermissionRequestPayloadSchema: z.ZodType = @@ -127,17 +121,14 @@ export const AgentTimelineItemPayloadSchema: z.ZodType = type: z.literal("user_message"), text: z.string(), messageId: z.string().optional(), - raw: z.unknown().optional(), }), z.object({ type: z.literal("assistant_message"), text: z.string(), - raw: z.unknown().optional(), }), z.object({ type: z.literal("reasoning"), text: z.string(), - raw: z.unknown().optional(), }), z.object({ type: z.literal("tool_call"), @@ -150,7 +141,6 @@ export const AgentTimelineItemPayloadSchema: z.ZodType = input: z.unknown().optional(), output: z.unknown().optional(), error: z.unknown().optional(), - raw: z.unknown().optional(), }), z.object({ type: z.literal("todo"), @@ -160,21 +150,13 @@ export const AgentTimelineItemPayloadSchema: z.ZodType = completed: z.boolean(), }) ), - raw: z.unknown().optional(), }), z.object({ type: z.literal("error"), message: z.string(), - raw: z.unknown().optional(), }), ]); -const ProviderEventPayloadSchema = z.object({ - type: z.literal("provider_event"), - provider: AgentProviderSchema, - raw: z.custom(), -}); - export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("thread_started"), @@ -200,7 +182,6 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [ provider: AgentProviderSchema, item: AgentTimelineItemPayloadSchema, }), - ProviderEventPayloadSchema, z.object({ type: z.literal("permission_requested"), provider: AgentProviderSchema, diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 0d130430f..07a5068c3 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -2029,10 +2029,7 @@ export class Session { this.emit({ type: "agent_stream_snapshot", - payload: { - agentId: agent.id, - events, - }, + payload: { agentId: agent.id, events }, }); return timeline.length;