agent(zcode): ZCode is an agent — its provider field puts every magpie model in ZCode's own model picker, as a provider named magpie in ~/.zcode/v2/config.json (kind anthropic, asked at the gateway's /v1/messages), kept in step with the catalog like OpenCode's list, and taken out again when cleared; its requests are known by the ZCode/ User-Agent (#72)
This commit is contained in:
10 files changed
+162
-4
No files matched your search
@@ -79,6 +79,7 @@ and there is a terminal version (`magpie tui`) and a plain CLI.
|
||||
| Devin | `~/.config/devin/config.json` (`%APPDATA%\devin\config.json` on Windows) | model |
|
||||
| Hermes Agent | `~/.hermes/config.yaml` (`$HERMES_HOME`) | model |
|
||||
| Grok Build | `~/.grok/config.toml` (`$GROK_HOME`) | model, effort |
|
||||
| ZCode | `~/.zcode/v2/config.json` | provider (magpie's models in ZCode's picker) |
|
||||
|
||||
Provider-scoped agents (OpenCode, Pi, Goose, Crush, omp, Hermes Agent) take `provider/model`.
|
||||
Only agents that are installed or configured are shown.
|
||||
|
||||
@@ -64,6 +64,7 @@ func All() []*Agent {
|
||||
devin(home, cfg),
|
||||
hermes(home),
|
||||
grok(home),
|
||||
zcode(home),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/yetone/magpie/internal/edit"
|
||||
"github.com/yetone/magpie/internal/gateway"
|
||||
)
|
||||
|
||||
// ZCode (Zhipu's desktop app) keeps its model providers in
|
||||
// ~/.zcode/v2/config.json, OpenCode's provider shape with a kind of its own:
|
||||
//
|
||||
// {"provider":{"magpie":{"name":"magpie","kind":"anthropic",
|
||||
// "options":{"apiKey":"magpie","baseURL":"http://127.0.0.1:3425"},
|
||||
// "enabled":true,"source":"custom",
|
||||
// "models":{"<id>":{"name":…,"limit":{"context":…},"modalities":{…}}}}}}
|
||||
//
|
||||
// An anthropic provider is asked at baseURL + /v1/messages. The model is
|
||||
// picked per task in ZCode's own picker and kept in its window, not in a
|
||||
// file, so what magpie sets is whether its models are in that picker.
|
||||
|
||||
func zcode(home string) *Agent {
|
||||
dir := filepath.Join(home, ".zcode")
|
||||
path := filepath.Join(dir, "v2", "config.json")
|
||||
key := "provider." + magpieID
|
||||
return &Agent{
|
||||
ID: "zcode", Name: "ZCode", Icon: "zcode", Aliases: []string{"z-code"},
|
||||
UA: []string{"zcode"},
|
||||
Dir: dir, Path: path,
|
||||
Notice: func() string {
|
||||
if Running(`ZCode\.app/`, `(^|/)ZCode( |$)`) {
|
||||
return "ZCode reads its providers at start-up — restart ZCode to see magpie's models in its picker."
|
||||
}
|
||||
return ""
|
||||
},
|
||||
Sync: func() error { return syncJSON(path, key, zcodeProviderJSON) },
|
||||
Fields: []Field{{
|
||||
Key: "provider", Label: "provider",
|
||||
Get: func() string {
|
||||
if _, ok := edit.GetJSON(path, key); ok {
|
||||
return magpieID
|
||||
}
|
||||
return ""
|
||||
},
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
return edit.DelJSON(path, key)
|
||||
}
|
||||
return edit.SetJSON(path, edit.KV{Path: key, Value: zcodeProviderJSON()})
|
||||
},
|
||||
Options: func(map[string]string) []Option {
|
||||
return []Option{{Value: magpieID, Label: "magpie", Icon: "magpie", Note: "every magpie model in ZCode's picker"}}
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func zcodeProviderJSON() any {
|
||||
ms := map[string]any{}
|
||||
for _, m := range magpieModels() {
|
||||
window := m.Context
|
||||
if window == 0 {
|
||||
window = 200000
|
||||
}
|
||||
in := []string{"text"}
|
||||
if m.Images {
|
||||
in = append(in, "image")
|
||||
}
|
||||
ms[m.ID] = map[string]any{"name": m.Name, "limit": map[string]any{"context": window},
|
||||
"modalities": map[string]any{"input": in, "output": []string{"text"}}}
|
||||
}
|
||||
return map[string]any{"name": "magpie", "kind": "anthropic", "enabled": true, "source": "custom",
|
||||
"options": map[string]any{"apiKey": gateway.Token, "baseURL": gateway.URL()}, "models": ms}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/yetone/magpie/internal/provider"
|
||||
)
|
||||
|
||||
func TestZCode(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
|
||||
if err := provider.Save(provider.Provider{ID: "deepseek", Name: "DeepSeek", Chat: "https://api.deepseek.com/v1", Key: "k", Models: []string{"pro"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(home, ".zcode", "v2", "config.json")
|
||||
os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
os.WriteFile(path, []byte(`{"provider":{"builtin:bigmodel":{"name":"Bigmodel","kind":"anthropic","enabled":true}}}`), 0o644)
|
||||
read := func() map[string]any {
|
||||
var c struct {
|
||||
Provider map[string]map[string]any `json:"provider"`
|
||||
}
|
||||
b, _ := os.ReadFile(path)
|
||||
if err := json.Unmarshal(b, &c); err != nil {
|
||||
t.Fatalf("%v\n%s", err, b)
|
||||
}
|
||||
if c.Provider["builtin:bigmodel"] == nil {
|
||||
t.Fatalf("ZCode's own provider went: %s", b)
|
||||
}
|
||||
return c.Provider["magpie"]
|
||||
}
|
||||
|
||||
a := zcode(home)
|
||||
if !a.Detected() {
|
||||
t.Fatal("not detected")
|
||||
}
|
||||
f := a.Field("provider")
|
||||
if f.Get() != "" {
|
||||
t.Fatalf("get: %q", f.Get())
|
||||
}
|
||||
if err := f.Set("magpie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := read()
|
||||
opts, _ := m["options"].(map[string]any)
|
||||
models, _ := m["models"].(map[string]any)
|
||||
pro, _ := models["deepseek/pro"].(map[string]any)
|
||||
if m["kind"] != "anthropic" || m["enabled"] != true || m["source"] != "custom" || opts["apiKey"] != "magpie" ||
|
||||
opts["baseURL"] == "" || pro == nil || pro["limit"] == nil || pro["modalities"] == nil {
|
||||
t.Fatalf("magpie provider: %v", m)
|
||||
}
|
||||
if f.Get() != "magpie" {
|
||||
t.Fatalf("get: %q", f.Get())
|
||||
}
|
||||
|
||||
// a provider added later reaches ZCode's picker
|
||||
if err := provider.Save(provider.Provider{ID: "kimi", Name: "Kimi", Chat: "https://api.moonshot.cn/v1", Key: "k", Models: []string{"k2"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := a.Sync(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if models, _ := read()["models"].(map[string]any); models["kimi/k2"] == nil {
|
||||
t.Fatalf("not synced: %v", models)
|
||||
}
|
||||
|
||||
if err := f.Set(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if read() != nil || f.Get() != "" {
|
||||
t.Fatal("magpie provider left behind")
|
||||
}
|
||||
// nothing to sync into a config that has no magpie provider
|
||||
if err := a.Sync(); err != nil || read() != nil {
|
||||
t.Fatal("sync added the provider")
|
||||
}
|
||||
}
|
||||
@@ -98,9 +98,9 @@ function icon(name) {
|
||||
return e;
|
||||
}
|
||||
if (name) {
|
||||
if (name.endsWith("-color") || name === "crush") {
|
||||
if (name.endsWith("-color") || name === "crush" || name === "zcode") {
|
||||
const img = el("img");
|
||||
img.src = `icons/${name}.${name === "crush" ? "png" : "svg"}`;
|
||||
img.src = `icons/${name}.${name === "crush" || name === "zcode" ? "png" : "svg"}`;
|
||||
img.alt = "";
|
||||
img.draggable = false;
|
||||
e.append(img);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -114,7 +114,7 @@
|
||||
<p>Keys live in <code>~/.config/magpie/providers.json</code>, readable by you alone. magpie never reads keys from your shell environment: what you add is what it uses.</p>
|
||||
|
||||
<h2 id="agents"><a class="anchor" href="#agents">2 · Pick a model for each agent</a></h2>
|
||||
<p>The <span class="ui">Agents</span> tab has a row for each agent installed or configured on this machine. magpie knows Claude Code, Codex, Gemini CLI, OpenCode, Pi, Goose, Cursor, Copilot CLI, Crush, DeepSeek Harness, Command Code, omp, Devin, Hermes Agent and Grok Build.</p>
|
||||
<p>The <span class="ui">Agents</span> tab has a row for each agent installed or configured on this machine. magpie knows Claude Code, Codex, Gemini CLI, OpenCode, Pi, Goose, Cursor, Copilot CLI, Crush, DeepSeek Harness, Command Code, omp, Devin, Hermes Agent, Grok Build and ZCode.</p>
|
||||
|
||||
<figure class="figure">
|
||||
<img class="for-light" src="/img/agents-light.png" width="2080" height="1058" loading="lazy" alt="The Agents tab: one row per agent with the model it uses, and saved profiles at the bottom">
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<p>密钥保存在 <code>~/.config/magpie/providers.json</code>,只有你自己可读。magpie 从不读取 shell 环境变量里的密钥:你添加什么,它就用什么。</p>
|
||||
|
||||
<h2 id="agents"><a class="anchor" href="#agents">2 · 给每个 Agent 选模型</a></h2>
|
||||
<p><span class="ui">Agent</span> 标签页里,本机装了或配置过的每个 Agent 各占一行。magpie 支持 Claude Code、Codex、Gemini CLI、OpenCode、Pi、Goose、Cursor、Copilot CLI、Crush、DeepSeek Harness、Command Code、omp、Devin、Hermes Agent 和 Grok Build。</p>
|
||||
<p><span class="ui">Agent</span> 标签页里,本机装了或配置过的每个 Agent 各占一行。magpie 支持 Claude Code、Codex、Gemini CLI、OpenCode、Pi、Goose、Cursor、Copilot CLI、Crush、DeepSeek Harness、Command Code、omp、Devin、Hermes Agent、Grok Build 和 ZCode。</p>
|
||||
|
||||
<figure class="figure">
|
||||
<img class="for-light" src="/img/agents-zh-light.png" width="2080" height="1058" loading="lazy" alt="Agent 页:每个 Agent 一行,显示当前模型,底部是保存的方案">
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -406,6 +406,7 @@ footer a:hover { color: var(--ink); }
|
||||
<li><img class="bird" src="/icons/devin.svg" alt="">Devin</li>
|
||||
<li><img class="bird" src="/icons/hermes.svg" alt="">Hermes Agent</li>
|
||||
<li><img class="bird" src="/icons/xai.svg" alt="">Grok Build</li>
|
||||
<li><img src="/icons/zcode.png" alt="">ZCode</li>
|
||||
</ul>
|
||||
<p style="margin-top:34px">Every model</p>
|
||||
<ul>
|
||||
|
||||
Reference in new issue
Block a user