返回 DeepSeek-Reasonix
blank_session_model.go
根目录 / desktop / blank_session_model.go
1 package main
2
3 import (
4 "fmt"
5 "strings"
6
7 "reasonix/internal/config"
8 "reasonix/internal/control"
9 )
10
11 // alignReusableBlankTabModel makes a reused empty session obey the same
12 // provider/model default as a newly-created session. Ready runtimes use the
13 // normal failure-atomic model switch. A tab that is still starting has no
14 // controller to swap, so invalidate its startup generation, update the empty
15 // session's model metadata, and restart the build from the intended provider.
16 func (a *App) alignReusableBlankTabModel(tab *WorkspaceTab, model string) error {
17 model = strings.TrimSpace(model)
18 if tab == nil || model == "" {
19 return nil
20 }
21
22 a.mu.RLock()
23 if tab.removed || a.tabs[tab.ID] != tab {
24 a.mu.RUnlock()
25 return fmt.Errorf("blank session changed while applying the default model; retry")
26 }
27 currentModel := strings.TrimSpace(tab.model)
28 ctrl := tab.Ctrl
29 buildDone := tab.buildDone
30 a.mu.RUnlock()
31
32 if ctrl != nil {
33 if currentModel != model {
34 if err := a.SetModelForTab(tab.ID, model); err != nil {
35 return err
36 }
37 }
38 return a.ensureReusableBlankIdentity(tab)
39 }
40 if currentModel == model && buildDone != nil {
41 // Reusing an in-flight blank must reuse its pending create, too. Cancelling
42 // an identical build can leave durable content awaiting registry attach;
43 // startup recovery would later publish that as a second blank session.
44 select {
45 case <-buildDone:
46 case <-a.bootContext().Done():
47 return a.bootContext().Err()
48 }
49 if err := a.ensureReusableBlankIdentity(tab); err != nil {
50 return err
51 }
52 a.mu.RLock()
53 ready := !tab.removed && a.tabs[tab.ID] == tab && tab.Ctrl != nil && tab.SessionID != ""
54 startupErr := tab.StartupErr
55 a.mu.RUnlock()
56 if !ready {
57 return fmt.Errorf("create session runtime: %s", startupErr)
58 }
59 return nil
60 }
61
62 // Fence the old build before publishing its replacement model. Legacy
63 // sources stay read-only; the replacement commits the selected model as a
64 // session/config event when it publishes its canonical identity.
65 a.mu.Lock()
66 if tab.removed || a.tabs[tab.ID] != tab {
67 a.mu.Unlock()
68 return fmt.Errorf("blank session changed while applying the default model; retry")
69 }
70 if tab.Ctrl != nil {
71 a.mu.Unlock()
72 return a.alignReusableBlankTabModel(tab, model)
73 }
74 a.supersedeTabBuildLocked(tab)
75 tab.effort = config.RebindSessionEffort(nil, tab.model, model, tab.effort)
76 tab.model = model
77 tab.Label = model
78 tab.Ready = false
79 clearTabStartupError(tab)
80 a.saveTabsLocked()
81 a.mu.Unlock()
82 a.buildTabController(tab)
83 if err := a.ensureReusableBlankIdentity(tab); err != nil {
84 return err
85 }
86 a.mu.RLock()
87 ready := tab.Ctrl != nil && tab.SessionID != ""
88 startupErr := tab.StartupErr
89 a.mu.RUnlock()
90 if !ready {
91 return fmt.Errorf("create session runtime: %s", startupErr)
92 }
93 return nil
94 }
95
96 // Reusing an empty historical shell is still a New operation. It must bind a
97 // current-store identity rather than making the next user turn a legacy write.
98 func (a *App) ensureReusableBlankIdentity(tab *WorkspaceTab) error {
99 release := a.lockRuntimeMutation("create reusable blank session")
100 defer release()
101 tab.turnStartMu.Lock()
102 defer tab.turnStartMu.Unlock()
103 ctrl := a.controllerForTab(tab)
104 if ctrl == nil {
105 return nil // The caller reports the recorded startup failure.
106 }
107 identity, ok := ctrl.(control.IdentityLifecycle)
108 if !ok || identity.SessionService() == nil {
109 return nil
110 }
111 if _, bound := identity.SessionRef(); bound && identity.UsesExclusiveSession() && identity.SessionService() == a.desktopSessionService("") {
112 return nil
113 }
114 if controllerHasActiveRuntimeWork(ctrl) || messagesHaveConversationContent(ctrl.History()) {
115 return fmt.Errorf("blank session changed before creation; retry")
116 }
117 if err := ctrl.NewSession(); err != nil {
118 return err
119 }
120 a.syncTabSessionIdentity(tab, ctrl)
121 return nil
122 }
123
123 lines GO