返回 DeepSeek-Reasonix
submission.go
根目录 / internal / session / submission.go
1 package session
2
3 import (
4 "encoding/json"
5 "fmt"
6 "maps"
7 )
8
9 // SubmissionReceipt is host-only metadata in an optional event. Keeping it out
10 // of provider.Message lets older strict message decoders read new sessions.
11 type SubmissionReceipt struct {
12 FingerprintVersion int `json:"fingerprintVersion,omitempty"`
13 AcceptedAttachmentDigests string `json:"acceptedAttachmentDigests,omitempty"`
14 SessionID string `json:"sessionId"`
15 SubmissionID string `json:"submissionId"`
16 Fingerprint string `json:"fingerprint"`
17 TurnID string `json:"turnId"`
18 MessageID string `json:"messageId"`
19 }
20
21 // Immutable indexes are shared by projection snapshots. Only admission clones
22 // them; streaming progress must not copy the entire submission history.
23 type SubmissionIndex struct {
24 byID map[string]SubmissionReceipt
25 byTurn map[string]SubmissionReceipt
26 byMessage map[string]SubmissionReceipt
27 }
28
29 // Lookup returns an immutable receipt from a query projection.
30 func (index SubmissionIndex) Lookup(sessionID, submissionID string) (SubmissionReceipt, bool) {
31 receipt, ok := index.byID[sessionID+"\x00"+submissionID]
32 return receipt, ok
33 }
34
35 func attachSubmissionEntries(index SubmissionIndex, sessionID string, entries []PersistentMessage) {
36 for i := range entries {
37 if receipt, ok := index.byMessage[sessionID+"\x00"+entries[i].MessageID]; ok {
38 entries[i].SubmissionID = receipt.SubmissionID
39 }
40 }
41 }
42
43 func (index SubmissionIndex) MarshalJSON() ([]byte, error) { return json.Marshal(index.byID) }
44
45 func (index *SubmissionIndex) UnmarshalJSON(data []byte) error {
46 var receipts map[string]SubmissionReceipt
47 if err := json.Unmarshal(data, &receipts); err != nil {
48 return err
49 }
50 next := SubmissionIndex{byID: receipts, byTurn: make(map[string]SubmissionReceipt), byMessage: make(map[string]SubmissionReceipt)}
51 for key, receipt := range receipts {
52 if key != receipt.SessionID+"\x00"+receipt.SubmissionID || receipt.TurnID == "" || receipt.MessageID == "" {
53 return fmt.Errorf("invalid cached submission identity")
54 }
55 next.byTurn[receipt.SessionID+"\x00"+receipt.TurnID] = receipt
56 next.byMessage[receipt.SessionID+"\x00"+receipt.MessageID] = receipt
57 }
58 *index = next
59 return nil
60 }
61
62 func (s *Session) SubmissionForTurn(turnID string) (SubmissionReceipt, bool) {
63 s.mu.Lock()
64 defer s.mu.Unlock()
65 receipt, ok := s.projection.Submissions.byTurn[s.id+"\x00"+turnID]
66 return receipt, ok
67 }
68
69 func (s *Session) Submission(id string) (SubmissionReceipt, bool) {
70 s.mu.Lock()
71 defer s.mu.Unlock()
72 receipt, ok := s.projection.Submissions.byID[s.id+"\x00"+id]
73 return receipt, ok
74 }
75
76 func projectSubmission(p *Projection, commit Commit, ev Event) error {
77 var receipt SubmissionReceipt
78 if err := json.Unmarshal(ev.Payload, &receipt); err != nil {
79 return err
80 }
81 if receipt.SessionID == "" || receipt.SubmissionID == "" || receipt.Fingerprint == "" || receipt.MessageID == "" || receipt.TurnID == "" || receipt.TurnID != commit.TurnID {
82 return fmt.Errorf("invalid submission receipt")
83 }
84 key := receipt.SessionID + "\x00" + receipt.SubmissionID
85 if prior, ok := p.Submissions.byID[key]; ok && prior != receipt {
86 return fmt.Errorf("conflicting submission receipt")
87 }
88 index := SubmissionIndex{maps.Clone(p.Submissions.byID), maps.Clone(p.Submissions.byTurn), maps.Clone(p.Submissions.byMessage)}
89 if index.byID == nil {
90 index.byID = make(map[string]SubmissionReceipt)
91 index.byTurn = make(map[string]SubmissionReceipt)
92 index.byMessage = make(map[string]SubmissionReceipt)
93 }
94 index.byID[key] = receipt
95 index.byTurn[receipt.SessionID+"\x00"+receipt.TurnID] = receipt
96 index.byMessage[receipt.SessionID+"\x00"+receipt.MessageID] = receipt
97 p.Submissions = index
98 return nil
99 }
100
100 lines GO