Files
magpie/internal/provider/migrate_zcode.go
T

104 lines
3.7 KiB
Go

package provider
import (
"encoding/json"
"errors"
"strings"
"time"
)
// ZCode's accounts on @magpie-community/opencode-zcode-auth: the plan's
// key (else ZCode's session token) as the oauth entry's access, the rest
// the requests need as its refresh, as the plugin's own sign-in keeps
// them. Nothing here rotates: a key lasts until it is deleted.
//
// ZCode's own sign-in goes marked as ZCode's (source "zcode"), with a copy
// of what magpie reads now: the plugin, 0.1.2 on (the move installs no
// older), reads ZCode's own credentials in its place for each request, as
// the built-in does, so it follows ZCode's switches and the session token
// ZCode renews (the Start Plan).
func init() {
movers["zcode"] = &mover{
pkg: "@magpie-community/opencode-zcode-auth",
min: "0.1.5", // a failure's status and its sign-in mark as the built-in's; MCP quota set aside as the built-in's
agents: []string{"zcode"},
out: func() ([]Moving, error) {
var out []Moving
for _, l := range zcodeLogins() {
// the own account's copy goes back to nothing: ZCode keeps it
out = append(out, Moving{User: l.User, First: l.Active, On: l.On, Lapsed: l.Lapsed != "", Plan: l.Plan, Own: l.Own, Auth: zcodeOut(l.User, l.Plan, l.key, l.Own)})
}
return out, nil
},
back: func(ls []savedLogin, user string, auth map[string]any) ([]savedLogin, string, error) {
var s struct {
Site, Device, Key, Base, JWT, Token, Org, Project, Plan, Source string
}
if json.Unmarshal([]byte(str(auth["refresh"])), &s) != nil {
// a key signed in to in the plugin
if str(auth["type"]) != "api" || str(auth["key"]) == "" {
return ls, "", errors.New("ZCode: an unreadable plugin sign-in")
}
s.Key = str(auth["key"])
if m, _ := auth["metadata"].(map[string]any); m != nil {
s.Site = str(m["site"])
}
} else if s.Key == "" && s.JWT == "" {
s.Key = str(auth["access"])
}
// ZCode's own goes back to nothing: ZCode keeps it
if _, _, ok := zcodeOwn(); ok && s.Source == "zcode" {
return ls, ownUser(ls, "zcode", firstNonEmpty(user, str(auth["accountId"]))), nil
}
k := zcodeKey{Key: s.Key, Base: s.Base, JWT: s.JWT, Token: s.Token, Org: s.Org, Project: s.Project}
if k.Base == "" {
k.Base = ZCodeZaiBase
if s.Site == "bigmodel" {
k.Base = ZCodeBigModelBase
}
}
if k.Key == "" && k.JWT == "" && !k.team() {
return ls, "", errors.New("ZCode: an unreadable plugin sign-in")
}
if user == "" {
user = firstNonEmpty(str(auth["accountId"]), "ZCode")
}
i := backInto(&ls, "zcode", user)
b, err := json.Marshal(k)
if err != nil {
return ls, "", err
}
ls[i].Auth, ls[i].Lapsed, ls[i].Seen = b, "", time.Now().UTC().Truncate(time.Second)
if s.Plan != "" {
ls[i].Plan = s.Plan
}
return ls, ls[i].User, nil
},
}
}
// zcodeOut is a ZCode account as the plugin's sign-in keeps one;
// ZCode's own is marked as ZCode's, for the plugin to follow.
func zcodeOut(user, plan string, k zcodeKey, own bool) map[string]any {
site := "zai"
if strings.Contains(k.Base, "bigmodel.cn") {
site = "bigmodel"
}
state := map[string]any{"site": site, "device": zcodeDeviceMid(), "base": k.Base}
if own {
state["source"] = "zcode"
}
for name, v := range map[string]string{"key": k.Key, "jwt": k.JWT, "token": k.Token, "org": k.Org, "project": k.Project, "plan": plan} {
if v != "" {
state[name] = v
}
}
expires := int64(0)
if k.Key == "" && !k.team() && !own { // ZCode's own is read anew each time
if exp, _ := jwtClaims(k.JWT)["exp"].(float64); exp > 0 {
expires = int64(exp) * 1000
}
}
return map[string]any{"type": "oauth", "access": firstNonEmpty(k.Key, k.JWT), "refresh": jsonText(state), "expires": expires, "accountId": user}
}