返回 DeepSeek-Reasonix
title_messages.go
根目录 / internal / session / title_messages.go
1 package session
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/provider"
11 "reasonix/internal/sessioncontent"
12 )
13
14 // TitleMessages reads the first authored turns from the durable UI history.
15 // Turn anchors avoid replaying large tool outputs or using compacted model
16 // context. Referenced bodies use the same bounded content reader as history.
17 func (q *Query) TitleMessages(ctx context.Context, ref SessionRef, limit int) ([]provider.Message, error) {
18 if q == nil {
19 return nil, errors.New("session: nil query")
20 }
21 if err := ctx.Err(); err != nil {
22 return nil, err
23 }
24 if err := ref.validate(q.hostID); err != nil {
25 return nil, err
26 }
27 if limit <= 0 {
28 return []provider.Message{}, nil
29 }
30 filesystem, ok := q.persistence.(*FilesystemPersistence)
31 if !ok {
32 return nil, fmt.Errorf("session: title history requires filesystem persistence")
33 }
34 preparation := q.prepareHistoryLocator(filesystem, ref.SessionID, historyIndexPath(filesystem.Root, ref.SessionID))
35 select {
36 case <-ctx.Done():
37 return nil, ctx.Err()
38 case <-preparation.done:
39 if preparation.err != nil {
40 return nil, preparation.err
41 }
42 }
43 baseline, err := q.ReadHistoryWindow(ctx, ref, HistoryWindowRequest{Anchor: "newest", Limit: 1})
44 if err != nil {
45 return nil, err
46 }
47 if baseline.Status != "ready" {
48 return nil, fmt.Errorf("session: title history is %s", baseline.Status)
49 }
50 messages := make([]provider.Message, 0, limit)
51 for turn := 1; turn <= baseline.TotalTurns && len(messages) < limit; turn++ {
52 page, err := q.ReadHistoryWindow(ctx, ref, HistoryWindowRequest{
53 Anchor: "turn", Turn: turn, Limit: 1, SnapshotSequence: &baseline.SnapshotSequence, Generation: baseline.Generation,
54 })
55 if err != nil {
56 return nil, err
57 }
58 if page.Status != "ready" {
59 return nil, fmt.Errorf("session: title history is %s", page.Status)
60 }
61 for _, entry := range page.Messages {
62 body := entry.Inline
63 if entry.ContentRef != nil {
64 if entry.ContentRef.Bytes > sessioncontent.MaxReadRange {
65 return nil, fmt.Errorf("session: title message exceeds the history body read budget")
66 }
67 body = nil
68 for offset := int64(0); offset < entry.ContentRef.Bytes; {
69 chunk, err := q.ReadContent(ctx, ref, *entry.ContentRef, offset, min(1<<20, entry.ContentRef.Bytes-offset))
70 if err != nil {
71 return nil, err
72 }
73 if len(chunk) == 0 {
74 return nil, fmt.Errorf("session: incomplete title message body")
75 }
76 body = append(body, chunk...)
77 offset += int64(len(chunk))
78 }
79 }
80 var message provider.Message
81 if err := json.Unmarshal(body, &message); err != nil {
82 return nil, fmt.Errorf("session: decode title message: %w", err)
83 }
84 if agent.IsUserAuthoredTurnMessage(message) {
85 messages = append(messages, message)
86 }
87 }
88 }
89 return messages, nil
90 }
91
91 lines GO