@magpie-community/opencode-trae-auth 0.2.2 → 0.2.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.
- package/README.md +31 -6
- package/index.mjs +175 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,7 +122,12 @@ it, as Max mode does.
|
|
|
122
122
|
|
|
123
123
|
Reasoning: the models reason, but Trae's request takes no effort or
|
|
124
124
|
thinking level, so the plugin lists no variants and a level an agent
|
|
125
|
-
picks does nothing.
|
|
125
|
+
picks does nothing. Every model it lists says so in its `capabilities`
|
|
126
|
+
(`reasoning: true`, with `toolcall`, `temperature`, `attachment`, the
|
|
127
|
+
input and output modalities and an empty `variants`), the fallback
|
|
128
|
+
list's own capabilities kept where it has an entry: magpie reads
|
|
129
|
+
`capabilities.reasoning` alone for a model a plugin lists, and a model
|
|
130
|
+
without it is shown as one that doesn't reason (0.2.4).
|
|
126
131
|
|
|
127
132
|
When no
|
|
128
133
|
list can be read, the plugin uses the realm's own fallback list. Trae CN's
|
|
@@ -133,10 +138,28 @@ replaces either.
|
|
|
133
138
|
|
|
134
139
|
## Renewal
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
Trae CN (since 0.2.3) renews the JWT as its clients do, bound to the
|
|
142
|
+
account's device:
|
|
143
|
+
|
|
144
|
+
- **Where:** `POST api.trae.cn/trae/api/v3/oauth/ExchangeToken`, with the
|
|
145
|
+
refresh token, the device (`DeviceInfo`: its id, machine id and public
|
|
146
|
+
key) and a `DeviceProof`.
|
|
147
|
+
- **The device key:** an ECDSA P-256 key pair made for each account at
|
|
148
|
+
sign-in (or at the first renewal of one signed in before 0.2.3), kept
|
|
149
|
+
with the sign-in.
|
|
150
|
+
- **The proof:** the key's ECDSA-SHA256 signature (base64) of
|
|
151
|
+
`POST\n/trae/api/v3/oauth/ExchangeToken\n{ClientID}\n{RefreshToken}\n{Timestamp}\n{Nonce}`.
|
|
152
|
+
- **As whom:** TRAE SOLO CN (`en1oxy7wnw8j9n`, `SOLO_PC`) first, then
|
|
153
|
+
Trae CN's IDE (`ono9krqynydwx5`, `IDE_PC`).
|
|
154
|
+
- **The refresh token:** kept; this renewal doesn't spend it.
|
|
155
|
+
|
|
156
|
+
A token the sign-in page gives is renewed this way at once. When Trae
|
|
157
|
+
turns the device-bound renewal away, the plugin renews as before 0.2.3.
|
|
158
|
+
|
|
159
|
+
Trae Global, and Trae CN as a fallback, renew at the realm's auth host's
|
|
160
|
+
`/cloudide/api/v3/trae/oauth/ExchangeToken` (`api.trae.cn`,
|
|
161
|
+
`growsg-normal.trae.ai`). There Trae issues a new refresh token each time
|
|
162
|
+
and spends the old one, so only one renewal runs at a time.
|
|
140
163
|
|
|
141
164
|
magpie renews the JWT ten minutes before it ends (`refreshLead`) through
|
|
142
165
|
`auth.refresh`. Each request also checks it, two minutes before the end, for
|
|
@@ -198,7 +221,9 @@ end), its device (`x-device-id`, `x-device-brand`, `x-device-type`,
|
|
|
198
221
|
`x-os-version`, `x-app-version`) and a client's User-Agent (TRAE SOLO
|
|
199
222
|
CN's Electron shell). Since 0.2.1: Bun's own User-Agent and the chat's
|
|
200
223
|
headers got 9074 「当前参与用户太多,请稍后再试」 every time
|
|
201
|
-
(yetone/magpie#808).
|
|
224
|
+
(yetone/magpie#808). Since 0.2.3 the JWT is also bound to the device (see
|
|
225
|
+
Renewal): a JWT renewed at `/cloudide/…` carries no device, and the claim
|
|
226
|
+
answered it 9074 even with the client's headers.
|
|
202
227
|
|
|
203
228
|
- **Status:** `POST api.trae.cn/trae/api/v2/ug/checkin_credits/status`,
|
|
204
229
|
body `{"req_source":1}` (magpie's `{}` is sent as that): `enable`,
|
package/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// The account's fetch also sends Trae CN's own pages on api.trae.cn
|
|
10
10
|
// (/trae/api/…) as the account, for magpie's daily check-in (每日签到:
|
|
11
11
|
// /trae/api/v2/ug/checkin_credits/status, then /claim).
|
|
12
|
-
import { randomBytes, randomUUID, randomInt } from "node:crypto"
|
|
12
|
+
import { generateKeyPairSync, randomBytes, randomUUID, randomInt, sign } from "node:crypto"
|
|
13
13
|
import { createServer, STATUS_CODES } from "node:http"
|
|
14
14
|
|
|
15
15
|
const SITES = {
|
|
@@ -35,6 +35,21 @@ const SITES = {
|
|
|
35
35
|
api: "https://trae-api-cn.mchost.guru", // the models
|
|
36
36
|
},
|
|
37
37
|
clientId: "ono9krqynydwx5", // Trae CN's IDE
|
|
38
|
+
// the clients' own renewal, which binds the JWT to a device: ExchangeToken
|
|
39
|
+
// under /trae/api/v3/oauth, signed with the device's P-256 key
|
|
40
|
+
// (DeviceProof). A JWT renewed at /cloudide/… carries no device, and
|
|
41
|
+
// Trae CN answers its check-in claim 9074 「当前参与用户太多」 every time,
|
|
42
|
+
// while the client's own token is let through (mintonight's side-by-side
|
|
43
|
+
// on yetone/magpie#808). Tried as TRAE SOLO CN first, the one
|
|
44
|
+
// qilimixingkong/trae-checkin found the server takes, then as Trae CN's
|
|
45
|
+
// IDE. It doesn't spend the refresh token.
|
|
46
|
+
deviceExchange: {
|
|
47
|
+
path: "/trae/api/v3/oauth/ExchangeToken",
|
|
48
|
+
as: [
|
|
49
|
+
{ clientId: "en1oxy7wnw8j9n", platform: "SOLO_PC", version: "0.1.64" },
|
|
50
|
+
{ clientId: "ono9krqynydwx5", platform: "IDE_PC", version: "3.3.99" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
38
53
|
appId: "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8",
|
|
39
54
|
ideVersion: "3.3.65", // named to the authorization page
|
|
40
55
|
ideVersionCode: "20260401",
|
|
@@ -220,7 +235,19 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
|
220
235
|
// device is the machine an account signs in from, made once at sign-in and
|
|
221
236
|
// named to trae.cn's authorization page, then sent with every request: a
|
|
222
237
|
// fresh one each time is what the relays saw requests dropped for.
|
|
223
|
-
const newDevice = () => ({ deviceId: digits(19), machineId: randomBytes(16).toString("hex") })
|
|
238
|
+
const newDevice = () => ({ deviceId: digits(19), machineId: randomBytes(16).toString("hex"), ...newDeviceKey() })
|
|
239
|
+
|
|
240
|
+
// newDeviceKey is the device's key pair, an ECDSA P-256 one as the clients
|
|
241
|
+
// keep (iCubeAuthInfo://icube-dc:<device>), whose public half goes with a
|
|
242
|
+
// renewal and whose private half signs it (DeviceProof). An account signed
|
|
243
|
+
// in before 0.2.3 has none and is given one at its next renewal.
|
|
244
|
+
function newDeviceKey() {
|
|
245
|
+
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" })
|
|
246
|
+
return {
|
|
247
|
+
devicePublicKey: publicKey.export({ type: "spki", format: "pem" }),
|
|
248
|
+
devicePrivateKey: privateKey.export({ type: "pkcs8", format: "pem" }),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
224
251
|
|
|
225
252
|
// whenOf reads one of Trae's expiry times: seconds, milliseconds or ISO
|
|
226
253
|
function whenOf(v) {
|
|
@@ -270,6 +297,7 @@ function toAuth(s, site) {
|
|
|
270
297
|
clientId: s.clientId || site.clientId,
|
|
271
298
|
deviceId: s.deviceId,
|
|
272
299
|
machineId: s.machineId,
|
|
300
|
+
...(s.devicePrivateKey ? { devicePublicKey: s.devicePublicKey, devicePrivateKey: s.devicePrivateKey } : {}),
|
|
273
301
|
// the model host the sign-in named, if it named one
|
|
274
302
|
...(site.modelHost(s.api) ? { api: s.api } : {}),
|
|
275
303
|
...(s.region ? { region: s.region } : {}),
|
|
@@ -287,6 +315,8 @@ function fromAuth(auth, site) {
|
|
|
287
315
|
clientId: auth.clientId || site.clientId,
|
|
288
316
|
deviceId: auth.deviceId || "",
|
|
289
317
|
machineId: auth.machineId || "",
|
|
318
|
+
devicePublicKey: auth.devicePublicKey || "",
|
|
319
|
+
devicePrivateKey: auth.devicePrivateKey || "",
|
|
290
320
|
api: auth.api || "",
|
|
291
321
|
region: site.regionOf(auth),
|
|
292
322
|
}
|
|
@@ -387,6 +417,92 @@ async function exchange(refresh, clientId, site, host = site.hosts.auth) {
|
|
|
387
417
|
throw new Error(`${site.realm}: renewing the sign-in: ${statusLine(res.status)} ${msg}`.trim())
|
|
388
418
|
}
|
|
389
419
|
|
|
420
|
+
// proofOf is a renewal's DeviceProof: the device key's ECDSA-SHA256
|
|
421
|
+
// signature (DER, base64) of the method, path, client, refresh token, time
|
|
422
|
+
// and nonce, one to a line, as the clients sign it
|
|
423
|
+
function proofOf(path, clientId, refresh, privateKey, at = Math.floor(Date.now() / 1000), nonce = randomBytes(16).toString("hex")) {
|
|
424
|
+
const text = ["POST", path, clientId, refresh, String(at), nonce].join("\n")
|
|
425
|
+
return { Signature: sign("sha256", Buffer.from(text), privateKey).toString("base64"), Timestamp: at, Nonce: nonce }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// deviceInfo is the device a renewal names, its public key with it
|
|
429
|
+
function deviceInfo(a, as, site) {
|
|
430
|
+
return {
|
|
431
|
+
DeviceID: a.deviceId,
|
|
432
|
+
MachineID: a.machineId,
|
|
433
|
+
PlatformCode: as.platform,
|
|
434
|
+
DeviceType: "PC",
|
|
435
|
+
DeviceName: "",
|
|
436
|
+
DeviceModel: "",
|
|
437
|
+
ClientVersion: as.version,
|
|
438
|
+
DevicePublicKey: a.devicePublicKey,
|
|
439
|
+
DeviceBrand: site.brand,
|
|
440
|
+
DeviceCPU: "AMD",
|
|
441
|
+
OSInfo: "Windows",
|
|
442
|
+
OSVersion: "10.0.22631",
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// boundExchange renews the JWT as the clients do (site.deviceExchange), so
|
|
447
|
+
// that it is bound to the account's device; an account with no device key
|
|
448
|
+
// is given one first. It tries each client the site names, and throws the
|
|
449
|
+
// last answer when none takes it.
|
|
450
|
+
async function boundExchange(a, site, host = site.hosts.auth) {
|
|
451
|
+
const dx = site.deviceExchange
|
|
452
|
+
const d = a.devicePrivateKey && a.devicePublicKey ? a : { ...a, ...newDeviceKey() }
|
|
453
|
+
let last
|
|
454
|
+
for (const as of dx.as) {
|
|
455
|
+
let res
|
|
456
|
+
try {
|
|
457
|
+
res = await fetch(host.replace(/\/+$/, "") + dx.path, {
|
|
458
|
+
method: "POST",
|
|
459
|
+
headers: { "Content-Type": "application/json", ...(a.token ? { Authorization: `Cloud-IDE-JWT ${a.token}` } : {}) },
|
|
460
|
+
body: JSON.stringify({
|
|
461
|
+
ClientID: as.clientId,
|
|
462
|
+
ClientSecret: "",
|
|
463
|
+
RefreshToken: a.refresh,
|
|
464
|
+
DeviceInfo: deviceInfo(d, as, site),
|
|
465
|
+
DeviceProof: proofOf(dx.path, as.clientId, a.refresh, d.devicePrivateKey),
|
|
466
|
+
IDEVersion: as.version,
|
|
467
|
+
}),
|
|
468
|
+
signal: AbortSignal.timeout(20_000),
|
|
469
|
+
})
|
|
470
|
+
} catch (e) {
|
|
471
|
+
last = new Error(`${site.realm}: renewing the sign-in for its device: ` + (e?.message ?? e))
|
|
472
|
+
continue
|
|
473
|
+
}
|
|
474
|
+
const text = await res.text()
|
|
475
|
+
const v = parseJSON(text)
|
|
476
|
+
const r = v.Result ?? v.result ?? {}
|
|
477
|
+
const token = r.Token ?? r.token ?? ""
|
|
478
|
+
if (res.ok && token) {
|
|
479
|
+
return {
|
|
480
|
+
token,
|
|
481
|
+
refresh: r.RefreshToken ?? r.refreshToken ?? a.refresh,
|
|
482
|
+
expires: whenOf(r.TokenExpireAt ?? r.tokenExpireAt) || jwtExp(token),
|
|
483
|
+
devicePublicKey: d.devicePublicKey,
|
|
484
|
+
devicePrivateKey: d.devicePrivateKey,
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const e = errorOf(text)
|
|
488
|
+
last = new Error(`${site.realm}: renewing the sign-in for its device: ${statusLine(res.status)} ${e.message || text.trim().slice(0, 200)}`.trim())
|
|
489
|
+
}
|
|
490
|
+
throw last
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// renewal renews an account's JWT: bound to its device where the site can
|
|
494
|
+
// (boundExchange), else, or when Trae turns that away, at /cloudide/… as
|
|
495
|
+
// before 0.2.3 (exchange). A device key made for the account goes with
|
|
496
|
+
// either, so the next renewal signs with the same one.
|
|
497
|
+
async function renewal(a, site) {
|
|
498
|
+
if (!site.deviceExchange) return exchange(a.refresh, a.clientId, site)
|
|
499
|
+
const keys = a.devicePrivateKey && a.devicePublicKey ? {} : newDeviceKey()
|
|
500
|
+
try {
|
|
501
|
+
return await boundExchange({ ...a, ...keys }, site)
|
|
502
|
+
} catch {}
|
|
503
|
+
return { ...(await exchange(a.refresh, a.clientId, site)), ...keys }
|
|
504
|
+
}
|
|
505
|
+
|
|
390
506
|
// ---- the browser sign-in --------------------------------------------------------
|
|
391
507
|
|
|
392
508
|
const page = (ok, title, text) => `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>${title}</title>
|
|
@@ -418,7 +534,16 @@ async function signedIn(q, device, site) {
|
|
|
418
534
|
}
|
|
419
535
|
if (!s.token) throw new Error(`${site.webHost} sent back no token`)
|
|
420
536
|
if (!s.refresh) throw new Error(`${site.webHost} sent back no refresh token`)
|
|
421
|
-
|
|
537
|
+
s = { ...s, ...device }
|
|
538
|
+
// the token the page gives is bound to no device: it is renewed for this
|
|
539
|
+
// one at once, so a check-in takes it; when Trae won't, it does as is
|
|
540
|
+
if (site.deviceExchange) {
|
|
541
|
+
try {
|
|
542
|
+
const x = await boundExchange(s, site)
|
|
543
|
+
s = { ...s, token: x.token, refresh: x.refresh, expires: x.expires || s.expires }
|
|
544
|
+
} catch {}
|
|
545
|
+
}
|
|
546
|
+
return s
|
|
422
547
|
}
|
|
423
548
|
|
|
424
549
|
// browserSignIn is the IDE's sign-in: trae.cn's authorization page, back
|
|
@@ -1451,8 +1576,9 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1451
1576
|
let r = renewing.get(key)
|
|
1452
1577
|
if (!r) {
|
|
1453
1578
|
r = (async () => {
|
|
1454
|
-
const x = await
|
|
1579
|
+
const x = await renewal(a, site)
|
|
1455
1580
|
const got = { ...a, token: x.token, refresh: x.refresh, expires: x.expires || jwtExp(x.token) || Date.now() + DAY, clientId: x.clientId || a.clientId, renewed: true }
|
|
1581
|
+
if (x.devicePrivateKey) Object.assign(got, { devicePublicKey: x.devicePublicKey, devicePrivateKey: x.devicePrivateKey })
|
|
1456
1582
|
renewed.set(a.uid || a.name, { was: a.refresh, got })
|
|
1457
1583
|
if (keep) await save(toAuth(got, site))
|
|
1458
1584
|
return got
|
|
@@ -1465,10 +1591,11 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1465
1591
|
// fresh is the account with a token that has a while to go, renewed
|
|
1466
1592
|
// when it hasn't
|
|
1467
1593
|
const fresh = async (getAuth) => {
|
|
1468
|
-
|
|
1594
|
+
let a = fromAuth(await getAuth(), site)
|
|
1469
1595
|
if (!a) throw new Expired(`${site.realm}: not signed in`)
|
|
1470
|
-
|
|
1471
|
-
|
|
1596
|
+
// a renewal the sign-in doesn't hold yet. A device-bound one keeps the
|
|
1597
|
+
// refresh token, so the token tells it, and it is renewed in its turn
|
|
1598
|
+
if (newerHere(a)) a = { ...renewed.get(a.uid || a.name).got, renewed: false }
|
|
1472
1599
|
if (!a.expires || a.expires - Date.now() > EARLY_MS || !a.refresh) return a
|
|
1473
1600
|
try {
|
|
1474
1601
|
return await renew(a, true)
|
|
@@ -1485,9 +1612,22 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1485
1612
|
const a = fromAuth(auth, site)
|
|
1486
1613
|
if (!a || !a.refresh) return undefined
|
|
1487
1614
|
const last = renewed.get(a.uid || a.name)
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1615
|
+
// a device key made for the account is kept with the sign-in
|
|
1616
|
+
const out = (s) => ({
|
|
1617
|
+
access: s.token,
|
|
1618
|
+
refresh: s.refresh,
|
|
1619
|
+
expires: s.expires,
|
|
1620
|
+
...(s.devicePrivateKey && s.devicePrivateKey !== a.devicePrivateKey ? { devicePublicKey: s.devicePublicKey, devicePrivateKey: s.devicePrivateKey } : {}),
|
|
1621
|
+
})
|
|
1622
|
+
if (newerHere(a)) return out(last.got)
|
|
1623
|
+
return out(await renew(a, false))
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// newerHere: this account was renewed here since the sign-in a was read
|
|
1627
|
+
// from, to a token that runs longer
|
|
1628
|
+
const newerHere = (a) => {
|
|
1629
|
+
const last = renewed.get(a.uid || a.name)
|
|
1630
|
+
return !!last && last.was === a.refresh && last.got.token !== a.token && (last.got.expires || 0) > (a.expires || 0)
|
|
1491
1631
|
}
|
|
1492
1632
|
|
|
1493
1633
|
// post asks one of Trae's JSON pages with the account
|
|
@@ -1664,6 +1804,13 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1664
1804
|
return [...out.values()].map((x) => x.m)
|
|
1665
1805
|
}
|
|
1666
1806
|
|
|
1807
|
+
// modelOf is a model of the account's list as OpenCode's provider
|
|
1808
|
+
// carries one. A model of the site's fallback list has capabilities
|
|
1809
|
+
// already (magpie's host spreads the config's into them); one the list
|
|
1810
|
+
// doesn't have gets them from MODEL's flat fields, so that magpie reads
|
|
1811
|
+
// it as a model that reasons rather than one that doesn't (its host
|
|
1812
|
+
// reads capabilities.reasoning alone, host.js). The fallback list stays
|
|
1813
|
+
// the authority where it has an entry.
|
|
1667
1814
|
const modelOf = (provider, m) => {
|
|
1668
1815
|
const id = String(m.config_name)
|
|
1669
1816
|
const was = provider.models?.[id] ?? {}
|
|
@@ -1671,7 +1818,23 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1671
1818
|
const ctx = Number(m.context_window_tokens?.dev ?? m.context_window_size?.max?.[0] ?? m.context_window_size?.max ?? m.context_window_tokens?.max ?? m.prompt_max_tokens) || was.limit?.context || MODEL.limit.context
|
|
1672
1819
|
const out = Number(devModel(m)?.max_tokens) || was.limit?.output || 0
|
|
1673
1820
|
const name = m.display_config?.display_name || m.display_name || m.display_model_name || was.name || id
|
|
1674
|
-
|
|
1821
|
+
const input = (k) => (MODEL.modalities?.input ?? []).includes(k)
|
|
1822
|
+
return {
|
|
1823
|
+
...MODEL, ...was, id, providerID: site.id, name: String(name), limit: { context: ctx, output: out },
|
|
1824
|
+
api: was.api ?? { id, url: site.hosts.api, npm: "@ai-sdk/openai-compatible" },
|
|
1825
|
+
capabilities: {
|
|
1826
|
+
...(was.capabilities ?? {}),
|
|
1827
|
+
reasoning: was.capabilities?.reasoning ?? MODEL.reasoning,
|
|
1828
|
+
temperature: was.capabilities?.temperature ?? MODEL.temperature,
|
|
1829
|
+
attachment: was.capabilities?.attachment ?? MODEL.attachment,
|
|
1830
|
+
toolcall: was.capabilities?.toolcall ?? MODEL.tool_call,
|
|
1831
|
+
input: was.capabilities?.input ?? { text: true, image: input("image"), audio: input("audio"), video: input("video"), pdf: input("pdf") },
|
|
1832
|
+
output: was.capabilities?.output ?? { text: true, image: false, audio: false, video: false, pdf: false },
|
|
1833
|
+
},
|
|
1834
|
+
// Trae's request takes no effort or thinking level, so a model has
|
|
1835
|
+
// none to pick from (see the README's Reasoning)
|
|
1836
|
+
variants: was.variants ?? {},
|
|
1837
|
+
}
|
|
1675
1838
|
}
|
|
1676
1839
|
|
|
1677
1840
|
return {
|
|
@@ -1819,4 +1982,4 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1819
1982
|
export const TraeCNAuthPlugin = makePlugin(SITES["trae-cn"])
|
|
1820
1983
|
export const TraeGlobalAuthPlugin = makePlugin(SITES["trae-global"])
|
|
1821
1984
|
|
|
1822
|
-
export const _internal = { HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, toolNamed, traeMessages, chatBody, credits, dollarUsageOf, whenOf, newDevice, SITES }
|
|
1985
|
+
export const _internal = { HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, toolNamed, traeMessages, chatBody, credits, dollarUsageOf, whenOf, newDevice, proofOf, SITES }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|