@magpie-community/opencode-trae-auth 0.1.6 → 0.1.8
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 +6 -4
- package/index.mjs +91 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,13 +114,15 @@ 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
|
-
(renewed first when near its end) and its
|
|
117
|
+
this plugin's fetch, which sends them as the account, as Trae CN's IDE
|
|
118
|
+
does: its Cloud-IDE-JWT (renewed first when near its end) and its
|
|
119
|
+
device's headers (`x-device-id`, `x-device-brand`, `x-device-type`,
|
|
120
|
+
`x-os-version`, `x-app-version`), and no others.
|
|
119
121
|
|
|
120
122
|
- **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
|
|
121
|
-
body `{}`: `enable`, `checked_in`, `credits`.
|
|
123
|
+
body `{"req_source":1}`: `enable`, `checked_in`, `credits`.
|
|
122
124
|
- **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
|
|
123
|
-
body `{}`, only while it is on and today's isn't in.
|
|
125
|
+
body `{"req_source":1}`, only while it is on and today's isn't in.
|
|
124
126
|
- **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
|
|
125
127
|
the account for a new sign-in. Code 9095 means this device has checked
|
|
126
128
|
in today; nothing sends another device id to get round it.
|
package/index.mjs
CHANGED
|
@@ -366,6 +366,42 @@ 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
|
+
|
|
369
405
|
// Trae's chat takes text only and has no turn for a tool call or its
|
|
370
406
|
// result. The agent's tools are named to it natively and in a system
|
|
371
407
|
// prompt that asks for each call as a tagged block; earlier calls and
|
|
@@ -574,16 +610,60 @@ class TextTools {
|
|
|
574
610
|
function nativeCall(tc) {
|
|
575
611
|
const f = tc?.function ?? tc?.function_call ?? tc
|
|
576
612
|
const name = f?.name ?? tc?.tool_name ?? ""
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
613
|
+
const index = Number.isInteger(tc?.index) ? tc.index : null
|
|
614
|
+
if (!name && index === null && !tc?.id) return null
|
|
615
|
+
const a = f?.arguments ?? f?.args ?? tc?.params ?? tc?.input ?? tc?.parameters ?? ""
|
|
616
|
+
return { id: tc?.id || tc?.tool_call_id || "", index, name: String(name), arguments: typeof a === "string" ? a : JSON.stringify(a) }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// NativeCalls puts together the tool calls Trae streams: a call comes in
|
|
620
|
+
// several events under one id (or index), each with the arguments so far
|
|
621
|
+
// or only the new piece, so a call is sent on once the answer is whole,
|
|
622
|
+
// not from its first event (#799: bash's command cut to `echo "T`)
|
|
623
|
+
class NativeCalls {
|
|
624
|
+
constructor() {
|
|
625
|
+
this.calls = []
|
|
626
|
+
}
|
|
627
|
+
push(tc) {
|
|
628
|
+
const c = nativeCall(tc)
|
|
629
|
+
if (!c) return
|
|
630
|
+
let o = this.calls.find((x) => (c.id && x.id === c.id) || (!c.id && c.index !== null && x.index === c.index))
|
|
631
|
+
if (!o && !c.id && c.index === null)
|
|
632
|
+
o = this.calls.findLast((x) => x.name === c.name && (x.arguments === c.arguments || (!done(x.arguments) && c.arguments.startsWith(x.arguments))))
|
|
633
|
+
if (!o) {
|
|
634
|
+
if (!c.name) return
|
|
635
|
+
this.calls.push({ ...c })
|
|
636
|
+
return
|
|
637
|
+
}
|
|
638
|
+
if (!o.name) o.name = c.name
|
|
639
|
+
if (c.index !== null && o.index === null) o.index = c.index
|
|
640
|
+
const prev = o.arguments
|
|
641
|
+
const next = c.arguments
|
|
642
|
+
if (!next || prev.startsWith(next)) return // nothing new
|
|
643
|
+
if (next.startsWith(prev) || done(prev)) o.arguments = next // the arguments so far
|
|
644
|
+
else o.arguments = prev + next // a piece
|
|
645
|
+
}
|
|
646
|
+
take() {
|
|
647
|
+
return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) => ({ id: id || callId(), name, arguments: a || "{}" }))
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// done says arguments are a whole JSON value already
|
|
652
|
+
function done(a) {
|
|
653
|
+
if (!a) return false
|
|
654
|
+
try {
|
|
655
|
+
JSON.parse(a)
|
|
656
|
+
return true
|
|
657
|
+
} catch {
|
|
658
|
+
return false
|
|
659
|
+
}
|
|
580
660
|
}
|
|
581
661
|
|
|
582
662
|
// parts turns Trae's events into what the answer is made of: text,
|
|
583
663
|
// reasoning, tool calls, the token counts, the finish, or an error.
|
|
584
664
|
async function* parts(events) {
|
|
585
665
|
const tt = new TextTools()
|
|
586
|
-
const
|
|
666
|
+
const nc = new NativeCalls()
|
|
587
667
|
for await (const { event, data } of events) {
|
|
588
668
|
const name = eventName(event)
|
|
589
669
|
const d = data && typeof data === "object" ? data : {}
|
|
@@ -614,20 +694,14 @@ async function* parts(events) {
|
|
|
614
694
|
if (r.text) yield { text: r.text }
|
|
615
695
|
for (const c of r.calls) yield { call: { ...c, id: callId() } }
|
|
616
696
|
}
|
|
617
|
-
for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : [])
|
|
618
|
-
const c = nativeCall(tc)
|
|
619
|
-
if (!c) continue
|
|
620
|
-
const key = c.id || c.name + c.arguments
|
|
621
|
-
if (seen.has(key)) continue
|
|
622
|
-
seen.add(key)
|
|
623
|
-
yield { call: { ...c, id: c.id || callId() } }
|
|
624
|
-
}
|
|
697
|
+
for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : []) nc.push(tc)
|
|
625
698
|
const u = tokensOf(d.usage)
|
|
626
699
|
if (u) yield { usage: u }
|
|
627
700
|
}
|
|
628
701
|
const r = tt.push("", true)
|
|
629
702
|
if (r.text) yield { text: r.text }
|
|
630
703
|
for (const c of r.calls) yield { call: { ...c, id: callId() } }
|
|
704
|
+
for (const c of nc.take()) yield { call: c }
|
|
631
705
|
}
|
|
632
706
|
|
|
633
707
|
// first reads events up to the first that is the answer's, so an answer
|
|
@@ -848,10 +922,13 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
848
922
|
if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
|
|
849
923
|
throw e
|
|
850
924
|
}
|
|
925
|
+
// the check-in goes as the IDE sends it (ugHeaders, ugBody); Trae's
|
|
926
|
+
// other pages with the chat's headers
|
|
927
|
+
const ug = ugPage(url)
|
|
851
928
|
const res = await fetch(url, {
|
|
852
929
|
method,
|
|
853
|
-
headers: ideHeaders(a, { Accept: "application/json" }),
|
|
854
|
-
body: method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}",
|
|
930
|
+
headers: ug ? ugHeaders(a, { Accept: "application/json" }) : ideHeaders(a, { Accept: "application/json" }),
|
|
931
|
+
body: method === "GET" || method === "HEAD" ? undefined : ug ? ugBody(body) : typeof body === "string" ? body : "{}",
|
|
855
932
|
signal: signal ?? AbortSignal.timeout(20_000),
|
|
856
933
|
})
|
|
857
934
|
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.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|