mirror of
https://github.com/ollama/ollama.git
synced 2026-04-17 19:54:03 +02:00
wip
This commit is contained in:
@@ -402,22 +402,25 @@ func (v *VSCode) ShowInModelPicker(models []string) error {
|
||||
// Build name→ID map from VS Code's cached model list.
|
||||
// VS Code uses numeric IDs like "ollama/Ollama/4", not "ollama/Ollama/kimi-k2.5:cloud".
|
||||
nameToID := make(map[string]string)
|
||||
var cached []map[string]any
|
||||
var cacheJSON string
|
||||
if err := db.QueryRow("SELECT value FROM ItemTable WHERE key = 'chat.cachedLanguageModels.v2'").Scan(&cacheJSON); err == nil {
|
||||
var cached []map[string]any
|
||||
if json.Unmarshal([]byte(cacheJSON), &cached) == nil {
|
||||
for _, entry := range cached {
|
||||
meta, _ := entry["metadata"].(map[string]any)
|
||||
if meta == nil {
|
||||
continue
|
||||
}
|
||||
if vendor, _ := meta["vendor"].(string); vendor == "ollama" {
|
||||
name, _ := meta["name"].(string)
|
||||
id, _ := entry["identifier"].(string)
|
||||
if name != "" && id != "" {
|
||||
nameToID[name] = id
|
||||
}
|
||||
}
|
||||
_ = json.Unmarshal([]byte(cacheJSON), &cached)
|
||||
}
|
||||
cachedNames := make(map[string]bool)
|
||||
for _, entry := range cached {
|
||||
meta, _ := entry["metadata"].(map[string]any)
|
||||
if meta == nil {
|
||||
continue
|
||||
}
|
||||
if vendor, _ := meta["vendor"].(string); vendor == "ollama" {
|
||||
name, _ := meta["name"].(string)
|
||||
id, _ := entry["identifier"].(string)
|
||||
if name != "" && id != "" {
|
||||
nameToID[name] = id
|
||||
}
|
||||
if name != "" {
|
||||
cachedNames[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,10 +457,56 @@ func (v *VSCode) ShowInModelPicker(models []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure configured models exist in the cached model list so VS Code can
|
||||
// restore the selection immediately on startup, before extensions load.
|
||||
// Without this, a model that was never previously used won't be in the
|
||||
// cache, and VS Code falls back to "auto" until the Ollama BYOK provider
|
||||
// discovers it via the API (which is slow).
|
||||
cacheChanged := false
|
||||
for _, m := range models {
|
||||
if cachedNames[m] {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(m, ":") && cachedNames[m+":latest"] {
|
||||
continue
|
||||
}
|
||||
cacheID := m
|
||||
if !strings.Contains(m, ":") {
|
||||
cacheID = m + ":latest"
|
||||
}
|
||||
cached = append(cached, map[string]any{
|
||||
"identifier": "ollama/Ollama/" + cacheID,
|
||||
"metadata": map[string]any{
|
||||
"extension": map[string]any{"value": "github.copilot-chat"},
|
||||
"name": m,
|
||||
"id": m,
|
||||
"vendor": "ollama",
|
||||
"version": "1.0.0",
|
||||
"family": m,
|
||||
"detail": "Ollama",
|
||||
"maxInputTokens": 4096,
|
||||
"maxOutputTokens": 4096,
|
||||
"isDefaultForLocation": map[string]any{},
|
||||
"isUserSelectable": true,
|
||||
"capabilities": map[string]any{"toolCalling": true},
|
||||
},
|
||||
})
|
||||
cacheChanged = true
|
||||
}
|
||||
if cacheChanged {
|
||||
cacheData, _ := json.Marshal(cached)
|
||||
if _, err := db.Exec("INSERT OR REPLACE INTO ItemTable (key, value) VALUES ('chat.cachedLanguageModels.v2', ?)", string(cacheData)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// modelVSCodeIDs returns all possible VS Code picker IDs for a model name.
|
||||
// The primary (first) ID should match the live identifier that VS Code assigns
|
||||
// at runtime via toModelIdentifier(vendor, group, m.id), where m.id comes from
|
||||
// /api/tags and always includes the tag (e.g. "llama3.2:latest").
|
||||
func (v *VSCode) modelVSCodeIDs(model string, nameToID map[string]string) []string {
|
||||
var ids []string
|
||||
if id, ok := nameToID[model]; ok {
|
||||
@@ -467,10 +516,13 @@ func (v *VSCode) modelVSCodeIDs(model string, nameToID map[string]string) []stri
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
ids = append(ids, "ollama/Ollama/"+model)
|
||||
// For untagged models, the live identifier includes :latest
|
||||
// (e.g. ollama/Ollama/llama3.2:latest), so prefer that format
|
||||
// to avoid a mismatch that causes VS Code to reset to "auto".
|
||||
if !strings.Contains(model, ":") {
|
||||
ids = append(ids, "ollama/Ollama/"+model+":latest")
|
||||
}
|
||||
ids = append(ids, "ollama/Ollama/"+model)
|
||||
return ids
|
||||
}
|
||||
|
||||
|
||||
@@ -416,12 +416,12 @@ func TestShowInModelPicker(t *testing.T) {
|
||||
|
||||
dbPath := testVSCodePath(t, tmpDir, filepath.Join("globalStorage", "state.vscdb"))
|
||||
panelModel := readValue(t, dbPath, "chat.currentLanguageModel.panel")
|
||||
if panelModel != "ollama/Ollama/llama3.2" {
|
||||
t.Errorf("expected panel model ollama/Ollama/llama3.2, got %q", panelModel)
|
||||
if panelModel != "ollama/Ollama/llama3.2:latest" {
|
||||
t.Errorf("expected panel model ollama/Ollama/llama3.2:latest, got %q", panelModel)
|
||||
}
|
||||
editorModel := readValue(t, dbPath, "chat.currentLanguageModel.editor")
|
||||
if editorModel != "ollama/Ollama/llama3.2" {
|
||||
t.Errorf("expected editor model ollama/Ollama/llama3.2, got %q", editorModel)
|
||||
if editorModel != "ollama/Ollama/llama3.2:latest" {
|
||||
t.Errorf("expected editor model ollama/Ollama/llama3.2:latest, got %q", editorModel)
|
||||
}
|
||||
panelDefault := readValue(t, dbPath, "chat.currentLanguageModel.panel.isDefault")
|
||||
if panelDefault != "false" {
|
||||
@@ -473,6 +473,111 @@ func TestShowInModelPicker(t *testing.T) {
|
||||
t.Error("expected llama3.2 to be re-shown")
|
||||
}
|
||||
})
|
||||
|
||||
// helper to read and parse the cached models from the state DB
|
||||
readCache := func(t *testing.T, dbPath string) []map[string]any {
|
||||
t.Helper()
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
var raw string
|
||||
if err := db.QueryRow("SELECT value FROM ItemTable WHERE key = 'chat.cachedLanguageModels.v2'").Scan(&raw); err != nil {
|
||||
return nil
|
||||
}
|
||||
var result []map[string]any
|
||||
_ = json.Unmarshal([]byte(raw), &result)
|
||||
return result
|
||||
}
|
||||
|
||||
t.Run("adds uncached model to cache for instant startup display", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
t.Setenv("XDG_CONFIG_HOME", "")
|
||||
// No seed cache — model has never been used in VS Code before
|
||||
dbPath := setupDB(t, testVSCodePath(t, tmpDir, ""), nil, nil)
|
||||
|
||||
err := v.ShowInModelPicker([]string{"qwen3:8b"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cache := readCache(t, dbPath)
|
||||
if len(cache) != 1 {
|
||||
t.Fatalf("expected 1 cached entry, got %d", len(cache))
|
||||
}
|
||||
entry := cache[0]
|
||||
if id, _ := entry["identifier"].(string); id != "ollama/Ollama/qwen3:8b" {
|
||||
t.Errorf("expected identifier ollama/Ollama/qwen3:8b, got %q", id)
|
||||
}
|
||||
meta, _ := entry["metadata"].(map[string]any)
|
||||
if meta == nil {
|
||||
t.Fatal("expected metadata in cache entry")
|
||||
}
|
||||
if v, _ := meta["vendor"].(string); v != "ollama" {
|
||||
t.Errorf("expected vendor ollama, got %q", v)
|
||||
}
|
||||
if sel, ok := meta["isUserSelectable"].(bool); !ok || !sel {
|
||||
t.Error("expected isUserSelectable to be true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not duplicate already-cached model", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
t.Setenv("XDG_CONFIG_HOME", "")
|
||||
cache := []map[string]any{
|
||||
{
|
||||
"identifier": "ollama/Ollama/4",
|
||||
"metadata": map[string]any{"vendor": "ollama", "name": "llama3.2"},
|
||||
},
|
||||
{
|
||||
"identifier": "copilot/copilot/auto",
|
||||
"metadata": map[string]any{"vendor": "copilot", "name": "Auto"},
|
||||
},
|
||||
}
|
||||
dbPath := setupDB(t, testVSCodePath(t, tmpDir, ""), nil, cache)
|
||||
|
||||
err := v.ShowInModelPicker([]string{"llama3.2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Cache should still have exactly 2 entries (no duplicate added)
|
||||
result := readCache(t, dbPath)
|
||||
if len(result) != 2 {
|
||||
t.Errorf("expected 2 cached entries (no duplicate), got %d", len(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("adds only missing models to existing cache", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
t.Setenv("XDG_CONFIG_HOME", "")
|
||||
cache := []map[string]any{
|
||||
{
|
||||
"identifier": "ollama/Ollama/4",
|
||||
"metadata": map[string]any{"vendor": "ollama", "name": "llama3.2"},
|
||||
},
|
||||
}
|
||||
dbPath := setupDB(t, testVSCodePath(t, tmpDir, ""), nil, cache)
|
||||
|
||||
// llama3.2 is cached, qwen3:8b is not
|
||||
err := v.ShowInModelPicker([]string{"llama3.2", "qwen3:8b"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := readCache(t, dbPath)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 cached entries, got %d", len(result))
|
||||
}
|
||||
// Second entry should be the newly added qwen3:8b
|
||||
if id, _ := result[1]["identifier"].(string); id != "ollama/Ollama/qwen3:8b" {
|
||||
t.Errorf("expected new entry ollama/Ollama/qwen3:8b, got %q", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseCopilotChatVersion(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user