返回 DeepSeek-Reasonix
remote_model_application.go
根目录 / desktop / remote_model_application.go
1 package main
2
3 import (
4 "crypto/rand"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "net/http"
9
10 "reasonix/internal/control"
11 )
12
13 // Once a source-bound Serve supports the enhanced protocol, it owns refresh
14 // and receipt lookup in one admission transaction. Desktop must not reject a
15 // retry before Serve can return an already accepted receipt.
16 func (a *App) remoteModelApplicationReady(tabID string) bool {
17 a.remoteTabMu.Lock()
18 defer a.remoteTabMu.Unlock()
19 t := a.remoteTabs[tabID]
20 return t != nil && t.capabilities["model-application-v1"] && t.settings.revision != "" && t.settings.generation == t.gen && t.settings.sessionPath == t.routing.currentPath
21 }
22
23 func (a *App) remoteModelApplicationDetails(tabID string) *ModelApplicationDetails {
24 a.remoteTabMu.Lock()
25 tab := a.remoteTabs[tabID]
26 if tab == nil || !tab.capabilities["model-application-v1"] {
27 a.remoteTabMu.Unlock()
28 return nil
29 }
30 generation := tab.gen
31 a.remoteTabMu.Unlock()
32 client, base, path, err := a.remoteTabCommandTarget(tabID)
33 if err != nil {
34 return nil
35 }
36 ctx, cancel := commandContext(a)
37 defer cancel()
38 status, err := remoteModelSettingsRequest(ctx, client, base, path, nil)
39 if err != nil {
40 return nil
41 }
42 a.remoteTabMu.Lock()
43 defer a.remoteTabMu.Unlock()
44 if current := a.remoteTabs[tabID]; current != tab || current.gen != generation || current.routing.currentPath != path {
45 return nil
46 }
47 return bindRemoteModelConfirmation(tab, generation, path, status.Application)
48 }
49
50 // A host token prevents a confirmation from being revived by a later status
51 // refresh after reconnect, even if Serve still has the same model runtime.
52 func bindRemoteModelConfirmation(tab *remoteTab, generation uint64, path string, detail *ModelApplicationDetails) *ModelApplicationDetails {
53 if detail == nil {
54 return nil
55 }
56 copy := *detail
57 copy.ConfirmationToken = "" // Only this Desktop connection can issue confirmations.
58 previous := tab.settings.details
59 if previous != nil && tab.settings.detailsGeneration == generation && tab.settings.detailsPath == path && previous.RuntimeIdentity == copy.RuntimeIdentity && previous.AppliedRevision == copy.AppliedRevision && previous.DesiredRevision == copy.DesiredRevision {
60 copy.ConfirmationToken = previous.ConfirmationToken
61 }
62 if copy.ConfirmationToken == "" {
63 copy.ConfirmationToken = rand.Text()
64 }
65 tab.settings.details = &copy
66 tab.settings.detailsGeneration, tab.settings.detailsPath = generation, path
67 return &copy
68 }
69
70 func (a *App) validateRemoteModelConfirmation(tabID string, choice control.ModelApplicationChoice) error {
71 a.remoteTabMu.Lock()
72 defer a.remoteTabMu.Unlock()
73 tab := a.remoteTabs[tabID]
74 if tab == nil || !tab.capabilities["model-application-v1"] || tab.settings.detailsGeneration != tab.gen || tab.settings.detailsPath != tab.routing.currentPath {
75 return &submissionNotAcceptedError{cause: control.ErrModelChoiceStale}
76 }
77 d := tab.settings.details
78 if d == nil || choice.ConfirmationToken == "" || choice.ConfirmationToken != d.ConfirmationToken || choice.ExpectedRuntimeIdentity != d.RuntimeIdentity || choice.ExpectedAppliedRevision != d.AppliedRevision || choice.ExpectedDesiredRevision != d.DesiredRevision {
79 return &submissionNotAcceptedError{cause: control.ErrModelChoiceStale}
80 }
81 return nil
82 }
83
84 func (a *App) SubmitRemoteTabWithModelApplication(tabID, text, submissionID string, choice control.ModelApplicationChoice) error {
85 if err := a.requireRemoteExecutionProtocol(tabID); err != nil {
86 return err
87 }
88 if err := a.requireRemotePermissionPresets(tabID); err != nil {
89 return err
90 }
91 a.remoteTabMu.Lock()
92 tab := a.remoteTabs[tabID]
93 supported := tab != nil && tab.capabilities["model-application-v1"]
94 var generation uint64
95 if tab != nil {
96 generation = tab.gen
97 }
98 a.remoteTabMu.Unlock()
99 if !supported {
100 return &submissionNotAcceptedError{cause: fmt.Errorf("upgrade Serve to use model application recovery")}
101 }
102 _, _, path, err := a.remoteTabCommandTarget(tabID)
103 if err != nil {
104 return err
105 }
106 if !a.remoteTabAdmissionCurrent(tabID, generation) {
107 return &submissionNotAcceptedError{cause: control.ErrModelChoiceStale}
108 }
109 if choice.Mode == "applied_once" {
110 if err := a.validateRemoteModelConfirmation(tabID, choice); err != nil {
111 // Serve must still be allowed to acknowledge an existing receipt.
112 // Empty identity can never authorize new execution; a missing
113 // receipt therefore returns its structured stale-choice rejection.
114 choice.ExpectedRuntimeIdentity = ""
115 }
116 }
117 body, err := json.Marshal(map[string]any{"input": text, "submissionId": submissionID, "modelApplication": choice})
118 if err != nil {
119 return err
120 }
121 err = a.submitWithRouteRetry(tabID, func(client *http.Client, base, expectedPath string) error {
122 ctx, cancel := commandContext(a)
123 defer cancel()
124 return servePostForSession(ctx, client, serveURL(base, "/submit"), body, expectedPath)
125 })
126 var detailed interface{ RPCErrorData() map[string]any }
127 if errors.As(err, &detailed) {
128 if raw, marshalErr := json.Marshal(detailed.RPCErrorData()["modelApplication"]); marshalErr == nil {
129 var detail *ModelApplicationDetails
130 if json.Unmarshal(raw, &detail) == nil && detail != nil {
131 a.remoteTabMu.Lock()
132 if current := a.remoteTabs[tabID]; current == tab && current.gen == generation && current.routing.currentPath == path {
133 detail = bindRemoteModelConfirmation(current, generation, path, detail)
134 var httpError *serveHTTPStatusError
135 if errors.As(err, &httpError) {
136 httpError.data["modelApplication"] = detail
137 }
138 }
139 a.remoteTabMu.Unlock()
140 }
141 }
142 }
143 if remoteBusySubmitError(err) {
144 // Busy submissions become durable follow-ups. The queue does not carry
145 // the per-submit model application choice; the message remains queued.
146 if queued, queueErr := a.queueBusyFollowup(tabID, text, submissionID); queueErr == nil {
147 return queued
148 }
149 }
150 return err
151 }
152
152 lines GO