返回 DeepSeek-Reasonix
tabs_persistence_types.go
根目录 / desktop / tabs_persistence_types.go
1 package main
2
3 import (
4 "bytes"
5 "encoding/json"
6
7 "reasonix/internal/boot"
8 "reasonix/internal/control"
9 )
10
11 type desktopTabEntry struct {
12 ID string `json:"id"`
13 Scope string `json:"scope"`
14 WorkspaceRoot string `json:"workspaceRoot"`
15 WorkspaceID string `json:"workspaceId,omitempty"`
16 TopicID string `json:"topicId"`
17 SessionPath string `json:"sessionPath,omitempty"`
18 SessionID string `json:"sessionId,omitempty"`
19 SessionHeadID string `json:"sessionHeadId,omitempty"`
20 CreateOperationID string `json:"createOperationId,omitempty"`
21 ReadOnly bool `json:"readOnly,omitempty"`
22 TakeoverSpectator bool `json:"takeoverSpectator,omitempty"`
23 Model string `json:"model,omitempty"`
24 Effort *string `json:"effort,omitempty"`
25 TokenMode string `json:"tokenMode,omitempty"`
26 AgentPreset string `json:"agentPreset,omitempty"`
27 QualityFloor string `json:"qualityFloor,omitempty"`
28 Mode string `json:"mode,omitempty"`
29 Goal string `json:"goal,omitempty"`
30 ToolApprovalMode string `json:"toolApprovalMode,omitempty"`
31 // PinnedFiles is read-only upgrade input from the unmerged tab-scoped
32 // implementation. New writers persist pins beside the owning session.
33 PinnedFiles []string `json:"pinnedFiles,omitempty"`
34 extra map[string]json.RawMessage
35 restoreBlocked bool
36 restoreBlockReason string
37 historicalSource *SessionSourceRef
38 }
39
40 type desktopTabsFile struct {
41 Tabs []desktopTabEntry `json:"tabs"`
42 ActiveTab string `json:"activeTab"`
43 RemoteTabs []desktopRemoteTabEntry `json:"remoteTabs,omitempty"`
44 RemoteTabOrder []string `json:"remoteTabOrder,omitempty"`
45 TabOrder []string `json:"tabOrder,omitempty"`
46 extra map[string]json.RawMessage
47 }
48
49 type desktopTabWorkspace struct {
50 ID string
51 }
52
53 func persistedDesktopTabEntry(tab *WorkspaceTab) desktopTabEntry {
54 return desktopTabEntry{
55 ID: tab.ID,
56 Scope: tab.Scope,
57 WorkspaceRoot: tab.WorkspaceRoot,
58 WorkspaceID: tab.SessionWorkspace.ID,
59 TopicID: tab.TopicID,
60 SessionPath: tab.currentSessionPath(),
61 SessionID: tab.SessionID,
62 SessionHeadID: tab.SessionHeadID,
63 CreateOperationID: tab.PendingCreateOperationID,
64 ReadOnly: tab.ReadOnly,
65 TakeoverSpectator: tab.Takeover.Spectator,
66 Model: tab.model,
67 Effort: cloneStringPtr(tab.effort),
68 AgentPreset: boot.AgentPresetStandard,
69 TokenMode: boot.TokenModeFull,
70 QualityFloor: control.QualityFloorStandard,
71 Mode: persistedTabMode(currentTabMode(tab)),
72 Goal: persistedTabGoal(tab),
73 ToolApprovalMode: persistedToolApprovalMode(currentTabToolApprovalMode(tab)),
74 PinnedFiles: tab.pendingLegacyPinnedFilesForPersistence(),
75 extra: cloneDesktopJSONFields(tab.persistenceExtra),
76 }
77 }
78
79 func (entry *desktopTabEntry) UnmarshalJSON(body []byte) error {
80 type plain desktopTabEntry
81 var decoded plain
82 if err := json.Unmarshal(body, &decoded); err != nil {
83 return err
84 }
85 extra, err := desktopUnknownJSONFields(body,
86 "id", "scope", "workspaceRoot", "workspaceId", "topicId", "sessionPath", "sessionId", "createOperationId",
87 "readOnly", "takeoverSpectator", "model", "effort", "tokenMode", "agentPreset", "qualityFloor", "mode",
88 "goal", "toolApprovalMode", "pinnedFiles",
89 )
90 if err != nil {
91 return err
92 }
93 *entry = desktopTabEntry(decoded)
94 entry.extra = extra
95 return nil
96 }
97
98 func (entry desktopTabEntry) MarshalJSON() ([]byte, error) {
99 type plain desktopTabEntry
100 body, err := json.Marshal(plain(entry))
101 if err != nil {
102 return nil, err
103 }
104 return mergeDesktopUnknownJSONFields(body, entry.extra)
105 }
106
107 func (entry *desktopRemoteTabEntry) UnmarshalJSON(body []byte) error {
108 type plain desktopRemoteTabEntry
109 var decoded plain
110 if err := json.Unmarshal(body, &decoded); err != nil {
111 return err
112 }
113 extra, err := desktopUnknownJSONFields(body,
114 "id", "hostId", "workspace", "topicTitle", "model", "sessionName", "sessionPath", "sessionId", "sessionReset",
115 )
116 if err != nil {
117 return err
118 }
119 *entry = desktopRemoteTabEntry(decoded)
120 entry.extra = extra
121 return nil
122 }
123
124 func (entry desktopRemoteTabEntry) MarshalJSON() ([]byte, error) {
125 type plain desktopRemoteTabEntry
126 body, err := json.Marshal(plain(entry))
127 if err != nil {
128 return nil, err
129 }
130 return mergeDesktopUnknownJSONFields(body, entry.extra)
131 }
132
133 func (file *desktopTabsFile) UnmarshalJSON(body []byte) error {
134 type plain desktopTabsFile
135 var decoded plain
136 if err := json.Unmarshal(body, &decoded); err != nil {
137 return err
138 }
139 extra, err := desktopUnknownJSONFields(body, "tabs", "activeTab", "remoteTabs", "remoteTabOrder", "tabOrder")
140 if err != nil {
141 return err
142 }
143 *file = desktopTabsFile(decoded)
144 file.extra = extra
145 return nil
146 }
147
148 func (file desktopTabsFile) MarshalJSON() ([]byte, error) {
149 type plain desktopTabsFile
150 body, err := json.Marshal(plain(file))
151 if err != nil {
152 return nil, err
153 }
154 return mergeDesktopUnknownJSONFields(body, file.extra)
155 }
156
157 func desktopUnknownJSONFields(body []byte, known ...string) (map[string]json.RawMessage, error) {
158 var fields map[string]json.RawMessage
159 if err := json.Unmarshal(body, &fields); err != nil {
160 return nil, err
161 }
162 for _, key := range known {
163 delete(fields, key)
164 }
165 return fields, nil
166 }
167
168 func mergeDesktopUnknownJSONFields(known []byte, extra map[string]json.RawMessage) ([]byte, error) {
169 if len(extra) == 0 {
170 return known, nil
171 }
172 var fields map[string]json.RawMessage
173 if err := json.Unmarshal(known, &fields); err != nil {
174 return nil, err
175 }
176 for key, value := range extra {
177 if _, exists := fields[key]; !exists {
178 fields[key] = bytes.Clone(value)
179 }
180 }
181 return json.Marshal(fields)
182 }
183
184 func cloneDesktopJSONFields(fields map[string]json.RawMessage) map[string]json.RawMessage {
185 if len(fields) == 0 {
186 return nil
187 }
188 clone := make(map[string]json.RawMessage, len(fields))
189 for key, value := range fields {
190 clone[key] = bytes.Clone(value)
191 }
192 return clone
193 }
194
194 lines GO