@magpie-community/opencode-trae-auth 0.1.3 → 0.1.4

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 +19 -0
  2. package/index.mjs +43 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -107,3 +107,22 @@ magpie's usage card shows the account's credits:
107
107
  unlimited) and what the pack has used.
108
108
  - **What it shows:** credits left as the balance, and the share used as a
109
109
  window.
110
+
111
+ ## Daily check-in
112
+
113
+ Trae CN gives credits for a daily check-in (每日签到). magpie can press it
114
+ once a day for each account (Settings, or the switch on the usage card).
115
+ It asks Trae CN's own pages through
116
+ this plugin's fetch, which sends them as the account: its Cloud-IDE-JWT
117
+ (renewed first when near its end) and its device id.
118
+
119
+ - **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
120
+ body `{}`: `enable`, `checked_in`, `credits`.
121
+ - **Claim:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/claim`,
122
+ body `{}`, only while it is on and today's isn't in.
123
+ - **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
124
+ the account for a new sign-in. Code 9095 means this device has checked
125
+ in today; nothing sends another device id to get round it.
126
+
127
+ Only `api.trae.cn`'s `/trae/api/` pages are sent this way; any other URL
128
+ that isn't a chat request is still refused (400). Needs 0.1.4 or later.
package/index.mjs CHANGED
@@ -6,6 +6,9 @@
6
6
  // agent's /api/agent/v3/llm_utils_chat, which answers in its own SSE
7
7
  // events. Worked out from two open-source relays (wangqi233/trae2api and
8
8
  // autumnsentiment/Trae2api-cn); not run against a real account here.
9
+ // The account's fetch also sends Trae CN's own pages on api.trae.cn
10
+ // (/trae/api/…) as the account, for magpie's daily check-in (每日签到:
11
+ // /trae/api/v2/ug/checkin_credits/status, then /claim).
9
12
  import { randomBytes, randomUUID, randomInt } from "node:crypto"
10
13
  import { createServer, STATUS_CODES } from "node:http"
11
14
 
@@ -132,6 +135,18 @@ function apiOf(a) {
132
135
  return /mchost\.guru|trae-api-/i.test(h) ? h : HOSTS.api
133
136
  }
134
137
 
138
+ // ownPage: url is one of Trae CN's own JSON pages on api.trae.cn
139
+ // (/trae/api/…: the daily check-in, credits), which the account's fetch
140
+ // sends as the account rather than as a chat
141
+ function ownPage(url) {
142
+ try {
143
+ const u = new URL(url)
144
+ return u.origin === new URL(HOSTS.auth).origin && u.pathname.startsWith("/trae/api/")
145
+ } catch {
146
+ return false
147
+ }
148
+ }
149
+
135
150
  // ---- errors --------------------------------------------------------------------
136
151
 
137
152
  class Expired extends Error {
@@ -819,6 +834,32 @@ export const TraeCNAuthPlugin = async ({ client }) => {
819
834
  return { status: res.status, text, v: parseJSON(text) }
820
835
  }
821
836
 
837
+ // ownRequest sends a request for one of Trae CN's own JSON pages as the
838
+ // account, as its IDE does: magpie asks the daily check-in
839
+ // (/trae/api/v2/ug/checkin_credits/status, then /claim) through the
840
+ // account's fetch, as it does WorkBuddy's. Trae's answer comes back as it
841
+ // is; a token it turned away (401, or code 1001 in a 200) marks the
842
+ // sign-in, as usage does.
843
+ const ownRequest = async (getAuth, a0, url, method, body, signal) => {
844
+ let a
845
+ try {
846
+ a = await fresh(getAuth)
847
+ } catch (e) {
848
+ if (e instanceof Expired) return errorResponse(401, `${a0.name || a0.uid}: ${e.message}`, "expired")
849
+ throw e
850
+ }
851
+ const res = await fetch(url, {
852
+ method,
853
+ headers: ideHeaders(a, { Accept: "application/json" }),
854
+ body: method === "GET" || method === "HEAD" ? undefined : typeof body === "string" ? body : "{}",
855
+ signal: signal ?? AbortSignal.timeout(20_000),
856
+ })
857
+ const text = await res.text()
858
+ const lapsed = res.status === 401 || lapsedCode(errorOf(text).code)
859
+ const headers = { "content-type": res.headers.get("content-type") ?? "application/json", "X-Magpie-Sign-In": lapsed ? "expired" : a.renewed ? "renewed" : "kept" }
860
+ return new Response(text, { status: res.status, headers })
861
+ }
862
+
822
863
  const usage = async (getAuth) => {
823
864
  let a
824
865
  try {
@@ -981,6 +1022,8 @@ export const TraeCNAuthPlugin = async ({ client }) => {
981
1022
  let body = init.body ?? (r0 ? await r0.text() : undefined)
982
1023
  if (body instanceof ArrayBuffer) body = new TextDecoder().decode(body)
983
1024
  else if (ArrayBuffer.isView(body)) body = new TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength))
1025
+ const url = r0 ? r0.url : input instanceof URL ? input.href : String(input)
1026
+ if (ownPage(url)) return ownRequest(getAuth, a0, url, init.method ?? r0?.method ?? "POST", body, init.signal ?? r0?.signal)
984
1027
  const req = parseJSON(body)
985
1028
  if (!Array.isArray(req.messages)) return errorResponse(400, "Trae CN: only chat completions are served")
986
1029
  let a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-trae-auth",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",