Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1768,23 +1768,27 @@ func (a *App) SetModel(name string) error {
prevPath = tab.Ctrl.SessionPath()
_ = tab.Ctrl.Snapshot()
carried = tab.Ctrl.History()
tab.Ctrl.Close()
}

newCtrl, err := boot.Build(a.ctx, boot.Options{
Model: name,
RequireKey: false,
Sink: tab.sink,
WorkspaceRoot: tab.WorkspaceRoot,
Stderr: io.Discard,
})
if err != nil {
return err
}
old := tab.Ctrl
a.mu.Lock()
tab.Ctrl = newCtrl
tab.model = name
tab.Label = newCtrl.Label()
a.mu.Unlock()
if old != nil {
old.Close()
}
newCtrl.EnableInteractiveApproval()

path := agent.ContinueSessionPath(prevPath, newCtrl.SessionDir(), newCtrl.Label())
Expand Down
10 changes: 9 additions & 1 deletion desktop/frontend/src/lib/useController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,15 @@ export function useController() {
const compact = useCallback(() => { app.Compact().catch(() => {}); }, []);

const setModel = useCallback(async (name: string) => {
await app.SetModel(name).catch(() => {});
try {
await app.SetModel(name);
} catch (e) {
if (activeTabId) {
const text = `Model switch failed: ${e instanceof Error ? e.message : String(e)}`;
dispatchTo(activeTabId, { type: "local_notice", level: "warn", text });
}
return;
}
if (!activeTabId) return;
try {
dispatchTo(activeTabId, { type: "meta", meta: await app.Meta() });
Expand Down
56 changes: 56 additions & 0 deletions desktop/setmodel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"context"
"os"
"path/filepath"
"testing"
)

func TestSetModelFailureLeavesTabStateIntact(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))

workspace := t.TempDir()
if err := os.WriteFile(filepath.Join(workspace, "reasonix.toml"), []byte(`
default_model = "test-model"

[codegraph]
enabled = false

[[providers]]
name = "test-model"
kind = "openai"
base_url = "https://example.invalid"
model = "x"
api_key_env = "REASONIX_TEST_KEY_UNSET"
`), 0o644); err != nil {
t.Fatal(err)
}

app := NewApp()
app.ctx = context.Background()
app.readyHook = func() {}
tab := app.createTabEntryWithID("project", workspace, "", "tab1")
app.tabs[tab.ID] = tab
app.activeTabID = tab.ID
app.buildTabController(tab)
if tab.Ctrl == nil {
t.Fatalf("tab controller failed to build: %s", tab.StartupErr)
}
defer tab.Ctrl.Close()

oldCtrl := tab.Ctrl
oldModel := tab.model

if err := app.SetModel("no-such-provider/no-such-model"); err == nil {
t.Fatal("SetModel with an unknown model should return an error")
}
if tab.Ctrl != oldCtrl {
t.Fatal("a failed model switch must keep the existing controller, not replace/close it")
}
if tab.model != oldModel {
t.Fatalf("tab.model changed to %q on a failed switch, want %q", tab.model, oldModel)
}
}
7 changes: 6 additions & 1 deletion desktop/settings_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"io"
"os"
"strings"

Expand Down Expand Up @@ -204,7 +205,6 @@ func (a *App) rebuild() error {
prevPath = tab.Ctrl.SessionPath()
_ = tab.Ctrl.Snapshot()
carried = tab.Ctrl.History()
tab.Ctrl.Close()
}
model := tab.model
if cfg, err := config.Load(); err == nil {
Expand All @@ -219,19 +219,24 @@ func (a *App) rebuild() error {
Model: model, RequireKey: false,
Sink: tab.sink,
WorkspaceRoot: tab.WorkspaceRoot,
Stderr: io.Discard,
})
if err != nil {
a.mu.Lock()
tab.StartupErr = err.Error()
a.mu.Unlock()
return err
}
old := tab.Ctrl
a.mu.Lock()
tab.Ctrl = ctrl
tab.model = model
tab.Label = ctrl.Label()
tab.StartupErr = ""
a.mu.Unlock()
if old != nil {
old.Close()
}
ctrl.EnableInteractiveApproval()
path := agent.ContinueSessionPath(prevPath, ctrl.SessionDir(), ctrl.Label())
if len(carried) > 0 {
Expand Down
2 changes: 2 additions & 0 deletions desktop/tabs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -416,6 +417,7 @@ func (a *App) buildTabController(tab *WorkspaceTab) {
RequireKey: false,
Sink: tab.sink,
WorkspaceRoot: root,
Stderr: io.Discard,
})
if err != nil {
a.mu.Lock()
Expand Down
Loading