@magpie-community/opencode-trae-auth 0.1.7 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.md +4 -6
  2. package/index.mjs +56 -53
  3. 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, 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.
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 `{"req_source":1}`: `enable`, `checked_in`, `credits`.
121
+ body `{}`: `enable`, `checked_in`, `credits`.
124
122
  - **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
125
- body `{"req_source":1}`, only while it is on and today's isn't in.
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
@@ -610,16 +574,60 @@ class TextTools {
610
574
  function nativeCall(tc) {
611
575
  const f = tc?.function ?? tc?.function_call ?? tc
612
576
  const name = f?.name ?? tc?.tool_name ?? ""
613
- if (!name) return null
614
- const a = f?.arguments ?? tc?.params ?? tc?.input ?? tc?.parameters ?? {}
615
- return { id: tc?.id || tc?.tool_call_id || "", name: String(name), arguments: typeof a === "string" ? a : JSON.stringify(a) }
577
+ const index = Number.isInteger(tc?.index) ? tc.index : null
578
+ if (!name && index === null && !tc?.id) return null
579
+ const a = f?.arguments ?? f?.args ?? tc?.params ?? tc?.input ?? tc?.parameters ?? ""
580
+ return { id: tc?.id || tc?.tool_call_id || "", index, name: String(name), arguments: typeof a === "string" ? a : JSON.stringify(a) }
581
+ }
582
+
583
+ // NativeCalls puts together the tool calls Trae streams: a call comes in
584
+ // several events under one id (or index), each with the arguments so far
585
+ // or only the new piece, so a call is sent on once the answer is whole,
586
+ // not from its first event (#799: bash's command cut to `echo "T`)
587
+ class NativeCalls {
588
+ constructor() {
589
+ this.calls = []
590
+ }
591
+ push(tc) {
592
+ const c = nativeCall(tc)
593
+ if (!c) return
594
+ let o = this.calls.find((x) => (c.id && x.id === c.id) || (!c.id && c.index !== null && x.index === c.index))
595
+ if (!o && !c.id && c.index === null)
596
+ o = this.calls.findLast((x) => x.name === c.name && (x.arguments === c.arguments || (!done(x.arguments) && c.arguments.startsWith(x.arguments))))
597
+ if (!o) {
598
+ if (!c.name) return
599
+ this.calls.push({ ...c })
600
+ return
601
+ }
602
+ if (!o.name) o.name = c.name
603
+ if (c.index !== null && o.index === null) o.index = c.index
604
+ const prev = o.arguments
605
+ const next = c.arguments
606
+ if (!next || prev.startsWith(next)) return // nothing new
607
+ if (next.startsWith(prev) || done(prev)) o.arguments = next // the arguments so far
608
+ else o.arguments = prev + next // a piece
609
+ }
610
+ take() {
611
+ return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) => ({ id: id || callId(), name, arguments: a || "{}" }))
612
+ }
613
+ }
614
+
615
+ // done says arguments are a whole JSON value already
616
+ function done(a) {
617
+ if (!a) return false
618
+ try {
619
+ JSON.parse(a)
620
+ return true
621
+ } catch {
622
+ return false
623
+ }
616
624
  }
617
625
 
618
626
  // parts turns Trae's events into what the answer is made of: text,
619
627
  // reasoning, tool calls, the token counts, the finish, or an error.
620
628
  async function* parts(events) {
621
629
  const tt = new TextTools()
622
- const seen = new Set()
630
+ const nc = new NativeCalls()
623
631
  for await (const { event, data } of events) {
624
632
  const name = eventName(event)
625
633
  const d = data && typeof data === "object" ? data : {}
@@ -650,20 +658,14 @@ async function* parts(events) {
650
658
  if (r.text) yield { text: r.text }
651
659
  for (const c of r.calls) yield { call: { ...c, id: callId() } }
652
660
  }
653
- for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : []) {
654
- const c = nativeCall(tc)
655
- if (!c) continue
656
- const key = c.id || c.name + c.arguments
657
- if (seen.has(key)) continue
658
- seen.add(key)
659
- yield { call: { ...c, id: c.id || callId() } }
660
- }
661
+ for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : []) nc.push(tc)
661
662
  const u = tokensOf(d.usage)
662
663
  if (u) yield { usage: u }
663
664
  }
664
665
  const r = tt.push("", true)
665
666
  if (r.text) yield { text: r.text }
666
667
  for (const c of r.calls) yield { call: { ...c, id: callId() } }
668
+ for (const c of nc.take()) yield { call: c }
667
669
  }
668
670
 
669
671
  // first reads events up to the first that is the answer's, so an answer
@@ -884,13 +886,14 @@ export const TraeCNAuthPlugin = async ({ client }) => {
884
886
  if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
885
887
  throw e
886
888
  }
887
- // the check-in goes as the IDE sends it (ugHeaders, ugBody); Trae's
888
- // other pages with the chat's headers
889
- const ug = ugPage(url)
889
+ // Trae's pages, the check-in among them, go with the chat's headers and
890
+ // the body given ({}): 0.1.7 sent the check-in with the IDE's growth
891
+ // headers and {req_source: 1} instead, and a check-in that worked on
892
+ // 0.1.6 failed from then on (yetone/magpie#808)
890
893
  const res = await fetch(url, {
891
894
  method,
892
- headers: ug ? ugHeaders(a, { Accept: "application/json" }) : ideHeaders(a, { Accept: "application/json" }),
893
- body: method === "GET" || method === "HEAD" ? undefined : ug ? ugBody(body) : typeof body === "string" ? body : "{}",
895
+ headers: ideHeaders(a, { Accept: "application/json" }),
896
+ body: method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}",
894
897
  signal: signal ?? AbortSignal.timeout(20_000),
895
898
  })
896
899
  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.7",
3
+ "version": "0.1.9",
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",