@magpie-community/opencode-trae-auth 0.1.1 → 0.1.3
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 +21 -11
- package/index.mjs +103 -23
- 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,9 +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
|
+
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
|
|
61
71
|
known to serve: GLM-5.2, GLM-5, Kimi K2.6, Qwen 3.7 Plus, DeepSeek V4 Pro
|
|
62
72
|
and DeepSeek V4 Flash.
|
|
63
73
|
|
package/index.mjs
CHANGED
|
@@ -18,16 +18,23 @@ const HOSTS = {
|
|
|
18
18
|
}
|
|
19
19
|
const CLIENT_ID = "ono9krqynydwx5" // Trae CN's IDE
|
|
20
20
|
const APP_ID = "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8"
|
|
21
|
-
const IDE_VERSION = "3.3.65"
|
|
21
|
+
const IDE_VERSION = "3.3.65" // named to the authorization page
|
|
22
22
|
const IDE_VERSION_CODE = "20260401"
|
|
23
|
+
// the client the model host is told it serves: TRAE SOLO CN 0.1.69, as its
|
|
24
|
+
// ai-agent names itself (same app id). Trae offers a model only to clients
|
|
25
|
+
// new enough for it, and an April IDE was offered no deepseek-v4.1-flash.
|
|
26
|
+
const CLIENT_VERSION = "0.1.69"
|
|
27
|
+
const CLIENT_VERSION_CODE = "20260917"
|
|
23
28
|
const PLUGIN_VERSION = "2.3.24254"
|
|
24
29
|
const BRAND = "ASUS TUF Gaming A15 FA507RM_FA507RM"
|
|
25
30
|
const SIGN_IN_TIMEOUT = 10 * 60 * 1000
|
|
26
31
|
const EARLY_MS = 2 * 60 * 1000 // a token this close to its end is renewed before a request
|
|
27
32
|
const LEAD_MS = 10 * 60 * 1000 // and this close, magpie renews it ahead of time (auth.refresh)
|
|
28
33
|
const DAY = 24 * 3600 * 1000
|
|
29
|
-
// the IDE's chat functions: the classic IDE's agent, then SOLO's
|
|
30
|
-
|
|
34
|
+
// the IDE's chat functions: the classic IDE's agent, then SOLO's Work
|
|
35
|
+
// mode, then the TRAE agent (solo_agent, which has deepseek-v4.1-flash),
|
|
36
|
+
// then SOLO Lite's agent
|
|
37
|
+
const FUNCTIONS = ["chat_v3", "solo_work_lite", "solo_agent", "solo_agent_lite"]
|
|
31
38
|
|
|
32
39
|
const MODEL = { attachment: false, tool_call: true, reasoning: true, temperature: true, limit: { context: 128_000, output: 32_000 }, modalities: { input: ["text"], output: ["text"] } }
|
|
33
40
|
// what Trae CN's chat_v3 is known to serve; the live list replaces it
|
|
@@ -330,8 +337,10 @@ function ideHeaders(a, extra = {}) {
|
|
|
330
337
|
"x-device-id": a.deviceId,
|
|
331
338
|
"x-machine-id": a.machineId,
|
|
332
339
|
"x-request-id": randomUUID(),
|
|
333
|
-
"x-
|
|
334
|
-
"x-
|
|
340
|
+
"x-app-version": CLIENT_VERSION,
|
|
341
|
+
"x-app-version-code": CLIENT_VERSION_CODE,
|
|
342
|
+
"x-ide-version": CLIENT_VERSION,
|
|
343
|
+
"x-ide-version-code": CLIENT_VERSION_CODE,
|
|
335
344
|
"x-ide-version-type": "stable",
|
|
336
345
|
"x-device-cpu": "AMD",
|
|
337
346
|
"x-device-brand": BRAND,
|
|
@@ -419,13 +428,16 @@ const nativeTools = (tools) =>
|
|
|
419
428
|
return { type: "function", function: { name: f.name, description: f.description ?? "", parameters: typeof f.parameters === "string" ? f.parameters : JSON.stringify(f.parameters ?? {}) } }
|
|
420
429
|
})
|
|
421
430
|
|
|
422
|
-
|
|
431
|
+
// chatBody is the request for llm_utils_chat; modelName is the model the
|
|
432
|
+
// function's list names for the config (its __dev one), when it named one
|
|
433
|
+
function chatBody(req, fn, modelName) {
|
|
423
434
|
const session = randomUUID()
|
|
424
435
|
const body = {
|
|
425
436
|
messages: traeMessages(req),
|
|
426
437
|
function: fn,
|
|
427
438
|
config_name: req.model,
|
|
428
439
|
model: req.model,
|
|
440
|
+
...(modelName ? { model_name: modelName } : {}),
|
|
429
441
|
stream: true, // Trae answers in SSE either way
|
|
430
442
|
request_id: session,
|
|
431
443
|
session_id: session,
|
|
@@ -689,9 +701,27 @@ async function openai(req, it) {
|
|
|
689
701
|
return new Response(stream, { status: 200, headers: { "content-type": "text/event-stream", "cache-control": "no-cache" } })
|
|
690
702
|
}
|
|
691
703
|
|
|
692
|
-
// a model or function this account's chat_v3 doesn't take:
|
|
704
|
+
// a model or function this account's chat_v3 doesn't take: another may
|
|
693
705
|
const wrongFunction = (code) => ["4001", "4023", "1005"].includes(String(code))
|
|
694
706
|
|
|
707
|
+
// ---- the model lists ------------------------------------------------------------
|
|
708
|
+
|
|
709
|
+
// chatModel: an entry of a function's list that is a model to chat with.
|
|
710
|
+
// The lists also hold the IDE's own helpers (summary, fast_apply, title
|
|
711
|
+
// generation: usage other than chat_completion), configs switched off, and
|
|
712
|
+
// the slots of custom models, which need a provider bound in the IDE.
|
|
713
|
+
function chatModel(m) {
|
|
714
|
+
const id = String(m?.config_name ?? "")
|
|
715
|
+
if (!id || /^custom_model/i.test(id)) return false
|
|
716
|
+
if (m.usage && m.usage !== "chat_completion") return false
|
|
717
|
+
if (m.config_switch === false) return false
|
|
718
|
+
if (m.is_custom_model === true || m.display_config?.is_custom_model === true) return false
|
|
719
|
+
return true
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// devModel is the model an entry serves requests with: its __dev one
|
|
723
|
+
const devModel = (m) => (Array.isArray(m?.model_detail_list) ? m.model_detail_list : []).find((d) => /__dev$/.test(String(d?.model_name ?? "")))
|
|
724
|
+
|
|
695
725
|
// ---- usage ------------------------------------------------------------------------
|
|
696
726
|
|
|
697
727
|
// credits adds up the account's entitlement packs as the IDE shows them:
|
|
@@ -727,8 +757,10 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
727
757
|
const renewed = new Map()
|
|
728
758
|
// the chat function that served each account last
|
|
729
759
|
const fnOf = new Map()
|
|
730
|
-
// the
|
|
760
|
+
// the chat function whose model list has each model, and the __dev
|
|
761
|
+
// model it names there
|
|
731
762
|
const listedBy = new Map()
|
|
763
|
+
const modelNames = new Map()
|
|
732
764
|
|
|
733
765
|
const save = async (auth) => {
|
|
734
766
|
try {
|
|
@@ -816,6 +848,27 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
816
848
|
return out
|
|
817
849
|
}
|
|
818
850
|
|
|
851
|
+
// batchLists is every chat function's model list in one ask, as TRAE
|
|
852
|
+
// SOLO CN's ai-agent asks (batch_get_detail_param): {function: entries}.
|
|
853
|
+
// null when Trae answers it with no lists.
|
|
854
|
+
const batchLists = async (a) => {
|
|
855
|
+
const r = await post(a, apiOf(a) + "/api/ide/v1/batch_get_detail_param", {
|
|
856
|
+
functions: FUNCTIONS, agent_type: "", current_config_info: { config_name: "", is_custom_model: false },
|
|
857
|
+
mode_type: 0, access_type: 0, ab_force_vids: "", ab_autotest_advanced_mode: 0, show_custom_model: true,
|
|
858
|
+
})
|
|
859
|
+
if (r.status === 401 || lapsedCode(errorOf(r.v).code)) throw new Expired("Trae CN's sign-in has expired; sign in again")
|
|
860
|
+
if (r.status !== 200) throw new Error(`Trae CN models: ${statusLine(r.status)}`)
|
|
861
|
+
const groups = r.v.function_configs ?? r.v.data?.function_configs
|
|
862
|
+
if (!Array.isArray(groups)) return null
|
|
863
|
+
const out = {}
|
|
864
|
+
for (const g of groups) {
|
|
865
|
+
const fn = String(g?.function ?? "")
|
|
866
|
+
if (!FUNCTIONS.includes(fn) || !Array.isArray(g.config_info_list)) continue
|
|
867
|
+
out[fn] = [...(out[fn] ?? []), ...g.config_info_list.filter((m) => m?.config_name)]
|
|
868
|
+
}
|
|
869
|
+
return Object.keys(out).length ? out : null
|
|
870
|
+
}
|
|
871
|
+
|
|
819
872
|
// listOf is one function's model list, as the IDE asks for it
|
|
820
873
|
const listOf = async (a, fn) => {
|
|
821
874
|
const r = await post(a, apiOf(a) + "/api/ide/v1/get_detail_param", {
|
|
@@ -828,32 +881,58 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
828
881
|
}
|
|
829
882
|
|
|
830
883
|
// liveModels is the account's model list: every chat function's, since
|
|
831
|
-
// SOLO lists models the classic IDE's chat_v3 doesn't (
|
|
832
|
-
// came to
|
|
884
|
+
// SOLO lists models the classic IDE's chat_v3 doesn't (deepseek-v4.1-flash
|
|
885
|
+
// came to the TRAE agent's first, yetone/magpie#681); a model listed by one function
|
|
833
886
|
// only is asked through that one. One function's list failing leaves the
|
|
834
887
|
// others'; all failing is the error.
|
|
888
|
+
//
|
|
889
|
+
// The lists come from batch_get_detail_param, as TRAE SOLO CN asks them;
|
|
890
|
+
// when that gives none, from get_detail_param, one function at a time.
|
|
891
|
+
// A model several functions list is asked through the first whose entry
|
|
892
|
+
// names a __dev model (one Trae serves), else the first.
|
|
835
893
|
const liveModels = async (a) => {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
894
|
+
let lists = null
|
|
895
|
+
try {
|
|
896
|
+
lists = await batchLists(a)
|
|
897
|
+
} catch (e) {
|
|
898
|
+
if (e instanceof Expired) throw e
|
|
899
|
+
}
|
|
900
|
+
if (!lists) {
|
|
901
|
+
const got = await Promise.allSettled(FUNCTIONS.map((fn) => listOf(a, fn)))
|
|
902
|
+
const expired = got.find((g) => g.status === "rejected" && g.reason instanceof Expired)
|
|
903
|
+
if (expired) throw expired.reason
|
|
904
|
+
if (got.every((g) => g.status === "rejected")) throw got[0].reason
|
|
905
|
+
lists = {}
|
|
906
|
+
got.forEach((g, i) => {
|
|
907
|
+
if (g.status === "fulfilled") lists[FUNCTIONS[i]] = g.value
|
|
908
|
+
})
|
|
909
|
+
}
|
|
840
910
|
const out = new Map()
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
911
|
+
for (const fn of FUNCTIONS) {
|
|
912
|
+
for (const m of lists[fn] ?? []) {
|
|
913
|
+
if (!chatModel(m)) continue
|
|
844
914
|
const id = String(m.config_name)
|
|
845
|
-
|
|
915
|
+
const was = out.get(id)
|
|
916
|
+
if (!was || (!devModel(was.m) && devModel(m))) out.set(id, { m, fn })
|
|
846
917
|
}
|
|
847
|
-
}
|
|
848
|
-
for (const [id, { fn }] of out)
|
|
918
|
+
}
|
|
919
|
+
for (const [id, { m, fn }] of out) {
|
|
920
|
+
listedBy.set(id, fn)
|
|
921
|
+
const dev = devModel(m)
|
|
922
|
+
if (dev) modelNames.set(id, { fn, name: dev.model_name })
|
|
923
|
+
else modelNames.delete(id)
|
|
924
|
+
}
|
|
849
925
|
return [...out.values()].map((x) => x.m)
|
|
850
926
|
}
|
|
851
927
|
|
|
852
928
|
const modelOf = (provider, m) => {
|
|
853
929
|
const id = String(m.config_name)
|
|
854
930
|
const was = provider.models?.[id] ?? {}
|
|
855
|
-
|
|
856
|
-
|
|
931
|
+
// context_window_tokens: dev is what a request gets, max only in Max mode
|
|
932
|
+
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
|
|
933
|
+
const out = Number(devModel(m)?.max_tokens) || was.limit?.output || MODEL.limit.output
|
|
934
|
+
const name = m.display_config?.display_name || m.display_name || m.display_model_name || was.name || id
|
|
935
|
+
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" } }
|
|
857
936
|
}
|
|
858
937
|
|
|
859
938
|
return {
|
|
@@ -917,11 +996,12 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
917
996
|
// served this account last, then the rest
|
|
918
997
|
const fns = [...new Set([listedBy.get(String(req.model)), fnOf.get(who), ...FUNCTIONS].filter(Boolean))]
|
|
919
998
|
let last
|
|
999
|
+
const named = modelNames.get(String(req.model))
|
|
920
1000
|
for (const fn of fns) {
|
|
921
1001
|
const res = await fetch(apiOf(a) + "/api/agent/v3/llm_utils_chat", {
|
|
922
1002
|
method: "POST",
|
|
923
1003
|
headers: ideHeaders(a, { Accept: "text/event-stream", "X-Request-ID": randomUUID() }),
|
|
924
|
-
body: JSON.stringify(chatBody(req, fn)),
|
|
1004
|
+
body: JSON.stringify(chatBody(req, fn, named?.fn === fn ? named.name : "")),
|
|
925
1005
|
signal: init.signal ?? r0?.signal,
|
|
926
1006
|
})
|
|
927
1007
|
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.3",
|
|
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",
|