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.
- package/package.json +2 -2
- package/src/chat/gateway.js +1 -1
- package/src/chat/prompt.js +1 -1
- package/src/chat/terminal.js +1 -1
- package/src/chooser.js +31 -12
- package/src/cli/commands/model/list-live.js +22 -0
- package/src/cli/commands/model/list-providers.js +1 -1
- package/src/cli/commands/model/list-render.js +2 -2
- package/src/cli/commands/model/list.js +2 -0
- package/src/cli/commands/model/live-models.js +24 -0
- package/src/cli/commands/provider/add.js +2 -1
- package/src/cli/commands/provider/bench-via.js +1 -1
- package/src/cli/commands/provider/cline-free.js +94 -0
- package/src/cli/commands/provider/cline-login.js +20 -15
- package/src/cli/commands/provider/codearts-login.js +45 -0
- package/src/cli/commands/provider/index.js +34 -0
- package/src/cli/commands/provider/keys.js +13 -2
- package/src/cli/commands/provider/models.js +13 -1
- package/src/cli/commands/provider/qoder-checkin.js +81 -0
- package/src/cli/commands/provider/qoder-login.js +103 -0
- package/src/cli/commands/provider/traework-login.js +202 -0
- package/src/cli/commands/provider/workbuddy-login.js +1 -1
- package/src/cli/commands/workbuddy.js +14 -21
- package/src/cli/index.js +3 -0
- package/src/cli/interactive.js +41 -4
- package/src/cli/provider-row.js +1 -1
- package/src/compat.js +23 -5
- package/src/model-capabilities/merge.js +2 -2
- package/src/providers/classify.js +1 -1
- package/src/providers/cline/auth.js +1 -1
- package/src/providers/cline/chat.js +14 -11
- package/src/providers/cline/free-catalog.js +71 -0
- package/src/providers/cline/index.js +2 -16
- package/src/providers/cline/models.js +28 -51
- package/src/providers/codearts/auth-pool.js +148 -0
- package/src/providers/codearts/chat.js +176 -0
- package/src/providers/codearts/const.js +60 -0
- package/src/providers/codearts/dpop.js +95 -0
- package/src/providers/codearts/index.js +160 -0
- package/src/providers/codearts/login.js +214 -0
- package/src/providers/codearts/models.js +182 -0
- package/src/providers/codearts/sign.js +96 -0
- package/src/providers/codearts/sse.js +204 -0
- package/src/providers/codearts/stream.js +209 -0
- package/src/providers/codearts/sts.js +124 -0
- package/src/providers/codearts.js +1 -0
- package/src/providers/model-id.js +4 -4
- package/src/providers/qoder/account-store.js +107 -0
- package/src/providers/qoder/aggregate.js +51 -0
- package/src/providers/qoder/baseprompt.json +592 -0
- package/src/providers/qoder/chat.js +72 -0
- package/src/providers/qoder/checkin.js +160 -0
- package/src/providers/qoder/constants.js +45 -0
- package/src/providers/qoder/encode.js +42 -0
- package/src/providers/qoder/fingerprint.js +39 -0
- package/src/providers/qoder/index.js +106 -0
- package/src/providers/qoder/models.js +62 -0
- package/src/providers/qoder/oauth.js +85 -0
- package/src/providers/qoder/payload.js +156 -0
- package/src/providers/qoder/request.js +26 -0
- package/src/providers/qoder/session.js +135 -0
- package/src/providers/qoder/sse.js +46 -0
- package/src/providers/qoder/stream.js +81 -0
- package/src/providers/qoder.js +2 -0
- package/src/providers/registry.js +16 -1
- package/src/providers/share-keys.js +7 -2
- package/src/providers/traework/account-store.js +149 -0
- package/src/providers/traework/chat.js +211 -0
- package/src/providers/traework/checkin.js +35 -0
- package/src/providers/traework/constants.js +21 -0
- package/src/providers/traework/errors.js +52 -0
- package/src/providers/traework/headers.js +47 -0
- package/src/providers/traework/index.js +122 -0
- package/src/providers/traework/models.js +97 -0
- package/src/providers/traework/payload.js +86 -0
- package/src/providers/traework/sse.js +184 -0
- package/src/providers/traework/token.js +52 -0
- package/src/providers/traework.js +2 -0
- package/src/providers/workbuddy/account-store.js +73 -7
- package/src/providers/workbuddy/chat.js +1 -1
- package/src/providers/workbuddy/index.js +10 -20
- package/src/readline-compat.js +9 -10
- package/src/routes/models-route.js +19 -1
- package/src/runtime/bootstrap.js +10 -0
- package/src/runtime/provider-gate.js +30 -0
- package/src/runtime/providers-setup.js +18 -3
- package/src/runtime/qoder-checkin.js +93 -0
- package/src/runtime/server-lifecycle.js +2 -2
- package/src/runtime/traework-checkin.js +104 -0
- package/src/state/migrations/cline-unify.js +94 -0
- package/src/state/migrations.js +33 -0
- package/src/state/schemas/token.js +6 -2
- package/src/state/schemas/use-group.js +4 -4
- package/src/sync-workbuddy.js +1 -1
- package/src/upstream-engine/index.js +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// qoder 消息/payload 组装(转译 qoder2api bridge.go CallQoderWithOpts + messages.go)
|
|
2
|
+
// 模板:baseprompt.json(vendored 数据文件,含 {UUID1..5}/{TIME1} 占位符,加载时替换一次)
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { newUUID, unixMs } from "./fingerprint.js";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
// 模板单例:进程内加载一次(替换占位符后 JSON.parse),每请求深拷贝
|
|
11
|
+
let _template = null;
|
|
12
|
+
export function loadTemplate(templatePath) {
|
|
13
|
+
if (_template && !templatePath) return _template;
|
|
14
|
+
let tmpl = readFileSync(templatePath || join(__dirname, "baseprompt.json"), "utf8");
|
|
15
|
+
for (const k of ["{UUID1}", "{UUID2}", "{UUID3}", "{UUID4}", "{UUID5}"]) tmpl = tmpl.split(k).join(newUUID());
|
|
16
|
+
tmpl = tmpl.split("{TIME1}").join(String(unixMs()));
|
|
17
|
+
_template = JSON.parse(tmpl);
|
|
18
|
+
return _template;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function blankResponseMeta() {
|
|
22
|
+
return {
|
|
23
|
+
id: "",
|
|
24
|
+
usage: {
|
|
25
|
+
prompt_tokens: 0, completion_tokens: 0, total_tokens: 0,
|
|
26
|
+
completion_tokens_details: { reasoning_tokens: 0 },
|
|
27
|
+
prompt_tokens_details: { cached_tokens: 0 },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// OpenAI user 消息 → Qoder contents 形状
|
|
33
|
+
export function buildUserMessage(text) {
|
|
34
|
+
return {
|
|
35
|
+
role: "user", content: "",
|
|
36
|
+
contents: [{ type: "text", text: String(text || "") }],
|
|
37
|
+
response_meta: blankResponseMeta(),
|
|
38
|
+
reasoning_content_signature: "",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildStructuredMessage(role, text) {
|
|
43
|
+
return { role, content: String(text || ""), response_meta: blankResponseMeta(), reasoning_content_signature: "" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeContent(content) {
|
|
47
|
+
if (typeof content === "string") return content;
|
|
48
|
+
if (content == null) return "";
|
|
49
|
+
if (Array.isArray(content)) {
|
|
50
|
+
let out = "";
|
|
51
|
+
for (const b of content) {
|
|
52
|
+
const t = typeof b === "object" && b ? b.text : b;
|
|
53
|
+
if (typeof t === "string") out += (out ? "\n\n" : "") + t;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify(content);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 提取最后一条 user 消息文本(仿 ExtractLatestUserPrompt)
|
|
61
|
+
export function extractLatestUserPrompt(messages) {
|
|
62
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
63
|
+
const m = messages[i];
|
|
64
|
+
if (m?.role === "user") return normalizeContent(m.content);
|
|
65
|
+
}
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// OpenAI messages → Qoder messages(仿 BuildQoderMessages:无 system 前置模板 system;
|
|
70
|
+
// user 含 tool_result 数组拆成 tool 行;assistant tool_calls 透传)
|
|
71
|
+
export function buildQoderMessages(template, incoming, toolsEnabled) {
|
|
72
|
+
const rebuilt = [];
|
|
73
|
+
const hasSystem = (incoming || []).some((m) => m?.role === "system");
|
|
74
|
+
if (!hasSystem && template) {
|
|
75
|
+
for (const m of template.messages || []) {
|
|
76
|
+
if (m?.role === "system") rebuilt.push(JSON.parse(JSON.stringify(m)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const m of incoming || []) {
|
|
80
|
+
if (!m || typeof m !== "object") continue;
|
|
81
|
+
const text = normalizeContent(m.content);
|
|
82
|
+
if (m.role === "user") {
|
|
83
|
+
// tool_result 数组形态 → tool 行
|
|
84
|
+
if (Array.isArray(m.content) && m.content[0]?.type === "tool_result") {
|
|
85
|
+
for (const b of m.content) {
|
|
86
|
+
if (b?.type !== "tool_result") continue;
|
|
87
|
+
rebuilt.push({ role: "tool", tool_call_id: b.tool_use_id || "", content: normalizeContent(b.content) });
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
rebuilt.push(buildUserMessage(text));
|
|
92
|
+
} else if (m.role === "assistant" && toolsEnabled && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
93
|
+
const out = buildStructuredMessage("assistant", text);
|
|
94
|
+
out.tool_calls = m.tool_calls;
|
|
95
|
+
rebuilt.push(out);
|
|
96
|
+
} else if (m.role === "tool") {
|
|
97
|
+
const out = buildStructuredMessage("tool", text);
|
|
98
|
+
if (m.tool_call_id) out.tool_call_id = m.tool_call_id;
|
|
99
|
+
if (m.name) out.name = m.name;
|
|
100
|
+
rebuilt.push(out);
|
|
101
|
+
} else if (text) {
|
|
102
|
+
rebuilt.push(buildStructuredMessage(m.role === "system" ? "system" : m.role || "user", text));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return rebuilt;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 组装上游请求体(仿 CallQoderWithOpts)
|
|
109
|
+
export function buildQoderBody({ template, userType, model, messages, tools, maxTokens, isReasoning }) {
|
|
110
|
+
const body = JSON.parse(JSON.stringify(template || loadTemplate()));
|
|
111
|
+
const nid = newUUID();
|
|
112
|
+
body.request_id = nid;
|
|
113
|
+
body.chat_record_id = nid;
|
|
114
|
+
body.request_set_id = newUUID();
|
|
115
|
+
body.session_id = newUUID();
|
|
116
|
+
body.stream = true;
|
|
117
|
+
body.aliyun_user_type = userType || "personal_standard";
|
|
118
|
+
if (body.model_config) {
|
|
119
|
+
body.model_config.key = model;
|
|
120
|
+
if (isReasoning) body.model_config.is_reasoning = true;
|
|
121
|
+
}
|
|
122
|
+
if (maxTokens > 0 && body.parameters) body.parameters.max_tokens = maxTokens;
|
|
123
|
+
const prompt = extractLatestUserPrompt(messages);
|
|
124
|
+
if (body.business) {
|
|
125
|
+
body.business.id = newUUID();
|
|
126
|
+
body.business.begin_at = unixMs();
|
|
127
|
+
body.business.name = prompt.length > 30 ? prompt.slice(0, 30) : prompt;
|
|
128
|
+
}
|
|
129
|
+
if (body.chat_context) {
|
|
130
|
+
if (body.chat_context.text) body.chat_context.text.text = prompt;
|
|
131
|
+
if (body.chat_context.extra?.originalContent) body.chat_context.extra.originalContent.text = prompt;
|
|
132
|
+
}
|
|
133
|
+
body.messages = messages;
|
|
134
|
+
if (tools != null) body.tools = tools;
|
|
135
|
+
return { body, prompt, mcSource: body.model_config?.source || "system" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 客户端模型名 → 上游 key(仿 defaultModelMapping 双向 substring,家族关键字兜底)
|
|
139
|
+
const DEFAULT_MAPPING = {
|
|
140
|
+
opus: "qmodel_38max", sonnet: "gmodel", haiku: "qfmodel",
|
|
141
|
+
gpt: "dmodel", gemini: "gmodel", glm: "gmodel", kimi: "kmodel", deepseek: "dmodel",
|
|
142
|
+
qwen: "qfmodel", flash: "qfmodel", mini: "efficient", pro: "performance", max: "performance",
|
|
143
|
+
};
|
|
144
|
+
// 上游原生 key 白名单(model/list 实测 15 个)——精确命中直接透传,防被家族关键字抢走
|
|
145
|
+
const UPSTREAM_KEYS = new Set(["auto", "ultimate", "performance", "efficient", "qmodel_38max", "qfmodel", "qmodel_latest", "qmodel", "kmodel_latest", "kmodel", "gmodel", "gfmodel", "dmodel", "dfmodel", "mmodel"]);
|
|
146
|
+
export function mapModel(model) {
|
|
147
|
+
const m = String(model || "").trim();
|
|
148
|
+
if (!m || m.toLowerCase() === "auto") return "qfmodel";
|
|
149
|
+
const low = m.toLowerCase();
|
|
150
|
+
if (UPSTREAM_KEYS.has(low)) return low;
|
|
151
|
+
if (DEFAULT_MAPPING[low]) return DEFAULT_MAPPING[low];
|
|
152
|
+
for (const [k, v] of Object.entries(DEFAULT_MAPPING)) {
|
|
153
|
+
if (low.includes(k) || k.includes(low)) return v;
|
|
154
|
+
}
|
|
155
|
+
return low;
|
|
156
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// qoder 上游请求装配(纯函数,无网络):OpenAI body → COSY 签名请求(url/headers/bodyStr)。
|
|
2
|
+
// 从 chat.js 拆出,使真流式/聚合两条管线共享同一装配口(单一来源,防两处签名漂移)。
|
|
3
|
+
import { getEndpoints, normalizeRegion } from "./constants.js";
|
|
4
|
+
import { buildCosyHeaders, pathSigFrom } from "./session.js";
|
|
5
|
+
import { cosyEncode } from "./encode.js";
|
|
6
|
+
import { buildQoderBody, mapModel } from "./payload.js";
|
|
7
|
+
|
|
8
|
+
export function buildUpstreamRequest({ sess, region, model, messages, tools, maxTokens }) {
|
|
9
|
+
const upstreamModel = mapModel(model);
|
|
10
|
+
const { body, mcSource } = buildQoderBody({
|
|
11
|
+
template: undefined,
|
|
12
|
+
userType: sess?.identity?.userType,
|
|
13
|
+
model: upstreamModel,
|
|
14
|
+
messages,
|
|
15
|
+
tools,
|
|
16
|
+
maxTokens,
|
|
17
|
+
});
|
|
18
|
+
const ep = getEndpoints(normalizeRegion(region));
|
|
19
|
+
const url = ep.chatStreamURL;
|
|
20
|
+
const bodyStr = cosyEncode(Buffer.from(JSON.stringify(body)));
|
|
21
|
+
const headers = buildCosyHeaders(sess, pathSigFrom(url), bodyStr, "text/event-stream");
|
|
22
|
+
headers["x-model-key"] = upstreamModel;
|
|
23
|
+
headers["x-model-source"] = mcSource;
|
|
24
|
+
headers["accept"] = "text/event-stream";
|
|
25
|
+
return { url, headers, bodyStr, upstreamModel, mcSource };
|
|
26
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// qoder COSY 签名与会话(转译 qoder2api internal/cosy/session.go + signature.go + auth.go)
|
|
2
|
+
// 会话:RSA-OAEP 加密 tempKey + AES-CBC 加密身份 payload → cosyKey + info
|
|
3
|
+
// 签名:md5(payloadB64 + "\n" + cosyKey + "\n" + date + "\n" + body + "\n" + pathSig)
|
|
4
|
+
import { createHash, randomBytes, createCipheriv, publicEncrypt } from "node:crypto";
|
|
5
|
+
import { cosyEncode } from "./encode.js";
|
|
6
|
+
import { newUUID, unixSec } from "./fingerprint.js";
|
|
7
|
+
|
|
8
|
+
export const APP_CODE = "cosy";
|
|
9
|
+
export const VERSION = "1.0.10";
|
|
10
|
+
// base64("war, war never changes")
|
|
11
|
+
const SECRET_B64 = "d2FyLCB3YXIgbmV2ZXIgY2hhbmdlcw==";
|
|
12
|
+
|
|
13
|
+
const SERVER_PUB_KEY = `-----BEGIN PUBLIC KEY-----
|
|
14
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8iMH5c02LilrsERw9t6Pv5Nc
|
|
15
|
+
4k6Pz1EaDicBMpdpxKduSZu5OANqUq8er4GM95omAGIOPOh+Nx0spthYA2BqGz+l
|
|
16
|
+
6HRkPJ7S236FZz73In/KVuLnwI8JJ2CbuJap8kvheCCZpmAWpb/cPx/3Vr/J6I17
|
|
17
|
+
XcW+ML9FoCI6AOvOzwIDAQAB
|
|
18
|
+
-----END PUBLIC KEY-----`;
|
|
19
|
+
|
|
20
|
+
export function signLegacy(date) {
|
|
21
|
+
const s = APP_CODE + "&" + SECRET_B64 + "&" + date;
|
|
22
|
+
return createHash("md5").update(s).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function currentDate() {
|
|
26
|
+
return new Date().toUTCString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildPayloadB64(info) {
|
|
30
|
+
const m = JSON.stringify({
|
|
31
|
+
cosyVersion: VERSION,
|
|
32
|
+
ideVersion: "",
|
|
33
|
+
info,
|
|
34
|
+
requestId: newUUID(),
|
|
35
|
+
version: "v1",
|
|
36
|
+
});
|
|
37
|
+
return Buffer.from(m).toString("base64");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function signRequest(payloadB64, cosyKey, cosyDate, body, pathWithoutAlgo) {
|
|
41
|
+
const s = payloadB64 + "\n" + cosyKey + "\n" + cosyDate + "\n" + body + "\n" + pathWithoutAlgo;
|
|
42
|
+
return createHash("md5").update(s).digest("hex");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function composeBearer(payloadB64, sig) {
|
|
46
|
+
return "Bearer COSY." + payloadB64 + "." + sig;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function psfRsaEncrypt(plaintext) {
|
|
50
|
+
return publicEncrypt(
|
|
51
|
+
{ key: SERVER_PUB_KEY, padding: 1 }, // PKCS1v15
|
|
52
|
+
plaintext
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function aesEncrypt(plain, key) {
|
|
57
|
+
const bs = 16;
|
|
58
|
+
const pad = bs - (plain.length % bs);
|
|
59
|
+
const padded = Buffer.alloc(plain.length + pad, pad);
|
|
60
|
+
plain.copy(padded);
|
|
61
|
+
const iv = key.slice(0, bs);
|
|
62
|
+
const cipher = createCipheriv("aes-128-cbc", key, iv);
|
|
63
|
+
return Buffer.concat([cipher.update(padded), cipher.final()]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function authPayloadJSON(id) {
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
name: id.name || "",
|
|
69
|
+
aid: id.aid || "",
|
|
70
|
+
uid: id.uid || "",
|
|
71
|
+
yx_uid: id.yxUid || "",
|
|
72
|
+
organization_id: id.organizationId || "",
|
|
73
|
+
organization_name: id.organizationName || "",
|
|
74
|
+
user_type: id.userType || "personal_standard",
|
|
75
|
+
security_oauth_token: id.securityOauthToken || "",
|
|
76
|
+
refresh_token: id.refreshToken || "",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function newSession(id, machineId, machineToken, machineType) {
|
|
81
|
+
const raw = randomBytes(16);
|
|
82
|
+
const tempKey = Buffer.from(raw.toString("hex").slice(0, 16), "utf8").slice(0, 16);
|
|
83
|
+
// 确保正好 16 字节
|
|
84
|
+
const key16 = Buffer.alloc(16);
|
|
85
|
+
tempKey.copy(key16);
|
|
86
|
+
|
|
87
|
+
const cosyKeyBytes = psfRsaEncrypt(key16);
|
|
88
|
+
const cosyKey = cosyKeyBytes.toString("base64");
|
|
89
|
+
|
|
90
|
+
const payloadBytes = Buffer.from(authPayloadJSON(id), "utf8");
|
|
91
|
+
const encPayload = aesEncrypt(payloadBytes, key16);
|
|
92
|
+
const info = encPayload.toString("base64");
|
|
93
|
+
|
|
94
|
+
return { tempKey: key16, cosyKey, info, identity: id, machineId, machineToken, machineType };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 去除 /algo 前缀的 PathSig
|
|
98
|
+
export function pathSigFrom(url) {
|
|
99
|
+
try {
|
|
100
|
+
const u = new URL(url);
|
|
101
|
+
let p = u.pathname;
|
|
102
|
+
if (p.startsWith("/algo")) p = p.slice(5);
|
|
103
|
+
return p;
|
|
104
|
+
} catch {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 构造请求头
|
|
110
|
+
export function buildCosyHeaders(sess, pathSig, bodyStr, accept) {
|
|
111
|
+
const payloadB64 = buildPayloadB64(sess.info);
|
|
112
|
+
const date = String(unixSec());
|
|
113
|
+
const sig = signRequest(payloadB64, sess.cosyKey, date, bodyStr || "", pathSig);
|
|
114
|
+
const bearer = composeBearer(payloadB64, sig);
|
|
115
|
+
return {
|
|
116
|
+
"cosy-data-policy": "agree",
|
|
117
|
+
"content-type": "application/json",
|
|
118
|
+
"cosy-machinetype": sess.machineType,
|
|
119
|
+
"cosy-clienttype": "5",
|
|
120
|
+
"cosy-date": date,
|
|
121
|
+
"cosy-user": sess.identity.uid,
|
|
122
|
+
"cosy-key": sess.cosyKey,
|
|
123
|
+
"cache-control": "no-cache",
|
|
124
|
+
"accept": accept || "application/json",
|
|
125
|
+
"authorization": bearer,
|
|
126
|
+
"cosy-version": VERSION,
|
|
127
|
+
"cosy-machineid": sess.machineId,
|
|
128
|
+
"cosy-machinetoken": sess.machineToken,
|
|
129
|
+
"login-version": "v2",
|
|
130
|
+
"user-agent": "Go-http-client/2.0",
|
|
131
|
+
"cosy-scene": "assistant",
|
|
132
|
+
"cosy-business-product": "ide",
|
|
133
|
+
"cosy-business-type": "agent",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// qoder 上游 SSE 信封解析(转译 qoder2api bridge.go ExtractDelta)
|
|
2
|
+
// 帧形态:data:{"headers":{},"body":"<内层JSON>","statusCodeValue":200}
|
|
3
|
+
// 信封 statusCodeValue!=200 = 瞬时上游错;内层 code!=0 = 业务错;usage 同帧合并
|
|
4
|
+
export function extractDelta(dataLine) {
|
|
5
|
+
let wrapper;
|
|
6
|
+
try { wrapper = JSON.parse(dataLine); } catch { return {}; }
|
|
7
|
+
if (wrapper.statusCodeValue !== undefined && Number(wrapper.statusCodeValue) !== 200) {
|
|
8
|
+
const detail = typeof wrapper.body === "string" && wrapper.body ? wrapper.body : JSON.stringify(wrapper).slice(0, 400);
|
|
9
|
+
return { err: { kind: "upstream", status: Number(wrapper.statusCodeValue) || 502, detail } };
|
|
10
|
+
}
|
|
11
|
+
const inner = wrapper.body;
|
|
12
|
+
if (!inner || typeof inner !== "string") return {};
|
|
13
|
+
let j;
|
|
14
|
+
try { j = JSON.parse(inner); } catch { return {}; }
|
|
15
|
+
let usageIn = 0, usageOut = 0;
|
|
16
|
+
if (j.usage && typeof j.usage === "object") {
|
|
17
|
+
usageIn = Math.trunc(Number(j.usage.prompt_tokens) || 0);
|
|
18
|
+
usageOut = Math.trunc(Number(j.usage.completion_tokens) || 0);
|
|
19
|
+
}
|
|
20
|
+
const choices = Array.isArray(j.choices) ? j.choices : [];
|
|
21
|
+
for (const ch of choices) {
|
|
22
|
+
const delta = ch?.delta;
|
|
23
|
+
if (!delta) continue;
|
|
24
|
+
const role = typeof delta.role === "string" ? delta.role : "";
|
|
25
|
+
const content = typeof delta.content === "string" ? delta.content : "";
|
|
26
|
+
const reasoning = typeof delta.reasoning_content === "string" ? delta.reasoning_content : "";
|
|
27
|
+
const toolCalls = Array.isArray(delta.tool_calls) && delta.tool_calls.length ? delta.tool_calls : null;
|
|
28
|
+
if (role || content || reasoning || toolCalls) {
|
|
29
|
+
return { role, content, reasoning, toolCalls, usageIn, usageOut };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (typeof j.code === "string" && j.code && j.code !== "0") {
|
|
33
|
+
return { err: { kind: /inappropriate|DataInspection|Sensitive|ContentFilter/i.test(String(j.message)) ? "content_policy" : "business", code: j.code, detail: String(j.message || "") } };
|
|
34
|
+
}
|
|
35
|
+
if (usageIn > 0 || usageOut > 0) return { usageIn, usageOut };
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 错误四分类 → HTTP 状态(对齐 errors.go ErrorStatus 语义)
|
|
40
|
+
export function errorStatus(err) {
|
|
41
|
+
if (!err) return 500;
|
|
42
|
+
if (err.kind === "content_policy") return 400;
|
|
43
|
+
if (err.kind === "auth") return [401, 403].includes(err.status) ? err.status : 401;
|
|
44
|
+
if (err.kind === "upstream") return [401, 403, 429].includes(err.status) ? err.status : 502;
|
|
45
|
+
return 502;
|
|
46
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// qoder 真流式转发:上游 Response.body → OpenAI SSE Response(边收边转,不攒数组)。
|
|
2
|
+
// 对标 traework chat.js reshapeSoloStream(reader.read 循环 + sendChunk 即时写)。
|
|
3
|
+
// usage 只在尾帧出现:收到即记,附在 finish chunk 上一次发出(与旧回放语义一致)。
|
|
4
|
+
import { extractDelta } from "./sse.js";
|
|
5
|
+
|
|
6
|
+
function openaiChunk({ model, chatId, delta, finish, usage }) {
|
|
7
|
+
const c = {
|
|
8
|
+
id: chatId,
|
|
9
|
+
object: "chat.completion.chunk",
|
|
10
|
+
created: Math.floor(Date.now() / 1000),
|
|
11
|
+
model,
|
|
12
|
+
choices: [{ index: 0, delta, finish_reason: finish || null }],
|
|
13
|
+
};
|
|
14
|
+
if (usage && finish) c.usage = usage;
|
|
15
|
+
return `data: ${JSON.stringify(c)}\n\n`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function errorEvent(e) {
|
|
19
|
+
return `event: error\ndata: ${JSON.stringify({ message: String(e?.detail || e?.message || e), type: e?.kind || "upstream_error" })}\n\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function reshapeQoderStream(upstreamRes, { model, chatId }) {
|
|
23
|
+
const src = upstreamRes.body;
|
|
24
|
+
const enc = new TextEncoder();
|
|
25
|
+
const dec = new TextDecoder();
|
|
26
|
+
let buf = "";
|
|
27
|
+
let usage = null;
|
|
28
|
+
let finish = "stop";
|
|
29
|
+
let sawContent = false;
|
|
30
|
+
let finishSent = false;
|
|
31
|
+
const stream = new ReadableStream({
|
|
32
|
+
async start(ctrl) {
|
|
33
|
+
const reader = src.getReader();
|
|
34
|
+
const send = (t) => ctrl.enqueue(enc.encode(t));
|
|
35
|
+
try {
|
|
36
|
+
send(openaiChunk({ model, chatId, delta: { role: "assistant" } }));
|
|
37
|
+
for (;;) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (value) buf += dec.decode(value, { stream: !done });
|
|
40
|
+
const lines = buf.split("\n");
|
|
41
|
+
buf = lines.pop();
|
|
42
|
+
for (const raw of lines) {
|
|
43
|
+
const line = raw.replace(/\r$/, "");
|
|
44
|
+
if (!line.startsWith("data:")) continue;
|
|
45
|
+
const payload = line.slice(5).trim();
|
|
46
|
+
if (!payload || payload === "[DONE]") continue;
|
|
47
|
+
const d = extractDelta(payload);
|
|
48
|
+
if (d.err) throw d.err;
|
|
49
|
+
if (d.usageIn || d.usageOut) {
|
|
50
|
+
usage = { prompt_tokens: d.usageIn, completion_tokens: d.usageOut, total_tokens: d.usageIn + d.usageOut };
|
|
51
|
+
}
|
|
52
|
+
const delta = {};
|
|
53
|
+
if (d.content) { delta.content = d.content; sawContent = true; }
|
|
54
|
+
if (d.reasoning) { delta.reasoning_content = d.reasoning; sawContent = true; }
|
|
55
|
+
if (d.toolCalls) { delta.tool_calls = d.toolCalls; finish = "tool_calls"; sawContent = true; }
|
|
56
|
+
if (Object.keys(delta).length) send(openaiChunk({ model, chatId, delta }));
|
|
57
|
+
}
|
|
58
|
+
if (done) break;
|
|
59
|
+
}
|
|
60
|
+
if (!sawContent && !usage) {
|
|
61
|
+
throw { kind: "upstream", status: 502, detail: "empty upstream stream" };
|
|
62
|
+
}
|
|
63
|
+
send(openaiChunk({ model, chatId, delta: {}, finish, usage }));
|
|
64
|
+
send("data: [DONE]\n\n");
|
|
65
|
+
finishSent = true;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
if (!finishSent) {
|
|
68
|
+
try { send(openaiChunk({ model, chatId, delta: {} })); } catch {}
|
|
69
|
+
try { send(errorEvent(e)); } catch {}
|
|
70
|
+
try { send("data: [DONE]\n\n"); } catch {}
|
|
71
|
+
finishSent = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try { ctrl.close(); } catch {}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
return new Response(stream, {
|
|
78
|
+
status: 200,
|
|
79
|
+
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -10,11 +10,26 @@ export const customProviders = [
|
|
|
10
10
|
match: (id, baseUrl) => id === "workbuddy" || String(baseUrl).includes("copilot.tencent"),
|
|
11
11
|
load: () => import("./workbuddy.js").then((m) => m.createWorkbuddyProvider),
|
|
12
12
|
},
|
|
13
|
+
{
|
|
14
|
+
id: "traework",
|
|
15
|
+
match: (id, baseUrl) => id === "traework" || String(baseUrl).includes("trae"),
|
|
16
|
+
load: () => import("./traework.js").then((m) => m.createTraeworkProvider),
|
|
17
|
+
},
|
|
13
18
|
{
|
|
14
19
|
id: "cline",
|
|
15
|
-
match: (id, baseUrl) => id === "cline" ||
|
|
20
|
+
match: (id, baseUrl) => id === "cline" || String(baseUrl).includes("cline.bot"),
|
|
16
21
|
load: () => import("./cline.js").then((m) => m.createClineProvider),
|
|
17
22
|
},
|
|
23
|
+
{
|
|
24
|
+
id: "codearts",
|
|
25
|
+
match: (id, baseUrl) => id === "codearts" || String(baseUrl).includes("myhuaweicloud.com"),
|
|
26
|
+
load: () => import("./codearts.js").then((m) => m.createCodeartsProvider),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "qoder",
|
|
30
|
+
match: (id, baseUrl) => id === "qoder" || String(baseUrl).includes("qoder"),
|
|
31
|
+
load: () => import("./qoder/index.js").then((m) => m.createQoderProvider),
|
|
32
|
+
},
|
|
18
33
|
];
|
|
19
34
|
|
|
20
35
|
// 供 bench/probe 等需要定制化解析模型列表的场景
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import { splitModelId, DEFAULT_PROVIDER } from "./model-id.js";
|
|
4
4
|
import { classifyProvider } from "./classify.js";
|
|
5
5
|
import { loadProviderKeys, listProviderIdsWithKeys } from "../state.js";
|
|
6
|
+
import { isRefreshToken } from "./cline/headers.js";
|
|
6
7
|
|
|
7
8
|
export const SHARE_KEYS_HEADER = "x-mslxdff-share-keys";
|
|
8
9
|
|
|
9
10
|
// 硬排除:refresh-token 型凭据(对端刷新会轮换 token,与本机互相踢下线)——正确性问题。
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// 二层保护:① 按 id 硬排除(cline 恒 local-only);② 按凭据形状兜底(见 shareableProviderIds)。
|
|
12
|
+
// traework 与 workbuddy 同为本机账号绑定型(auths/*.json + uid),恒不借出。
|
|
13
|
+
const NEVER_SHARE_IDS = new Set(["cline", "codearts", "traework", "qoder"]);
|
|
12
14
|
// 本节点应 cast key 到出站转发的供应商 id 集合 = 所有「本机有 key」的供应商,
|
|
13
15
|
// 减去:opencode(无 key 且恒排除)、local-only(workbuddy/cline系,本就不走组员)、刷新型凭据。
|
|
14
16
|
export function shareableProviderIds({ file } = {}) {
|
|
@@ -17,6 +19,9 @@ export function shareableProviderIds({ file } = {}) {
|
|
|
17
19
|
if (id === DEFAULT_PROVIDER) continue;
|
|
18
20
|
if (NEVER_SHARE_IDS.has(id)) continue;
|
|
19
21
|
try { if (classifyProvider(id) === "local-only") continue; } catch {}
|
|
22
|
+
// 形状兜底:id 白名单可能有残留(迁移未跑/回滚/手改 config),只要该供应商现有 key 里
|
|
23
|
+
// 有一个是 refreshToken 形态,就一律不外借——refreshToken 借出去会被对端刷新轮换,双向踢下线。
|
|
24
|
+
try { if (loadProviderKeys(id, { file }).some((k) => isRefreshToken(k, id))) continue; } catch {}
|
|
20
25
|
ids.push(id);
|
|
21
26
|
}
|
|
22
27
|
return ids;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// traework 账号落盘单一源:auths/trae-<uid>.json(0600 tmp+rename)+ state.json keys/auths 按 uid 去重。
|
|
2
|
+
// 仿 src/providers/workbuddy/account-store.js。
|
|
3
|
+
import { writeFileSync, mkdirSync, existsSync, renameSync, unlinkSync, readdirSync, readFileSync, chmodSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { isTestEnv, defaultStateFile } from "../../state/store.js";
|
|
7
|
+
|
|
8
|
+
export function authDirFor({ explicit = "", testEnv = false, stateFile } = {}) {
|
|
9
|
+
if (explicit) return explicit;
|
|
10
|
+
if (testEnv) return join(tmpdir(), "mslxdff-test-auths");
|
|
11
|
+
return join(dirname(stateFile), "auths");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveAuthDir() {
|
|
15
|
+
return authDirFor({
|
|
16
|
+
explicit: process.env.TRAEWORK_AUTH_DIR || "",
|
|
17
|
+
testEnv: isTestEnv(),
|
|
18
|
+
stateFile: defaultStateFile(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function authDirCandidates({ primary, explicit = "", testEnv = false, cwdDir = "" } = {}) {
|
|
23
|
+
const dirs = [primary];
|
|
24
|
+
if (!explicit && !testEnv && cwdDir && cwdDir !== primary) dirs.push(cwdDir);
|
|
25
|
+
return dirs;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveAuthDirs() {
|
|
29
|
+
return authDirCandidates({
|
|
30
|
+
primary: resolveAuthDir(),
|
|
31
|
+
explicit: process.env.TRAEWORK_AUTH_DIR || "",
|
|
32
|
+
testEnv: isTestEnv(),
|
|
33
|
+
cwdDir: join(process.cwd(), "auths"),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findAccountFile(uid) {
|
|
38
|
+
for (const dir of resolveAuthDirs()) {
|
|
39
|
+
const fp = join(dir, `trae-${uid}.json`);
|
|
40
|
+
if (existsSync(fp)) return fp;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 扫描 trae-*.json,返回已校验的凭证行。读取侧单一真相。
|
|
46
|
+
export function listAccountDocs({ dirs } = {}) {
|
|
47
|
+
const out = [];
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (const dir of dirs || resolveAuthDirs()) {
|
|
50
|
+
let files = [];
|
|
51
|
+
try {
|
|
52
|
+
if (!existsSync(dir)) continue;
|
|
53
|
+
files = readdirSync(dir).filter((f) => f.startsWith("trae-") && f.endsWith(".json"));
|
|
54
|
+
} catch { continue; }
|
|
55
|
+
for (const f of files) {
|
|
56
|
+
try {
|
|
57
|
+
const doc = JSON.parse(readFileSync(join(dir, f), "utf8"));
|
|
58
|
+
const uid = doc?.account?.uid || doc?.uid;
|
|
59
|
+
const at = doc?.auth?.accessToken || doc?.accessToken;
|
|
60
|
+
if (!uid || !at) continue;
|
|
61
|
+
if (seen.has(String(uid))) continue;
|
|
62
|
+
seen.add(String(uid));
|
|
63
|
+
out.push({ uid: String(uid), file: join(dir, f), dir, doc });
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeAccountFile({ uid, accessToken, refreshToken = "", expiresAt = 0, domain = "trae.cn", apiHost = "", machineId = "", deviceId = "", enterpriseId = "", nickname = "" }) {
|
|
71
|
+
const authDir = resolveAuthDir();
|
|
72
|
+
mkdirSync(authDir, { recursive: true });
|
|
73
|
+
const exp = expiresAt || Math.floor(Date.now() / 1000) + 5184000;
|
|
74
|
+
const doc = {
|
|
75
|
+
account: { uid, enterpriseId, nickname },
|
|
76
|
+
auth: { accessToken, refreshToken, expiresAt: exp, domain, apiHost, machineId, deviceId },
|
|
77
|
+
};
|
|
78
|
+
const fp = join(authDir, `trae-${uid}.json`);
|
|
79
|
+
const tmp = `${fp}.tmp`;
|
|
80
|
+
writeFileSync(tmp, JSON.stringify(doc, null, 2), { mode: 0o600 });
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(fp)) { try { chmodSync(fp, 0o600); } catch {} unlinkSync(fp); }
|
|
83
|
+
renameSync(tmp, fp);
|
|
84
|
+
} catch {
|
|
85
|
+
writeFileSync(fp, JSON.stringify(doc, null, 2), { mode: 0o600 });
|
|
86
|
+
try { unlinkSync(tmp); } catch {}
|
|
87
|
+
}
|
|
88
|
+
try { chmodSync(fp, 0o600); } catch {}
|
|
89
|
+
return fp;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function persistAccounts(accounts, file, extraKeys = []) {
|
|
93
|
+
const { loadProviderConfigs, saveProviderConfig } = await import("../../state.js");
|
|
94
|
+
const cfg = (loadProviderConfigs(file ? { file } : {})?.traework) || {};
|
|
95
|
+
const keys = [...(Array.isArray(cfg.keys) ? cfg.keys : [])];
|
|
96
|
+
const auths = [...(Array.isArray(cfg.auths) ? cfg.auths : [])];
|
|
97
|
+
let updated = 0;
|
|
98
|
+
for (const acc of accounts) {
|
|
99
|
+
if (!acc?.uid || !acc?.accessToken) continue;
|
|
100
|
+
const row = { uid: acc.uid, domain: acc.domain || "trae.cn", apiHost: acc.apiHost || "", machineId: acc.machineId || "", deviceId: acc.deviceId || "", enterpriseId: acc.enterpriseId || "", refreshToken: acc.refreshToken || "" };
|
|
101
|
+
const atIdx = auths.findIndex((a) => a?.uid === acc.uid);
|
|
102
|
+
if (atIdx >= 0) { keys[atIdx] = acc.accessToken; auths[atIdx] = row; updated += 1; }
|
|
103
|
+
else { keys.push(acc.accessToken); auths.push(row); }
|
|
104
|
+
}
|
|
105
|
+
for (const k of extraKeys) if (k && !keys.includes(k)) keys.push(k);
|
|
106
|
+
saveProviderConfig("traework", { baseUrl: cfg.baseUrl || "https://trae-api-cn.mchost.guru", keys, auths }, file ? { file } : {});
|
|
107
|
+
return { keys, auths, updated };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function saveTraeworkAccount({ uid, enterpriseId = "", nickname = "", accessToken, refreshToken = "", expiresAt = 0, domain = "trae.cn", apiHost = "", machineId = "", deviceId = "", file } = {}) {
|
|
111
|
+
if (!uid || !accessToken) throw new Error("saveTraeworkAccount: 缺少 uid/accessToken");
|
|
112
|
+
const fp = writeAccountFile({ uid, accessToken, refreshToken, expiresAt, domain, apiHost, machineId, deviceId, enterpriseId, nickname });
|
|
113
|
+
const st = await persistAccounts([{ uid, accessToken, refreshToken, domain, apiHost, machineId, deviceId, enterpriseId }], file);
|
|
114
|
+
return { file: fp, accounts: st.keys.length, updated: st.updated > 0 };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function accountsFromArrays(keys, authList) {
|
|
118
|
+
const accounts = [];
|
|
119
|
+
const extraKeys = [];
|
|
120
|
+
const n = Math.max(keys.length, authList.length);
|
|
121
|
+
for (let i = 0; i < n; i++) {
|
|
122
|
+
const a = authList[i];
|
|
123
|
+
if (a && typeof a === "object" && a.uid) {
|
|
124
|
+
accounts.push({ uid: a.uid, accessToken: keys[i] || "", refreshToken: a.refreshToken || "", domain: a.domain, apiHost: a.apiHost, machineId: a.machineId, deviceId: a.deviceId, enterpriseId: a.enterpriseId });
|
|
125
|
+
} else if (keys[i]) extraKeys.push(keys[i]);
|
|
126
|
+
}
|
|
127
|
+
return { accounts, extraKeys };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 刷新后的 token 落盘:平行数组按下标更新 + state + auths 文件单点落盘。
|
|
131
|
+
export async function applyTokenRefresh({ uid, oldKey, newToken, refreshToken = "", domain = "trae.cn", apiHost = "", machineId = "", deviceId = "", enterpriseId = "", auth = null, keys = [], authList = [], file } = {}) {
|
|
132
|
+
if (!uid || !newToken) return { updated: false, accounts: keys.length, file: null };
|
|
133
|
+
const keyIdx = keys.indexOf(oldKey);
|
|
134
|
+
const uidIdx = authList.findIndex((a) => a && String(a.uid) === String(uid));
|
|
135
|
+
const idx = keyIdx >= 0 ? keyIdx : uidIdx;
|
|
136
|
+
const matched = idx >= 0;
|
|
137
|
+
if (idx >= 0) keys[idx] = newToken;
|
|
138
|
+
else if (!keys.includes(newToken)) keys.push(newToken);
|
|
139
|
+
if (uidIdx >= 0) {
|
|
140
|
+
const prev = authList[uidIdx] && typeof authList[uidIdx] === "object" ? authList[uidIdx] : null;
|
|
141
|
+
authList[uidIdx] = { ...(prev || auth || {}), uid, domain: prev?.domain || auth?.domain || domain, apiHost: prev?.apiHost || auth?.apiHost || apiHost, machineId: prev?.machineId || auth?.machineId || machineId, deviceId: prev?.deviceId || auth?.deviceId || deviceId, enterpriseId: prev?.enterpriseId || auth?.enterpriseId || enterpriseId, refreshToken };
|
|
142
|
+
} else {
|
|
143
|
+
authList.push({ ...(auth || {}), uid, domain: auth?.domain || domain, apiHost: auth?.apiHost || apiHost, machineId: auth?.machineId || machineId, deviceId: auth?.deviceId || deviceId, enterpriseId: auth?.enterpriseId || enterpriseId, refreshToken });
|
|
144
|
+
}
|
|
145
|
+
const fp = writeAccountFile({ uid, accessToken: newToken, refreshToken, domain, apiHost, machineId, deviceId, enterpriseId });
|
|
146
|
+
const { accounts, extraKeys } = accountsFromArrays(keys, authList);
|
|
147
|
+
const st = await persistAccounts(accounts, file, extraKeys);
|
|
148
|
+
return { updated: matched, accounts: st.keys.length, file: fp };
|
|
149
|
+
}
|