From e02fbc3cd701490e608e7de5f9731076376d371e Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 8 Jan 2026 04:49:13 +1000 Subject: [PATCH] fix: correct message ordering and add debug JSON tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix chronological ordering by saving ASSISTANT message before TOOL messages - Collect tool results during streaming, save after assistant message - Add "Debug JSON" tab to view raw messages array - Shows all message fields including toolCalls, toolResult, tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../app/agents/[id]/chat/[chatId]/page.tsx | 89 +++++++++++++++---- .../conversation/[conversationId]/route.ts | 39 +++++--- 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx index 5c290bc..f10585e 100644 --- a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx +++ b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx @@ -227,6 +227,7 @@ export default function PublicAgentChatPage(): React.ReactElement { const [hasMoreMessages, setHasMoreMessages] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(true); + const [viewMode, setViewMode] = useState<'chat' | 'debug'>('chat'); // Track first item index for prepending (Virtuoso pattern) const [firstItemIndex, setFirstItemIndex] = useState(10000); @@ -643,32 +644,80 @@ export default function PublicAgentChatPage(): React.ReactElement { {/* Main Chat Area */}
{/* Chat Header */} -
-
+
+
+
+ +
+

{agent.name}

+

+ {PROVIDER_DISPLAY_NAMES[agent.provider]} • {agent.modelId} +

+
+
+ +
+ {/* View Mode Tabs */} +
+ -
-

{agent.name}

-

- {PROVIDER_DISPLAY_NAMES[agent.provider]} • {agent.modelId} -

-
-
- {/* Messages */} -
- {messages.length === 0 && !streamingContent ? ( + {/* Debug JSON View */} + {viewMode === 'debug' && ( +
+
+

+ Raw Messages Array ({messages.length} messages) +

+

+ Messages are ordered by createdAt. Each message includes role, content, and tool + call data. +

+
+
+                {JSON.stringify(messages, null, 2)}
+              
+
+ )} + + {/* Chat View */} + {viewMode === 'chat' && ( + <> + {/* Messages */} +
+ {messages.length === 0 && !streamingContent ? (
@@ -897,6 +946,8 @@ export default function PublicAgentChatPage(): React.ReactElement { Press Enter to send, Shift+Enter for new line

+ + )}
diff --git a/apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts b/apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts index b14b313..936165e 100644 --- a/apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts +++ b/apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts @@ -269,6 +269,12 @@ export async function POST(request: NextRequest, context: RouteContext): Promise // Accumulate tool calls with their input args const toolCallsMap: Map = new Map(); + // Collect tool results to save AFTER assistant message (for correct chronological order) + const pendingToolResults: Array<{ + toolCallId: string; + toolName: string; + output: unknown; + }> = []; let inputTokens = 0; let outputTokens = 0; @@ -312,7 +318,7 @@ export async function POST(request: NextRequest, context: RouteContext): Promise } } - // Send tool results + // Send tool results via SSE but don't save yet (save after assistant message for correct order) if (toolResults && toolResults.length > 0) { for (const tr of toolResults) { sendEvent('tool_result', { @@ -320,16 +326,11 @@ export async function POST(request: NextRequest, context: RouteContext): Promise output: tr.output, }); - // Save tool message to database - await prisma.message.create({ - data: { - conversationId: conversation.id, - role: 'TOOL', - content: JSON.stringify(tr.output), - toolCallId: tr.toolCallId, - toolName: tr.toolName, - toolResult: tr.output as object, - }, + // Collect tool results to save after assistant message + pendingToolResults.push({ + toolCallId: tr.toolCallId, + toolName: tr.toolName, + output: tr.output, }); } } @@ -360,7 +361,7 @@ export async function POST(request: NextRequest, context: RouteContext): Promise outputTokens = finalUsage.outputTokens ?? outputTokens; } - // Save assistant message + // Save assistant message FIRST (so it has earlier createdAt than tool results) const assistantMessage = await prisma.message.create({ data: { conversationId: conversation.id, @@ -376,6 +377,20 @@ export async function POST(request: NextRequest, context: RouteContext): Promise }, }); + // Now save TOOL messages (after assistant, for correct chronological order) + for (const tr of pendingToolResults) { + await prisma.message.create({ + data: { + conversationId: conversation.id, + role: 'TOOL', + content: JSON.stringify(tr.output), + toolCallId: tr.toolCallId, + toolName: tr.toolName, + toolResult: tr.output as object, + }, + }); + } + // Update conversation timestamp await prisma.conversation.update({ where: { id: conversation.id },