Files
magpie/internal/provider/zcode_device.go
T

65 lines
1.9 KiB
Go

package provider
// ZCode names the machine it runs on to zcode.z.ai: its apiClient sends
// X-Device-Mid, a UUID it keeps as deviceMid, on every request, and
// /api/v1/zcode-plan/billing/balance refuses one without it (400, code
// 3001 "parameter error", #282). The server only asks that it is there, so
// magpie keeps a UUID of its own, made once for this machine beside
// providers.json — never ZCode's.
import (
"crypto/rand"
"encoding/hex"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
)
var zcodeDevices = struct {
sync.Mutex
m map[string]string // by the file it is kept in
}{m: map[string]string{}}
var zcodeUUIDRe = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
func zcodeDevicePath() string { return filepath.Join(filepath.Dir(Path()), "zcode-device-mid") }
// zcodeDeviceMid is this machine's X-Device-Mid: the one kept, or a new
// one, kept for next time (and used for now if it can't be).
func zcodeDeviceMid() string {
p := zcodeDevicePath()
zcodeDevices.Lock()
defer zcodeDevices.Unlock()
if id := zcodeDevices.m[p]; id != "" {
return id
}
if b, err := os.ReadFile(p); err == nil {
if id := strings.ToLower(strings.TrimSpace(string(b))); zcodeUUIDRe.MatchString(id) {
zcodeDevices.m[p] = id
return id
}
}
var b [16]byte
_, _ = rand.Read(b[:])
b[6] = b[6]&0x0f | 0x40 // version 4
b[8] = b[8]&0x3f | 0x80 // variant 10
h := hex.EncodeToString(b[:])
id := h[0:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:32]
if os.MkdirAll(filepath.Dir(p), 0o755) == nil {
_ = os.WriteFile(p, []byte(id+"\n"), 0o600)
}
zcodeDevices.m[p] = id
return id
}
// zcodeDeviceHeader names the machine on a request to zcode.z.ai, as
// ZCode does; Z.ai's and BigModel's business APIs are asked without it.
func zcodeDeviceHeader(req *http.Request) {
if strings.HasPrefix(req.URL.String(), zcodeAPI+"/") {
req.Header.Set("X-Device-Mid", zcodeDeviceMid())
}
}