Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion components/backend/websocket/agui.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ func RouteAGUIEvent(sessionID string, event map[string]interface{}) {

// If no active run found, check if event has a runId we should create
if activeRunState == nil {
// Ensure timestamp is set before any early returns
if event["timestamp"] == nil || event["timestamp"] == "" {
event["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano)
}

// Don't create lazy runs for terminal events - they should only apply to existing runs
if isTerminalEventType(eventType) {
go persistAGUIEventMap(sessionID, "", event)
Expand Down Expand Up @@ -163,13 +168,17 @@ func RouteAGUIEvent(sessionID string, event map[string]interface{}) {
threadID = eventThreadID
}

// Fill in missing IDs only if not present
// Fill in missing IDs and timestamp
if event["threadId"] == nil || event["threadId"] == "" {
event["threadId"] = threadID
}
if event["runId"] == nil || event["runId"] == "" {
event["runId"] = runID
}
// Add timestamp if not present - critical for message timestamp tracking
if event["timestamp"] == nil || event["timestamp"] == "" {
event["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano)
}

// Broadcast to run-specific SSE subscribers
activeRunState.BroadcastFull(event)
Expand Down
6 changes: 5 additions & 1 deletion components/backend/websocket/agui_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,17 @@ func handleStreamedEvent(sessionID, runID, threadID, jsonData string, runState *

eventType, _ := event["type"].(string)

// Ensure threadId and runId are set
// Ensure threadId, runId, and timestamp are set
if _, ok := event["threadId"]; !ok {
event["threadId"] = threadID
}
if _, ok := event["runId"]; !ok {
event["runId"] = runID
}
// Add timestamp if not present - critical for message timestamp tracking
if _, ok := event["timestamp"]; !ok {
event["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano)
}

// Check for terminal events
switch eventType {
Expand Down
17 changes: 14 additions & 3 deletions components/backend/websocket/compaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ func (c *MessageCompactor) handleTextMessageStart(event map[string]interface{})
if role == "" {
role = types.RoleAssistant
}
// Preserve timestamp from the event
timestamp, _ := event["timestamp"].(string)

c.currentMessage = &types.Message{
ID: messageID,
Role: role,
Content: "",
ID: messageID,
Role: role,
Content: "",
Timestamp: timestamp,
}
}

Expand Down Expand Up @@ -228,17 +231,25 @@ func (c *MessageCompactor) handleToolCallEnd(event map[string]interface{}) {
tc.Status = "error"
}

// Preserve timestamp from the event
timestamp, _ := event["timestamp"].(string)

// Add to message
// Check if we need to create a new message or add to current
if c.currentMessage != nil && c.currentMessage.Role == types.RoleAssistant {
// Add to current message
c.currentMessage.ToolCalls = append(c.currentMessage.ToolCalls, tc)
// Update timestamp if not already set
if c.currentMessage.Timestamp == "" && timestamp != "" {
c.currentMessage.Timestamp = timestamp
}
} else {
// Create new message for this tool call
c.messages = append(c.messages, types.Message{
ID: uuid.New().String(),
Role: types.RoleAssistant,
ToolCalls: []types.ToolCall{tc},
Timestamp: timestamp,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,8 @@ export default function ProjectSessionDetailPage({
const allToolCalls = new Map<string, { tc: AGUIToolCall; timestamp: string }>();

for (const msg of aguiState.messages) {
// Use msg.timestamp from backend, fallback to current time for legacy messages
// Note: After backend fix, new messages will have proper timestamps
const timestamp = msg.timestamp || new Date().toISOString();

if (msg.toolCalls && Array.isArray(msg.toolCalls)) {
Expand Down Expand Up @@ -852,6 +854,7 @@ export default function ProjectSessionDetailPage({

// Phase C: Process messages and build hierarchical structure
for (const msg of aguiState.messages) {
// Use msg.timestamp from backend, fallback to current time for legacy messages
const timestamp = msg.timestamp || new Date().toISOString();

// Handle text content by role
Expand Down
6 changes: 6 additions & 0 deletions components/frontend/src/hooks/use-agui-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
id: newState.currentMessage.id || crypto.randomUUID(),
role: newState.currentMessage.role || AGUIRole.ASSISTANT,
content: newState.currentMessage.content,
timestamp: event.timestamp,
}
newState.messages = [...newState.messages, msg]
onMessage?.(msg)
Expand Down Expand Up @@ -214,6 +215,7 @@
id: messageId,
role: newState.currentMessage.role || AGUIRole.ASSISTANT,
content: newState.currentMessage.content,
timestamp: event.timestamp,
}
newState.messages = [...newState.messages, msg]
onMessage?.(msg)
Expand Down Expand Up @@ -415,6 +417,7 @@
toolCallId: toolCallId,
name: toolCallName,
toolCalls: [completedToolCall],
timestamp: event.timestamp,
}
messages.push(toolMessage)
}
Expand Down Expand Up @@ -546,6 +549,7 @@
thinking: actualRawData.thinking as string,
signature: actualRawData.signature as string,
},
timestamp: event.timestamp,
}
newState.messages = [...newState.messages, msg]
onMessage?.(msg)
Expand All @@ -562,6 +566,7 @@
id: messageId,
role: AGUIRole.USER,
content: actualRawData.content as string,
timestamp: event.timestamp,
}
newState.messages = [...newState.messages, msg]
onMessage?.(msg)
Expand All @@ -575,6 +580,7 @@
id: (actualRawData.id as string) || crypto.randomUUID(),
role: actualRawData.role as AGUIMessage['role'],
content: actualRawData.content as string,
timestamp: event.timestamp,
}
newState.messages = [...newState.messages, msg]
onMessage?.(msg)
Expand All @@ -598,7 +604,7 @@
return newState
})
},
[onEvent, onMessage, onError],

Check warning on line 607 in components/frontend/src/hooks/use-agui-stream.ts

View workflow job for this annotation

GitHub Actions / lint-frontend

React Hook useCallback has a missing dependency: 'onTraceId'. Either include it or remove the dependency array
)

// Connect to the AG-UI event stream
Expand Down
Loading