@magpie-community/opencode-trae-auth 0.2.24 → 0.2.25

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 +12 -0
  2. package/index.mjs +48 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -448,6 +448,18 @@ answered it 9074 even with the client's headers.
448
448
  changed or rotated to get round a 9095 or a 9074, and no account is
449
449
  given another account's device or its own client's device unless a
450
450
  client is signed in as that account.
451
+ - **Which device, said (0.2.25):** a 9074 or 9095 message ends with
452
+ the device the claim went on: this machine's Trae CN client's, signed in
453
+ as the account, or magpie's own for it, with how many client profiles
454
+ were found and that none is signed in as the account. A client device
455
+ that another profile, signed in as another account, also has is said
456
+ too: both accounts check in on one device, which Trae takes once a day.
457
+ No uid or device id is named. On plugins#436 two accounts shared one
458
+ machine whose two profiles (`Trae CN`, `TRAE SOLO CN`) were both the
459
+ first account's; it checked in and the second got 9074, which took
460
+ running `clientDevice` by hand to explain. A profile started with its
461
+ own `--user-data-dir` is still not read: whether TRAE CN keeps a
462
+ separate, registered device there is not known yet (plugins#436).
451
463
  - **A claim's code 0 is not the answer (0.2.22):** Trae answers a
452
464
  claim code 0 for one that registered nothing, so the status is read back
453
465
  after every claim and only a day that is in is reported as `claimed`.
package/index.mjs CHANGED
@@ -871,6 +871,15 @@ function clientStoragePaths(platform, home, env) {
871
871
  // seen (and one unreadable file hid every profile after it).
872
872
  async function clientDevice(uid) {
873
873
  if (!uid) return undefined
874
+ return (await clientProfiles()).find((p) => String(p.uid) === String(uid))?.device
875
+ }
876
+
877
+ // clientProfiles are this machine's Trae CN client profiles that can be
878
+ // made out, in ownClient.paths' order: the account each is signed in as
879
+ // and its one device. A file that isn't there, can't be read or parsed,
880
+ // names no single device or has a sign-in that doesn't decode is left out.
881
+ async function clientProfiles() {
882
+ const out = []
874
883
  for (const p of ownClient.paths()) {
875
884
  let text
876
885
  try {
@@ -888,12 +897,34 @@ async function clientDevice(uid) {
888
897
  const ids = Object.keys(doc ?? {}).filter((k) => k.startsWith(DC_KEY)).map((k) => k.slice(DC_KEY.length))
889
898
  if (ids.length !== 1 || !/^\d{8,20}$/.test(ids[0])) continue
890
899
  const who = clientAuth(doc[AUTH_KEY])?.userId
891
- if (who != null && String(who) === String(uid)) return ids[0]
900
+ if (who != null && String(who) !== "") out.push({ uid: String(who), device: ids[0] })
892
901
  } catch {
893
902
  continue // a sign-in that doesn't decode: the next profile may be
894
903
  }
895
904
  }
896
- return undefined
905
+ return out
906
+ }
907
+
908
+ // deviceNote says which device a Trae CN check-in went on, for the message
909
+ // of a claim Trae refused (9074) or found already in (9095): the device of
910
+ // a client on this machine signed in as uid, or magpie's own for the
911
+ // account and why — how many client profiles were found, and that none is
912
+ // signed in as it. It names no uid and no device id. A client's device
913
+ // that another profile, signed in as another account, also has is said:
914
+ // both accounts check in on one device, and Trae takes one a day
915
+ // (plugins#436). It only states what was read; it promises no fix.
916
+ async function deviceNote(uid) {
917
+ const all = await clientProfiles()
918
+ const mine = all.find((p) => String(p.uid) === String(uid ?? ""))
919
+ if (mine) {
920
+ const shared = all.some((p) => p.device === mine.device && String(p.uid) !== String(uid))
921
+ return shared
922
+ ? "sent on this machine's Trae CN client's device, signed in as this account — another profile here, signed in as another account, has the same device"
923
+ : "sent on this machine's Trae CN client's device, signed in as this account"
924
+ }
925
+ if (!all.length) return "sent on magpie's own device for this account: no Trae CN client profile was found on this machine"
926
+ const n = all.length
927
+ return `sent on magpie's own device for this account: ${n} Trae CN client profile${n === 1 ? " is" : "s are"} on this machine, none signed in as this account`
897
928
  }
898
929
 
899
930
  // clientAuth is the client's sign-in envelope, as stored or decoded
@@ -2740,9 +2771,12 @@ const MAX = "-max"
2740
2771
  // same names, as reasoning_effort, so a level an agent picks is sent as Trae
2741
2772
  // spells it. OpenCode knows the three as low/high/xhigh, so the map goes both
2742
2773
  // ways. Trae's client names two more, medium and max (its bundle's
2743
- // reasoning_effort.{low,medium,high,extra_high,max}), handed out per model:
2744
- // its app offers DeepSeek V4.1 Flash max (#434), so they are spelled here too,
2745
- // and still offered only for a model whose entry names them.
2774
+ // reasoning_effort.{low,medium,high,extra_high,max}), handed out per model,
2775
+ // so they are spelled here too, and offered only for a model whose entry
2776
+ // names them. No list seen yet names either: TRAE CN's app offers DeepSeek
2777
+ // V4.1 Flash 轻/高/极高 (light/high/extra_high) alone, and its "Max" is the
2778
+ // 更大上下文 (Max) 1M-window switch, which is the model id + MAX below, not a
2779
+ // level (#434's screenshot).
2746
2780
  //
2747
2781
  // An entry without the config takes no level: Trae serves it at its own
2748
2782
  // default and nothing is sent. The plugin used to send none for every model,
@@ -3075,7 +3109,7 @@ const makePlugin = (site) => async ({ client }) => {
3075
3109
  const ce = errorOf(answer)
3076
3110
  if (claim.status === 401 || lapsedCode(ce.code, site)) return { outcome: "failed", message: ce.message || `${a0.name || a0.uid}: Trae's sign-in has expired` }
3077
3111
  // 9095: this device is in for today, whoever took it — the day's answer
3078
- if (String(ce.code) === "9095") return { outcome: "ineligible", message: ce.message || "this device has checked in today" }
3112
+ if (String(ce.code) === "9095") return { outcome: "ineligible", message: `${ce.message || "this device has checked in today"} (${await deviceNote(a0.uid)})` }
3079
3113
  // 9074: Trae did not take this claim for this account's device. Its own
3080
3114
  // words say the pool is busy, which measurements do not bear out: the
3081
3115
  // same account, token and headers were taken on a 16-digit id where a
@@ -3089,8 +3123,13 @@ const makePlugin = (site) => async ({ client }) => {
3089
3123
  // it in. Nothing is said of signing one in: it is the one thing seen
3090
3124
  // to change a 9074, but it hasn't been tried on a real account, and
3091
3125
  // on a claim that already rode a client's device it is what the user
3092
- // did — the advice would only send them back to do it again.
3093
- if (String(ce.code) === "9074") return { outcome: "ineligible", message: `${ce.message} (9074) — Trae CN did not take this check-in for this account's device` }
3126
+ // did — the advice would only send them back to do it again. What it
3127
+ // does say, after both 9074 and 9095, is which device the claim went
3128
+ // on and why (deviceNote): moxuanchen (plugins#436) had to run
3129
+ // clientDevice by hand to learn that the account Trae CN took (A, 100
3130
+ // credits) rode the client's device and the one it refused 9074 (B)
3131
+ // rode magpie's own, since both of that machine's profiles were A's.
3132
+ if (String(ce.code) === "9074") return { outcome: "ineligible", message: `${ce.message} (9074) — Trae CN did not take this check-in for this account's device (${await deviceNote(a0.uid)})` }
3094
3133
  if (claim.status !== 200 || (ce.code && String(ce.code) !== "0")) return { outcome: "failed", message: ce.message || statusLine(claim.status) }
3095
3134
  if (!answer || typeof answer !== "object" || Array.isArray(answer)) return { outcome: "failed", message: `check-in claim: unreadable reply ${claimText.trim().slice(0, 120) || "(empty)"}` }
3096
3135
  // a claim's own code 0 is not proof the day is in: Trae answers it for
@@ -3542,4 +3581,4 @@ const makePlugin = (site) => async ({ client }) => {
3542
3581
  export const TraeCNAuthPlugin = makePlugin(SITES["trae-cn"])
3543
3582
  export const TraeGlobalAuthPlugin = makePlugin(SITES["trae-global"])
3544
3583
 
3545
- export const _internal = { ownClient, clientStoragePaths, clientDevice, clientDecode, thisMachine, traeHost, accountOf, HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, nameCall, toolNamed, unwrapped, traeMessages, chatBody, nativeTools, moonshotSchema, credits, dollarUsageOf, whenOf, newDevice, ugDevice, proofOf, SITES, SESSION, conversationID, LEVELS, levelsOf, variantsOf, effortOf }
3584
+ export const _internal = { ownClient, clientStoragePaths, clientDevice, clientProfiles, deviceNote, clientDecode, thisMachine, traeHost, accountOf, HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, nameCall, toolNamed, unwrapped, traeMessages, chatBody, nativeTools, moonshotSchema, credits, dollarUsageOf, whenOf, newDevice, ugDevice, proofOf, SITES, SESSION, conversationID, LEVELS, levelsOf, variantsOf, effortOf }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-trae-auth",
3
- "version": "0.2.24",
3
+ "version": "0.2.25",
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",