mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(session): add per-message created_at timestamps
- Persistence layer (jsonl.go addMsg/SetHistory) normalizes CreatedAt when missing so the invariant is guaranteed at the storage boundary - API layer (session.go) exposes created_at on all transcript message types with session.updated fallback for legacy messages - Frontend uses per-message timestamps when available - messagesContentEqual ignores CreatedAt for tail-matching after JSONL roundtrip Fixes #2787
This commit is contained in:
@@ -51,6 +51,7 @@ type sessionChatMessage struct {
|
||||
Content string `json:"content"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
ModelName string `json:"model_name,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
Media []string `json:"media,omitempty"`
|
||||
Attachments []sessionChatAttachment `json:"attachments,omitempty"`
|
||||
ToolCalls []utils.VisibleToolCall `json:"tool_calls,omitempty"`
|
||||
@@ -512,6 +513,7 @@ func sessionTranscriptMessages(
|
||||
Role: "user",
|
||||
Content: msg.Content,
|
||||
ModelName: msg.ModelName,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
Media: append([]string(nil), msg.Media...),
|
||||
Attachments: attachments,
|
||||
}
|
||||
@@ -533,8 +535,9 @@ func sessionTranscriptMessages(
|
||||
msg.ToolCalls,
|
||||
msg.ModelName,
|
||||
toolFeedbackMaxArgsLength,
|
||||
msg.CreatedAt,
|
||||
)
|
||||
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls, msg.ModelName)
|
||||
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls, msg.ModelName, msg.CreatedAt)
|
||||
|
||||
// Pico web chat can persist both visible `message` tool output and a
|
||||
// later plain assistant reply in the same turn. Hide only the fixed
|
||||
@@ -560,6 +563,7 @@ func sessionTranscriptMessages(
|
||||
Role: "assistant",
|
||||
Content: content,
|
||||
ModelName: msg.ModelName,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
Media: append([]string(nil), msg.Media...),
|
||||
Attachments: attachments,
|
||||
}
|
||||
@@ -690,6 +694,7 @@ func assistantThoughtMessage(msg providers.Message) (sessionChatMessage, bool) {
|
||||
Content: reasoning,
|
||||
Kind: "thought",
|
||||
ModelName: msg.ModelName,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
}, true
|
||||
}
|
||||
|
||||
@@ -697,6 +702,7 @@ func assistantToolCallsMessage(
|
||||
toolCalls []providers.ToolCall,
|
||||
modelName string,
|
||||
toolFeedbackMaxArgsLength int,
|
||||
createdAt *time.Time,
|
||||
) (sessionChatMessage, bool) {
|
||||
if len(toolCalls) == 0 {
|
||||
return sessionChatMessage{}, false
|
||||
@@ -714,6 +720,7 @@ func assistantToolCallsMessage(
|
||||
Role: "assistant",
|
||||
Kind: "tool_calls",
|
||||
ModelName: modelName,
|
||||
CreatedAt: createdAt,
|
||||
ToolCalls: visibleToolCalls,
|
||||
}, true
|
||||
}
|
||||
@@ -725,7 +732,7 @@ func visibleAssistantToolArgsPreview(
|
||||
return utils.VisibleToolCallArgumentsPreview(tc, toolFeedbackMaxArgsLength)
|
||||
}
|
||||
|
||||
func visibleAssistantToolMessages(toolCalls []providers.ToolCall, modelName string) []sessionChatMessage {
|
||||
func visibleAssistantToolMessages(toolCalls []providers.ToolCall, modelName string, createdAt *time.Time) []sessionChatMessage {
|
||||
if len(toolCalls) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -744,6 +751,7 @@ func visibleAssistantToolMessages(toolCalls []providers.ToolCall, modelName stri
|
||||
Role: "assistant",
|
||||
Content: content,
|
||||
ModelName: modelName,
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -926,6 +934,11 @@ func (h *Handler) handleGetSession(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
for i := range sess.Messages {
|
||||
if sess.Messages[i].CreatedAt == nil {
|
||||
sess.Messages[i].CreatedAt = &sess.Updated
|
||||
}
|
||||
}
|
||||
messages := detailSessionMessages(sess.Messages, toolFeedbackMaxArgsLength)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
Reference in New Issue
Block a user