diff --git a/README.md b/README.md index 8ee1e7c4..a2582761 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ Write your instructions, MCP servers and skills once. magpie writes them into ea
-Claude Code · Claude Desktop · Codex · Gemini CLI · OpenCode · OpenChamber · MiMo Code · Pi · Aside · OmO · Goose · Cursor CLI · Zed · VS Code Chat · VS Code Insiders · JetBrains Air · Copilot CLI · Crush · DeepSeek Harness · Command Code · fx · oh-my-pi · Devin · Hermes Agent · Mister Morph · Kimi Code · Muse Code · Empryo · MiniMax Code · Droid · Cline · Qoder · Qoder CN · Grok Build · ZCode · WorkBuddy · T3 Code · OpenHanako · AtomCode · Alma +Claude Code · Claude Desktop · Codex · Gemini CLI · OpenCode · OpenChamber · MiMo Code · Pi · Aside · OmO · Goose · Cursor CLI · Zed · VS Code Chat · VS Code Insiders · VSCodium Chat · JetBrains Air · Copilot CLI · Crush · DeepSeek Harness · Command Code · fx · oh-my-pi · Devin · Hermes Agent · Mister Morph · Kimi Code · Muse Code · Empryo · MiniMax Code · Droid · Cline · Qoder · Qoder CN · Grok Build · ZCode · WorkBuddy · T3 Code · OpenHanako · AtomCode · Alma
@@ -235,6 +235,18 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3425 ANTHROPIC_API_KEY=magpie export GOOGLE_GEMINI_BASE_URL=http://127.0.0.1:3425 GEMINI_API_KEY=magpie ``` +### VSCodium Chat + +VSCodium's Chat features are disabled by default. To use the Chat model +picker with magpie, set `"chat.disableAIFeatures": false` in VSCodium's +settings and add the `defaultChatAgent` and `trustedExtensionAuthAccess` +entries required by [VSCodium's Copilot guide](https://github.com/VSCodium/vscodium/blob/master/docs/ext-github-copilot.md) +to VSCodium's `product.json`. The guide also explains how to install a +compatible GitHub Copilot Chat extension, since the Open VSX registry does +not normally provide Microsoft's extension. Restart VSCodium, or run +**Developer: Reload Window**, after changing these files. Magpie's models +then appear under the `magpie` custom endpoint group. + ### Zed-compatible Agent paths Magpie can use a Zed-compatible fork or installation whose executable or diff --git a/internal/agent/agents.go b/internal/agent/agents.go index 5c84332c..3808e119 100644 --- a/internal/agent/agents.go +++ b/internal/agent/agents.go @@ -81,6 +81,7 @@ func All() []*Agent { zed(home, cfg), vscode(home, cfg), vscodeInsidersAgent(home, cfg), + vscodium(home, cfg), air(home, cfg), copilot(home), crush(home, cfg), diff --git a/internal/agent/vscode.go b/internal/agent/vscode.go index c73ef24b..4cb8137e 100644 --- a/internal/agent/vscode.go +++ b/internal/agent/vscode.go @@ -58,6 +58,7 @@ type vscodeKind struct { var ( vscodeStable = vscodeKind{id: "vscode", name: "VS Code", folder: "Code", bin: "code", aliases: []string{"vs-code", "copilot-chat", "vscode-chat"}, ua: []string{vscodeUA}} vscodeInsiders = vscodeKind{id: "vscode-insiders", name: "VS Code Insiders", folder: "Code - Insiders", bin: "code-insiders", aliases: []string{"vs-code-insiders", "code-insiders"}} + vscodiumKind = vscodeKind{id: "vscodium", name: "VSCodium", folder: "VSCodium", bin: "codium", aliases: []string{"codium", "vscodium-chat"}} ) // token is the bearer token its models send: Stable's the gateway's own, @@ -75,6 +76,8 @@ func vscode(home, cfg string) *Agent { return vscodeOf(vscodeStable, home, cfg) func vscodeInsidersAgent(home, cfg string) *Agent { return vscodeOf(vscodeInsiders, home, cfg) } +func vscodium(home, cfg string) *Agent { return vscodeOf(vscodiumKind, home, cfg) } + func vscodeOf(k vscodeKind, home, cfg string) *Agent { switch runtime.GOOS { case "darwin": @@ -161,6 +164,9 @@ func vscodeKindAt(k vscodeKind, dir string) *Agent { }, Notice: func() string { if joined() { + if k.id == "vscodium" { + return "VSCodium's Chat features must be enabled (chat.disableAIFeatures=false) and its product.json must include defaultChatAgent and trustedExtensionAuthAccess for GitHub.copilot-chat. Then run Developer: Reload Window in VSCodium." + } return "magpie's models are in " + k.name + "'s Chat model picker, under magpie, in each of its profiles (VS Code 1.122 or later). If they don't show, run Developer: Reload Window in " + k.name + "." } return "" diff --git a/internal/agent/vscode_test.go b/internal/agent/vscode_test.go index ccc319d6..01af1510 100644 --- a/internal/agent/vscode_test.go +++ b/internal/agent/vscode_test.go @@ -289,3 +289,74 @@ func TestVSCodeInsiders(t *testing.T) { t.Fatal("disconnected, magpie's group kept") } } + +func TestVSCodium(t *testing.T) { + syncHome(t) + c, err := Find("vscodium") + if err != nil { + t.Fatal(err) + } + vs, err := Find("vscode") + if err != nil { + t.Fatal(err) + } + if c.Name != "VSCodium" || c.Bin != "codium" || c.UA != nil || !strings.Contains(c.Dir, filepath.Join("VSCodium", "User")) { + t.Fatalf("vscodium: %+v", c) + } + for _, alias := range []string{"codium", "vscodium-chat"} { + a, err := Find(alias) + if err != nil || a.ID != "vscodium" { + t.Fatalf("Find(%q): %v %v", alias, a, err) + } + } + writeFile(t, c.Path, "{}\n") + writeFile(t, vs.Path, `{"chat.defaultModel":"gpt-5"}`+"\n") + if !c.Detected() { + t.Fatal("VSCodium not detected") + } + if err := c.Connect(); err != nil { + t.Fatal(err) + } + g, ok := edit.GetJSONItem(filepath.Join(c.Dir, "chatLanguageModels.json"), vscodeGroup) + if !ok || gjson.Get(g, "models.0.requestHeaders.Authorization").String() != "Bearer magpie-vscodium" || !c.Wired() || c.Check() != "" { + t.Fatalf("connected: %s %v %q", g, c.Wired(), c.Check()) + } + if isFile(filepath.Join(vs.Dir, "chatLanguageModels.json")) || readFile(vs.Path) != `{"chat.defaultModel":"gpt-5"}`+"\n" || vs.Wired() { + t.Fatalf("VS Code touched:\n%s", readFile(vs.Path)) + } + if got := usage.AgentOf("vscodium"); got != "vscodium" { + t.Fatalf("token: %q", got) + } + if err := c.Disconnect(); err != nil { + t.Fatal(err) + } + if _, ok := edit.GetJSONItem(filepath.Join(c.Dir, "chatLanguageModels.json"), vscodeGroup); ok || c.Wired() { + t.Fatal("disconnected, magpie's group kept") + } +} + +func TestVSCodiumModelsUseTheirOwnVisibility(t *testing.T) { + syncHome(t) + if err := provider.Save(provider.Provider{ID: "relay", Name: "Relay", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"visible", "hidden"}}); err != nil { + t.Fatal(err) + } + if err := provider.SetHiddenModels("vscodium", []string{"relay/hidden"}); err != nil { + t.Fatal(err) + } + if err := provider.SetHiddenModels("vscode", []string{"relay/visible"}); err != nil { + t.Fatal(err) + } + has := func(k vscodeKind, id string) bool { + b, err := json.Marshal(vscodeGroupJSON(k)) + if err != nil { + t.Fatal(err) + } + return gjson.GetBytes(b, "models.#(id==\""+id+"\")").Exists() + } + if has(vscodiumKind, "relay/hidden") || !has(vscodiumKind, "relay/visible") { + t.Fatal("VSCodium did not use its own hidden-model list") + } + if !has(vscodeStable, "relay/hidden") || has(vscodeStable, "relay/visible") { + t.Fatal("VS Code did not retain its own hidden-model list") + } +}