mirror of
https://github.com/ollama/ollama.git
synced 2026-07-23 09:10:53 -05:00
launch: Fix launch provider drift (#16683)
This commit is contained in:
@@ -764,7 +764,8 @@ func (c *launcherClient) launchEditorIntegration(ctx context.Context, name strin
|
||||
}
|
||||
|
||||
var launchModels []LaunchModel
|
||||
if (needsConfigure || req.ModelOverride != "") && !savedMatchesModels(saved, models) {
|
||||
liveConfigMatches := slices.Equal(editor.Models(), models)
|
||||
if needsConfigure || req.ModelOverride != "" || !savedMatchesModels(saved, models) || !liveConfigMatches {
|
||||
launchModels = c.modelInventory().Resolve(ctx, models)
|
||||
if err := prepareEditorIntegration(name, editor, launchModels); err != nil {
|
||||
return err
|
||||
|
||||
@@ -17,10 +17,12 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ollama/ollama/cmd/config"
|
||||
"github.com/ollama/ollama/cmd/internal/fileutil"
|
||||
)
|
||||
|
||||
type launcherEditorRunner struct {
|
||||
paths []string
|
||||
models []string
|
||||
edited [][]string
|
||||
ranModel string
|
||||
}
|
||||
@@ -35,11 +37,14 @@ func (r *launcherEditorRunner) String() string { return "LauncherEditor" }
|
||||
func (r *launcherEditorRunner) Paths() []string { return r.paths }
|
||||
|
||||
func (r *launcherEditorRunner) Edit(models []LaunchModel) error {
|
||||
r.edited = append(r.edited, launchModelNames(models))
|
||||
names := launchModelNames(models)
|
||||
r.edited = append(r.edited, names)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *launcherEditorRunner) Models() []string { return nil }
|
||||
func (r *launcherEditorRunner) Models() []string {
|
||||
return append([]string(nil), r.models...)
|
||||
}
|
||||
|
||||
type launcherSingleRunner struct {
|
||||
ranModel string
|
||||
@@ -2208,6 +2213,86 @@ func TestLaunchIntegration_EditorForceConfigure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_ClineRewritesWhenLiveProviderDrifted(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setLaunchTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
|
||||
binDir := t.TempDir()
|
||||
writeFakeBinary(t, binDir, "cline")
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
if err := config.SaveIntegration("cline", []string{"llama3.2"}); err != nil {
|
||||
t.Fatalf("failed to seed saved config: %v", err)
|
||||
}
|
||||
|
||||
providersPath := clineProvidersPath(tmpDir)
|
||||
if err := os.MkdirAll(filepath.Dir(providersPath), 0o755); err != nil {
|
||||
t.Fatalf("failed to create providers dir: %v", err)
|
||||
}
|
||||
existingProviders := map[string]any{
|
||||
"version": float64(1),
|
||||
"lastUsedProvider": "openai-codex-cli",
|
||||
"providers": map[string]any{
|
||||
"openai-codex-cli": map[string]any{
|
||||
"settings": map[string]any{
|
||||
"provider": "openai-codex-cli",
|
||||
"model": "gpt-5.5",
|
||||
"reasoning": "medium",
|
||||
},
|
||||
"updatedAt": "2026-06-01T12:00:00Z",
|
||||
"tokenSource": "manual",
|
||||
},
|
||||
},
|
||||
}
|
||||
data, _ := json.Marshal(existingProviders)
|
||||
if err := os.WriteFile(providersPath, data, 0o644); err != nil {
|
||||
t.Fatalf("failed to seed providers config: %v", err)
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/show":
|
||||
var req apiShowRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
fmt.Fprintf(w, `{"model":%q}`, req.Model)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
t.Setenv("OLLAMA_HOST", srv.URL)
|
||||
|
||||
if err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{
|
||||
Name: "cline",
|
||||
}); err != nil {
|
||||
t.Fatalf("LaunchIntegration returned error: %v", err)
|
||||
}
|
||||
|
||||
providersConfig, err := fileutil.ReadJSON(providersPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read providers config: %v", err)
|
||||
}
|
||||
if providersConfig["lastUsedProvider"] != clineLaunchProvider {
|
||||
t.Fatalf("lastUsedProvider = %v, want %s", providersConfig["lastUsedProvider"], clineLaunchProvider)
|
||||
}
|
||||
providers, _ := providersConfig["providers"].(map[string]any)
|
||||
if _, ok := providers["openai-codex-cli"]; !ok {
|
||||
t.Fatal("expected existing openai-codex-cli provider to be preserved")
|
||||
}
|
||||
ollamaProvider, _ := providers[clineLaunchProvider].(map[string]any)
|
||||
settings, _ := ollamaProvider["settings"].(map[string]any)
|
||||
if settings["provider"] != clineLaunchProvider {
|
||||
t.Fatalf("ollama settings.provider = %v, want %s", settings["provider"], clineLaunchProvider)
|
||||
}
|
||||
if settings["model"] != "llama3.2" {
|
||||
t.Fatalf("ollama settings.model = %v, want llama3.2", settings["model"])
|
||||
}
|
||||
if settings["baseUrl"] != srv.URL+"/v1" {
|
||||
t.Fatalf("ollama settings.baseUrl = %v, want %s/v1", settings["baseUrl"], srv.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_EditorForceConfigure_FloatsCheckedModelsInPicker(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setLaunchTestHome(t, tmpDir)
|
||||
@@ -2217,7 +2302,7 @@ func TestLaunchIntegration_EditorForceConfigure_FloatsCheckedModelsInPicker(t *t
|
||||
writeFakeBinary(t, binDir, "droid")
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
editor := &launcherEditorRunner{}
|
||||
editor := &launcherEditorRunner{models: []string{"llama3.2", "missing-local"}}
|
||||
withIntegrationOverride(t, "droid", editor)
|
||||
|
||||
if err := config.SaveIntegration("droid", []string{"qwen3.5:cloud", "qwen3.5"}); err != nil {
|
||||
@@ -2792,7 +2877,7 @@ func TestLaunchIntegration_ConfiguredEditorLaunchValidatesPrimaryOnly(t *testing
|
||||
writeFakeBinary(t, binDir, "droid")
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
editor := &launcherEditorRunner{}
|
||||
editor := &launcherEditorRunner{models: []string{"llama3.2", "missing-local"}}
|
||||
withIntegrationOverride(t, "droid", editor)
|
||||
|
||||
if err := config.SaveIntegration("droid", []string{"llama3.2", "missing-local"}); err != nil {
|
||||
@@ -2861,7 +2946,7 @@ func TestLaunchIntegration_ConfiguredEditorLaunchSkipsReconfigure(t *testing.T)
|
||||
if err := os.WriteFile(settingsPath, []byte("{}"), 0o644); err != nil {
|
||||
t.Fatalf("failed to seed editor settings: %v", err)
|
||||
}
|
||||
editor := &launcherEditorRunner{paths: []string{settingsPath}}
|
||||
editor := &launcherEditorRunner{paths: []string{settingsPath}, models: []string{"llama3.2", "qwen3:8b"}}
|
||||
withIntegrationOverride(t, "droid", editor)
|
||||
|
||||
if err := config.SaveIntegration("droid", []string{"llama3.2", "qwen3:8b"}); err != nil {
|
||||
@@ -2904,6 +2989,58 @@ func TestLaunchIntegration_ConfiguredEditorLaunchSkipsReconfigure(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_ConfiguredEditorLaunchRewritesDriftedLiveConfig(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setLaunchTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
|
||||
binDir := t.TempDir()
|
||||
writeFakeBinary(t, binDir, "droid")
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
editor := &launcherEditorRunner{models: []string{"qwen3:8b"}}
|
||||
withIntegrationOverride(t, "droid", editor)
|
||||
|
||||
if err := config.SaveIntegration("droid", []string{"llama3.2", "mistral"}); err != nil {
|
||||
t.Fatalf("failed to seed config: %v", err)
|
||||
}
|
||||
|
||||
DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
|
||||
t.Fatalf("did not expect prompt during a normal editor launch: %s", prompt)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/show" {
|
||||
var req apiShowRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
fmt.Fprintf(w, `{"model":%q}`, req.Model)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
t.Setenv("OLLAMA_HOST", srv.URL)
|
||||
|
||||
if err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: "droid"}); err != nil {
|
||||
t.Fatalf("LaunchIntegration returned error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff([][]string{{"llama3.2", "mistral"}}, editor.edited); diff != "" {
|
||||
t.Fatalf("expected editor config rewrite when live config drifts (-want +got):\n%s", diff)
|
||||
}
|
||||
if editor.ranModel != "llama3.2" {
|
||||
t.Fatalf("expected launch to use saved primary model, got %q", editor.ranModel)
|
||||
}
|
||||
|
||||
saved, err := config.LoadIntegration("droid")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload saved config: %v", err)
|
||||
}
|
||||
if diff := compareStrings(saved.Models, []string{"llama3.2", "mistral"}); diff != "" {
|
||||
t.Fatalf("unexpected saved models (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_OpenclawPreservesExistingModelList(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setLaunchTestHome(t, tmpDir)
|
||||
@@ -2913,7 +3050,7 @@ func TestLaunchIntegration_OpenclawPreservesExistingModelList(t *testing.T) {
|
||||
writeFakeBinary(t, binDir, "openclaw")
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
editor := &launcherEditorRunner{}
|
||||
editor := &launcherEditorRunner{models: []string{"llama3.2", "mistral"}}
|
||||
withIntegrationOverride(t, "openclaw", editor)
|
||||
|
||||
if err := config.SaveIntegration("openclaw", []string{"llama3.2", "mistral"}); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user