@magpie-community/opencode-trae-auth 0.2.0 → 0.2.2
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 +12 -5
- package/index.mjs +60 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -192,16 +192,23 @@ Trae CN gives credits for a daily check-in (每日签到); Trae Global has no
|
|
|
192
192
|
such page, so this is CN only. magpie can press it
|
|
193
193
|
once a day for each account (Settings, or the switch on the usage card).
|
|
194
194
|
It asks Trae CN's own pages through
|
|
195
|
-
this plugin's fetch, which sends them as the account
|
|
196
|
-
(renewed first when near its
|
|
195
|
+
this plugin's fetch, which sends them as the account, as Trae CN's IDE
|
|
196
|
+
(3.3.104) sends them: its Cloud-IDE-JWT (renewed first when near its
|
|
197
|
+
end), its device (`x-device-id`, `x-device-brand`, `x-device-type`,
|
|
198
|
+
`x-os-version`, `x-app-version`) and a client's User-Agent (TRAE SOLO
|
|
199
|
+
CN's Electron shell). Since 0.2.1: Bun's own User-Agent and the chat's
|
|
200
|
+
headers got 9074 「当前参与用户太多,请稍后再试」 every time
|
|
201
|
+
(yetone/magpie#808).
|
|
197
202
|
|
|
198
203
|
- **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
|
|
199
|
-
body `{}
|
|
204
|
+
body `{"req_source":1}` (magpie's `{}` is sent as that): `enable`,
|
|
205
|
+
`checked_in`, `credits`.
|
|
200
206
|
- **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
|
|
201
|
-
|
|
207
|
+
the same, only while it is on and today's isn't in.
|
|
202
208
|
- **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
|
|
203
209
|
the account for a new sign-in. Code 9095 means this device has checked
|
|
204
|
-
in today; nothing sends another device id to get round it
|
|
210
|
+
in today; nothing sends another device id to get round it, nor to get
|
|
211
|
+
round a 9074.
|
|
205
212
|
|
|
206
213
|
Only `api.trae.cn`'s `/trae/api/` pages are sent this way; any other URL
|
|
207
214
|
that isn't a chat request is still refused (400). Needs 0.1.4 or later.
|
package/index.mjs
CHANGED
|
@@ -529,6 +529,42 @@ function ideHeaders(a, site, extra = {}) {
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
// ugUA is the User-Agent a growth page (/trae/api/v2/ug/…: the daily
|
|
533
|
+
// check-in) is sent with: TRAE SOLO CN's Electron shell, as its version
|
|
534
|
+
// is named. Bun's own (Bun/1.x) reads as a script, and Trae answers a
|
|
535
|
+
// script's check-in with 9074 「当前参与用户太多,请稍后再试」 however
|
|
536
|
+
// seldom it asks (yetone/magpie#808; qilimixingkong/trae-checkin found
|
|
537
|
+
// the same).
|
|
538
|
+
const ugUA = (site) =>
|
|
539
|
+
`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) TRAE-SOLO-CN/${site.clientVersion} Chrome/120.0.6099.291 Electron/28.2.10 Safari/537.36`
|
|
540
|
+
|
|
541
|
+
// ugHeaders are the headers Trae CN's IDE (3.3.104) sends a growth page
|
|
542
|
+
// with, and only those: its JWT, then its device (fb: x-device-id,
|
|
543
|
+
// x-device-brand, x-device-type, x-os-version, x-app-version), not the
|
|
544
|
+
// chat's tokens and versions.
|
|
545
|
+
function ugHeaders(a, site, extra = {}) {
|
|
546
|
+
return {
|
|
547
|
+
"Content-Type": "application/json",
|
|
548
|
+
Authorization: `Cloud-IDE-JWT ${a.token}`,
|
|
549
|
+
"User-Agent": ugUA(site),
|
|
550
|
+
"x-device-id": a.deviceId,
|
|
551
|
+
"x-device-brand": site.brand,
|
|
552
|
+
"x-device-type": "Windows",
|
|
553
|
+
"x-os-version": "10.0.22631",
|
|
554
|
+
"x-app-version": site.clientVersion,
|
|
555
|
+
...extra,
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ugPage: url is one of Trae's growth pages (the check-in)
|
|
560
|
+
const ugPage = (url) => {
|
|
561
|
+
try {
|
|
562
|
+
return new URL(url).pathname.startsWith("/trae/api/v2/ug/")
|
|
563
|
+
} catch {
|
|
564
|
+
return false
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
532
568
|
// Trae's chat takes text only and has no turn for a tool call or its
|
|
533
569
|
// result. The agent's tools are named to it natively and in a system
|
|
534
570
|
// prompt that asks for each call as a tagged block; earlier calls and
|
|
@@ -1047,6 +1083,7 @@ class NativeCalls {
|
|
|
1047
1083
|
const next = c.arguments
|
|
1048
1084
|
if (!next || prev.startsWith(next)) return // nothing new
|
|
1049
1085
|
if (next.startsWith(prev) || done(prev)) o.arguments = next // the arguments so far
|
|
1086
|
+
else if (resent(prev, next)) o.arguments = next // the whole call again, written apart (plugins#24)
|
|
1050
1087
|
else o.arguments = prev + next // a piece
|
|
1051
1088
|
}
|
|
1052
1089
|
take(tools = []) {
|
|
@@ -1064,6 +1101,19 @@ class NativeCalls {
|
|
|
1064
1101
|
}
|
|
1065
1102
|
}
|
|
1066
1103
|
|
|
1104
|
+
// resent says next is the call's whole arguments sent again after a
|
|
1105
|
+
// partial snapshot, not a piece of them: Trae resends a snapshot whose
|
|
1106
|
+
// punctuation isn't the first one's (− for -, ′ for '), so it doesn't
|
|
1107
|
+
// start with it. A whole object under the same first key as prev's, which
|
|
1108
|
+
// prev and it joined don't make whole; a piece that is a value of its own
|
|
1109
|
+
// (12, "ls", {}) doesn't open with prev's first key.
|
|
1110
|
+
function resent(prev, next) {
|
|
1111
|
+
const key = (s) => s.match(/^\s*\{\s*"((?:[^"\\]|\\.)*)"/)?.[1]
|
|
1112
|
+
if (!next.trimStart().startsWith("{") || !done(next) || done(prev + next)) return false
|
|
1113
|
+
const k = key(prev)
|
|
1114
|
+
return k !== undefined && k === key(next)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1067
1117
|
// done says arguments are a whole JSON value already
|
|
1068
1118
|
function done(a) {
|
|
1069
1119
|
if (!a) return false
|
|
@@ -1461,14 +1511,18 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1461
1511
|
if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
|
|
1462
1512
|
throw e
|
|
1463
1513
|
}
|
|
1464
|
-
//
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
1467
|
-
// 0.1.6
|
|
1514
|
+
// a growth page (the check-in) goes as the IDE sends it: its headers,
|
|
1515
|
+
// a client's User-Agent, and {req_source: 1} for an empty body. Every
|
|
1516
|
+
// check-in sent with Bun's User-Agent came back 9074, with the chat's
|
|
1517
|
+
// headers and {} (0.1.6, 0.1.9) as with the IDE's (0.1.7)
|
|
1518
|
+
// (yetone/magpie#808). Other pages go with the chat's headers.
|
|
1519
|
+
const ug = ugPage(url)
|
|
1520
|
+
let sent = method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}"
|
|
1521
|
+
if (ug && sent !== undefined && /^\s*(\{\s*\})?\s*$/.test(sent)) sent = JSON.stringify({ req_source: 1 })
|
|
1468
1522
|
const res = await fetch(url, {
|
|
1469
1523
|
method,
|
|
1470
|
-
headers: ideHeaders(a, site, { Accept: "application/json" }),
|
|
1471
|
-
body:
|
|
1524
|
+
headers: ug ? ugHeaders(a, site, { Accept: "application/json" }) : ideHeaders(a, site, { Accept: "application/json" }),
|
|
1525
|
+
body: sent,
|
|
1472
1526
|
signal: signal ?? AbortSignal.timeout(20_000),
|
|
1473
1527
|
})
|
|
1474
1528
|
const text = await res.text()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "OpenCode provider plugins for Trae: Trae CN (trae.cn) and Trae Global (trae.ai), free tier included, with Trae's own browser sign-in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|