merge upstream and keep full message timestamps

This commit is contained in:
zhigangwang 2026-04-23 20:39:48 +08:00
commit f2be124c77
9 changed files with 44 additions and 26 deletions

View file

@ -522,8 +522,9 @@ func (s *JSONLStore) AddMessage(
_ context.Context, sessionKey, role, content string,
) error {
return s.addMsg(sessionKey, providers.Message{
Role: role,
Content: content,
Role: role,
Content: content,
Timestamp: time.Now().Format(time.RFC3339Nano),
})
}
@ -539,6 +540,10 @@ func (s *JSONLStore) addMsg(sessionKey string, msg providers.Message) error {
l.Lock()
defer l.Unlock()
if strings.TrimSpace(msg.Timestamp) == "" {
msg.Timestamp = time.Now().Format(time.RFC3339Nano)
}
// Append the message as a single JSON line.
line, err := json.Marshal(msg)
if err != nil {

View file

@ -61,9 +61,15 @@ func TestAddMessage_BasicRoundtrip(t *testing.T) {
if history[0].Role != "user" || history[0].Content != "hello" {
t.Errorf("msg[0] = %+v", history[0])
}
if history[0].Timestamp == "" {
t.Errorf("msg[0].Timestamp is empty")
}
if history[1].Role != "assistant" || history[1].Content != "hi there" {
t.Errorf("msg[1] = %+v", history[1])
}
if history[1].Timestamp == "" {
t.Errorf("msg[1].Timestamp is empty")
}
}
func TestAddMessage_AutoCreatesSession(t *testing.T) {

View file

@ -74,6 +74,7 @@ type Attachment struct {
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
Timestamp string `json:"timestamp,omitempty"`
Media []string `json:"media,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`

View file

@ -72,6 +72,10 @@ func (sm *SessionManager) AddFullMessage(sessionKey string, msg providers.Messag
sm.mu.Lock()
defer sm.mu.Unlock()
if strings.TrimSpace(msg.Timestamp) == "" {
msg.Timestamp = time.Now().Format(time.RFC3339Nano)
}
session, ok := sm.sessions[sessionKey]
if !ok {
session = &Session{

View file

@ -48,6 +48,7 @@ type sessionListItem struct {
type sessionChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Timestamp string `json:"timestamp,omitempty"`
Media []string `json:"media,omitempty"`
Attachments []sessionChatAttachment `json:"attachments,omitempty"`
}
@ -477,6 +478,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
for _, msg := range messages {
attachments := sessionAttachments(msg)
timestamp := strings.TrimSpace(msg.Timestamp)
switch msg.Role {
case "tool":
@ -486,6 +488,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
chatMsg := sessionChatMessage{
Role: "user",
Content: msg.Content,
Timestamp: timestamp,
Media: append([]string(nil), msg.Media...),
Attachments: attachments,
}
@ -500,12 +503,12 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
continue
}
toolSummaryMessages := visibleAssistantToolSummaryMessages(msg.ToolCalls, toolFeedbackMaxArgsLength)
toolSummaryMessages := visibleAssistantToolSummaryMessages(msg.ToolCalls, timestamp, toolFeedbackMaxArgsLength)
if len(toolSummaryMessages) > 0 {
transcript = append(transcript, toolSummaryMessages...)
}
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls)
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls, timestamp)
if len(visibleToolMessages) > 0 {
transcript = append(transcript, visibleToolMessages...)
}
@ -534,6 +537,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
chatMsg := sessionChatMessage{
Role: "assistant",
Content: content,
Timestamp: timestamp,
Media: append([]string(nil), msg.Media...),
Attachments: attachments,
}
@ -686,6 +690,7 @@ func assistantMessageInternalOnly(msg providers.Message) bool {
func visibleAssistantToolSummaryMessages(
toolCalls []providers.ToolCall,
timestamp string,
toolFeedbackMaxArgsLength int,
) []sessionChatMessage {
if len(toolCalls) == 0 {
@ -716,6 +721,7 @@ func visibleAssistantToolSummaryMessages(
name,
visibleAssistantToolSummaryText(tc, toolFeedbackMaxArgsLength),
),
Timestamp: timestamp,
})
}
@ -745,7 +751,7 @@ func visibleAssistantToolSummaryText(
return utils.Truncate(strings.TrimSpace(argsJSON), toolFeedbackMaxArgsLength)
}
func visibleAssistantToolMessages(toolCalls []providers.ToolCall) []sessionChatMessage {
func visibleAssistantToolMessages(toolCalls []providers.ToolCall, timestamp string) []sessionChatMessage {
if len(toolCalls) == 0 {
return nil
}
@ -761,8 +767,9 @@ func visibleAssistantToolMessages(toolCalls []providers.ToolCall) []sessionChatM
continue
}
messages = append(messages, sessionChatMessage{
Role: "assistant",
Content: content,
Role: "assistant",
Content: content,
Timestamp: timestamp,
})
}

View file

@ -166,8 +166,8 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
sessionKey := legacyPicoSessionPrefix + "detail-jsonl"
for _, msg := range []providers.Message{
{Role: "user", Content: "first"},
{Role: "assistant", Content: "second"},
{Role: "user", Content: "first", Timestamp: "2026-04-23T11:05:00+08:00"},
{Role: "assistant", Content: "second", Timestamp: "2026-04-23T11:06:00+08:00"},
{Role: "tool", Content: "ignored"},
} {
if err := store.AddFullMessage(nil, sessionKey, msg); err != nil {
@ -194,8 +194,9 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
ID string `json:"id"`
Summary string `json:"summary"`
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
Role string `json:"role"`
Content string `json:"content"`
Timestamp string `json:"timestamp"`
} `json:"messages"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
@ -213,9 +214,15 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
if resp.Messages[0].Role != "user" || resp.Messages[0].Content != "first" {
t.Fatalf("first message = %#v, want user/first", resp.Messages[0])
}
if resp.Messages[0].Timestamp != "2026-04-23T11:05:00+08:00" {
t.Fatalf("resp.Messages[0].Timestamp = %q, want %q", resp.Messages[0].Timestamp, "2026-04-23T11:05:00+08:00")
}
if resp.Messages[1].Role != "assistant" || resp.Messages[1].Content != "second" {
t.Fatalf("second message = %#v, want assistant/second", resp.Messages[1])
}
if resp.Messages[1].Timestamp != "2026-04-23T11:06:00+08:00" {
t.Fatalf("resp.Messages[1].Timestamp = %q, want %q", resp.Messages[1].Timestamp, "2026-04-23T11:06:00+08:00")
}
}
func TestHandleGetSession_HidesHandledToolAttachmentsBackedByMediaRefs(t *testing.T) {

View file

@ -14,6 +14,7 @@ export interface SessionDetail {
messages: {
role: "user" | "assistant"
content: string
timestamp?: string
media?: string[]
attachments?: {
type?: "image" | "audio" | "video" | "file"

View file

@ -50,7 +50,7 @@ export async function loadSessionMessages(
media: message.media,
attachments: message.attachments,
}),
timestamp: fallbackTime,
timestamp: message.timestamp?.trim() || fallbackTime,
}))
}

View file

@ -38,20 +38,7 @@ export function formatMessageTime(dateRaw: number | string | Date): string {
if (!date.isValid()) {
return ""
}
const now = dayjs()
const isToday = date.isSame(now, "day")
const isThisYear = date.isSame(now, "year")
if (isToday) {
return date.format("LT")
}
if (isThisYear) {
return date.format("MMM D LT")
}
return date.format("ll LT")
return date.format("YYYY-MM-DD HH:mm")
}
export function usePicoChat() {