Files
magpie/qwen_cli_test.go
易良 ae39bc8ec8 agents: Qwen Code (qwen) as a managed agent (#1311)
* agents: Qwen Code (qwen) as a managed agent (#1279)

Qwen Code (QwenLM/qwen-code, a Gemini CLI fork) keeps custom model
providers in ~/.qwen/settings.json modelProviders.<protocol> entries
({baseUrl, envKey, id, name}, envKey naming a variable in the same
file's env object) and its session model in settings.model
{name, baseUrl}.

magpie writes one modelProviders.openai entry per catalog model —
wire id the gateway takes, name magpie/<provider>/<model>, every one
on env.MAGPIE_API_KEY holding the gateway's key, which is how an entry
of magpie's is told from the user's own — and points settings.model at
a pick. Stash, Check/drift, Sync, port-move Rewire and disconnect work
as for the other JSON-config agents; on disconnect the user's entries,
model and key are restored, the file byte-identical to before.

Verified: go build, GOOS=linux nogui + GOOS=windows builds, go vet
./...; go test -tags nogui ./internal/agent (TestQwen* pass;
TestDriftUnwiredEveryAgent/qwen and TestPortMovesEveryAgent pass after
binding drift identity to the entry, not the address), go test -race
-count=3 -run TestQwen, go test . (TestAgentQwenDefault: set then
'default' restores the settings byte-for-byte). TestQwen fails with
'unknown agent' when the registration is reverted. Real use: a copy of
a live 341-line settings.json in a sandbox HOME — wire, then
'qwen default' left it byte-identical. Not run: GUI suite (no
gui/assets change); TestReasonixCLI* and TestPluginHTTP2TakesTheProxy
fail on main too (fixture/network), unchanged by this.

* agents: qwen honors QWEN_HOME, sets auth type, owns a qwen-specific key name, carries context windows (#1279 review)

- $QWEN_HOME stands for ~/.qwen, resolved as the other agents'
  (via place.getenv, appdir.Getenv; a distro's isn't read), and is in
  agentenv.Vars so the sandbox clears it
- wiring sets security.auth.selectedType openai so qwen skips the
  /auth prompt; the user's own (including a pre-existing "openai",
  told from magpie's by a stash marker) is restored on disengage
- envKey is MAGPIE_QWEN_API_KEY: a shell-exported or ~/.qwen/.env
  value wins over the settings' env, so the magpie-generic name is
  not used
- entries carry generationConfig.contextWindowSize from the catalog
- a settings.json without modelProviders reads as no models of anyone,
  instead of gjson's 'cannot use null as iterable'

Verified: TestQwen* incl. TestQwenAuthOpenAIOfTheUsersOwn and
TestQwenHomeOverride, TestAgentQwenDefault (CLI), -race -count=3;
unwired-drift and port-move generic tests pass. Real end-to-end:
dev gateway on 127.0.0.1:3999 in a sandbox HOME, real qwen binary in
a sandbox QWEN_HOME ran a prompt through the gateway (PONG), and
wire → default left the settings byte-identical. Reasonix fixture
tests fail on main too (environment).

* agents: qwen's requests are attributed by their QwenCode/<version> User-Agent
2026-10-08 20:33:11 +08:00

61 lines
1.5 KiB
Go

package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// `magpie qwen a/m` writes its provider entries and the gateway's key,
// `magpie qwen default` takes magpie back out and restores the model the
// user had, their own provider entry and settings kept.
func TestAgentQwenDefault(t *testing.T) {
groupsHome(t)
path := filepath.Join(os.Getenv("HOME"), ".qwen", "settings.json")
os.MkdirAll(filepath.Dir(path), 0o755)
own := `{
"env": {
"IDEALAB_API_KEY": "team-key"
},
"modelProviders": {
"openai": [
{
"baseUrl": "https://idealab.example.com/v1",
"envKey": "IDEALAB_API_KEY",
"id": "glm-5",
"name": "[IdeaLab] glm-5"
}
]
},
"model": {
"baseUrl": "https://idealab.example.com/v1",
"name": "glm-5"
},
"outputLanguage": "zh-CN"
}
`
if err := os.WriteFile(path, []byte(own), 0o644); err != nil {
t.Fatal(err)
}
read := func() string {
b, _ := os.ReadFile(path)
return string(b)
}
if err := run([]string{"qwen", "a/m"}); err != nil {
t.Fatal(err)
}
s := read()
for _, want := range []string{`"id": "a/m"`, `"name": "magpie/a/m"`, `"MAGPIE_QWEN_API_KEY"`, `"selectedType": "openai"`, `"baseUrl": "https://idealab.example.com/v1"`, `"outputLanguage": "zh-CN"`} {
if !strings.Contains(s, want) {
t.Fatalf("not wired (%s missing):\n%s", want, s)
}
}
if err := run([]string{"qwen", "default"}); err != nil {
t.Fatal(err)
}
if s = read(); s != own {
t.Fatalf("default didn't put back what was there:\n%s", s)
}
}