@magpie-community/opencode-trae-auth 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/index.mjs +68 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,3 +107,22 @@ magpie's usage card shows the account's credits:
|
|
|
107
107
|
unlimited) and what the pack has used.
|
|
108
108
|
- **What it shows:** credits left as the balance, and the share used as a
|
|
109
109
|
window.
|
|
110
|
+
|
|
111
|
+
## Daily check-in
|
|
112
|
+
|
|
113
|
+
Trae CN gives credits for a daily check-in (每日签到). magpie can press it
|
|
114
|
+
once a day for each account (Settings, or the switch on the usage card).
|
|
115
|
+
It asks Trae CN's own pages through
|
|
116
|
+
this plugin's fetch, which sends them as the account: its Cloud-IDE-JWT
|
|
117
|
+
(renewed first when near its end) and its device id.
|
|
118
|
+
|
|
119
|
+
- **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
|
|
120
|
+
body `{}`: `enable`, `checked_in`, `credits`.
|
|
121
|
+
- **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
|
|
122
|
+
body `{}`, only while it is on and today's isn't in.
|
|
123
|
+
- **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
|
|
124
|
+
the account for a new sign-in. Code 9095 means this device has checked
|
|
125
|
+
in today; nothing sends another device id to get round it.
|
|
126
|
+
|
|
127
|
+
Only `api.trae.cn`'s `/trae/api/` pages are sent this way; any other URL
|
|
128
|
+
that isn't a chat request is still refused (400). Needs 0.1.4 or later.
|
package/index.mjs
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
// agent's /api/agent/v3/llm_utils_chat, which answers in its own SSE
|
|
7
7
|
// events. Worked out from two open-source relays (wangqi233/trae2api and
|
|
8
8
|
// autumnsentiment/Trae2api-cn); not run against a real account here.
|
|
9
|
+
// The account's fetch also sends Trae CN's own pages on api.trae.cn
|
|
10
|
+
// (/trae/api/…) as the account, for magpie's daily check-in (每日签到:
|
|
11
|
+
// /trae/api/v2/ug/checkin_credits/status, then /claim).
|
|
9
12
|
import { randomBytes, randomUUID, randomInt } from "node:crypto"
|
|
10
13
|
import { createServer, STATUS_CODES } from "node:http"
|
|
11
14
|
|
|
@@ -132,6 +135,18 @@ function apiOf(a) {
|
|
|
132
135
|
return /mchost\.guru|trae-api-/i.test(h) ? h : HOSTS.api
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
// ownPage: url is one of Trae CN's own JSON pages on api.trae.cn
|
|
139
|
+
// (/trae/api/…: the daily check-in, credits), which the account's fetch
|
|
140
|
+
// sends as the account rather than as a chat
|
|
141
|
+
function ownPage(url) {
|
|
142
|
+
try {
|
|
143
|
+
const u = new URL(url)
|
|
144
|
+
return u.origin === new URL(HOSTS.auth).origin && u.pathname.startsWith("/trae/api/")
|
|
145
|
+
} catch {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
// ---- errors --------------------------------------------------------------------
|
|
136
151
|
|
|
137
152
|
class Expired extends Error {
|
|
@@ -819,6 +834,32 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
819
834
|
return { status: res.status, text, v: parseJSON(text) }
|
|
820
835
|
}
|
|
821
836
|
|
|
837
|
+
// ownRequest sends a request for one of Trae CN's own JSON pages as the
|
|
838
|
+
// account, as its IDE does: magpie asks the daily check-in
|
|
839
|
+
// (/trae/api/v2/ug/checkin_credits/status, then /claim) through the
|
|
840
|
+
// account's fetch, as it does WorkBuddy's. Trae's answer comes back as it
|
|
841
|
+
// is; a token it turned away (401, or code 1001 in a 200) marks the
|
|
842
|
+
// sign-in, as usage does.
|
|
843
|
+
const ownRequest = async (getAuth, a0, url, method, body, signal) => {
|
|
844
|
+
let a
|
|
845
|
+
try {
|
|
846
|
+
a = await fresh(getAuth)
|
|
847
|
+
} catch (e) {
|
|
848
|
+
if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
|
|
849
|
+
throw e
|
|
850
|
+
}
|
|
851
|
+
const res = await fetch(url, {
|
|
852
|
+
method,
|
|
853
|
+
headers: ideHeaders(a, { Accept: "application/json" }),
|
|
854
|
+
body: method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}",
|
|
855
|
+
signal: signal ?? AbortSignal.timeout(20_000),
|
|
856
|
+
})
|
|
857
|
+
const text = await res.text()
|
|
858
|
+
const lapsed = res.status === 401 || lapsedCode(errorOf(text).code)
|
|
859
|
+
const headers = { "content-type": res.headers.get("content-type") ?? "application/json", "X-Magpie-Sign-In": lapsed ? "expired" : a.renewed ? "renewed" : "kept" }
|
|
860
|
+
return new Response(text, { status: res.status, headers })
|
|
861
|
+
}
|
|
862
|
+
|
|
822
863
|
const usage = async (getAuth) => {
|
|
823
864
|
let a
|
|
824
865
|
try {
|
|
@@ -889,7 +930,8 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
889
930
|
// The lists come from batch_get_detail_param, as TRAE SOLO CN asks them;
|
|
890
931
|
// when that gives none, from get_detail_param, one function at a time.
|
|
891
932
|
// A model several functions list is asked through the first whose entry
|
|
892
|
-
// names a __dev model (one Trae serves), else the
|
|
933
|
+
// names a __dev model (one Trae serves) and isn't misnamed, else the
|
|
934
|
+
// first that names one, else the first.
|
|
893
935
|
const liveModels = async (a) => {
|
|
894
936
|
let lists = null
|
|
895
937
|
try {
|
|
@@ -907,13 +949,35 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
907
949
|
if (g.status === "fulfilled") lists[FUNCTIONS[i]] = g.value
|
|
908
950
|
})
|
|
909
951
|
}
|
|
952
|
+
// a function can give a model another's name: chat_v3 calls
|
|
953
|
+
// deepseek-v4.1-flash "DeepSeek-V4-Flash 正式版", as it does
|
|
954
|
+
// DeepSeek-V4-Flash-Official, where SOLO's lists call it
|
|
955
|
+
// DeepSeek-V4.1-Flash (yetone/magpie#681) — Trae's usage page then books
|
|
956
|
+
// it under that name too. Such an entry is misnamed: its name is another
|
|
957
|
+
// model's in the same list, and other lists name it otherwise.
|
|
958
|
+
const nameOf = (m) => String(m.display_config?.display_name || m.display_name || m.display_model_name || m.config_name)
|
|
959
|
+
const names = new Map() // id → the names its lists give it
|
|
960
|
+
for (const fn of FUNCTIONS) {
|
|
961
|
+
for (const m of lists[fn] ?? []) {
|
|
962
|
+
if (!chatModel(m)) continue
|
|
963
|
+
const id = String(m.config_name)
|
|
964
|
+
names.set(id, (names.get(id) ?? new Set()).add(nameOf(m)))
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const misnamed = (fn, m) => {
|
|
968
|
+
const id = String(m.config_name), name = nameOf(m)
|
|
969
|
+
return names.get(id).size > 1 && (lists[fn] ?? []).some((o) => chatModel(o) && String(o.config_name) !== id && nameOf(o) === name)
|
|
970
|
+
}
|
|
971
|
+
// a function's entry is worth more when it names a __dev model, then
|
|
972
|
+
// when it isn't misnamed; the first of the best is taken
|
|
973
|
+
const worth = (fn, m) => (devModel(m) ? 2 : 0) + (misnamed(fn, m) ? 0 : 1)
|
|
910
974
|
const out = new Map()
|
|
911
975
|
for (const fn of FUNCTIONS) {
|
|
912
976
|
for (const m of lists[fn] ?? []) {
|
|
913
977
|
if (!chatModel(m)) continue
|
|
914
978
|
const id = String(m.config_name)
|
|
915
979
|
const was = out.get(id)
|
|
916
|
-
if (!was || (
|
|
980
|
+
if (!was || worth(fn, m) > was.worth) out.set(id, { m, fn, worth: worth(fn, m) })
|
|
917
981
|
}
|
|
918
982
|
}
|
|
919
983
|
for (const [id, { m, fn }] of out) {
|
|
@@ -981,6 +1045,8 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
981
1045
|
let body = init.body ?? (r0 ? await r0.text() : undefined)
|
|
982
1046
|
if (body instanceof ArrayBuffer) body = new TextDecoder().decode(body)
|
|
983
1047
|
else if (ArrayBuffer.isView(body)) body = new TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength))
|
|
1048
|
+
const url = r0 ? r0.url : input instanceof URL ? input.href : String(input)
|
|
1049
|
+
if (ownPage(url)) return ownRequest(getAuth, a0, url, init.method ?? r0?.method ?? "POST", body, init.signal ?? r0?.signal)
|
|
984
1050
|
const req = parseJSON(body)
|
|
985
1051
|
if (!Array.isArray(req.messages)) return errorResponse(400, "Trae CN: only chat completions are served")
|
|
986
1052
|
let a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "OpenCode provider plugin for Trae CN (trae.cn): your Trae CN account's models, free tier included, with Trae's own browser sign-in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|