@magpie-community/opencode-trae-auth 0.1.2 → 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.
- package/README.md +40 -12
- package/index.mjs +142 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,14 +31,17 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
|
|
|
31
31
|
|
|
32
32
|
- **Request:**
|
|
33
33
|
- The messages go as `{role, content: [{type: "text", text}]}`.
|
|
34
|
-
- The model goes as `config_name` and `model
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
- The model goes as `config_name` and `model`, and as `model_name` the
|
|
35
|
+
`__dev` model the function's list names for it, when it names one.
|
|
36
|
+
- The function is the one whose model list has the model, the first that
|
|
37
|
+
names a `__dev` model for it (in the order `chat_v3`, the classic IDE's;
|
|
38
|
+
`solo_work_lite`, SOLO's Work mode; `solo_agent`, the TRAE agent's;
|
|
39
|
+
`solo_agent_lite`). If Trae answers 4001, 4023 or 1005, the plugin tries
|
|
40
|
+
the others once and remembers which one worked.
|
|
41
|
+
- **Headers:** TRAE SOLO CN 0.1.69's (`x-ide-version`/`x-app-version`
|
|
42
|
+
0.1.69, version code 20260917; Trae offers a model only to clients new
|
|
43
|
+
enough for it): `Authorization: Cloud-IDE-JWT …`, `X-Cloudide-Token`,
|
|
44
|
+
`x-app-id`, the device headers and `x-uid`.
|
|
42
45
|
- **Answer:** Trae always answers in its own SSE events:
|
|
43
46
|
- `output` gives `response`/`content` and `reasoning_content`/`reasoning`.
|
|
44
47
|
- `token_usage`, `done` and `error` mark usage, the end and failures.
|
|
@@ -55,10 +58,16 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
|
|
|
55
58
|
- Earlier calls and their results go back in as text.
|
|
56
59
|
- Images aren't sent.
|
|
57
60
|
|
|
58
|
-
The model list is the account's own, from
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
The model list is the account's own, from
|
|
62
|
+
`/api/ide/v1/batch_get_detail_param`, every function's list in one ask, as
|
|
63
|
+
TRAE SOLO CN asks it (`/api/ide/v1/get_detail_param`, one function at a
|
|
64
|
+
time, when the batch gives none). The lists are put together: SOLO and the
|
|
65
|
+
TRAE agent list models `chat_v3` doesn't (deepseek-v4.1-flash is the TRAE
|
|
66
|
+
agent's, `solo_agent`). Left out are the IDE's helpers (`usage` other than
|
|
67
|
+
`chat_completion`: summary, fast_apply…), configs switched off and the
|
|
68
|
+
custom-model slots. Context is the list's `context_window_tokens.dev`
|
|
69
|
+
(`max` is Max mode's), output the `__dev` model's `max_tokens`. When no
|
|
70
|
+
list can be read, the plugin uses the models Trae CN's `chat_v3` is
|
|
62
71
|
known to serve: GLM-5.2, GLM-5, Kimi K2.6, Qwen 3.7 Plus, DeepSeek V4 Pro
|
|
63
72
|
and DeepSeek V4 Flash.
|
|
64
73
|
|
|
@@ -98,3 +107,22 @@ magpie's usage card shows the account's credits:
|
|
|
98
107
|
unlimited) and what the pack has used.
|
|
99
108
|
- **What it shows:** credits left as the balance, and the share used as a
|
|
100
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
|
|
|
@@ -18,8 +21,13 @@ const HOSTS = {
|
|
|
18
21
|
}
|
|
19
22
|
const CLIENT_ID = "ono9krqynydwx5" // Trae CN's IDE
|
|
20
23
|
const APP_ID = "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8"
|
|
21
|
-
const IDE_VERSION = "3.3.65"
|
|
24
|
+
const IDE_VERSION = "3.3.65" // named to the authorization page
|
|
22
25
|
const IDE_VERSION_CODE = "20260401"
|
|
26
|
+
// the client the model host is told it serves: TRAE SOLO CN 0.1.69, as its
|
|
27
|
+
// ai-agent names itself (same app id). Trae offers a model only to clients
|
|
28
|
+
// new enough for it, and an April IDE was offered no deepseek-v4.1-flash.
|
|
29
|
+
const CLIENT_VERSION = "0.1.69"
|
|
30
|
+
const CLIENT_VERSION_CODE = "20260917"
|
|
23
31
|
const PLUGIN_VERSION = "2.3.24254"
|
|
24
32
|
const BRAND = "ASUS TUF Gaming A15 FA507RM_FA507RM"
|
|
25
33
|
const SIGN_IN_TIMEOUT = 10 * 60 * 1000
|
|
@@ -27,9 +35,9 @@ const EARLY_MS = 2 * 60 * 1000 // a token this close to its end is renewed befor
|
|
|
27
35
|
const LEAD_MS = 10 * 60 * 1000 // and this close, magpie renews it ahead of time (auth.refresh)
|
|
28
36
|
const DAY = 24 * 3600 * 1000
|
|
29
37
|
// the IDE's chat functions: the classic IDE's agent, then SOLO's Work
|
|
30
|
-
// mode, then the TRAE agent (solo_agent
|
|
31
|
-
//
|
|
32
|
-
const FUNCTIONS = ["chat_v3", "solo_work_lite", "solo_agent"]
|
|
38
|
+
// mode, then the TRAE agent (solo_agent, which has deepseek-v4.1-flash),
|
|
39
|
+
// then SOLO Lite's agent
|
|
40
|
+
const FUNCTIONS = ["chat_v3", "solo_work_lite", "solo_agent", "solo_agent_lite"]
|
|
33
41
|
|
|
34
42
|
const MODEL = { attachment: false, tool_call: true, reasoning: true, temperature: true, limit: { context: 128_000, output: 32_000 }, modalities: { input: ["text"], output: ["text"] } }
|
|
35
43
|
// what Trae CN's chat_v3 is known to serve; the live list replaces it
|
|
@@ -127,6 +135,18 @@ function apiOf(a) {
|
|
|
127
135
|
return /mchost\.guru|trae-api-/i.test(h) ? h : HOSTS.api
|
|
128
136
|
}
|
|
129
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
|
+
|
|
130
150
|
// ---- errors --------------------------------------------------------------------
|
|
131
151
|
|
|
132
152
|
class Expired extends Error {
|
|
@@ -332,8 +352,10 @@ function ideHeaders(a, extra = {}) {
|
|
|
332
352
|
"x-device-id": a.deviceId,
|
|
333
353
|
"x-machine-id": a.machineId,
|
|
334
354
|
"x-request-id": randomUUID(),
|
|
335
|
-
"x-
|
|
336
|
-
"x-
|
|
355
|
+
"x-app-version": CLIENT_VERSION,
|
|
356
|
+
"x-app-version-code": CLIENT_VERSION_CODE,
|
|
357
|
+
"x-ide-version": CLIENT_VERSION,
|
|
358
|
+
"x-ide-version-code": CLIENT_VERSION_CODE,
|
|
337
359
|
"x-ide-version-type": "stable",
|
|
338
360
|
"x-device-cpu": "AMD",
|
|
339
361
|
"x-device-brand": BRAND,
|
|
@@ -421,13 +443,16 @@ const nativeTools = (tools) =>
|
|
|
421
443
|
return { type: "function", function: { name: f.name, description: f.description ?? "", parameters: typeof f.parameters === "string" ? f.parameters : JSON.stringify(f.parameters ?? {}) } }
|
|
422
444
|
})
|
|
423
445
|
|
|
424
|
-
|
|
446
|
+
// chatBody is the request for llm_utils_chat; modelName is the model the
|
|
447
|
+
// function's list names for the config (its __dev one), when it named one
|
|
448
|
+
function chatBody(req, fn, modelName) {
|
|
425
449
|
const session = randomUUID()
|
|
426
450
|
const body = {
|
|
427
451
|
messages: traeMessages(req),
|
|
428
452
|
function: fn,
|
|
429
453
|
config_name: req.model,
|
|
430
454
|
model: req.model,
|
|
455
|
+
...(modelName ? { model_name: modelName } : {}),
|
|
431
456
|
stream: true, // Trae answers in SSE either way
|
|
432
457
|
request_id: session,
|
|
433
458
|
session_id: session,
|
|
@@ -694,6 +719,24 @@ async function openai(req, it) {
|
|
|
694
719
|
// a model or function this account's chat_v3 doesn't take: another may
|
|
695
720
|
const wrongFunction = (code) => ["4001", "4023", "1005"].includes(String(code))
|
|
696
721
|
|
|
722
|
+
// ---- the model lists ------------------------------------------------------------
|
|
723
|
+
|
|
724
|
+
// chatModel: an entry of a function's list that is a model to chat with.
|
|
725
|
+
// The lists also hold the IDE's own helpers (summary, fast_apply, title
|
|
726
|
+
// generation: usage other than chat_completion), configs switched off, and
|
|
727
|
+
// the slots of custom models, which need a provider bound in the IDE.
|
|
728
|
+
function chatModel(m) {
|
|
729
|
+
const id = String(m?.config_name ?? "")
|
|
730
|
+
if (!id || /^custom_model/i.test(id)) return false
|
|
731
|
+
if (m.usage && m.usage !== "chat_completion") return false
|
|
732
|
+
if (m.config_switch === false) return false
|
|
733
|
+
if (m.is_custom_model === true || m.display_config?.is_custom_model === true) return false
|
|
734
|
+
return true
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// devModel is the model an entry serves requests with: its __dev one
|
|
738
|
+
const devModel = (m) => (Array.isArray(m?.model_detail_list) ? m.model_detail_list : []).find((d) => /__dev$/.test(String(d?.model_name ?? "")))
|
|
739
|
+
|
|
697
740
|
// ---- usage ------------------------------------------------------------------------
|
|
698
741
|
|
|
699
742
|
// credits adds up the account's entitlement packs as the IDE shows them:
|
|
@@ -729,8 +772,10 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
729
772
|
const renewed = new Map()
|
|
730
773
|
// the chat function that served each account last
|
|
731
774
|
const fnOf = new Map()
|
|
732
|
-
// the
|
|
775
|
+
// the chat function whose model list has each model, and the __dev
|
|
776
|
+
// model it names there
|
|
733
777
|
const listedBy = new Map()
|
|
778
|
+
const modelNames = new Map()
|
|
734
779
|
|
|
735
780
|
const save = async (auth) => {
|
|
736
781
|
try {
|
|
@@ -789,6 +834,32 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
789
834
|
return { status: res.status, text, v: parseJSON(text) }
|
|
790
835
|
}
|
|
791
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
|
+
|
|
792
863
|
const usage = async (getAuth) => {
|
|
793
864
|
let a
|
|
794
865
|
try {
|
|
@@ -818,6 +889,27 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
818
889
|
return out
|
|
819
890
|
}
|
|
820
891
|
|
|
892
|
+
// batchLists is every chat function's model list in one ask, as TRAE
|
|
893
|
+
// SOLO CN's ai-agent asks (batch_get_detail_param): {function: entries}.
|
|
894
|
+
// null when Trae answers it with no lists.
|
|
895
|
+
const batchLists = async (a) => {
|
|
896
|
+
const r = await post(a, apiOf(a) + "/api/ide/v1/batch_get_detail_param", {
|
|
897
|
+
functions: FUNCTIONS, agent_type: "", current_config_info: { config_name: "", is_custom_model: false },
|
|
898
|
+
mode_type: 0, access_type: 0, ab_force_vids: "", ab_autotest_advanced_mode: 0, show_custom_model: true,
|
|
899
|
+
})
|
|
900
|
+
if (r.status === 401 || lapsedCode(errorOf(r.v).code)) throw new Expired("Trae CN's sign-in has expired; sign in again")
|
|
901
|
+
if (r.status !== 200) throw new Error(`Trae CN models: ${statusLine(r.status)}`)
|
|
902
|
+
const groups = r.v.function_configs ?? r.v.data?.function_configs
|
|
903
|
+
if (!Array.isArray(groups)) return null
|
|
904
|
+
const out = {}
|
|
905
|
+
for (const g of groups) {
|
|
906
|
+
const fn = String(g?.function ?? "")
|
|
907
|
+
if (!FUNCTIONS.includes(fn) || !Array.isArray(g.config_info_list)) continue
|
|
908
|
+
out[fn] = [...(out[fn] ?? []), ...g.config_info_list.filter((m) => m?.config_name)]
|
|
909
|
+
}
|
|
910
|
+
return Object.keys(out).length ? out : null
|
|
911
|
+
}
|
|
912
|
+
|
|
821
913
|
// listOf is one function's model list, as the IDE asks for it
|
|
822
914
|
const listOf = async (a, fn) => {
|
|
823
915
|
const r = await post(a, apiOf(a) + "/api/ide/v1/get_detail_param", {
|
|
@@ -834,28 +926,54 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
834
926
|
// came to the TRAE agent's first, yetone/magpie#681); a model listed by one function
|
|
835
927
|
// only is asked through that one. One function's list failing leaves the
|
|
836
928
|
// others'; all failing is the error.
|
|
929
|
+
//
|
|
930
|
+
// The lists come from batch_get_detail_param, as TRAE SOLO CN asks them;
|
|
931
|
+
// when that gives none, from get_detail_param, one function at a time.
|
|
932
|
+
// A model several functions list is asked through the first whose entry
|
|
933
|
+
// names a __dev model (one Trae serves), else the first.
|
|
837
934
|
const liveModels = async (a) => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
935
|
+
let lists = null
|
|
936
|
+
try {
|
|
937
|
+
lists = await batchLists(a)
|
|
938
|
+
} catch (e) {
|
|
939
|
+
if (e instanceof Expired) throw e
|
|
940
|
+
}
|
|
941
|
+
if (!lists) {
|
|
942
|
+
const got = await Promise.allSettled(FUNCTIONS.map((fn) => listOf(a, fn)))
|
|
943
|
+
const expired = got.find((g) => g.status === "rejected" && g.reason instanceof Expired)
|
|
944
|
+
if (expired) throw expired.reason
|
|
945
|
+
if (got.every((g) => g.status === "rejected")) throw got[0].reason
|
|
946
|
+
lists = {}
|
|
947
|
+
got.forEach((g, i) => {
|
|
948
|
+
if (g.status === "fulfilled") lists[FUNCTIONS[i]] = g.value
|
|
949
|
+
})
|
|
950
|
+
}
|
|
842
951
|
const out = new Map()
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
952
|
+
for (const fn of FUNCTIONS) {
|
|
953
|
+
for (const m of lists[fn] ?? []) {
|
|
954
|
+
if (!chatModel(m)) continue
|
|
846
955
|
const id = String(m.config_name)
|
|
847
|
-
|
|
956
|
+
const was = out.get(id)
|
|
957
|
+
if (!was || (!devModel(was.m) && devModel(m))) out.set(id, { m, fn })
|
|
848
958
|
}
|
|
849
|
-
}
|
|
850
|
-
for (const [id, { fn }] of out)
|
|
959
|
+
}
|
|
960
|
+
for (const [id, { m, fn }] of out) {
|
|
961
|
+
listedBy.set(id, fn)
|
|
962
|
+
const dev = devModel(m)
|
|
963
|
+
if (dev) modelNames.set(id, { fn, name: dev.model_name })
|
|
964
|
+
else modelNames.delete(id)
|
|
965
|
+
}
|
|
851
966
|
return [...out.values()].map((x) => x.m)
|
|
852
967
|
}
|
|
853
968
|
|
|
854
969
|
const modelOf = (provider, m) => {
|
|
855
970
|
const id = String(m.config_name)
|
|
856
971
|
const was = provider.models?.[id] ?? {}
|
|
857
|
-
|
|
858
|
-
|
|
972
|
+
// context_window_tokens: dev is what a request gets, max only in Max mode
|
|
973
|
+
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
|
|
974
|
+
const out = Number(devModel(m)?.max_tokens) || was.limit?.output || MODEL.limit.output
|
|
975
|
+
const name = m.display_config?.display_name || m.display_name || m.display_model_name || was.name || id
|
|
976
|
+
return { ...MODEL, ...was, id, providerID: ID, name: String(name), limit: { context: ctx, output: out }, api: was.api ?? { id, url: HOSTS.api, npm: "@ai-sdk/openai-compatible" } }
|
|
859
977
|
}
|
|
860
978
|
|
|
861
979
|
return {
|
|
@@ -904,6 +1022,8 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
904
1022
|
let body = init.body ?? (r0 ? await r0.text() : undefined)
|
|
905
1023
|
if (body instanceof ArrayBuffer) body = new TextDecoder().decode(body)
|
|
906
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)
|
|
907
1027
|
const req = parseJSON(body)
|
|
908
1028
|
if (!Array.isArray(req.messages)) return errorResponse(400, "Trae CN: only chat completions are served")
|
|
909
1029
|
let a
|
|
@@ -919,11 +1039,12 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
919
1039
|
// served this account last, then the rest
|
|
920
1040
|
const fns = [...new Set([listedBy.get(String(req.model)), fnOf.get(who), ...FUNCTIONS].filter(Boolean))]
|
|
921
1041
|
let last
|
|
1042
|
+
const named = modelNames.get(String(req.model))
|
|
922
1043
|
for (const fn of fns) {
|
|
923
1044
|
const res = await fetch(apiOf(a) + "/api/agent/v3/llm_utils_chat", {
|
|
924
1045
|
method: "POST",
|
|
925
1046
|
headers: ideHeaders(a, { Accept: "text/event-stream", "X-Request-ID": randomUUID() }),
|
|
926
|
-
body: JSON.stringify(chatBody(req, fn)),
|
|
1047
|
+
body: JSON.stringify(chatBody(req, fn, named?.fn === fn ? named.name : "")),
|
|
927
1048
|
signal: init.signal ?? r0?.signal,
|
|
928
1049
|
})
|
|
929
1050
|
if (!res.ok) {
|
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.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",
|