返回 DeepSeek-Reasonix
model_catalog.go
根目录 / desktop / model_catalog.go
1 package main
2
3 import (
4 "log/slog"
5 "time"
6
7 "reasonix/internal/config"
8 "reasonix/internal/control"
9 "reasonix/internal/provider"
10 )
11
12 func (a *App) recordModelSwitchTiming(tabID string, timing *modelSwitchTiming, started time.Time, result *error) {
13 timing.Total = time.Since(started)
14 if *result != nil {
15 timing.Outcome = "failed"
16 } else {
17 timing.Outcome = "ok"
18 }
19 slog.Debug(
20 "desktop: model switch timing",
21 "tab", tabID,
22 "outcome", timing.Outcome,
23 "total_ms", timing.Total.Milliseconds(),
24 "lock_wait_ms", timing.LockWait.Milliseconds(),
25 "prepare_ms", timing.Prepare.Milliseconds(),
26 "config_ms", timing.Config.Milliseconds(),
27 "snapshot_ms", timing.Snapshot.Milliseconds(),
28 "build_ms", timing.Build.Milliseconds(),
29 "lease_resume_ms", timing.LeaseAndResume.Milliseconds(),
30 "swap_persist_ms", timing.SwapAndPersist.Milliseconds(),
31 )
32 if a.modelSwitchTimingHook != nil {
33 a.modelSwitchTimingHook(*timing)
34 }
35 }
36
37 func (a *App) ModelsForTab(tabID string) []ModelInfo {
38 if cur, ok := a.remoteTabCurrentModel(tabID); ok {
39 if !a.remoteTabLocalProxy(tabID) {
40 if infos, err := a.remoteServeModelsForTab(tabID, cur); err == nil {
41 return infos
42 }
43 return []ModelInfo{}
44 }
45 return a.remoteProxyModelCatalog(cur)
46 }
47 a.mu.RLock()
48 curModel := ""
49 workspaceRoot := ""
50 var ctrl control.SessionAPI
51 if tab := a.tabByIDLocked(tabID); tab != nil {
52 curModel = tab.model
53 workspaceRoot = tab.WorkspaceRoot
54 ctrl = tab.Ctrl
55 }
56 a.mu.RUnlock()
57 return a.desktopModelCatalog(curModel, workspaceRoot, ctrl)
58 }
59
60 func (a *App) ModelsForDraft(draftID string) []ModelInfo {
61 record, op, err := a.draftStore().State(a.bootContext(), draftID)
62 if err != nil {
63 return []ModelInfo{}
64 }
65 view, err := a.draftViewForOperation(record, op)
66 if err != nil {
67 return []ModelInfo{}
68 }
69 root := record.WorkspaceRoot
70 if record.Scope != "project" {
71 root = globalWorkspaceRoot()
72 }
73 return a.desktopModelCatalog(view.Settings.Model, root, nil)
74 }
75
76 func (a *App) remoteProxyModelCatalog(curModel string) []ModelInfo {
77 cfg, err := config.Load()
78 if err != nil {
79 return []ModelInfo{}
80 }
81 canonical := curModel
82 if current, ok := cfg.ResolveModel(curModel); ok {
83 canonical = current.Name + "/" + current.Model
84 }
85 out := []ModelInfo{}
86 for i := range cfg.Providers {
87 entry := &cfg.Providers[i]
88 if !modelProviderAccessAllowed(cfg.Desktop.ProviderAccess, entry.Name) || !entry.Configured() {
89 continue
90 }
91 for _, model := range entry.ChatModelList() {
92 ref := entry.Name + "/" + model
93 out = append(out, configuredModelInfo(cfg, entry.Name, model, ref == canonical))
94 }
95 }
96 return out
97 }
98
99 func (a *App) desktopModelCatalog(curModel, workspaceRoot string, ctrl control.SessionAPI) []ModelInfo {
100 // A cold extension catalog fetch can block on a sidecar RPC, so read it
101 // before loading config and without holding App.mu.
102 var extensionCatalog []provider.Descriptor
103 if ctrl != nil {
104 extensionCatalog = ctrl.ProviderCatalog()
105 }
106 cfg, err := config.LoadForRoot(workspaceRoot)
107 if err != nil {
108 return []ModelInfo{}
109 }
110 if entry, ok := cfg.ResolveModel(curModel); ok {
111 curModel = entry.Name + "/" + entry.Model
112 }
113 out := []ModelInfo{}
114 for i := range cfg.Providers {
115 p := &cfg.Providers[i]
116 if !modelProviderAccessAllowed(cfg.Desktop.ProviderAccess, p.Name) || !p.Configured() {
117 continue
118 }
119 for _, m := range p.ChatModelList() {
120 ref := p.Name + "/" + m
121 out = append(out, configuredModelInfo(cfg, p.Name, m, ref == curModel))
122 }
123 }
124 return mergeExtensionModelInfos(out, extensionCatalog, curModel)
125 }
126
127 // configuredModelInfo projects the same per-model overrides and capability
128 // resolver used by settings, without guessing capabilities for unknown models.
129 func configuredModelInfo(cfg *config.Config, name, model string, current bool) ModelInfo {
130 info := ModelInfo{Ref: name + "/" + model, Provider: name, Model: model, Current: current}
131 if entry, ok := cfg.ResolveModel(info.Ref); ok {
132 info.DisplayName = entry.DisplayName
133 info.ContextWindow = entry.ContextWindow
134 capability := config.NewModelCapabilityResolver().Resolve(entry)
135 info.Vision = string(capability.State) == "supported"
136 }
137 return info
138 }
139
139 lines GO