Files
magpie/internal/qoder/codec.go
T
yetone fa9daa6945 docs: every built-in subscription a plugin serves once moved says so in its code, and AGENTS.md says where a fix for it goes (user: 对内置的订阅 provider 在代码层面和文档层面加上注释,防止又有别的 agent 在改内置订阅 provider 的代码而不知道生效的其实是对应的插件代码) — a moved Cursor, Devin, Grok, Command Code plan, Kiro, Zed, Qoder, Factory, Xiaomi MiMo, ZCode or WorkBuddy (provider.Moved; since v0.1.642 also the default for a new sign-in, provider.Adopt) is signed in, listed, requested and metered by its @magpie-community plugin, and none of the built-in's code in internal/provider/<name>*.go, internal/gateway/<name>.go, internal/zed or internal/qoder serves it; a fix made there alone never reaches those users. Each of those files now opens with a PLUGIN-SERVED notice naming the subscription's id, its plugin package, where the plugin lives (magpie-community/plugins, packages/<name>) and the mover whose min to raise so moved users get the fixed plugin (keepMovedCurrent); Qoder's adds that Qoder CN isn't moved and still runs there. AGENTS.md (CLAUDE.md imports it) lists the moved built-ins with their packages and movers, how to tell whether a user's is moved (move.state "plugin", onPlugins), the order a fix goes in (the plugin, publish, raise min, the built-in only for those not moved), the magpie code that does run for a moved one (internal/plugin, plugins.go, plugin_usage.go, pluginsignin.go, migrate*.go, app.js's subOf/pluginSubs/startPluginSignIn), and the parity tests. movers in migrate.go and SUBS in app.js point there too. TestMovedBuiltinsSayTheirPlugin fails for a mover whose code carries no PLUGIN-SERVED notice naming its id and package (it failed while a wrapped package name was split). Comments and docs only, no behaviour changed. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut failed once on their timing under load, pass alone; untouched).
2026-10-02 19:37:02 +08:00

130 lines
3.8 KiB
Go

package qoder
// PLUGIN-SERVED (see AGENTS.md): Qoder ("qoder") is a deprecated built-in
// subscription served by its plugin, @magpie-community/opencode-qoder-auth,
// once moved onto it (provider.Moved; the default for a new sign-in). A
// moved one's sign-ins, models, requests and usage are all the plugin's,
// never this code's (only the move, in migrate*.go, still reads its
// accounts). A fix here alone doesn't reach those users; fix the plugin
// (github.com/magpie-community/plugins, packages/qoder) and raise the
// mover's min in internal/provider/migrate_qoder.go. Qoder CN ("qoder-cn")
// isn't moved and still runs here.
import "strings"
// The body codec: the worker base64-encodes the plaintext with a shuffled
// alphabet, then swaps the first and last thirds of the result. Ported
// verbatim from CLIProxyAPI, whose alphabet and grouping were dumped from a
// live Qoder client and verified end-to-end against captured traffic.
//
// Alphabet (64 chars, dumped from live Qoder.exe memory):
//
// _doRTgHZBKcGVjlvpC,@aFSx#DPuNJme&i*MzLOEn)sUrthbf%Y^w.(kIQyXqWA!
//
// Grouping is standard base64: four chars = 24 bits = three plaintext bytes.
// '!' is the 64th char and encodes the 6-bit value 63; '$' is the pad, which
// holds a slot but carries no data.
const (
// BodyAlphabet is the custom base64 alphabet.
BodyAlphabet = "_doRTgHZBKcGVjlvpC,@aFSx#DPuNJme&i*MzLOEn)sUrthbf%Y^w.(kIQyXqWA!"
)
var bodyAlphabetIndex = func() [256]int8 {
var m [256]int8
for i := range m {
m[i] = -1
}
for i, c := range BodyAlphabet {
m[c] = int8(i)
}
return m
}()
// groupBytes decodes one 4-char group to its N bytes, N = floor(6*k/8) where
// k is the number of non-pad chars. '$' is skipped; '!' maps to 63.
func groupBytes(grp string) []byte {
var vals []int
for _, c := range grp {
if c == '$' {
continue
}
vals = append(vals, int(bodyAlphabetIndex[c]))
}
nv := len(vals)
if nv == 0 {
return nil
}
x := 0
for _, v := range vals {
x = (x << 6) | v
}
nb := (6 * nv) / 8
out := make([]byte, nb)
for k := 0; k < nb; k++ {
sh := 6*nv - 8*(k+1)
out[k] = byte((x >> sh) & 255)
}
return out
}
// segmentEncode encodes bytes through the alphabet as one continuous base64
// bitstream (MSB first): each byte adds 8 bits, every 6 emitted as a char; a
// final partial group is zero-padded into one char; the char count is padded
// out to a multiple of 4 with '$'. The 6-bit value 63 is emitted as '!'.
func segmentEncode(data []byte) string {
var sb strings.Builder
acc, nb := 0, 0
emit := func(v int) { sb.WriteByte(BodyAlphabet[v]) }
for _, b := range data {
acc = (acc << 8) | int(b)
nb += 8
for nb >= 6 {
nb -= 6
emit((acc >> nb) & 0x3F)
}
}
if nb > 0 {
emit((acc << (6 - nb)) & 0x3F)
}
for sb.Len()%4 != 0 {
sb.WriteByte('$')
}
return sb.String()
}
// BodyEncode encodes one plaintext into custom base64 (no third swap).
func BodyEncode(data []byte) string { return segmentEncode(data) }
// BodyDecode decodes custom base64 (no third swap).
func BodyDecode(s string) []byte {
var out []byte
for gi := 0; gi+4 <= len(s); gi += 4 {
if b := groupBytes(s[gi : gi+4]); b != nil {
out = append(out, b...)
}
}
return out
}
// swapBodyOuterThirds exchanges the first and last thirds of an encoded
// string; the middle third stays put.
func swapBodyOuterThirds(encoded string) string {
third := len(encoded) / 3
if third == 0 {
return encoded
}
return encoded[len(encoded)-third:] + encoded[third:len(encoded)-third] + encoded[:third]
}
// EncodeRequestBody is the wire body for an agent_chat_generation request:
// custom base64, then the outer-third swap.
func EncodeRequestBody(plaintext []byte) string {
return swapBodyOuterThirds(BodyEncode(plaintext))
}
// DecodeRequestBody reverses the swap, then decodes.
func DecodeRequestBody(wire string) []byte {
return BodyDecode(swapBodyOuterThirds(wire))
}