@magpie-community/opencode-trae-auth 0.1.8 → 0.1.10
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 +4 -6
- package/index.mjs +69 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,15 +114,13 @@ magpie's usage card shows the account's credits:
|
|
|
114
114
|
Trae CN gives credits for a daily check-in (每日签到). magpie can press it
|
|
115
115
|
once a day for each account (Settings, or the switch on the usage card).
|
|
116
116
|
It asks Trae CN's own pages through
|
|
117
|
-
this plugin's fetch, which sends them as the account
|
|
118
|
-
|
|
119
|
-
device's headers (`x-device-id`, `x-device-brand`, `x-device-type`,
|
|
120
|
-
`x-os-version`, `x-app-version`), and no others.
|
|
117
|
+
this plugin's fetch, which sends them as the account: its Cloud-IDE-JWT
|
|
118
|
+
(renewed first when near its end) and its device id.
|
|
121
119
|
|
|
122
120
|
- **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
|
|
123
|
-
body `{
|
|
121
|
+
body `{}`: `enable`, `checked_in`, `credits`.
|
|
124
122
|
- **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
|
|
125
|
-
body `{
|
|
123
|
+
body `{}`, only while it is on and today's isn't in.
|
|
126
124
|
- **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
|
|
127
125
|
the account for a new sign-in. Code 9095 means this device has checked
|
|
128
126
|
in today; nothing sends another device id to get round it.
|
package/index.mjs
CHANGED
|
@@ -366,42 +366,6 @@ function ideHeaders(a, extra = {}) {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
// ugHeaders are the headers Trae CN's IDE sends its growth pages
|
|
370
|
-
// (api.trae.cn/trae/api/v2/ug/…, the daily check-in) with, and no others:
|
|
371
|
-
// its eb()/fb() (Trae CN 3.3.104's out/main.js) send Content-Type, the
|
|
372
|
-
// Cloud-IDE-JWT and the device (x-device-id, x-device-brand,
|
|
373
|
-
// x-device-type, x-os-version, x-app-version), none of the chat's
|
|
374
|
-
// x-uid, x-app-id, x-ide-token and the like
|
|
375
|
-
function ugHeaders(a, extra = {}) {
|
|
376
|
-
return {
|
|
377
|
-
"Content-Type": "application/json",
|
|
378
|
-
Authorization: `Cloud-IDE-JWT ${a.token}`,
|
|
379
|
-
"x-device-id": a.deviceId,
|
|
380
|
-
"x-device-brand": BRAND,
|
|
381
|
-
"x-device-type": "windows",
|
|
382
|
-
"x-os-version": "Windows 10",
|
|
383
|
-
"x-app-version": CLIENT_VERSION,
|
|
384
|
-
...extra,
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// ugBody is the body the IDE posts a growth page: {req_source: 1}, its
|
|
389
|
-
// own (2 is SOLO Lite's), where magpie gave none or {}; anything else
|
|
390
|
-
// given goes as it is
|
|
391
|
-
function ugBody(body) {
|
|
392
|
-
const s = typeof body === "string" ? body.trim() : ""
|
|
393
|
-
if (s === "" || s === "{}") return JSON.stringify({ req_source: 1 })
|
|
394
|
-
return s
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const ugPage = (url) => {
|
|
398
|
-
try {
|
|
399
|
-
return new URL(url).pathname.startsWith("/trae/api/v2/ug/")
|
|
400
|
-
} catch {
|
|
401
|
-
return false
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
369
|
// Trae's chat takes text only and has no turn for a tool call or its
|
|
406
370
|
// result. The agent's tools are named to it natively and in a system
|
|
407
371
|
// prompt that asks for each call as a tagged block; earlier calls and
|
|
@@ -587,8 +551,8 @@ class TextTools {
|
|
|
587
551
|
text += this.buf.slice(0, i)
|
|
588
552
|
const raw = this.buf.slice(i + OPEN.length, j).trim()
|
|
589
553
|
this.buf = this.buf.slice(j + CLOSE.length)
|
|
590
|
-
const v =
|
|
591
|
-
if (v.name) calls.push({ name: String(v.name), arguments:
|
|
554
|
+
const v = looseJSON(raw) ?? {}
|
|
555
|
+
if (v.name) calls.push({ name: String(v.name), arguments: callArgs(v) })
|
|
592
556
|
else text += OPEN + raw + CLOSE
|
|
593
557
|
}
|
|
594
558
|
if (end) {
|
|
@@ -606,14 +570,62 @@ class TextTools {
|
|
|
606
570
|
}
|
|
607
571
|
}
|
|
608
572
|
|
|
573
|
+
// looseJSON reads a JSON object as a model writes one: a string holding a
|
|
574
|
+
// raw newline or tab (a file's content written out, #799) is read as if
|
|
575
|
+
// they were escaped. null when it isn't one even so.
|
|
576
|
+
function looseJSON(s) {
|
|
577
|
+
for (const t of [s, escapeRaw(s)]) {
|
|
578
|
+
try {
|
|
579
|
+
const v = JSON.parse(t)
|
|
580
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v
|
|
581
|
+
} catch {}
|
|
582
|
+
}
|
|
583
|
+
return null
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// escapeRaw escapes the control characters inside JSON strings
|
|
587
|
+
function escapeRaw(s) {
|
|
588
|
+
let out = ""
|
|
589
|
+
let str = false
|
|
590
|
+
let esc = false
|
|
591
|
+
for (const ch of String(s ?? "")) {
|
|
592
|
+
if (str && !esc && ch < " ") {
|
|
593
|
+
out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : ch === "\t" ? "\\t" : "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
596
|
+
if (str && ch === "\\" && !esc) esc = true
|
|
597
|
+
else {
|
|
598
|
+
if (ch === '"' && !esc) str = !str
|
|
599
|
+
esc = false
|
|
600
|
+
}
|
|
601
|
+
out += ch
|
|
602
|
+
}
|
|
603
|
+
return out
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// callArgs is a tagged block's arguments as JSON: under arguments (an
|
|
607
|
+
// object, or a string of one), input or parameters, else the block's other
|
|
608
|
+
// keys, as a model that writes them beside the name means them (#799: a
|
|
609
|
+
// write that went out as {})
|
|
610
|
+
function callArgs(v) {
|
|
611
|
+
let a = v.arguments ?? v.input ?? v.parameters
|
|
612
|
+
if (a === undefined) {
|
|
613
|
+
const { name, type, id, ...rest } = v
|
|
614
|
+
a = rest
|
|
615
|
+
}
|
|
616
|
+
if (typeof a === "string") return a.trim() ? (looseJSON(a) ? JSON.stringify(looseJSON(a)) : a) : "{}"
|
|
617
|
+
return JSON.stringify(a ?? {})
|
|
618
|
+
}
|
|
619
|
+
|
|
609
620
|
// nativeCall is a tool call as Trae's events carry one
|
|
610
621
|
function nativeCall(tc) {
|
|
611
622
|
const f = tc?.function ?? tc?.function_call ?? tc
|
|
612
623
|
const name = f?.name ?? tc?.tool_name ?? ""
|
|
613
624
|
const index = Number.isInteger(tc?.index) ? tc.index : null
|
|
614
|
-
if (!name && index === null && !tc?.id) return null
|
|
615
625
|
const a = f?.arguments ?? f?.args ?? tc?.params ?? tc?.input ?? tc?.parameters ?? ""
|
|
616
|
-
|
|
626
|
+
const args = typeof a === "string" ? a : JSON.stringify(a)
|
|
627
|
+
if (!name && index === null && !tc?.id && !args) return null
|
|
628
|
+
return { id: tc?.id || tc?.tool_call_id || "", index, name: String(name), arguments: args }
|
|
617
629
|
}
|
|
618
630
|
|
|
619
631
|
// NativeCalls puts together the tool calls Trae streams: a call comes in
|
|
@@ -630,6 +642,12 @@ class NativeCalls {
|
|
|
630
642
|
let o = this.calls.find((x) => (c.id && x.id === c.id) || (!c.id && c.index !== null && x.index === c.index))
|
|
631
643
|
if (!o && !c.id && c.index === null)
|
|
632
644
|
o = this.calls.findLast((x) => x.name === c.name && (x.arguments === c.arguments || (!done(x.arguments) && c.arguments.startsWith(x.arguments))))
|
|
645
|
+
// a piece with nothing to say whose it is (or an index its call's
|
|
646
|
+
// first event didn't give) belongs to the call still being written
|
|
647
|
+
if (!o && !c.name && !c.id) {
|
|
648
|
+
o = this.calls.findLast((x) => !done(x.arguments))
|
|
649
|
+
if (o && c.index !== null && o.index !== null && o.index !== c.index) o = null
|
|
650
|
+
}
|
|
633
651
|
if (!o) {
|
|
634
652
|
if (!c.name) return
|
|
635
653
|
this.calls.push({ ...c })
|
|
@@ -644,7 +662,10 @@ class NativeCalls {
|
|
|
644
662
|
else o.arguments = prev + next // a piece
|
|
645
663
|
}
|
|
646
664
|
take() {
|
|
647
|
-
return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) =>
|
|
665
|
+
return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) => {
|
|
666
|
+
const v = a && !done(a) ? looseJSON(a) : null
|
|
667
|
+
return { id: id || callId(), name, arguments: v ? JSON.stringify(v) : a || "{}" }
|
|
668
|
+
})
|
|
648
669
|
}
|
|
649
670
|
}
|
|
650
671
|
|
|
@@ -922,13 +943,14 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
922
943
|
if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
|
|
923
944
|
throw e
|
|
924
945
|
}
|
|
925
|
-
// the check-in
|
|
926
|
-
//
|
|
927
|
-
|
|
946
|
+
// Trae's pages, the check-in among them, go with the chat's headers and
|
|
947
|
+
// the body given ({}): 0.1.7 sent the check-in with the IDE's growth
|
|
948
|
+
// headers and {req_source: 1} instead, and a check-in that worked on
|
|
949
|
+
// 0.1.6 failed from then on (yetone/magpie#808)
|
|
928
950
|
const res = await fetch(url, {
|
|
929
951
|
method,
|
|
930
|
-
headers:
|
|
931
|
-
body: method === "GET" || method === "HEAD" ? undefined :
|
|
952
|
+
headers: ideHeaders(a, { Accept: "application/json" }),
|
|
953
|
+
body: method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}",
|
|
932
954
|
signal: signal ?? AbortSignal.timeout(20_000),
|
|
933
955
|
})
|
|
934
956
|
const text = await res.text()
|
|
@@ -1189,4 +1211,4 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
1189
1211
|
}
|
|
1190
1212
|
|
|
1191
1213
|
// for tests
|
|
1192
|
-
export const _internal = { HOSTS, MODELS, TextTools, traeMessages, chatBody, credits, whenOf, newDevice }
|
|
1214
|
+
export const _internal = { HOSTS, MODELS, TextTools, NativeCalls, looseJSON, traeMessages, chatBody, credits, whenOf, newDevice }
|
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.10",
|
|
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",
|