mslxdff 0.1.136 → 0.1.140

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 (95) hide show
  1. package/package.json +2 -2
  2. package/src/chat/gateway.js +1 -1
  3. package/src/chat/prompt.js +1 -1
  4. package/src/chat/terminal.js +1 -1
  5. package/src/chooser.js +31 -12
  6. package/src/cli/commands/model/list-live.js +22 -0
  7. package/src/cli/commands/model/list-providers.js +1 -1
  8. package/src/cli/commands/model/list-render.js +2 -2
  9. package/src/cli/commands/model/list.js +2 -0
  10. package/src/cli/commands/model/live-models.js +24 -0
  11. package/src/cli/commands/provider/add.js +2 -1
  12. package/src/cli/commands/provider/bench-via.js +1 -1
  13. package/src/cli/commands/provider/cline-free.js +94 -0
  14. package/src/cli/commands/provider/cline-login.js +20 -15
  15. package/src/cli/commands/provider/codearts-login.js +45 -0
  16. package/src/cli/commands/provider/index.js +34 -0
  17. package/src/cli/commands/provider/keys.js +13 -2
  18. package/src/cli/commands/provider/models.js +13 -1
  19. package/src/cli/commands/provider/qoder-checkin.js +81 -0
  20. package/src/cli/commands/provider/qoder-login.js +103 -0
  21. package/src/cli/commands/provider/traework-login.js +202 -0
  22. package/src/cli/commands/provider/workbuddy-login.js +1 -1
  23. package/src/cli/commands/workbuddy.js +14 -21
  24. package/src/cli/index.js +3 -0
  25. package/src/cli/interactive.js +41 -4
  26. package/src/cli/provider-row.js +1 -1
  27. package/src/compat.js +23 -5
  28. package/src/model-capabilities/merge.js +2 -2
  29. package/src/providers/classify.js +1 -1
  30. package/src/providers/cline/auth.js +1 -1
  31. package/src/providers/cline/chat.js +14 -11
  32. package/src/providers/cline/free-catalog.js +71 -0
  33. package/src/providers/cline/index.js +2 -16
  34. package/src/providers/cline/models.js +28 -51
  35. package/src/providers/codearts/auth-pool.js +148 -0
  36. package/src/providers/codearts/chat.js +176 -0
  37. package/src/providers/codearts/const.js +60 -0
  38. package/src/providers/codearts/dpop.js +95 -0
  39. package/src/providers/codearts/index.js +160 -0
  40. package/src/providers/codearts/login.js +214 -0
  41. package/src/providers/codearts/models.js +182 -0
  42. package/src/providers/codearts/sign.js +96 -0
  43. package/src/providers/codearts/sse.js +204 -0
  44. package/src/providers/codearts/stream.js +209 -0
  45. package/src/providers/codearts/sts.js +124 -0
  46. package/src/providers/codearts.js +1 -0
  47. package/src/providers/model-id.js +4 -4
  48. package/src/providers/qoder/account-store.js +107 -0
  49. package/src/providers/qoder/aggregate.js +51 -0
  50. package/src/providers/qoder/baseprompt.json +592 -0
  51. package/src/providers/qoder/chat.js +72 -0
  52. package/src/providers/qoder/checkin.js +160 -0
  53. package/src/providers/qoder/constants.js +45 -0
  54. package/src/providers/qoder/encode.js +42 -0
  55. package/src/providers/qoder/fingerprint.js +39 -0
  56. package/src/providers/qoder/index.js +106 -0
  57. package/src/providers/qoder/models.js +62 -0
  58. package/src/providers/qoder/oauth.js +85 -0
  59. package/src/providers/qoder/payload.js +156 -0
  60. package/src/providers/qoder/request.js +26 -0
  61. package/src/providers/qoder/session.js +135 -0
  62. package/src/providers/qoder/sse.js +46 -0
  63. package/src/providers/qoder/stream.js +81 -0
  64. package/src/providers/qoder.js +2 -0
  65. package/src/providers/registry.js +16 -1
  66. package/src/providers/share-keys.js +7 -2
  67. package/src/providers/traework/account-store.js +149 -0
  68. package/src/providers/traework/chat.js +211 -0
  69. package/src/providers/traework/checkin.js +35 -0
  70. package/src/providers/traework/constants.js +21 -0
  71. package/src/providers/traework/errors.js +52 -0
  72. package/src/providers/traework/headers.js +47 -0
  73. package/src/providers/traework/index.js +122 -0
  74. package/src/providers/traework/models.js +97 -0
  75. package/src/providers/traework/payload.js +86 -0
  76. package/src/providers/traework/sse.js +184 -0
  77. package/src/providers/traework/token.js +52 -0
  78. package/src/providers/traework.js +2 -0
  79. package/src/providers/workbuddy/account-store.js +73 -7
  80. package/src/providers/workbuddy/chat.js +1 -1
  81. package/src/providers/workbuddy/index.js +10 -20
  82. package/src/readline-compat.js +9 -10
  83. package/src/routes/models-route.js +19 -1
  84. package/src/runtime/bootstrap.js +10 -0
  85. package/src/runtime/provider-gate.js +30 -0
  86. package/src/runtime/providers-setup.js +18 -3
  87. package/src/runtime/qoder-checkin.js +93 -0
  88. package/src/runtime/server-lifecycle.js +2 -2
  89. package/src/runtime/traework-checkin.js +104 -0
  90. package/src/state/migrations/cline-unify.js +94 -0
  91. package/src/state/migrations.js +33 -0
  92. package/src/state/schemas/token.js +6 -2
  93. package/src/state/schemas/use-group.js +4 -4
  94. package/src/sync-workbuddy.js +1 -1
  95. package/src/upstream-engine/index.js +1 -1
@@ -0,0 +1,211 @@
1
+ // traework 对话服务:createTransport 直调(仿 workbuddy/chat.js),不走 base.js Bearer runner。
2
+ // 上游恒 stream:true;请求方 stream=true 直接透传转换后的 OpenAI SSE,false 则聚合回 JSON。
3
+ import { joinUrl } from "../base.js";
4
+ import { createTransport } from "../../transport/index.js";
5
+ import { AGENT_HOST, EP_CHAT, FUNCTION } from "./constants.js";
6
+ import { soloHeaders } from "./headers.js";
7
+ import { prepareBody } from "./payload.js";
8
+ import { ErrKind, UpstreamError, SOLOStreamError, classify, isSessionDead } from "./errors.js";
9
+ import { aggregateStreamReader, convertToOpenAIChunks } from "./sse.js";
10
+ import { exchangeRefresh } from "./token.js";
11
+ import { applyTokenRefresh } from "./account-store.js";
12
+
13
+ const REFRESH_SKEW_S = 24 * 60 * 60;
14
+
15
+ function needsRefresh(auth) {
16
+ const exp = Number(auth?.expiresAt) || 0;
17
+ if (!exp) return false;
18
+ return Math.floor(Date.now() / 1000) + REFRESH_SKEW_S >= exp;
19
+ }
20
+
21
+ function stripOwnPrefix(model, providerId) {
22
+ const s = String(model || "");
23
+ const i = s.indexOf("/");
24
+ if (i <= 0) return s;
25
+ return s.slice(0, i).toLowerCase() === String(providerId || "").toLowerCase() ? s.slice(i + 1) : s;
26
+ }
27
+
28
+ function errRes(status, msg, reason) {
29
+ return new Response(JSON.stringify({ error: { message: msg, type: "upstream_error", reason } }), {
30
+ status, headers: { "Content-Type": "application/json" },
31
+ });
32
+ }
33
+
34
+ // 上游 SOLO SSE Response → OpenAI SSE Response(边读边转,透传流)。
35
+ function reshapeSoloStream(upstreamRes, { model, chatId }) {
36
+ const src = upstreamRes.body;
37
+ const enc = new TextEncoder();
38
+ const dec = new TextDecoder();
39
+ let buf = "";
40
+ let usage = null;
41
+ let finishSent = false;
42
+ const stream = new ReadableStream({
43
+ async start(ctrl) {
44
+ const reader = src.getReader();
45
+ const send = (text) => ctrl.enqueue(enc.encode(text));
46
+ const sendChunk = (delta, finish) => {
47
+ const chunk = { id: chatId, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model || "", choices: [{ index: 0, delta }] };
48
+ if (finish) chunk.choices[0].finish_reason = finish;
49
+ if (usage) { chunk.usage = usage; usage = null; }
50
+ send(`data: ${JSON.stringify(chunk)}\n\n`);
51
+ };
52
+ try {
53
+ for (;;) {
54
+ const { done, value } = await reader.read();
55
+ if (value) buf += dec.decode(value, { stream: !done });
56
+ const lines = buf.split("\n");
57
+ buf = lines.pop();
58
+ let event = "";
59
+ let data = "";
60
+ const emit = (ev, dt) => {
61
+ if (ev === "output") {
62
+ let raw = null;
63
+ try { raw = JSON.parse(dt); } catch { return; }
64
+ const delta = {};
65
+ if (typeof raw.response === "string" && raw.response) delta.content = raw.response;
66
+ if (typeof raw.reasoning_content === "string" && raw.reasoning_content) delta.reasoning_content = raw.reasoning_content;
67
+ if (raw.tool_calls != null) {
68
+ const arr = Array.isArray(raw.tool_calls) ? raw.tool_calls : [raw.tool_calls];
69
+ delta.tool_calls = arr.filter(Boolean).map((c) => {
70
+ const call = { ...(c || {}) };
71
+ if (call.function_call && typeof call.function_call === "object") { call.function = call.function_call; delete call.function_call; }
72
+ if (call.function && typeof call.function === "object") { delete call.function.namespace; delete call.function.partial_arguments; }
73
+ return call;
74
+ });
75
+ }
76
+ if (Object.keys(delta).length) sendChunk(delta, "");
77
+ } else if (ev === "token_usage") { try { usage = JSON.parse(dt); } catch {} }
78
+ else if (ev === "done") { let f = "stop"; try { f = JSON.parse(dt)?.finish_reason || "stop"; } catch {} sendChunk({}, f); send("data: [DONE]\n\n"); finishSent = true; }
79
+ else if (ev === "error") { let m = dt; try { const j = JSON.parse(dt); m = `solo error code=${j.code} msg=${j.message}`; } catch {} send(`event: error\ndata: ${JSON.stringify(m)}\n\n`); send("data: [DONE]\n\n"); finishSent = true; }
80
+ };
81
+ for (const line of lines) {
82
+ const s = line.replace(/\r$/, "");
83
+ if (s === "") { if (event) { emit(event, data); event = ""; data = ""; } continue; }
84
+ if (s.startsWith("event:")) event = s.slice(6).trim();
85
+ else if (s.startsWith("data:")) data += s.slice(5);
86
+ }
87
+ if (done) { if (event) emit(event, data); break; }
88
+ }
89
+ } catch (e) { try { ctrl.error(e); } catch {} return; }
90
+ if (!finishSent) { try { send("data: [DONE]\n\n"); } catch {} }
91
+ try { ctrl.close(); } catch {}
92
+ },
93
+ });
94
+ return new Response(stream, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
95
+ }
96
+
97
+ export function createChatService({
98
+ id = "traework",
99
+ baseUrl = AGENT_HOST,
100
+ chatPath = EP_CHAT,
101
+ keys = [],
102
+ authList = [],
103
+ ring,
104
+ fetchImpl,
105
+ dispatcher,
106
+ connectTimeoutMs = 30_000,
107
+ file,
108
+ clock = Date.now,
109
+ cooldownMs = 30_000,
110
+ } = {}) {
111
+ const resolvedBase = String(baseUrl || AGENT_HOST).trim().replace(/\/+$/, "");
112
+ const transport = createTransport({ fetchImpl, dispatcher, keepAlive: !!dispatcher, timeoutMs: connectTimeoutMs, retry: {} });
113
+
114
+ function authForKey(key) {
115
+ const idx = keys.indexOf(key);
116
+ if (idx >= 0 && authList[idx]) return authList[idx];
117
+ if (authList.length) return authList[0];
118
+ return { uid: "", accessToken: key || "", refreshToken: "", expiresAt: 0, domain: "trae.cn", apiHost: "", machineId: "", deviceId: "" };
119
+ }
120
+
121
+ async function maybeRefresh(auth, key) {
122
+ if (!auth?.uid || !needsRefresh(auth) || !auth.refreshToken) return;
123
+ try {
124
+ const r = await exchangeRefresh(fetchImpl, auth.apiHost, auth.refreshToken);
125
+ await applyTokenRefresh({ uid: auth.uid, oldKey: key, newToken: r.token, refreshToken: r.refreshToken, domain: auth.domain, apiHost: auth.apiHost, machineId: auth.machineId, deviceId: auth.deviceId, enterpriseId: auth.enterpriseId, auth, keys, authList, file });
126
+ try { ring.replace(key, r.token); } catch {}
127
+ } catch {}
128
+ }
129
+
130
+ async function postOnce(key, auth, payload) {
131
+ const cred = { ...auth, accessToken: key };
132
+ return transport.request({
133
+ url: joinUrl(resolvedBase, chatPath),
134
+ method: "POST",
135
+ headers: soloHeaders(cred, true),
136
+ body: payload,
137
+ stream: true,
138
+ timeoutMs: connectTimeoutMs,
139
+ });
140
+ }
141
+
142
+ async function runChat(body, activeRing, opts = {}) {
143
+ const t0 = clock();
144
+ const wantStream = body?.stream !== false;
145
+ const rawModel = stripOwnPrefix(body?.model, id);
146
+ const payload = prepareBody({ ...(body || {}), model: rawModel || body?.model, stream: true, function: FUNCTION });
147
+ const chatId = `chatcmpl-${Date.now()}`;
148
+ const tried = new Set();
149
+ const maxTries = Math.min(3, Math.max(1, activeRing?.size || keys.length || 1));
150
+ let lastErr = null;
151
+ for (let attempt = 0; attempt < maxTries; attempt++) {
152
+ const key = activeRing ? activeRing.next() : keys[attempt];
153
+ if (!key) break;
154
+ const auth = authForKey(key);
155
+ const uid = auth?.uid || "";
156
+ if (tried.has(uid || key)) continue;
157
+ tried.add(uid || key);
158
+ await maybeRefresh(auth, key);
159
+ const curKey = keys.includes(key) ? key : (keys[keys.indexOf(key)] || key);
160
+ let res;
161
+ try { res = await postOnce(curKey, authForKey(curKey), payload); }
162
+ catch (e) { try { activeRing?.onError(key); } catch {} lastErr = e; continue; }
163
+ if (res.status >= 400) {
164
+ const txt = await res.text().catch(() => "");
165
+ const kind = classify(res.status, txt);
166
+ if (kind === ErrKind.SESSION_DEAD || isSessionDead(res.status, txt)) {
167
+ try { activeRing?.onError(key); } catch {}
168
+ continue; // 禁用语义:换号(同请求最多轮转3号)
169
+ }
170
+ if (kind === ErrKind.PLAN_LIMIT || kind === ErrKind.SOFT_RATE || res.status >= 500) {
171
+ try { activeRing?.onError(key); } catch {}
172
+ lastErr = new UpstreamError(kind, res.status, txt.slice(0, 200));
173
+ continue;
174
+ }
175
+ if (res.status === 404) { lastErr = new UpstreamError(kind, res.status, txt.slice(0, 200)); continue; }
176
+ return new Response(txt, { status: res.status, headers: { "Content-Type": "application/json" } });
177
+ }
178
+ if (wantStream) {
179
+ const out = reshapeSoloStream(res, { model: rawModel, chatId });
180
+ try { out._t = { attempts: [], waitMs: 0, totalMs: Math.round(clock() - t0) }; } catch {}
181
+ return out;
182
+ }
183
+ try {
184
+ const completion = await aggregateStreamReader(res.body, { model: rawModel, chatId });
185
+ const out = new Response(JSON.stringify(completion), { status: 200, headers: { "Content-Type": "application/json" } });
186
+ try { out._t = { attempts: [], waitMs: 0, totalMs: Math.round(clock() - t0) }; } catch {}
187
+ return out;
188
+ } catch (e) {
189
+ // 流内 event:error:4023 上游瞬时错误/1005 plan 权益不足等。错误体原样透传给客户端(冷却按分类):
190
+ // 1005 → 长冷却语义;其他 code(如 4023 通用失败)→ 短冷却换号重试,不把号拉黑。
191
+ if (e instanceof SOLOStreamError) {
192
+ const kind = e.kind();
193
+ // 1005 plan 权益不足:冷却该号换号重试;其余 code 上游通用失败:错误透传(不冷却,防雪崩)
194
+ if (kind === ErrKind.PLAN_LIMIT) {
195
+ try { activeRing?.onError(key); } catch {}
196
+ if ((activeRing?.available?.() ?? keys.length) > tried.size) { lastErr = e; continue; }
197
+ }
198
+ lastErr = e;
199
+ return errRes(502, e.message, kind);
200
+ }
201
+ if (e?.code !== undefined) { try { activeRing?.onError(key); } catch {} lastErr = e; continue; }
202
+ throw e;
203
+ }
204
+ }
205
+ if (lastErr instanceof UpstreamError) return errRes(lastErr.status >= 400 ? lastErr.status : 502, lastErr.message, lastErr.kind);
206
+ if (lastErr) throw lastErr;
207
+ return errRes(503, `${id}: all traework accounts exhausted or unavailable`, "exhausted");
208
+ }
209
+
210
+ return { runChat, authForKey, convertToOpenAIChunks, aggregateStreamReader };
211
+ }
@@ -0,0 +1,35 @@
1
+ // traework 签到/积分(照抄 traework2api internal/upstream/client.go Checkin* + UserEntUsage)。
2
+ import { UG_HOST, EP_CHECKIN_STATUS, EP_CHECKIN_CLAIM, EP_ENT_USAGE } from "./constants.js";
3
+ import { ugHeaders } from "./headers.js";
4
+ import { compatFetch, timeoutSignal } from "../../compat.js";
5
+
6
+ async function postJson(fetchImpl, url, cred, timeoutMs = 15000) {
7
+ const res = await fetchImpl(url, { method: "POST", headers: ugHeaders(cred), body: "{}", signal: timeoutSignal(timeoutMs) });
8
+ const txt = await res.text().catch(() => "");
9
+ let j = null;
10
+ try { j = JSON.parse(txt); } catch { j = { _raw: txt.slice(0, 200) }; }
11
+ return { res, json: j };
12
+ }
13
+
14
+ export async function checkinStatus({ cred, baseUrl = UG_HOST, fetchImpl = compatFetch, timeoutMs = 15000 } = {}) {
15
+ const { res, json } = await postJson(fetchImpl, `${String(baseUrl).replace(/\/+$/, "")}${EP_CHECKIN_STATUS}`, cred, timeoutMs);
16
+ if (res.status >= 400) throw Object.assign(new Error(`checkin status http ${res.status}`), { status: res.status });
17
+ return { checkedIn: Boolean(json?.checked_in), credits: Number(json?.credits) || 0, enable: Boolean(json?.enable) };
18
+ }
19
+
20
+ export async function checkinClaim({ cred, baseUrl = UG_HOST, fetchImpl = compatFetch, timeoutMs = 15000 } = {}) {
21
+ const { res } = await postJson(fetchImpl, `${String(baseUrl).replace(/\/+$/, "")}${EP_CHECKIN_CLAIM}`, cred, timeoutMs);
22
+ if (res.status >= 400) throw Object.assign(new Error(`checkin claim http ${res.status}`), { status: res.status });
23
+ return { ok: true };
24
+ }
25
+
26
+ // 积分:ide_user_ent_usage 的 credits_limit 求和。
27
+ export async function entUsage({ cred, baseUrl = UG_HOST, fetchImpl = compatFetch, timeoutMs = 15000 } = {}) {
28
+ const { res, json } = await postJson(fetchImpl, `${String(baseUrl).replace(/\/+$/, "")}${EP_ENT_USAGE}`, cred, timeoutMs);
29
+ if (res.status >= 400) throw Object.assign(new Error(`ent usage http ${res.status}`), { status: res.status });
30
+ const packs = json?.user_entitlement_pack_list;
31
+ if (!Array.isArray(packs)) return { remain: 0, isCreditsBilling: Boolean(json?.is_credits_billing) };
32
+ let remain = 0;
33
+ for (const p of packs) remain += Number(p?.entitlement_base_info?.quota?.credits_limit) || 0;
34
+ return { remain, isCreditsBilling: Boolean(json?.is_credits_billing) };
35
+ }
@@ -0,0 +1,21 @@
1
+ // traework SOLO 上游技术常量(照抄 traework2api internal/upstream/constants.go,禁止改值)。
2
+ export const AGENT_HOST = "https://trae-api-cn.mchost.guru";
3
+ export const UG_HOST = "https://api.trae.cn";
4
+ export const OAUTH_HOST = "https://api.trae.com.cn";
5
+ export const CONSOLE_HOST = "https://www.trae.cn";
6
+ export const CLIENT_ID = "en1oxy7wnw8j9n";
7
+ export const APP_ID = "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8";
8
+ export const IDE_VERSION = "0.1.43";
9
+ export const IDE_VERSION_CODE = "20260716";
10
+ export const DEVICE_BRAND = "83DG";
11
+ export const OS_VERSION = "Windows 11 Pro";
12
+ export const FUNCTION = "solo_work_lite";
13
+ export const TRAE_UA = "Trae/0.1.43";
14
+ export const DEFAULT_MODEL = "glm-5.2";
15
+ export const EP_CHAT = "/api/agent/v3/llm_utils_chat";
16
+ export const EP_MODELS = "/api/ide/v1/get_detail_param";
17
+ export const EP_EXCHANGE = "/cloudide/api/v3/trae/oauth/ExchangeToken";
18
+ export const EP_USER_INFO = "/cloudide/api/v3/trae/GetUserInfo";
19
+ export const EP_CHECKIN_STATUS = "/trae/api/v2/ug/checkin_credits/status";
20
+ export const EP_CHECKIN_CLAIM = "/trae/api/v2/ug/checkin_credits/claim";
21
+ export const EP_ENT_USAGE = "/trae/api/v2/pay/ide_user_ent_usage";
@@ -0,0 +1,52 @@
1
+ // SOLO 上游错误分类(照抄 traework2api internal/upstream/client.go Classify)。
2
+ export const ErrKind = {
3
+ NONE: "none",
4
+ PLAN_LIMIT: "plan_limit",
5
+ SOFT_RATE: "soft_rate",
6
+ SESSION_DEAD: "session_dead",
7
+ NOT_FOUND: "not_found",
8
+ SERVER: "server",
9
+ CLIENT: "client",
10
+ };
11
+
12
+ // 长冷却时长:plan_limit → 12h。
13
+ export const PLAN_COOLDOWN_MS = 12 * 60 * 60 * 1000;
14
+ export const SOFT_COOLDOWN_MS = 60 * 1000;
15
+
16
+ const SESSION_DEAD_MARKERS = ["login", "token 失效", "token invalid", "session", "unauthorized", "401"];
17
+
18
+ export class UpstreamError extends Error {
19
+ constructor(kind, status, msg) {
20
+ super(`upstream ${kind} (http ${status}): ${msg}`);
21
+ this.kind = kind;
22
+ this.status = status;
23
+ }
24
+ }
25
+
26
+ // 流内业务错误(event:error)。
27
+ export class SOLOStreamError extends Error {
28
+ constructor(code, msg) {
29
+ super(`solo error code=${code} msg=${msg}`);
30
+ this.code = code;
31
+ this.msg = msg;
32
+ }
33
+ kind() { return this.code === 1005 ? ErrKind.PLAN_LIMIT : ErrKind.CLIENT; }
34
+ }
35
+
36
+ export function classify(status, body) {
37
+ const txt = String(body || "");
38
+ const lower = txt.toLowerCase();
39
+ if (txt.includes('"code":1005') || (txt.includes("1005") && lower.includes("plan"))) return ErrKind.PLAN_LIMIT;
40
+ if (status === 401) return ErrKind.SESSION_DEAD;
41
+ if (status === 429) return ErrKind.SOFT_RATE;
42
+ if (status === 404) return ErrKind.NOT_FOUND;
43
+ if (status >= 500) return ErrKind.SERVER;
44
+ if (status >= 400) return ErrKind.CLIENT;
45
+ return ErrKind.NONE;
46
+ }
47
+
48
+ export function isSessionDead(status, body) {
49
+ if (status === 401) return true;
50
+ const lower = String(body || "").toLowerCase();
51
+ return SESSION_DEAD_MARKERS.some((m) => lower.includes(m.toLowerCase()));
52
+ }
@@ -0,0 +1,47 @@
1
+ // SOLO 三类请求头(照抄 traework2api internal/upstream/headers.go)。纯函数,可测。
2
+ import { APP_ID, DEVICE_BRAND, IDE_VERSION, IDE_VERSION_CODE, OS_VERSION, TRAE_UA } from "./constants.js";
3
+
4
+ // 对话/模型表(Cloud-IDE-JWT 鉴权 + IDE 指纹)。
5
+ export function soloHeaders(cred = {}, stream = true) {
6
+ const at = cred?.accessToken || "";
7
+ const h = {
8
+ "Content-Type": "application/json",
9
+ Accept: stream ? "text/event-stream" : "application/json",
10
+ "User-Agent": TRAE_UA,
11
+ Authorization: `Cloud-IDE-JWT ${at}`,
12
+ "X-Cloudide-Token": at,
13
+ "X-Ide-Token": at,
14
+ "X-App-Id": APP_ID,
15
+ "X-App-Version": "default",
16
+ "X-Ide-Version": IDE_VERSION,
17
+ "X-Ide-Version-Code": IDE_VERSION_CODE,
18
+ "X-App-Version-Code": IDE_VERSION_CODE,
19
+ "X-Ide-Version-Type": "stable",
20
+ "X-Device-Type": "windows",
21
+ "X-OS-Version": OS_VERSION,
22
+ "X-Device-Brand": DEVICE_BRAND,
23
+ "Request-Traffic-Type": "prod",
24
+ };
25
+ if (cred?.uid) h["X-Uid"] = cred.uid;
26
+ if (cred?.machineId) h["X-Machine-Id"] = cred.machineId;
27
+ if (cred?.deviceId) h["X-Device-Id"] = cred.deviceId;
28
+ return h;
29
+ }
30
+
31
+ // 签到/积分(api.trae.cn)。
32
+ export function ugHeaders(cred = {}) {
33
+ const h = {
34
+ "Content-Type": "application/json",
35
+ Accept: "application/json",
36
+ "User-Agent": TRAE_UA,
37
+ Authorization: `Cloud-IDE-JWT ${cred?.accessToken || ""}`,
38
+ "X-User-Region": "CN",
39
+ };
40
+ if (cred?.deviceId) h["X-Device-Id"] = cred.deviceId;
41
+ return h;
42
+ }
43
+
44
+ // ExchangeToken / GetUserInfo(无签名,仅 UA)。
45
+ export function oauthHeaders() {
46
+ return { "Content-Type": "application/json", Accept: "application/json", "User-Agent": TRAE_UA };
47
+ }
@@ -0,0 +1,122 @@
1
+ // traework provider 门面:薄聚合(读 state keys/auths,无则扫 auth 目录)。
2
+ import { createKeyRing } from "../keyring.js";
3
+ import { loadProviderKeys, loadProviderAuths, loadProviderBaseUrl } from "../../state.js";
4
+ import { compatFetch, getUndici } from "../../compat.js";
5
+ import { envInt, createAgent } from "../base.js";
6
+ import { AGENT_HOST } from "./constants.js";
7
+ import { listAccountDocs } from "./account-store.js";
8
+ import { createChatService } from "./chat.js";
9
+ import { createModelsService } from "./models.js";
10
+
11
+ const { UndiciFetch } = getUndici();
12
+
13
+ function resolveBaseUrl(baseUrl) {
14
+ if (baseUrl) return String(baseUrl).trim().replace(/\/+$/, "");
15
+ try {
16
+ const env = loadProviderBaseUrl("traework");
17
+ if (env) return env;
18
+ } catch {}
19
+ return AGENT_HOST;
20
+ }
21
+
22
+ export function createTraeworkProvider({
23
+ id = "traework",
24
+ baseUrl,
25
+ apiKeys,
26
+ apiKey,
27
+ auths,
28
+ connectTimeoutMs = Number(process.env.MSLXDFF_TRAEWORK_TIMEOUT_MS) || 30_000,
29
+ cooldownMs = envInt("MSLXDFF_TRAEWORK_COOLDOWN_MS", 30_000),
30
+ fetchImpl,
31
+ file,
32
+ clock = Date.now,
33
+ } = {}) {
34
+ if (id !== "traework") throw new Error(`traework provider id mismatch: ${id}`);
35
+ const resolvedBase = resolveBaseUrl(baseUrl);
36
+ if (!fetchImpl) fetchImpl = UndiciFetch || compatFetch;
37
+
38
+ const keysFromState = (() => { try { return loadProviderKeys(id, file ? { file } : {}); } catch { return []; } })();
39
+ const authsFromState = (() => { try { return loadProviderAuths(id, file ? { file } : {}); } catch { return []; } })();
40
+ const keys = (() => {
41
+ const list = [
42
+ ...(Array.isArray(apiKeys) ? apiKeys : [apiKeys].filter(Boolean)),
43
+ apiKey,
44
+ ...(apiKeys === undefined && apiKey === undefined ? keysFromState : []),
45
+ ].filter((k) => typeof k === "string" && k.trim().length);
46
+ return [...new Set(list.map((k) => k.trim()))];
47
+ })();
48
+ let authList = Array.isArray(auths) && auths.length ? [...auths] : [...authsFromState];
49
+
50
+ if (!authList.length && !keys.length) {
51
+ try {
52
+ for (const { uid, doc } of listAccountDocs()) {
53
+ const at = doc?.auth?.accessToken || doc?.accessToken;
54
+ if (at && !keys.includes(at)) keys.push(at);
55
+ authList.push({
56
+ uid,
57
+ domain: doc?.auth?.domain || "trae.cn",
58
+ apiHost: doc?.auth?.apiHost || "",
59
+ machineId: doc?.auth?.machineId || "",
60
+ deviceId: doc?.auth?.deviceId || "",
61
+ enterpriseId: doc?.account?.enterpriseId || "",
62
+ refreshToken: doc?.auth?.refreshToken || "",
63
+ expiresAt: doc?.auth?.expiresAt || 0,
64
+ nickname: doc?.account?.nickname || "",
65
+ });
66
+ }
67
+ } catch {}
68
+ }
69
+ // auths 行自带 refreshToken 但无 accessToken(login 后 keys 与 auths 平行);
70
+ // keys 与 authList 不配对(如 state 被外部改写)→ 从 auth 目录按 uid 补齐 accessToken,防 ring 空
71
+ if (authList.length && keys.length !== authList.length) {
72
+ try {
73
+ const docs = new Map(listAccountDocs().map((d) => [String(d.uid), d.doc]));
74
+ authList.forEach((a, i) => {
75
+ const at = docs.get(String(a.uid))?.auth?.accessToken;
76
+ if (at && !keys.includes(at)) keys[i] = at;
77
+ });
78
+ } catch {}
79
+ }
80
+
81
+ const ring = createKeyRing(keys, { cooldownMs, now: clock });
82
+ let agent = null; let dispatcher = null;
83
+ try {
84
+ const a = createAgent({
85
+ keepAliveTimeout: envInt("MSLXDFF_TRAEWORK_KEEPALIVE_TIMEOUT", 30_000),
86
+ keepAliveMaxTimeout: envInt("MSLXDFF_TRAEWORK_KEEPALIVE_MAX_TIMEOUT", 60_000),
87
+ connections: envInt("MSLXDFF_TRAEWORK_KEEPALIVE_CONNECTIONS", 20),
88
+ });
89
+ agent = a.agent; dispatcher = a.dispatcher;
90
+ } catch {}
91
+
92
+ function getKey() { return ring.next() || keys[0] || ""; }
93
+ function getAuth(k) {
94
+ const idx = keys.indexOf(k);
95
+ if (idx >= 0 && authList[idx]) return authList[idx];
96
+ if (authList.length) return authList[0];
97
+ return { uid: "", accessToken: k || "", refreshToken: "", expiresAt: 0, domain: "trae.cn", apiHost: "", machineId: "", deviceId: "" };
98
+ }
99
+
100
+ const chatSvc = createChatService({ id, baseUrl: resolvedBase, keys, authList, ring, fetchImpl, dispatcher, connectTimeoutMs, file, clock, cooldownMs });
101
+ const modelsSvc = createModelsService({ id, baseUrl: resolvedBase, fetchImpl, getKey, getAuth, clock });
102
+
103
+ async function chat(body, opts = {}) { return chatSvc.runChat(body, ring, opts); }
104
+
105
+ async function chatWithKeys(body, keysOverride) {
106
+ const tmpKeys = [...keysOverride].filter((k) => typeof k === "string" && k.trim().length).map((k) => k.trim());
107
+ const tmpAuth = authList[0] || getAuth("");
108
+ const tmpAuthList = tmpKeys.map(() => tmpAuth);
109
+ const tmpRing = createKeyRing(tmpKeys, { cooldownMs, now: clock });
110
+ const tmpSvc = createChatService({ id, baseUrl: resolvedBase, keys: tmpKeys, authList: tmpAuthList, ring: tmpRing, fetchImpl, dispatcher: null, connectTimeoutMs, file, clock, cooldownMs });
111
+ return tmpSvc.runChat(body, tmpRing);
112
+ }
113
+
114
+ async function close() { if (agent && typeof agent.close === "function") { try { await agent.close(); } catch {} } }
115
+
116
+ return { id, chat, chatWithKeys, listModels: modelsSvc.listModels, preheat: modelsSvc.preheat, close, agent, keyRing: ring, baseUrl: resolvedBase };
117
+ }
118
+
119
+ export { mapModel, normalizeModelName, staticModels } from "./models.js";
120
+ export { soloHeaders, ugHeaders, oauthHeaders } from "./headers.js";
121
+ export { prepareBody } from "./payload.js";
122
+ export { classify, ErrKind } from "./errors.js";
@@ -0,0 +1,97 @@
1
+ // traework 模型服务:get_detail_param 动态拉取(10min 缓存)+ 静态 32 个回退。
2
+ import { AGENT_HOST, EP_MODELS, FUNCTION, DEFAULT_MODEL } from "./constants.js";
3
+ import { soloHeaders } from "./headers.js";
4
+ import { joinModelId } from "../model-id.js";
5
+
6
+ const CTX = 131072;
7
+ const CREATED = 1753600000;
8
+ const OWNER = "trae-solo";
9
+
10
+ export const STATIC_MODEL_IDS = [
11
+ "Doubao-Seed-2.1-Pro", "seed-code-pro-0430", "Doubao-Seed-2.1-Turbo", "Doubao-Seed-2.0-Code",
12
+ "DeepSeek-V4-Flash-Official", "browser_use_subagent", "glm-5.2", "glm-5-turbo", "glm-5",
13
+ "DeepSeek-V4-Pro", "DeepSeek-V4-Flash", "kimi-k3", "kimi-k2.7-code", "kimi-k2.6",
14
+ "minimax-m3", "qwen-3.7-plus", "sagitta", "aquila", "custom_model_gemini",
15
+ "custom_model_placeholder", "custom_model_1M_text", "custom_model_1M", "custom_model_kimi",
16
+ "custom_model_claude", "custom_model_gpt-5", "custom_model_no-fc", "custom_model_deepseek_chat",
17
+ "custom_model_deepseek_reasoner", "custom_model_deepseek_v4", "explore_sub_agent_v13",
18
+ "explore_sub_agent_v2", "summary",
19
+ ];
20
+
21
+ export function staticModels(id = "traework") {
22
+ return STATIC_MODEL_IDS.map((m) => ({ id: joinModelId(id, m), object: "model", created: CREATED, owned_by: OWNER, context_length: CTX }));
23
+ }
24
+
25
+ export function normalizeModelName(s) {
26
+ return String(s || "").split("_").map((p) => (p ? p[0].toUpperCase() + p.slice(1).toLowerCase() : p)).join("-");
27
+ }
28
+
29
+ // 模型映射:空/auto→默认;去 __ 后缀;下划线转横线宽松匹配;未知→400。
30
+ export function mapModel(model, knownIds, def = DEFAULT_MODEL) {
31
+ const m = String(model || "").trim();
32
+ if (!m || m === "auto") return { configName: def };
33
+ const base = m.includes("__") ? m.slice(0, m.indexOf("__")) : m;
34
+ const known = new Set(knownIds || []);
35
+ if (known.has(base)) return { configName: base };
36
+ const norm = normalizeModelName(base);
37
+ if (known.has(norm)) return { configName: norm };
38
+ // 大小写不敏感兜底
39
+ const low = new Map([...known].map((k) => [String(k).toLowerCase(), k]));
40
+ if (low.has(base.toLowerCase())) return { configName: low.get(base.toLowerCase()) };
41
+ if (low.has(norm.toLowerCase())) return { configName: low.get(norm.toLowerCase()) };
42
+ const err = new Error(`unknown model ${JSON.stringify(model)}`);
43
+ err.status = 400;
44
+ throw err;
45
+ }
46
+
47
+ export function createModelsService({
48
+ id = "traework",
49
+ baseUrl = AGENT_HOST,
50
+ fetchImpl,
51
+ getKey,
52
+ getAuth,
53
+ clock = Date.now,
54
+ } = {}) {
55
+ const CACHE_TTL_MS = 10 * 60 * 1000;
56
+ let cache = null;
57
+ let fetchedAt = 0;
58
+ const resolvedBase = String(baseUrl || AGENT_HOST).trim().replace(/\/+$/, "");
59
+
60
+ async function fetchDynamic() {
61
+ const key = getKey ? getKey() : "";
62
+ const auth = getAuth ? getAuth(key) : null;
63
+ const cred = { ...(auth || {}), accessToken: key || auth?.accessToken || "" };
64
+ const res = await fetchImpl(`${resolvedBase}${EP_MODELS}`, {
65
+ method: "POST",
66
+ headers: soloHeaders(cred, false),
67
+ body: JSON.stringify({ function: FUNCTION, config_names: null, need_prompt: false, current_config_info: null, poly_prompt: true, mode_type: null, agent_type: null }),
68
+ });
69
+ if (!res.ok) throw new Error(`models http ${res.status}`);
70
+ const j = await res.json().catch(() => ({}));
71
+ const list = j?.config_info_list;
72
+ if (!Array.isArray(list) || !list.length) throw new Error("models api returned empty list");
73
+ return list.filter((c) => c?.config_name).map((c) => ({ id: c.config_name, name: c?.display_config?.display_name || c.config_name }));
74
+ }
75
+
76
+ async function listModels() {
77
+ const now = clock();
78
+ if (cache && now - fetchedAt < CACHE_TTL_MS) return cache;
79
+ try {
80
+ const dyn = await fetchDynamic();
81
+ cache = dyn.map((m) => ({ id: joinModelId(id, m.id), object: "model", created: CREATED, owned_by: OWNER, context_length: CTX, name: m.name }));
82
+ fetchedAt = now;
83
+ return cache;
84
+ } catch {
85
+ cache = staticModels(id);
86
+ fetchedAt = now;
87
+ return cache;
88
+ }
89
+ }
90
+
91
+ async function preheat() {
92
+ try { await listModels(); return { ok: true }; }
93
+ catch (err) { return { ok: false, error: String(err?.message || err).slice(0, 120) }; }
94
+ }
95
+
96
+ return { listModels, preheat, mapModel: (m, known) => mapModel(m, known), staticModels: () => staticModels(id), normalizeModelName };
97
+ }