mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
产品名早已是 ModelRouter,但仓库标识符仍沿用旧名。这次统一,读写两侧都做兼容, 已部署的机器不会因为顺序问题立刻失效。 - go module `llmsproxy` → `modelrouter`;全仓 import 与 `cmd/modelrouter` 同步。 - `Makefile BINARY=modelrouter`;发行包名、systemd unit、/usr/share 资源、 `/usr/local/bin` 目标全部改为 modelrouter。 - 默认配置目录 `/etc/llmsproxy` → `/etc/modelrouter`。**读取侧回退**:插件与 `storeDir` 仍会检查旧路径,所以"先换二进制、后改 unit"不会立即失效。 - 环境变量 `LLMSPROXY_*`(PLUGIN_DIR / XDISPLAY_BASE / ARCH / LIVE_ZEN / BUILD_DIR)改为 `MODELROUTER_*`;`LLMS_PROXY_MASTER_KEY` → `MODELROUTER_MASTER_KEY`。旧名在读取侧继续接受。 - 桌面端内嵌核心 `bin/llmsproxy` → `bin/modelrouter`,并保留旧文件名回退, 避免升级到一半的 profile 被判成"core binary missing"。 - 新增 `packaging/migrate-to-modelrouter.sh`:**默认 dry-run**,复制而非移动、 旧目录永不删除、支持 `--rollback`;随 release tar 分发并由 core-dist 校验。 - `deploy.sh` 增加改名保护:发现旧 `/etc/llmsproxy` 而新配置不存在时**中止**, 否则会部署出一个"能启动但没有 key、没有源"的空实例。 - 临时目录前缀 `/tmp/llmsproxy-*` → `/tmp/modelrouter-*`;清理逻辑同时回收两代。 兼容别名(`__llmsproxy_*` 全局、`llmsproxy` SDK 别名、旧路径/旧 env)刻意保留, 它们是升级路径而非遗留;docs/plugins.md 已标注为废弃。 本提交未包含:`adapters/opencodezen.lua`、`adapter_opencodezen_test.go`、 `adapter_opencodezen_live_test.go`(OpenCode UA/免费池的既有改动,另批提交)。
76 lines
2.6 KiB
Go
76 lines
2.6 KiB
Go
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"modelrouter/internal/lua"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestLobsterExchangePreservesIdentity(t *testing.T) {
|
|
var calls int
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
var b map[string]string
|
|
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if b["authCode"] != "quote\"&code" || len(b["uuid"]) != 36 || b["firstKeyfrom"] == "" || b["firstKeyfrom"] == "cli" {
|
|
t.Errorf("invalid exchange fields: %#v", b)
|
|
}
|
|
if r.Header.Get("Content-Type") != "application/json" || r.Header.Get("User-Agent") != "LobsterAI/0.1.0" {
|
|
t.Error("wrong headers")
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"code":0,"data":{"accessToken":"at-test","refreshToken":"rt-test","expiresIn":3600,"user":{"id":"uid1","userId":"account1","nickname":"test"}}}`))
|
|
}))
|
|
defer ts.Close()
|
|
c := kernelTestCore(t)
|
|
ps := c.Plugins()
|
|
src, err := readBundledChannels(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = ps.LoadSource("channels", src); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The fixture changes only the transport destination, not the upstream contract.
|
|
if _, err = ps.Call("channels", "set_endpoints", map[string]interface{}{"platform": "lobsterai", "callback": map[string]interface{}{"token_url": ts.URL}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := ps.InvokeHandler("channels", "start_login", &lua.HandlerRequest{Method: "POST", Body: `{"platform":"lobsterai"}`, Role: "admin"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nonce := out.JSON.(map[string]interface{})["session"].(map[string]interface{})["nonce"].(string)
|
|
req := &lua.HandlerRequest{Method: "GET", Query: map[string]string{"state": nonce, "authCode": "quote\"&code"}}
|
|
out, err = ps.InvokeHandler("channels", "oauth_callback", req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Status != 200 {
|
|
t.Fatalf("callback: %#v", out)
|
|
}
|
|
raw, ok, err := ps.GetSecret("channels", "channels:lobsterai")
|
|
if err != nil || !ok {
|
|
t.Fatal("missing credential", err)
|
|
}
|
|
var cred map[string]interface{}
|
|
if err = json.Unmarshal([]byte(raw), &cred); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, k := range []string{"access_token", "refresh_token", "uuid", "first_keyfrom", "latest_keyfrom", "expires_at", "uid", "user_id"} {
|
|
if cred[k] == nil || cred[k] == "" {
|
|
t.Errorf("missing %s", k)
|
|
}
|
|
}
|
|
out, err = ps.InvokeHandler("channels", "oauth_callback", req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Status != 409 || calls != 1 {
|
|
t.Fatalf("replay accepted: status=%d calls=%d", out.Status, calls)
|
|
}
|
|
}
|