mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
feat(gui): Electron desktop with embedded core, tray, autostart, win/linux packaging
- cmd/gui: Electron shell (Clash-Verge style) embedding the full WebUI 1:1 - embedded llmsproxy core (luajit) with auto-generated profile - key stored in keys[] (non-seed) so no replace-the-key warning - gw_key cookie injection: web UI works without login - side-rail toggles for autostart / silent start - system tray with status + controls, silent start (--silent) - win cross-build (mingw luajit exe + dll) / deb / AppImage via electron-builder - Makefile: build / gui / gui-dist / gui-deb / gui-win targets - README: desktop GUI section - lua(adapter): opencode normalizes non-whitelisted roles to system
This commit is contained in:
@ -16,6 +16,17 @@ adapter.headers = {
|
||||
-- zen 上游 schema 只接受 text content part(无视觉/音频能力):多模态 part
|
||||
-- (image_url / input_audio / file 等)一律剥离;因此失去全部 content 的
|
||||
-- 消息整条丢弃,避免上游 "unknown variant `image_url`, expected `text`"。
|
||||
-- zen 上游角色白名单只有 system / user / assistant / tool / latest_reminder:
|
||||
-- OpenAI 的 developer(及 function 等)不在其中,直接透传会触发上游
|
||||
-- "unknown variant `developer`, expected one of ..." 错误;统一归一化为 system。
|
||||
local ROLE_WHITELIST = {
|
||||
system = true,
|
||||
user = true,
|
||||
assistant = true,
|
||||
tool = true,
|
||||
latest_reminder = true,
|
||||
}
|
||||
|
||||
function adapter.transform_request(raw_body)
|
||||
local ok, req = pcall(json.decode, raw_body)
|
||||
if not ok then return raw_body end
|
||||
@ -24,6 +35,9 @@ function adapter.transform_request(raw_body)
|
||||
if req.messages then
|
||||
local kept = {}
|
||||
for _, msg in ipairs(req.messages) do
|
||||
if type(msg.role) == "string" and not ROLE_WHITELIST[msg.role] then
|
||||
msg.role = "system"
|
||||
end
|
||||
msg.reasoning_content = nil
|
||||
local drop = false
|
||||
if type(msg.content) == "table" then
|
||||
|
||||
@ -302,3 +302,71 @@ func TestMultimodalTransform(t *testing.T) {
|
||||
t.Fatalf("openai passthrough lost multimodal content: %s", po)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenCodeAdapterNormalizesDeveloperRole(t *testing.T) {
|
||||
vm := NewVM(freshAdapterDir(t))
|
||||
if err := vm.Start(); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
defer vm.Stop()
|
||||
|
||||
raw := `{"model":"zen","messages":[` +
|
||||
`{"role":"developer","content":"be concise"},` +
|
||||
`{"role":"user","content":"hi"},` +
|
||||
`{"role":"assistant","content":"hello"},` +
|
||||
`{"role":"function","content":"{\"a\":1}"},` +
|
||||
`{"role":"latest_reminder","content":"remind"}]}`
|
||||
out, err := vm.Transform("opencode", "transform_request", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("transform_request: %v", err)
|
||||
}
|
||||
var req struct {
|
||||
Messages []struct {
|
||||
Role string `json:"role"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &req); err != nil {
|
||||
t.Fatalf("output not JSON: %v\n%s", err, out)
|
||||
}
|
||||
want := []string{"system", "user", "assistant", "system", "latest_reminder"}
|
||||
if len(req.Messages) != len(want) {
|
||||
t.Fatalf("got %d messages, want %d: %s", len(req.Messages), len(want), out)
|
||||
}
|
||||
for i, w := range want {
|
||||
if req.Messages[i].Role != w {
|
||||
t.Errorf("message %d role = %q, want %q", i, req.Messages[i].Role, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenCodeAdapterKeepsWhitelistedRolesAndDropsMultimodal(t *testing.T) {
|
||||
vm := NewVM(freshAdapterDir(t))
|
||||
if err := vm.Start(); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
defer vm.Stop()
|
||||
|
||||
raw := `{"messages":[` +
|
||||
`{"role":"system","content":"sys"},` +
|
||||
`{"role":"user","content":[{"type":"text","text":"keep"},{"type":"image_url","url":"x"}]},` +
|
||||
`{"role":"user","content":[{"type":"image_url","url":"x"}]}]}`
|
||||
out, err := vm.Transform("opencode", "transform_request", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("transform_request: %v", err)
|
||||
}
|
||||
var req struct {
|
||||
Messages []struct {
|
||||
Role string `json:"role"`
|
||||
Content any `json:"content"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &req); err != nil {
|
||||
t.Fatalf("output not JSON: %v\n%s", err, out)
|
||||
}
|
||||
if len(req.Messages) != 2 {
|
||||
t.Fatalf("got %d messages, want 2 (multimodal-only dropped): %s", len(req.Messages), out)
|
||||
}
|
||||
if req.Messages[0].Role != "system" || req.Messages[1].Role != "user" {
|
||||
t.Fatalf("unexpected roles: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user