返回 DeepSeek-Reasonix
session_draft_model.go
根目录 / desktop / session_draft_model.go
1 package main
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "strings"
9
10 "reasonix/desktop/internal/draftstate"
11 "reasonix/internal/config"
12 "reasonix/internal/extension/providerext"
13 )
14
15 func draftSubmissionFingerprint(request SessionDraftSubmissionRequest) (string, string, error) {
16 request.DraftID = strings.TrimSpace(request.DraftID)
17 request.Display = strings.TrimSpace(request.Display)
18 request.Input = strings.TrimSpace(request.Input)
19 if request.Kind == "" {
20 request.Kind = "turn"
21 }
22 data, err := json.Marshal(request)
23 if err != nil {
24 return "", "", err
25 }
26 // Request identity is transport metadata; equivalent concurrent requests
27 // must still converge on the same operation.
28 request.RequestID = ""
29 request.SourceContentJSON = ""
30 if request.Settings.ModelSource == draftModelSourceDefault {
31 request.Settings.Model = ""
32 }
33 semantic, err := json.Marshal(request)
34 if err != nil {
35 return "", "", err
36 }
37 digest := sha256.Sum256(semantic)
38 return hex.EncodeToString(digest[:]), string(data), nil
39 }
40
41 const (
42 draftModelSourceDefault = "default"
43 draftModelSourceExplicit = "explicit"
44 )
45
46 func (a *App) draftViewForOperation(record draftstate.Draft, op *draftstate.Operation) (SessionDraftView, error) {
47 view, err := a.draftView(record)
48 if err != nil || op == nil || op.Phase == "cancelled" || op.Phase == "terminal_failed" {
49 return view, err
50 }
51 // A resumable submission owns a frozen model. The editor and picker must
52 // show the same model that Continue will run, even after a default change.
53 var request SessionDraftSubmissionRequest
54 if json.Unmarshal([]byte(op.RequestJSON), &request) == nil && request.SnapshotVersion >= 3 &&
55 request.SnapshotVersion <= draftstate.SnapshotVersion && request.Settings.Model != "" {
56 view.Settings.Model = request.Settings.Model
57 }
58 return view, nil
59 }
60
61 func draftWorkspaceRoot(record draftstate.Draft) string {
62 if record.Scope == "project" {
63 return record.WorkspaceRoot
64 }
65 return globalWorkspaceRoot()
66 }
67
68 func (a *App) normalizeDraftSettingsForStorage(record draftstate.Draft, settings SessionDraftSettings) SessionDraftSettings {
69 if settings.ModelSource == draftModelSourceDefault {
70 // Keep a valid compatibility mirror for previous readers while the source
71 // marker tells current readers to resolve the default live.
72 settings.Model, _ = desktopNewSessionDefaults(record.Scope, draftWorkspaceRoot(record))
73 }
74 if settings.DisabledMCP == nil {
75 settings.DisabledMCP = map[string]ServerView{}
76 }
77 if settings.MCPOrder == nil {
78 settings.MCPOrder = []string{}
79 }
80 return settings
81 }
82
83 // migrateLegacyUntouchedDraftModel is the only safe legacy inference: revision
84 // one proves that no model pick or other edit was ever durably saved. Later
85 // legacy revisions keep their concrete model because its provenance is
86 // ambiguous and may represent an explicit user choice.
87 func (a *App) migrateLegacyUntouchedDraftModel(record draftstate.Draft) (draftstate.Draft, error) {
88 if record.Revision != 1 || draftHasContent(record.ContentJSON) {
89 return record, nil
90 }
91 var settings SessionDraftSettings
92 if err := json.Unmarshal([]byte(record.SettingsJSON), &settings); err != nil {
93 return record, err
94 }
95 if settings.ModelSource != "" || strings.TrimSpace(settings.Model) == "" {
96 return record, nil
97 }
98 settings.ModelSource = draftModelSourceDefault
99 settings = a.normalizeDraftSettingsForStorage(record, settings)
100 payload, err := json.Marshal(settings)
101 if err != nil {
102 return record, err
103 }
104 upgraded, err := a.draftStore().Save(a.bootContext(), record.ID, record.Revision, record.ContentJSON, string(payload), false)
105 if errors.Is(err, draftstate.ErrConflict) || errors.Is(err, draftstate.ErrOperationConflict) {
106 // Submission owns the draft once an operation exists. Its request already
107 // contains the frozen model, so migration must neither block recovery nor
108 // revise the source snapshot under that operation.
109 return a.draftStore().Get(a.bootContext(), record.ID)
110 }
111 return upgraded, err
112 }
113
114 func (a *App) freezeDraftSubmissionModel(record draftstate.Draft, view SessionDraftView, request SessionDraftSubmissionRequest) (SessionDraftSubmissionRequest, error) {
115 if request.SnapshotVersion < 3 {
116 request.Settings = view.Settings
117 request.SnapshotVersion = 3
118 }
119 if request.Settings.ModelSource == draftModelSourceDefault {
120 request.Settings.Model = view.Settings.Model
121 }
122 if providerext.PluginRefOwner(request.Settings.Model) != "" {
123 return request, nil
124 }
125 cfg, err := config.LoadForRootReadOnly(draftWorkspaceRoot(record))
126 if err != nil {
127 return request, err
128 }
129 request.Settings.Model, err = resolveDraftCreateModelStrict(cfg, request.Settings.Model)
130 return request, err
131 }
132
132 lines GO