'use client'; import { useEffect, useState, useCallback } from 'react'; import type { Message, MessageType } from '@/lib/types/http'; import { MessageRenderer } from '@/components/task/chat/message-renderer'; import { fetchFixtureFiles, fetchNormalizedMessages, fetchNormalizedFiles, fetchNormalizedEventsAsMessages, type NormalizedFixture, type DiscoveredFile, } from '@/lib/api/domains/debug-api'; import { IconChevronDown, IconChevronRight, IconRefresh } from '@tabler/icons-react'; type ToolFilter = 'all' | 'tool_edit' | 'tool_read' | 'tool_execute' | 'tool_call'; type ViewMode = 'fixtures' | 'events'; const TOOL_TABS: { value: ToolFilter; label: string }[] = [ { value: 'all', label: 'All' }, { value: 'tool_edit', label: 'Edit' }, { value: 'tool_read', label: 'Read' }, { value: 'tool_execute', label: 'Execute' }, { value: 'tool_call', label: 'Call' }, ]; function fixtureToMessage(fixture: NormalizedFixture, index: number): Message { const payload = fixture.payload as Record; const toolType = fixture.tool_type as MessageType; let content = ''; if (payload.file_path) { content = `${fixture.tool_type}: ${payload.file_path}`; } else if (payload.command) { content = `Execute: ${payload.command}`; } else { content = fixture.tool_type; } return { id: `fixture-${index}`, session_id: 'demo', task_id: 'demo-task', author_type: 'agent', type: toolType, content, metadata: { ...payload, tool_call_id: `fixture-${index}`, status: 'complete', }, created_at: new Date().toISOString(), }; } // --- Hooks --- function useAgentMessages() { const [viewMode, setViewMode] = useState('events'); const [fixtureFiles, setFixtureFiles] = useState([]); const [selectedFixtureFile, setSelectedFixtureFile] = useState(''); const [fixtures, setFixtures] = useState([]); const [eventFiles, setEventFiles] = useState([]); const [selectedEventFile, setSelectedEventFile] = useState(''); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [toolFilter, setToolFilter] = useState('all'); useEffect(() => { async function loadFiles() { try { const [fixtureFilesData, eventFilesData] = await Promise.all([ fetchFixtureFiles(), fetchNormalizedFiles(), ]); setFixtureFiles(fixtureFilesData); setEventFiles(eventFilesData); if (eventFilesData.length > 0 && !selectedEventFile) { setSelectedEventFile(eventFilesData[0].path); } if (fixtureFilesData.length > 0 && !selectedFixtureFile) { setSelectedFixtureFile(fixtureFilesData[0].path); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load files'); } finally { setLoading(false); } } loadFiles(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const loadFixtures = useCallback(async () => { if (!selectedFixtureFile) return; setLoading(true); setError(null); try { const data = await fetchNormalizedMessages(selectedFixtureFile); setFixtures(data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load fixtures'); } finally { setLoading(false); } }, [selectedFixtureFile]); const loadMessages = useCallback(async () => { if (!selectedEventFile) return; setLoading(true); setError(null); try { const data = await fetchNormalizedEventsAsMessages(selectedEventFile); setMessages(data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load messages'); } finally { setLoading(false); } }, [selectedEventFile]); useEffect(() => { if (viewMode === 'fixtures' && selectedFixtureFile) { loadFixtures(); } else if (viewMode === 'events' && selectedEventFile) { loadMessages(); } }, [viewMode, selectedFixtureFile, selectedEventFile, loadFixtures, loadMessages]); const filteredFixtures = fixtures.filter((f) => toolFilter === 'all' || f.tool_type === toolFilter); const currentFiles = viewMode === 'fixtures' ? fixtureFiles : eventFiles; const selectedFile = viewMode === 'fixtures' ? selectedFixtureFile : selectedEventFile; const setSelectedFile = viewMode === 'fixtures' ? setSelectedFixtureFile : setSelectedEventFile; const loadContent = viewMode === 'fixtures' ? loadFixtures : loadMessages; const itemCount = viewMode === 'fixtures' ? filteredFixtures.length : messages.length; const totalCount = viewMode === 'fixtures' ? fixtures.length : messages.length; return { viewMode, setViewMode, loading, error, toolFilter, setToolFilter, currentFiles, selectedFile, setSelectedFile, loadContent, filteredFixtures, messages, itemCount, totalCount, }; } // --- UI Components --- function JsonPanel({ title, data, defaultExpanded = false }: { title: string; data: unknown; defaultExpanded?: boolean }) { const [expanded, setExpanded] = useState(defaultExpanded); return (
{expanded && (
          {JSON.stringify(data, null, 2)}
        
)}
); } function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: number }) { const message = fixtureToMessage(fixture, index); return (
{fixture.protocol} {fixture.tool_type} {fixture.tool_name}
Rendered Output:
); } function MessageCard({ message }: { message: Message }) { const toolName = message.metadata?.tool_name as string | undefined; const status = message.metadata?.status as string | undefined; return (
{message.type} {toolName && ( {toolName} )} {status && ( {status} )}
Rendered Output:
); } function ViewModeTabs({ viewMode, setViewMode }: { viewMode: ViewMode; setViewMode: (v: ViewMode) => void }) { return (
{(['events', 'fixtures'] as const).map((mode) => ( ))}
); } function FiltersBar({ viewMode, currentFiles, selectedFile, setSelectedFile, toolFilter, setToolFilter, }: { viewMode: ViewMode; currentFiles: DiscoveredFile[]; selectedFile: string; setSelectedFile: (v: string) => void; toolFilter: ToolFilter; setToolFilter: (v: ToolFilter) => void; }) { return (
{viewMode === 'events' ? 'Event File:' : 'Fixture File:'}
{viewMode === 'fixtures' && (
Tool Type:
{TOOL_TABS.map((tab) => ( ))}
)}
); } function ContentArea({ viewMode, loading, error, itemCount, filteredFixtures, messages, }: { viewMode: ViewMode; loading: boolean; error: string | null; itemCount: number; filteredFixtures: NormalizedFixture[]; messages: Message[]; }) { if (loading) { return (
); } if (error) { return (
Error loading {viewMode}
{error}
); } if (itemCount === 0) { return (
{viewMode === 'events' ? 'No normalized event files found. Run an agent with KANDEV_DEBUG_AGENT_MESSAGES=1 to generate event logs.' : 'No fixtures found. Select a fixture file from the dropdown above.'}
); } if (viewMode === 'fixtures') { return (
{filteredFixtures.map((fixture, index) => ( ))}
); } return (
{messages.map((message, index) => ( ))}
); } // --- Page --- export default function AgentMessagesPage() { const data = useAgentMessages(); return (

Agent Messages QA

Visual inspection of agent message normalization. Each card shows the rendered output alongside raw JSON.

Showing {data.itemCount} of {data.totalCount} {data.viewMode === 'events' ? 'messages' : 'fixtures'}
); }