Files
Lyric WaiandClaude Opus 5.5 5fb7799b11 agents: Mister Morph (morph) — picking a magpie model points ~/.morph/config.yaml's llm at the gateway ($MISTER_MORPH_CONFIG names another file) (#728)
Morph has no list of providers to add magpie to, so magpie takes llm over:
inference_provider openai_response_compatible (the Responses API, morph's
own default), endpoint the gateway's /v1 (morph adds no second /v1), api_key
magpie's token, model the catalog id, context_window_tokens the window magpie
knows (morph knows one only from its own list), and headers.User-Agent
mistermorph so the gateway credits its requests to morph. llm.provider is
left alone: morph derives it from inference_provider. What the user had under
those keys is stashed and put back when they pick a model of their own or
reset; comments, other headers and other keys stay. An effort field sets
llm.reasoning_effort. Found by ~/.morph, not by the morph command (a common
name). Also on WSL. In NO_PICKER, as morph's /models switches profiles, not
models.

TestMorph and TestMorphContextWindow. go vet, builds (GOOS=linux/darwin
-tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp
HOME pass, but for TestPluginProxy, TestPluginSOCKSProxy and
TestCodexArchivedSameSizeRewriteInvalidatesDiscovery, which fail on main too.
With the installed morph against a sandbox gateway and a mock vendor, morph
run's requests went to /v1/responses, were relayed as chat completions and
were credited to morph; magpie morph default restored the user's file. Not
run against a real model.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:57:44 +08:00

166 lines
5.6 KiB
Go

package agent
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/gateway"
"github.com/yetone/magpie/internal/provider"
"gopkg.in/yaml.v3"
)
func TestMorph(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("MISTER_MORPH_CONFIG", "")
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", "flash"}}); err != nil {
t.Fatal(err)
}
dir := filepath.Join(home, ".morph")
path := filepath.Join(dir, "config.yaml")
read := func() (map[string]any, string) {
var c map[string]any
b, _ := os.ReadFile(path)
yaml.Unmarshal(b, &c)
llm, _ := c["llm"].(map[string]any)
return llm, string(b)
}
os.MkdirAll(dir, 0o755)
mine := "# mine\nllm:\n inference_provider: openai\n provider: openai_resp\n model: \"gpt-5.4\" # main\n" +
" endpoint: \"https://api.openai.com\"\n api_key: \"${OPENAI_API_KEY}\"\n context_window_tokens: 100000\n" +
" headers:\n X-Team: core\n reasoning_effort: high\ntools:\n bash:\n enabled: true\n"
os.WriteFile(path, []byte(mine), 0o644)
a := morph(home)
if !a.Detected() {
t.Fatal("not found by ~/.morph")
}
f := a.Field("model")
if f.Get() != "gpt-5.4" || a.Check() != "" {
t.Fatalf("get: %q, check %q", f.Get(), a.Check())
}
if err := f.Set("magpie/deepseek/pro"); err != nil {
t.Fatal(err)
}
llm, raw := read()
h, _ := llm["headers"].(map[string]any)
if llm["inference_provider"] != "openai_response_compatible" || llm["provider"] != "openai_resp" || llm["model"] != "deepseek/pro" ||
llm["endpoint"] != gateway.URL()+"/v1" || llm["api_key"] != gateway.Token || llm["context_window_tokens"] != nil ||
h["User-Agent"] != morphUA || h["X-Team"] != "core" || llm["reasoning_effort"] != "high" ||
!strings.Contains(raw, "# mine") || !strings.Contains(raw, "enabled: true") {
t.Fatalf("config:\n%s", raw)
}
if f.Get() != "magpie/deepseek/pro" || a.Check() != "" {
t.Fatalf("get: %q, check %q", f.Get(), a.Check())
}
// another magpie model keeps what was stashed first
if err := f.Set("magpie/deepseek/flash"); err != nil {
t.Fatal(err)
}
if llm, _ := read(); llm["model"] != "deepseek/flash" {
t.Fatalf("flash: %v", llm)
}
// its own model: what the user had comes back, magpie's header goes
if err := f.Set("gpt-5.5"); err != nil {
t.Fatal(err)
}
llm, raw = read()
h, _ = llm["headers"].(map[string]any)
if llm["model"] != "gpt-5.5" || llm["inference_provider"] != "openai" || llm["provider"] != "openai_resp" ||
llm["endpoint"] != "https://api.openai.com" || llm["api_key"] != "${OPENAI_API_KEY}" ||
llm["context_window_tokens"] != 100000 || len(h) != 1 || h["X-Team"] != "core" {
t.Fatalf("own:\n%s", raw)
}
// reset from magpie: back to what the user had
f.Set("magpie/deepseek/flash")
if err := f.Set(""); err != nil {
t.Fatal(err)
}
if llm, raw = read(); llm["model"] != "gpt-5.5" || llm["api_key"] != "${OPENAI_API_KEY}" {
t.Fatalf("reset:\n%s", raw)
}
// a config with nothing under llm: nothing of magpie's stays, headers
// magpie's User-Agent was alone in included
os.WriteFile(path, []byte("tools:\n bash:\n enabled: true\n"), 0o644)
f.Set("magpie/deepseek/pro")
if llm, raw = read(); llm["provider"] != nil {
t.Fatalf("provider written:\n%s", raw)
}
if err := f.Set(""); err != nil {
t.Fatal(err)
}
if llm, raw = read(); len(llm) != 0 || !strings.Contains(raw, "enabled: true") {
t.Fatalf("reset, nothing of its own:\n%s", raw)
}
// the gateway's address changed under it: said so
f.Set("magpie/deepseek/pro")
b, _ := os.ReadFile(path)
os.WriteFile(path, []byte(strings.Replace(string(b), gateway.URL(), "http://127.0.0.1:1", 1)), 0o644)
if c := a.Check(); !strings.Contains(c, "endpoint") {
t.Fatalf("check: %q", c)
}
// MISTER_MORPH_CONFIG names the file
other := filepath.Join(home, "elsewhere.yaml")
t.Setenv("MISTER_MORPH_CONFIG", other)
if err := morph(home).Field("model").Set("magpie/deepseek/pro"); err != nil {
t.Fatal(err)
}
if b, err := os.ReadFile(other); err != nil || !strings.Contains(string(b), "deepseek/pro") {
t.Fatalf("MISTER_MORPH_CONFIG: %s %v", b, err)
}
if a, err := Find("morph"); err != nil || a.Name != "Mister Morph" {
t.Fatal("not among All()")
}
}
// The window magpie knows a model to take is handed to morph, which knows
// one only from a list of its own.
func TestMorphContextWindow(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("MISTER_MORPH_CONFIG", "")
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", "flash"}, Contexts: map[string]int{"pro": 1000000}}); err != nil {
t.Fatal(err)
}
path := filepath.Join(home, ".morph", "config.yaml")
window := func() (int, string) {
var c struct {
LLM struct {
Window int `yaml:"context_window_tokens"`
} `yaml:"llm"`
}
b, _ := os.ReadFile(path)
yaml.Unmarshal(b, &c)
return c.LLM.Window, string(b)
}
f := morph(home).Field("model")
if err := f.Set("magpie/deepseek/pro"); err != nil {
t.Fatal(err)
}
if n, raw := window(); n != 1000000 {
t.Fatalf("pro: %d\n%s", n, raw)
}
// one whose window magpie doesn't know: morph's own guess, not pro's
if err := f.Set("magpie/deepseek/flash"); err != nil {
t.Fatal(err)
}
if n, raw := window(); n != 0 {
t.Fatalf("flash: %d\n%s", n, raw)
}
}