dsh-multi-router 0.6.11 → 1.0.0
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 +2 -427
- package/package.json +5 -81
- package/CHANGELOG.md +0 -237
- package/LICENSE +0 -21
- package/THIRD_PARTY_NOTICES.md +0 -171
- package/cordis.patch.yml +0 -3
- package/lib/accounts.js +0 -19
- package/lib/accounts.js.map +0 -7
- package/lib/api-service.js +0 -11
- package/lib/api-service.js.map +0 -7
- package/lib/chunk-2UM45OXY.js +0 -181
- package/lib/chunk-2UM45OXY.js.map +0 -7
- package/lib/chunk-4L2CBGXN.js +0 -674
- package/lib/chunk-4L2CBGXN.js.map +0 -7
- package/lib/chunk-4V52V5GO.js +0 -105
- package/lib/chunk-4V52V5GO.js.map +0 -7
- package/lib/chunk-DNQPZMPF.js +0 -33
- package/lib/chunk-DNQPZMPF.js.map +0 -7
- package/lib/chunk-FLL3DUVP.js +0 -2017
- package/lib/chunk-FLL3DUVP.js.map +0 -7
- package/lib/chunk-GEBZMIMA.js +0 -287
- package/lib/chunk-GEBZMIMA.js.map +0 -7
- package/lib/chunk-KWXLQJS5.js +0 -1102
- package/lib/chunk-KWXLQJS5.js.map +0 -7
- package/lib/chunk-NZF36O5I.js +0 -5421
- package/lib/chunk-NZF36O5I.js.map +0 -7
- package/lib/chunk-S5KMFAQM.js +0 -50
- package/lib/chunk-S5KMFAQM.js.map +0 -7
- package/lib/chunk-TM5XDMGY.js +0 -332
- package/lib/chunk-TM5XDMGY.js.map +0 -7
- package/lib/chunk-WDIT4E2W.js +0 -120
- package/lib/chunk-WDIT4E2W.js.map +0 -7
- package/lib/chunk-YCAO7GCT.js +0 -2059
- package/lib/chunk-YCAO7GCT.js.map +0 -7
- package/lib/cli.js +0 -103
- package/lib/cli.js.map +0 -7
- package/lib/client.js +0 -2793
- package/lib/client.js.map +0 -7
- package/lib/clients.js +0 -30
- package/lib/clients.js.map +0 -7
- package/lib/host-v0.6.11.js +0 -414
- package/lib/host.js +0 -414
- package/lib/index.js +0 -414
- package/lib/index.js.map +0 -7
- package/lib/manager.js +0 -27
- package/lib/manager.js.map +0 -7
- package/lib/region-DO3KAV7V.js +0 -15
- package/lib/region-DO3KAV7V.js.map +0 -7
- package/lib/router.js +0 -14
- package/lib/router.js.map +0 -7
- package/lib/shim.js +0 -10
- package/lib/shim.js.map +0 -7
- package/lib/upstream-VP6J7LU2.js +0 -25
- package/lib/upstream-VP6J7LU2.js.map +0 -7
- package/router.example.json +0 -289
- package/upstream-lock.json +0 -17
package/lib/chunk-KWXLQJS5.js
DELETED
|
@@ -1,1102 +0,0 @@
|
|
|
1
|
-
// upstream/workbuddy/src/upstream.ts
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
|
|
4
|
-
// upstream/workbuddy/src/app-version.ts
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
9
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
10
|
-
var FALLBACK_APP_VERSION = "5.5.2";
|
|
11
|
-
var WORKBUDDY_APP_VERSION_FILENAME = ".workbuddy-ai-version.json";
|
|
12
|
-
function validAppVersion(value) {
|
|
13
|
-
return typeof value === "string" && /^\d{1,6}(?:\.\d{1,6}){1,3}$/u.test(value);
|
|
14
|
-
}
|
|
15
|
-
function macAppRoots() {
|
|
16
|
-
return ["/Applications", join(homedir(), "Applications")];
|
|
17
|
-
}
|
|
18
|
-
async function readBundleVersion(plistPath) {
|
|
19
|
-
let text;
|
|
20
|
-
try {
|
|
21
|
-
text = await readFile(plistPath, "utf8");
|
|
22
|
-
} catch {
|
|
23
|
-
return void 0;
|
|
24
|
-
}
|
|
25
|
-
const match = /<key>\s*CFBundleShortVersionString\s*<\/key>\s*<string>([^<]*)<\/string>/u.exec(text);
|
|
26
|
-
const version = match?.[1]?.trim();
|
|
27
|
-
return validAppVersion(version) ? version : void 0;
|
|
28
|
-
}
|
|
29
|
-
async function installedAppVersion() {
|
|
30
|
-
if (process.platform !== "darwin") return void 0;
|
|
31
|
-
for (const root of macAppRoots()) {
|
|
32
|
-
const bundle = join(root, "WorkBuddy AI.app");
|
|
33
|
-
const version = await readBundleVersion(join(bundle, "Contents", "Info.plist"));
|
|
34
|
-
if (version !== void 0) return { version, bundle };
|
|
35
|
-
}
|
|
36
|
-
return void 0;
|
|
37
|
-
}
|
|
38
|
-
function appVersionPath() {
|
|
39
|
-
return join(resolveDshHome(), WORKBUDDY_APP_VERSION_FILENAME);
|
|
40
|
-
}
|
|
41
|
-
async function resolveAppVersion(options = {}) {
|
|
42
|
-
const path = options.path ?? appVersionPath();
|
|
43
|
-
const installed = await (options.installed ?? installedAppVersion)();
|
|
44
|
-
if (installed !== void 0 && validAppVersion(installed.version)) {
|
|
45
|
-
try {
|
|
46
|
-
await writeFileAtomic(
|
|
47
|
-
path,
|
|
48
|
-
`${JSON.stringify({ version: installed.version, bundle: installed.bundle, observedAt: Date.now() }, null, 2)}
|
|
49
|
-
`,
|
|
50
|
-
{ mode: 384, dirMode: 448 }
|
|
51
|
-
);
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
return { version: installed.version, source: "installed", bundle: installed.bundle };
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
const saved = JSON.parse(await readFile(path, "utf8"));
|
|
58
|
-
if (typeof saved === "object" && saved !== null) {
|
|
59
|
-
const version = saved["version"];
|
|
60
|
-
if (validAppVersion(version)) return { version, source: "saved" };
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
}
|
|
64
|
-
return { version: FALLBACK_APP_VERSION, source: "fallback" };
|
|
65
|
-
}
|
|
66
|
-
function appUserAgent(version) {
|
|
67
|
-
if (!validAppVersion(version)) throw new Error(`invalid WorkBuddy AI version for User-Agent: ${JSON.stringify(version)}`);
|
|
68
|
-
return `WorkBuddyAI/${version}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// upstream/workbuddy/src/client-identity.ts
|
|
72
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
73
|
-
import { homedir as homedir2 } from "node:os";
|
|
74
|
-
import { join as join2 } from "node:path";
|
|
75
|
-
import { writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
|
|
76
|
-
import { resolveDshHome as resolveDshHome2 } from "@deepseek-ai/dsh-home-paths";
|
|
77
|
-
var FALLBACK_CN_APP_VERSION = "5.5.6";
|
|
78
|
-
var CN_APP_VERSION_FILENAME = ".workbuddy-app-version.json";
|
|
79
|
-
function validCliVersion(value) {
|
|
80
|
-
return typeof value === "string" && /^\d{1,6}(?:\.\d{1,6}){1,3}(?:-[0-9A-Za-z.]+)?$/u.test(value);
|
|
81
|
-
}
|
|
82
|
-
function macAppRoots2() {
|
|
83
|
-
return ["/Applications", join2(homedir2(), "Applications")];
|
|
84
|
-
}
|
|
85
|
-
function cliPackagePath(bundle) {
|
|
86
|
-
return join2(bundle, "Contents", "Resources", "app.asar.unpacked", "cli", "package.json");
|
|
87
|
-
}
|
|
88
|
-
async function readCliVersion(bundle) {
|
|
89
|
-
let document;
|
|
90
|
-
try {
|
|
91
|
-
document = JSON.parse(await readFile2(cliPackagePath(bundle), "utf8"));
|
|
92
|
-
} catch {
|
|
93
|
-
return void 0;
|
|
94
|
-
}
|
|
95
|
-
if (typeof document !== "object" || document === null || Array.isArray(document)) return void 0;
|
|
96
|
-
const pkg = document;
|
|
97
|
-
const declared = pkg["version"];
|
|
98
|
-
if (validCliVersion(declared) && declared !== "0.0.0") return declared;
|
|
99
|
-
const publishConfig = pkg["publishConfig"];
|
|
100
|
-
const customPackage = typeof publishConfig === "object" && publishConfig !== null && !Array.isArray(publishConfig) ? publishConfig : void 0;
|
|
101
|
-
const custom = typeof customPackage?.["customPackage"] === "object" && customPackage["customPackage"] !== null && !Array.isArray(customPackage["customPackage"]) ? customPackage["customPackage"] : void 0;
|
|
102
|
-
const customVersion = custom?.["version"];
|
|
103
|
-
return validCliVersion(customVersion) ? customVersion : void 0;
|
|
104
|
-
}
|
|
105
|
-
function chatUserAgent(identity, region) {
|
|
106
|
-
if (!validAppVersion(identity.clientVersion)) {
|
|
107
|
-
throw new Error(`invalid client version for chat User-Agent: ${JSON.stringify(identity.clientVersion)}`);
|
|
108
|
-
}
|
|
109
|
-
if (identity.cliVersion !== void 0 && !validCliVersion(identity.cliVersion)) {
|
|
110
|
-
throw new Error(`invalid CLI version for chat User-Agent: ${JSON.stringify(identity.cliVersion)}`);
|
|
111
|
-
}
|
|
112
|
-
const product = region === "global" ? "WorkBuddy AI" : "WorkBuddy";
|
|
113
|
-
const parts = [`WorkBuddy/${identity.clientVersion}`, `${product}/${identity.clientVersion}`];
|
|
114
|
-
if (identity.cliVersion !== void 0) parts.push(`CLI/${identity.cliVersion}`);
|
|
115
|
-
return parts.join(" ");
|
|
116
|
-
}
|
|
117
|
-
async function installedCnApp() {
|
|
118
|
-
if (process.platform !== "darwin") return void 0;
|
|
119
|
-
for (const root of macAppRoots2()) {
|
|
120
|
-
const bundle = join2(root, "WorkBuddy.app");
|
|
121
|
-
const version = await readBundleVersion(join2(bundle, "Contents", "Info.plist"));
|
|
122
|
-
if (version !== void 0) return { version, bundle };
|
|
123
|
-
}
|
|
124
|
-
return void 0;
|
|
125
|
-
}
|
|
126
|
-
function cnSavedVersionPath() {
|
|
127
|
-
return join2(resolveDshHome2(), CN_APP_VERSION_FILENAME);
|
|
128
|
-
}
|
|
129
|
-
async function resolveChatIdentity(region, options = {}) {
|
|
130
|
-
const injectable = options.installedCn !== void 0 || options.resolveIntl !== void 0 || options.cliVersion !== void 0 || options.cnSavedPath !== void 0;
|
|
131
|
-
if (!injectable) {
|
|
132
|
-
const cached = cache.get(region);
|
|
133
|
-
if (cached !== void 0) return cached;
|
|
134
|
-
}
|
|
135
|
-
let identity;
|
|
136
|
-
try {
|
|
137
|
-
identity = region === "global" ? await resolveGlobalIdentity(options) : await resolveCnIdentity(options);
|
|
138
|
-
} catch {
|
|
139
|
-
return fallbackChatIdentity(region);
|
|
140
|
-
}
|
|
141
|
-
if (!injectable) cache.set(region, identity);
|
|
142
|
-
return identity;
|
|
143
|
-
}
|
|
144
|
-
var cache = /* @__PURE__ */ new Map();
|
|
145
|
-
function fallbackChatIdentity(region) {
|
|
146
|
-
return { clientVersion: region === "global" ? FALLBACK_APP_VERSION : FALLBACK_CN_APP_VERSION };
|
|
147
|
-
}
|
|
148
|
-
async function resolveCnIdentity(options) {
|
|
149
|
-
const savedPath = options.cnSavedPath ?? cnSavedVersionPath();
|
|
150
|
-
const installed = await (options.installedCn ?? installedCnApp)();
|
|
151
|
-
if (installed !== void 0 && validAppVersion(installed.version)) {
|
|
152
|
-
const cliVersion = await (options.cliVersion ?? readCliVersion)(installed.bundle);
|
|
153
|
-
const identity = {
|
|
154
|
-
clientVersion: installed.version,
|
|
155
|
-
...cliVersion !== void 0 && validCliVersion(cliVersion) ? { cliVersion } : {}
|
|
156
|
-
};
|
|
157
|
-
try {
|
|
158
|
-
await writeFileAtomic2(
|
|
159
|
-
savedPath,
|
|
160
|
-
`${JSON.stringify({ version: identity.clientVersion, observedAt: Date.now() }, null, 2)}
|
|
161
|
-
`,
|
|
162
|
-
{ mode: 384, dirMode: 448 }
|
|
163
|
-
);
|
|
164
|
-
} catch {
|
|
165
|
-
}
|
|
166
|
-
return identity;
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
const saved = JSON.parse(await readFile2(savedPath, "utf8"));
|
|
170
|
-
if (typeof saved === "object" && saved !== null && !Array.isArray(saved)) {
|
|
171
|
-
const document = saved;
|
|
172
|
-
if (validAppVersion(document["version"])) {
|
|
173
|
-
return { clientVersion: document["version"] };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
} catch {
|
|
177
|
-
}
|
|
178
|
-
return fallbackChatIdentity("cn");
|
|
179
|
-
}
|
|
180
|
-
async function resolveGlobalIdentity(options) {
|
|
181
|
-
const info = await (options.resolveIntl ?? resolveAppVersion)();
|
|
182
|
-
const clientVersion = validAppVersion(info.version) ? info.version : FALLBACK_APP_VERSION;
|
|
183
|
-
let cliVersion;
|
|
184
|
-
if (info.bundle !== void 0) {
|
|
185
|
-
const read = await (options.cliVersion ?? readCliVersion)(info.bundle);
|
|
186
|
-
if (read !== void 0 && validCliVersion(read)) cliVersion = read;
|
|
187
|
-
}
|
|
188
|
-
return { clientVersion, ...cliVersion === void 0 ? {} : { cliVersion } };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// upstream/workbuddy/src/probe.ts
|
|
192
|
-
var PROBE_PROMPT = "ping";
|
|
193
|
-
var PROBE_MAX_TOKENS = 1;
|
|
194
|
-
|
|
195
|
-
// upstream/workbuddy/src/upstream.ts
|
|
196
|
-
var CN_CHAT_BASE = "https://copilot.tencent.com";
|
|
197
|
-
var CN_BILLING_BASE = "https://www.codebuddy.cn";
|
|
198
|
-
var GLOBAL_BASE = "https://www.workbuddy.ai";
|
|
199
|
-
var enterprisePackageName = "enterprise";
|
|
200
|
-
function describeShape(document) {
|
|
201
|
-
if (typeof document !== "object" || document === null || Array.isArray(document)) {
|
|
202
|
-
return typeof document;
|
|
203
|
-
}
|
|
204
|
-
const record = document;
|
|
205
|
-
const at = (source) => {
|
|
206
|
-
const keys = Object.keys(source).slice(0, 24);
|
|
207
|
-
return keys.length === 0 ? "(empty)" : keys.map((key) => `${key}:${typeof source[key]}`).join(", ");
|
|
208
|
-
};
|
|
209
|
-
const top = `top-level { ${at(record)} }`;
|
|
210
|
-
const data = record["data"];
|
|
211
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) return top;
|
|
212
|
-
return `${top}; data { ${at(data)} }`;
|
|
213
|
-
}
|
|
214
|
-
var CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2";
|
|
215
|
-
var JSON_TIMEOUT_MS = 3e4;
|
|
216
|
-
var ERROR_BODY_LIMIT = 4096;
|
|
217
|
-
var HARD_CREDIT_MARKERS = [
|
|
218
|
-
"insufficient credit",
|
|
219
|
-
"no credit",
|
|
220
|
-
"credit exhausted",
|
|
221
|
-
"credits exhausted",
|
|
222
|
-
"out of credit",
|
|
223
|
-
"quota exceeded",
|
|
224
|
-
"quota exhaust",
|
|
225
|
-
"payment required",
|
|
226
|
-
"credit not enough",
|
|
227
|
-
"not enough credit",
|
|
228
|
-
"\u79EF\u5206\u4E0D\u8DB3",
|
|
229
|
-
"\u989D\u5EA6\u4E0D\u8DB3",
|
|
230
|
-
"\u4F59\u989D\u4E0D\u8DB3",
|
|
231
|
-
"\u79EF\u5206\u7528\u5B8C",
|
|
232
|
-
"\u989D\u5EA6\u7528\u5C3D",
|
|
233
|
-
"\u6CA1\u6709\u79EF\u5206"
|
|
234
|
-
];
|
|
235
|
-
var EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
236
|
-
var BADGE_PREFIX = "badge:";
|
|
237
|
-
function resolveUpstreamReasoning(wrapped) {
|
|
238
|
-
const supports = wrapped["supportsReasoning"] === true;
|
|
239
|
-
const onlyReasoning = wrapped["onlyReasoning"] === true;
|
|
240
|
-
const rawReasoning = wrapped["reasoning"];
|
|
241
|
-
let supportedEfforts;
|
|
242
|
-
let defaultEffort;
|
|
243
|
-
let canDisableThinking = true;
|
|
244
|
-
if (typeof rawReasoning === "object" && rawReasoning !== null && !Array.isArray(rawReasoning)) {
|
|
245
|
-
const reasoning = rawReasoning;
|
|
246
|
-
const rawEfforts = reasoning["supportedEfforts"];
|
|
247
|
-
if (Array.isArray(rawEfforts)) {
|
|
248
|
-
const efforts = rawEfforts.filter((value) => typeof value === "string" && EFFORT_VALUES.includes(value));
|
|
249
|
-
if (efforts.length > 0) supportedEfforts = efforts;
|
|
250
|
-
}
|
|
251
|
-
if (typeof reasoning["defaultEffort"] === "string" && EFFORT_VALUES.includes(reasoning["defaultEffort"])) {
|
|
252
|
-
defaultEffort = reasoning["defaultEffort"];
|
|
253
|
-
} else if (typeof reasoning["effort"] === "string" && EFFORT_VALUES.includes(reasoning["effort"])) {
|
|
254
|
-
defaultEffort = reasoning["effort"];
|
|
255
|
-
}
|
|
256
|
-
canDisableThinking = reasoning["canDisableThinking"] === true;
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
reasoning: {
|
|
260
|
-
supports,
|
|
261
|
-
onlyReasoning,
|
|
262
|
-
...supportedEfforts === void 0 ? {} : { supportedEfforts },
|
|
263
|
-
...defaultEffort === void 0 ? {} : { defaultEffort },
|
|
264
|
-
canDisableThinking
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
function normalizeCredits(credits) {
|
|
269
|
-
if (credits === void 0) return void 0;
|
|
270
|
-
const trimmed = credits.trim();
|
|
271
|
-
if (trimmed === "") return void 0;
|
|
272
|
-
if (/^credits?$/iu.test(trimmed)) return void 0;
|
|
273
|
-
const bare = trimmed.replace(/\s+credits?$/iu, "").trim();
|
|
274
|
-
return bare === "" ? void 0 : bare;
|
|
275
|
-
}
|
|
276
|
-
function resolveUpstreamBilling(wrapped, extraTags) {
|
|
277
|
-
const rawCredits = wrapped["credits"];
|
|
278
|
-
const credits = typeof rawCredits === "string" && rawCredits.trim() !== "" ? rawCredits.trim() : void 0;
|
|
279
|
-
const badges = [];
|
|
280
|
-
const rawTags = [...Array.isArray(wrapped["tags"]) ? wrapped["tags"] : [], ...extraTags ?? []];
|
|
281
|
-
for (const tag of rawTags) {
|
|
282
|
-
if (typeof tag !== "string") continue;
|
|
283
|
-
const lowered = tag.toLowerCase();
|
|
284
|
-
if (!lowered.startsWith(BADGE_PREFIX)) continue;
|
|
285
|
-
const label = tag.slice(BADGE_PREFIX.length).split(":")[0] ?? tag.slice(BADGE_PREFIX.length);
|
|
286
|
-
if (label !== "" && !badges.includes(label)) badges.push(label);
|
|
287
|
-
}
|
|
288
|
-
const multiplier = normalizeCredits(credits);
|
|
289
|
-
const free = multiplier !== void 0 && /^x?0\.0+$/u.test(multiplier);
|
|
290
|
-
return {
|
|
291
|
-
billing: {
|
|
292
|
-
...credits === void 0 ? {} : { credits },
|
|
293
|
-
...badges.length === 0 ? {} : { badges },
|
|
294
|
-
free
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
var SESSION_DEAD_MARKERS = ["Offline user session not found", "12153"];
|
|
299
|
-
function classifyUpstreamError(status, body) {
|
|
300
|
-
if (status === 402) return "hard_credit";
|
|
301
|
-
if (status === 401) return "session_dead";
|
|
302
|
-
const lower = body.toLowerCase();
|
|
303
|
-
for (const marker of HARD_CREDIT_MARKERS) {
|
|
304
|
-
if (lower.includes(marker.toLowerCase()) || body.includes(marker)) return "hard_credit";
|
|
305
|
-
}
|
|
306
|
-
for (const marker of SESSION_DEAD_MARKERS) {
|
|
307
|
-
if (body.includes(marker)) return "session_dead";
|
|
308
|
-
}
|
|
309
|
-
if (status === 429) return "soft_rate";
|
|
310
|
-
if (status === 404) return "not_found";
|
|
311
|
-
if (status >= 500) return "server";
|
|
312
|
-
if (status >= 400) return "client";
|
|
313
|
-
return "client";
|
|
314
|
-
}
|
|
315
|
-
function extractDisplayErrorMessage(body) {
|
|
316
|
-
const trimmed = body.trim();
|
|
317
|
-
if (!trimmed.startsWith("{")) return void 0;
|
|
318
|
-
try {
|
|
319
|
-
const parsed = JSON.parse(trimmed);
|
|
320
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
321
|
-
const obj = parsed;
|
|
322
|
-
const displayMsg = obj["displayMsg"];
|
|
323
|
-
if (typeof displayMsg === "object" && displayMsg !== null && !Array.isArray(displayMsg)) {
|
|
324
|
-
const localized = displayMsg;
|
|
325
|
-
if (typeof localized["zh"] === "string" && localized["zh"].trim() !== "") {
|
|
326
|
-
return localized["zh"].trim();
|
|
327
|
-
}
|
|
328
|
-
if (typeof localized["en"] === "string" && localized["en"].trim() !== "") {
|
|
329
|
-
return localized["en"].trim();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (typeof obj["msg"] === "string" && obj["msg"].trim() !== "") {
|
|
333
|
-
return obj["msg"].trim();
|
|
334
|
-
}
|
|
335
|
-
} catch {
|
|
336
|
-
}
|
|
337
|
-
return void 0;
|
|
338
|
-
}
|
|
339
|
-
function regionOf(domain) {
|
|
340
|
-
const lowered = domain.trim().toLowerCase();
|
|
341
|
-
if (lowered === "workbuddy.ai" || lowered.endsWith(".workbuddy.ai")) return "global";
|
|
342
|
-
return "cn";
|
|
343
|
-
}
|
|
344
|
-
function chatBase(credential) {
|
|
345
|
-
return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_CHAT_BASE;
|
|
346
|
-
}
|
|
347
|
-
function billingBase(credential) {
|
|
348
|
-
return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_BILLING_BASE;
|
|
349
|
-
}
|
|
350
|
-
function originReferer(credential) {
|
|
351
|
-
return regionOf(credential.domain) === "global" ? GLOBAL_BASE : CN_BILLING_BASE;
|
|
352
|
-
}
|
|
353
|
-
function commonHeaders(credential) {
|
|
354
|
-
return {
|
|
355
|
-
"Accept": "application/json, text/plain, */*",
|
|
356
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
357
|
-
"Origin": originReferer(credential),
|
|
358
|
-
"Referer": `${originReferer(credential)}/`,
|
|
359
|
-
"User-Agent": CLIENT_UA
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
function conversationHeaders() {
|
|
363
|
-
return {
|
|
364
|
-
"X-Conversation-ID": randomUUID(),
|
|
365
|
-
"X-Conversation-Request-ID": randomUUID(),
|
|
366
|
-
"X-Conversation-Message-ID": randomUUID()
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
function chatHeaders(credential, userAgent, clientVersion) {
|
|
370
|
-
const headers = {
|
|
371
|
-
...commonHeaders(credential),
|
|
372
|
-
"User-Agent": userAgent,
|
|
373
|
-
"Content-Type": "application/json",
|
|
374
|
-
// 安全红线:chat 请求绝不携带 refresh token。
|
|
375
|
-
...credential.uid === "" ? { "X-No-User-Id": "1" } : { "X-User-Id": credential.uid },
|
|
376
|
-
...credential.enterpriseId === void 0 || credential.enterpriseId === "" ? { "X-No-Enterprise-Id": "1" } : { "X-Enterprise-Id": credential.enterpriseId },
|
|
377
|
-
...credential.domain === "" ? { "X-No-Department-Info": "1" } : { "X-Domain": credential.domain },
|
|
378
|
-
"X-IDE-Type": "WorkBuddy",
|
|
379
|
-
"X-IDE-Name": "WorkBuddy",
|
|
380
|
-
"X-IDE-Version": clientVersion,
|
|
381
|
-
"X-Product": "SaaS",
|
|
382
|
-
...conversationHeaders()
|
|
383
|
-
};
|
|
384
|
-
return headers;
|
|
385
|
-
}
|
|
386
|
-
function refreshHeaders(credential) {
|
|
387
|
-
const headers = {
|
|
388
|
-
...commonHeaders(credential),
|
|
389
|
-
"X-Refresh-Token": credential.refreshToken,
|
|
390
|
-
"X-Auth-Refresh-Source": "workbuddy"
|
|
391
|
-
};
|
|
392
|
-
if (credential.enterpriseId !== void 0 && credential.enterpriseId !== "") {
|
|
393
|
-
headers["X-Enterprise-Id"] = credential.enterpriseId;
|
|
394
|
-
}
|
|
395
|
-
return headers;
|
|
396
|
-
}
|
|
397
|
-
function billingHeaders(credential) {
|
|
398
|
-
const headers = {
|
|
399
|
-
"Authorization": `Bearer ${credential.accessToken}`,
|
|
400
|
-
"Accept": "application/json",
|
|
401
|
-
"Content-Type": "application/json"
|
|
402
|
-
};
|
|
403
|
-
if (credential.uid !== "") headers["X-User-Id"] = credential.uid;
|
|
404
|
-
if (credential.enterpriseId !== void 0 && credential.enterpriseId !== "") {
|
|
405
|
-
headers["X-Enterprise-Id"] = credential.enterpriseId;
|
|
406
|
-
headers["X-Tenant-Id"] = credential.enterpriseId;
|
|
407
|
-
}
|
|
408
|
-
if (credential.domain !== "") headers["X-Domain"] = credential.domain;
|
|
409
|
-
return headers;
|
|
410
|
-
}
|
|
411
|
-
function prepareChatBody(source) {
|
|
412
|
-
let body;
|
|
413
|
-
try {
|
|
414
|
-
body = JSON.parse(source);
|
|
415
|
-
} catch {
|
|
416
|
-
return source;
|
|
417
|
-
}
|
|
418
|
-
if (typeof body !== "object" || body === null || Array.isArray(body)) return source;
|
|
419
|
-
const obj = body;
|
|
420
|
-
obj["stream"] = true;
|
|
421
|
-
normalizeDeveloperRole(obj);
|
|
422
|
-
normalizeToolChoice(obj);
|
|
423
|
-
return JSON.stringify(obj);
|
|
424
|
-
}
|
|
425
|
-
function normalizeDeveloperRole(obj) {
|
|
426
|
-
const messages = obj["messages"];
|
|
427
|
-
if (!Array.isArray(messages)) return;
|
|
428
|
-
for (const message of messages) {
|
|
429
|
-
if (typeof message !== "object" || message === null || Array.isArray(message)) continue;
|
|
430
|
-
const wrapped = message;
|
|
431
|
-
if (wrapped["role"] === "developer") wrapped["role"] = "system";
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
function normalizeToolChoice(obj) {
|
|
435
|
-
const suppress = () => {
|
|
436
|
-
delete obj["tools"];
|
|
437
|
-
delete obj["functions"];
|
|
438
|
-
};
|
|
439
|
-
const present = "tool_choice" in obj;
|
|
440
|
-
if (!present) return;
|
|
441
|
-
const choice = obj["tool_choice"];
|
|
442
|
-
if (typeof choice === "string") {
|
|
443
|
-
if (choice.trim().toLowerCase() === "none") {
|
|
444
|
-
delete obj["tool_choice"];
|
|
445
|
-
suppress();
|
|
446
|
-
}
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
if (typeof choice === "object" && choice !== null && !Array.isArray(choice)) {
|
|
450
|
-
const wrapped = choice;
|
|
451
|
-
const type = typeof wrapped["type"] === "string" ? wrapped["type"].trim().toLowerCase() : "";
|
|
452
|
-
if (type === "none") {
|
|
453
|
-
delete obj["tool_choice"];
|
|
454
|
-
suppress();
|
|
455
|
-
} else if (type === "auto" || type === "required") {
|
|
456
|
-
obj["tool_choice"] = type;
|
|
457
|
-
} else if (type === "function") {
|
|
458
|
-
const fn = typeof wrapped["function"] === "object" && wrapped["function"] !== null ? wrapped["function"] : void 0;
|
|
459
|
-
let name = typeof fn?.["name"] === "string" ? fn["name"] : "";
|
|
460
|
-
if (name === "" && typeof wrapped["name"] === "string") name = wrapped["name"];
|
|
461
|
-
name = name.trim();
|
|
462
|
-
obj["tool_choice"] = name !== "" ? name : "auto";
|
|
463
|
-
} else {
|
|
464
|
-
delete obj["tool_choice"];
|
|
465
|
-
}
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
delete obj["tool_choice"];
|
|
469
|
-
}
|
|
470
|
-
async function readEnvelope(response) {
|
|
471
|
-
const text = await response.text();
|
|
472
|
-
let parsed;
|
|
473
|
-
try {
|
|
474
|
-
parsed = JSON.parse(text);
|
|
475
|
-
} catch {
|
|
476
|
-
throw new Error(`workbuddy upstream returned non-JSON (http ${response.status}): ${text.slice(0, 160)}`);
|
|
477
|
-
}
|
|
478
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
479
|
-
throw new Error(`workbuddy upstream returned an unexpected document (http ${response.status})`);
|
|
480
|
-
}
|
|
481
|
-
const document = parsed;
|
|
482
|
-
const envelope = {
|
|
483
|
-
code: typeof document["code"] === "number" ? document["code"] : 0,
|
|
484
|
-
msg: typeof document["msg"] === "string" ? document["msg"] : "",
|
|
485
|
-
data: "data" in document ? document["data"] : void 0,
|
|
486
|
-
document
|
|
487
|
-
};
|
|
488
|
-
return envelope;
|
|
489
|
-
}
|
|
490
|
-
function envelopeError(status, envelope) {
|
|
491
|
-
const kind = classifyUpstreamError(status, envelope.msg);
|
|
492
|
-
return new Error(`workbuddy upstream ${kind} (http ${status}): ${envelope.msg.slice(0, 160)}`);
|
|
493
|
-
}
|
|
494
|
-
function parseCycleEndTime(value) {
|
|
495
|
-
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
496
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/u.exec(value.trim());
|
|
497
|
-
if (match === null) return void 0;
|
|
498
|
-
const [, y, mo, d, h, mi, s] = match;
|
|
499
|
-
const parts = [y, mo, d, h, mi, s].map(Number);
|
|
500
|
-
const [year, month, day, hour, minute, second] = parts;
|
|
501
|
-
const date = new Date(year, month - 1, day, hour, minute, second);
|
|
502
|
-
const parsed = date.getTime();
|
|
503
|
-
if (!Number.isFinite(parsed)) return void 0;
|
|
504
|
-
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day || date.getHours() !== hour || date.getMinutes() !== minute || date.getSeconds() !== second) {
|
|
505
|
-
return void 0;
|
|
506
|
-
}
|
|
507
|
-
return parsed;
|
|
508
|
-
}
|
|
509
|
-
var NO_EXPIRING_CREDIT = { total: 0, packages: 0 };
|
|
510
|
-
function expiringWithin(accounts, nowMs, horizonMs) {
|
|
511
|
-
return sumExpiring(accounts, (end) => end > nowMs && end - nowMs <= horizonMs);
|
|
512
|
-
}
|
|
513
|
-
function sumExpiring(accounts, matches) {
|
|
514
|
-
let total = 0;
|
|
515
|
-
let packages = 0;
|
|
516
|
-
let earliestEndTimeMs;
|
|
517
|
-
for (const account of accounts) {
|
|
518
|
-
const end = account.endTimeMs;
|
|
519
|
-
if (end === void 0 || account.remain <= 0) continue;
|
|
520
|
-
if (!matches(end)) continue;
|
|
521
|
-
total += account.remain;
|
|
522
|
-
packages += 1;
|
|
523
|
-
if (earliestEndTimeMs === void 0 || end < earliestEndTimeMs) earliestEndTimeMs = end;
|
|
524
|
-
}
|
|
525
|
-
if (packages === 0) return NO_EXPIRING_CREDIT;
|
|
526
|
-
return {
|
|
527
|
-
total,
|
|
528
|
-
packages,
|
|
529
|
-
...earliestEndTimeMs === void 0 ? {} : { earliestEndTimeMs }
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
function summariseExpiry(accounts, nowMs) {
|
|
533
|
-
if (!accounts.some((account) => account.endTimeMs !== void 0)) return {};
|
|
534
|
-
const HOUR = 3600 * 1e3;
|
|
535
|
-
const DAY = 24 * HOUR;
|
|
536
|
-
return {
|
|
537
|
-
expiry: {
|
|
538
|
-
within24h: expiringWithin(accounts, nowMs, DAY),
|
|
539
|
-
within3d: expiringWithin(accounts, nowMs, 3 * DAY),
|
|
540
|
-
within7d: expiringWithin(accounts, nowMs, 7 * DAY)
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
var WorkBuddyUpstreamClient = class {
|
|
545
|
-
/**
|
|
546
|
-
* Resolves the App-shaped UA version for international catalog requests.
|
|
547
|
-
* Injectable so tests never read the real filesystem.
|
|
548
|
-
*/
|
|
549
|
-
resolveAppVersion;
|
|
550
|
-
/** Chat-identity resolver; see {@link WorkBuddyUpstreamClientOptions.resolveChatIdentity}. */
|
|
551
|
-
resolveChatIdentity;
|
|
552
|
-
/** Provenance of the most recent successful catalog fetch, for the card. */
|
|
553
|
-
lastCatalog;
|
|
554
|
-
constructor(options = {}) {
|
|
555
|
-
this.resolveAppVersion = options.resolveAppVersion ?? (() => resolveAppVersion());
|
|
556
|
-
this.resolveChatIdentity = options.resolveChatIdentity ?? ((region) => resolveChatIdentity(region));
|
|
557
|
-
}
|
|
558
|
-
/** POST the chat endpoint; a successful answer is the raw SSE response. */
|
|
559
|
-
async chatStream(credential, bodyJson, signal) {
|
|
560
|
-
const region = regionOf(credential.domain);
|
|
561
|
-
let identity;
|
|
562
|
-
let userAgent;
|
|
563
|
-
try {
|
|
564
|
-
identity = await this.resolveChatIdentity(region);
|
|
565
|
-
userAgent = chatUserAgent(identity, region);
|
|
566
|
-
} catch {
|
|
567
|
-
identity = fallbackChatIdentity(region);
|
|
568
|
-
userAgent = chatUserAgent(identity, region);
|
|
569
|
-
}
|
|
570
|
-
let response;
|
|
571
|
-
try {
|
|
572
|
-
response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
|
|
573
|
-
method: "POST",
|
|
574
|
-
headers: { ...chatHeaders(credential, userAgent, identity.clientVersion), "Authorization": `Bearer ${credential.accessToken}` },
|
|
575
|
-
body: region === "global" ? prepareInternationalChatBody(bodyJson) : prepareCnChatBody(bodyJson),
|
|
576
|
-
...signal === void 0 ? {} : { signal }
|
|
577
|
-
});
|
|
578
|
-
} catch (error) {
|
|
579
|
-
return { ok: false, status: 0, kind: "server", message: `transport error: ${String(error)}` };
|
|
580
|
-
}
|
|
581
|
-
if (response.ok) return { ok: true, response };
|
|
582
|
-
const text = (await response.text()).slice(0, ERROR_BODY_LIMIT);
|
|
583
|
-
return {
|
|
584
|
-
ok: false,
|
|
585
|
-
status: response.status,
|
|
586
|
-
kind: classifyUpstreamError(response.status, text),
|
|
587
|
-
message: text
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
/** POST the token-refresh endpoint; the caller merges the outcome. */
|
|
591
|
-
async refreshToken(credential) {
|
|
592
|
-
const response = await fetch(`${chatBase(credential)}/v2/plugin/auth/token/refresh`, {
|
|
593
|
-
method: "POST",
|
|
594
|
-
headers: refreshHeaders(credential),
|
|
595
|
-
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
596
|
-
});
|
|
597
|
-
const envelope = await readEnvelope(response);
|
|
598
|
-
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
599
|
-
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
600
|
-
const accessToken = typeof data["accessToken"] === "string" ? data["accessToken"] : "";
|
|
601
|
-
if (accessToken === "") throw new Error("workbuddy token refresh returned no accessToken; sign in again in the WorkBuddy app");
|
|
602
|
-
const outcome = { accessToken };
|
|
603
|
-
if (typeof data["refreshToken"] === "string" && data["refreshToken"] !== "") outcome.refreshToken = data["refreshToken"];
|
|
604
|
-
if (typeof data["expiresIn"] === "number" && data["expiresIn"] > 0) outcome.expiresInSec = data["expiresIn"];
|
|
605
|
-
if (typeof data["domain"] === "string" && data["domain"] !== "") outcome.domain = data["domain"];
|
|
606
|
-
return outcome;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* GET the personal model catalog.
|
|
610
|
-
*
|
|
611
|
-
* Both variants read `/v3/config`, the product document the desktop product
|
|
612
|
-
* itself fetches. CN used to read `/console/enterprises/personal/models`
|
|
613
|
-
* (the console catalog) instead, and that was why its model list drifted
|
|
614
|
-
* from the desktop App's selector: the console document lags the product
|
|
615
|
-
* one, and the product roster itself churns day to day (`auto`,
|
|
616
|
-
* `kimi-k3-1`, `minimax-m3` have each appeared and disappeared within a
|
|
617
|
-
* week).
|
|
618
|
-
*
|
|
619
|
-
* What distinguishes the two variants here is the User-Agent, not the path:
|
|
620
|
-
* the gateway splits `/v3/config` by client identity, and the split is
|
|
621
|
-
* load-bearing. A CLI-shaped UA yields the CLI's roster — the chat models
|
|
622
|
-
* this plugin serves — while an App-shaped UA yields the App's internal
|
|
623
|
-
* roster. CN keeps the CLI UA it sends for chat, so the catalog it
|
|
624
|
-
* advertises is exactly the one its own requests can use. The international
|
|
625
|
-
* variant has no CLI identity, so it keeps the App-shaped UA.
|
|
626
|
-
*
|
|
627
|
-
* Membership is the `cli` roster intersected with the usable rows (see
|
|
628
|
-
* {@link parseModelCatalog}); the promo badges the product document does
|
|
629
|
-
* not carry are merged in from a best-effort console read — see
|
|
630
|
-
* {@link fetchPromoBadges}.
|
|
631
|
-
*
|
|
632
|
-
* Responses are unwrapped and classified the same way — `readEnvelope` plus
|
|
633
|
-
* `envelopeError` — so an expired session or exhausted credit is reported as
|
|
634
|
-
* such rather than as a generic catalog failure.
|
|
635
|
-
*/
|
|
636
|
-
async fetchModels(credential, signal) {
|
|
637
|
-
const international = regionOf(credential.domain) === "global";
|
|
638
|
-
const appVersion = international ? await this.resolveAppVersion() : void 0;
|
|
639
|
-
const response = await fetch(`${chatBase(credential)}/v3/config`, {
|
|
640
|
-
headers: {
|
|
641
|
-
Authorization: `Bearer ${credential.accessToken}`,
|
|
642
|
-
Accept: "application/json",
|
|
643
|
-
Origin: originReferer(credential),
|
|
644
|
-
Referer: `${originReferer(credential)}/`,
|
|
645
|
-
...international ? { "X-Requested-With": "XMLHttpRequest", "X-Product": "SaaS" } : {},
|
|
646
|
-
"User-Agent": appVersion === void 0 ? CLIENT_UA : appUserAgent(appVersion.version)
|
|
647
|
-
},
|
|
648
|
-
signal: signal === void 0 ? AbortSignal.timeout(JSON_TIMEOUT_MS) : AbortSignal.any([signal, AbortSignal.timeout(JSON_TIMEOUT_MS)])
|
|
649
|
-
});
|
|
650
|
-
const envelope = await readEnvelope(response);
|
|
651
|
-
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
652
|
-
const data = isObject(envelope.data) ? envelope.data : "models" in envelope.document || "agents" in envelope.document ? envelope.document : {};
|
|
653
|
-
const promoBadges = international ? void 0 : await this.fetchPromoBadges(credential, signal);
|
|
654
|
-
const models = parseModelCatalog(data, international, promoBadges);
|
|
655
|
-
this.lastCatalog = {
|
|
656
|
-
fetchedAtMs: Date.now(),
|
|
657
|
-
source: international ? "workbuddy-ai:app" : "workbuddy:cli",
|
|
658
|
-
...appVersion === void 0 ? {} : { appVersion }
|
|
659
|
-
};
|
|
660
|
-
return models;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Read the console catalog's promotional tags, by model id.
|
|
664
|
-
*
|
|
665
|
-
* `/v3/config` carries no `badge:<label>:<color>` tags — the discount labels
|
|
666
|
-
* the cards render (`限时免费`, `夜间折扣`, …) live only in
|
|
667
|
-
* `/console/enterprises/personal/models`. Since the roster now comes from
|
|
668
|
-
* the product document, those tags are read from the console one in a
|
|
669
|
-
* second request and merged by id.
|
|
670
|
-
*
|
|
671
|
-
* Best-effort by construction: a badge is a label on a price, so failing to
|
|
672
|
-
* read this document must not fail a catalog refresh. Every failure —
|
|
673
|
-
* network, envelope, an unreadable body — returns undefined, and the models
|
|
674
|
-
* simply ship without badges.
|
|
675
|
-
*/
|
|
676
|
-
async fetchPromoBadges(credential, signal) {
|
|
677
|
-
try {
|
|
678
|
-
const response = await fetch(`${chatBase(credential)}/console/enterprises/personal/models`, {
|
|
679
|
-
headers: {
|
|
680
|
-
Authorization: `Bearer ${credential.accessToken}`,
|
|
681
|
-
Accept: "application/json",
|
|
682
|
-
Origin: originReferer(credential),
|
|
683
|
-
Referer: `${originReferer(credential)}/`,
|
|
684
|
-
"User-Agent": CLIENT_UA
|
|
685
|
-
},
|
|
686
|
-
signal: signal === void 0 ? AbortSignal.timeout(JSON_TIMEOUT_MS) : AbortSignal.any([signal, AbortSignal.timeout(JSON_TIMEOUT_MS)])
|
|
687
|
-
});
|
|
688
|
-
if (!response.ok) return void 0;
|
|
689
|
-
const envelope = await readEnvelope(response);
|
|
690
|
-
const data = isObject(envelope.data) ? envelope.data : envelope.document;
|
|
691
|
-
const rawModels = Array.isArray(data["models"]) ? data["models"] : [];
|
|
692
|
-
const badges = /* @__PURE__ */ new Map();
|
|
693
|
-
for (const model of rawModels) {
|
|
694
|
-
if (!isObject(model)) continue;
|
|
695
|
-
const id = typeof model["id"] === "string" ? model["id"] : "";
|
|
696
|
-
if (id === "") continue;
|
|
697
|
-
const tags = Array.isArray(model["tags"]) ? model["tags"].filter((tag) => typeof tag === "string" && tag.toLowerCase().startsWith(BADGE_PREFIX)) : [];
|
|
698
|
-
if (tags.length > 0) badges.set(id, tags);
|
|
699
|
-
}
|
|
700
|
-
return badges.size === 0 ? void 0 : badges;
|
|
701
|
-
} catch {
|
|
702
|
-
return void 0;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* POST the billing endpoint for the aggregated remaining credit.
|
|
707
|
-
*
|
|
708
|
-
* Two upstream shapes, chosen by account type:
|
|
709
|
-
*
|
|
710
|
-
* - **CN enterprise** (`regionOf === 'cn'` and `enterpriseId` non-empty) asks
|
|
711
|
-
* `/v2/billing/meter/get-enterprise-user-usage`, which answers with a single
|
|
712
|
-
* cycle quota. The personal endpoint serves these accounts an empty
|
|
713
|
-
* `Accounts` list, which the card then renders as "0 credit" — a wrong
|
|
714
|
-
* number rather than a visible failure (issue #31).
|
|
715
|
-
* - **Everyone else** keeps the personal endpoint unchanged.
|
|
716
|
-
*
|
|
717
|
-
* The region gate is load-bearing: the enterprise endpoint is unverified for
|
|
718
|
-
* the global region, so an international credential that happens to carry an
|
|
719
|
-
* `enterpriseId` must stay on the measured personal path instead of being
|
|
720
|
-
* moved onto an unmeasured one.
|
|
721
|
-
*/
|
|
722
|
-
async fetchCredits(credential) {
|
|
723
|
-
if (regionOf(credential.domain) === "cn" && credential.enterpriseId !== void 0 && credential.enterpriseId !== "") {
|
|
724
|
-
return await this.fetchEnterpriseCredits(credential);
|
|
725
|
-
}
|
|
726
|
-
const now = /* @__PURE__ */ new Date();
|
|
727
|
-
const format = (date) => [
|
|
728
|
-
date.getFullYear().toString().padStart(4, "0"),
|
|
729
|
-
(date.getMonth() + 1).toString().padStart(2, "0"),
|
|
730
|
-
date.getDate().toString().padStart(2, "0")
|
|
731
|
-
].join("-") + " " + [
|
|
732
|
-
date.getHours().toString().padStart(2, "0"),
|
|
733
|
-
date.getMinutes().toString().padStart(2, "0"),
|
|
734
|
-
date.getSeconds().toString().padStart(2, "0")
|
|
735
|
-
].join(":");
|
|
736
|
-
const response = await fetch(`${billingBase(credential)}/v2/billing/meter/get-user-resource`, {
|
|
737
|
-
method: "POST",
|
|
738
|
-
headers: billingHeaders(credential),
|
|
739
|
-
body: JSON.stringify({
|
|
740
|
-
PageNumber: 1,
|
|
741
|
-
PageSize: 100,
|
|
742
|
-
ProductCode: "p_tcaca",
|
|
743
|
-
Status: [0, 3],
|
|
744
|
-
PackageEndTimeRangeBegin: format(now),
|
|
745
|
-
PackageEndTimeRangeEnd: format(new Date(now.getTime() + 365 * 101 * 24 * 3600 * 1e3))
|
|
746
|
-
}),
|
|
747
|
-
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
748
|
-
});
|
|
749
|
-
const envelope = await readEnvelope(response);
|
|
750
|
-
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
751
|
-
const responseWrapper = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
752
|
-
const data = typeof responseWrapper["Response"] === "object" && responseWrapper["Response"] !== null ? responseWrapper["Response"] : {};
|
|
753
|
-
const inner = typeof data["Data"] === "object" && data["Data"] !== null ? data["Data"] : {};
|
|
754
|
-
const rawAccounts = Array.isArray(inner["Accounts"]) ? inner["Accounts"] : [];
|
|
755
|
-
const accounts = [];
|
|
756
|
-
let total = 0;
|
|
757
|
-
for (const raw of rawAccounts) {
|
|
758
|
-
if (typeof raw !== "object" || raw === null) continue;
|
|
759
|
-
const account = raw;
|
|
760
|
-
const numberField = (key) => typeof account[key] === "number" ? account[key] : 0;
|
|
761
|
-
const size = numberField("CycleCapacitySize");
|
|
762
|
-
const cycleRemain = numberField("CycleCapacityRemain");
|
|
763
|
-
const cycleUsed = numberField("CycleCapacityUsed");
|
|
764
|
-
const capacityRemain = numberField("CapacityRemain");
|
|
765
|
-
let remain;
|
|
766
|
-
if (size > 0) remain = cycleRemain;
|
|
767
|
-
else if (cycleRemain > 0 || cycleUsed > 0) remain = cycleRemain;
|
|
768
|
-
else remain = capacityRemain;
|
|
769
|
-
if (remain < 0) remain = 0;
|
|
770
|
-
total += remain;
|
|
771
|
-
const endTimeMs = parseCycleEndTime(account["CycleEndTime"]);
|
|
772
|
-
accounts.push({
|
|
773
|
-
packageName: typeof account["PackageName"] === "string" ? account["PackageName"] : "(unnamed)",
|
|
774
|
-
remain,
|
|
775
|
-
size: size > 0 ? size : numberField("CapacitySize"),
|
|
776
|
-
...endTimeMs === void 0 ? {} : { endTimeMs }
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
return { total, accounts, ...summariseExpiry(accounts, now.getTime()) };
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* CN enterprise credit read: a single cycle quota instead of a package list.
|
|
783
|
-
*
|
|
784
|
-
* Verified against the WorkBuddy desktop app (`app.asar`,
|
|
785
|
-
* `BackendProvider.getEnterpriseUsage` and `CloudAccountRepo.billing`): the
|
|
786
|
-
* body is an empty object and the account identity travels only in the
|
|
787
|
-
* headers. The two official call sites disagree on the field spelling
|
|
788
|
-
* (`limitNum`/`credit` vs `limit_num`/`used_num`), so both are accepted.
|
|
789
|
-
*
|
|
790
|
-
* A body carrying no recognisable quota field is a hard error rather than a
|
|
791
|
-
* zero. Rendering `0` for "we did not understand the answer" is exactly how
|
|
792
|
-
* issue #31 stayed invisible while users saw a plausible wrong number.
|
|
793
|
-
*
|
|
794
|
-
* The error names fields and types only: it reaches the browser, and the
|
|
795
|
-
* response body may describe the account's usage.
|
|
796
|
-
*/
|
|
797
|
-
async fetchEnterpriseCredits(credential) {
|
|
798
|
-
const response = await fetch(`${CN_BILLING_BASE}/v2/billing/meter/get-enterprise-user-usage`, {
|
|
799
|
-
method: "POST",
|
|
800
|
-
headers: billingHeaders(credential),
|
|
801
|
-
body: JSON.stringify({}),
|
|
802
|
-
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
803
|
-
});
|
|
804
|
-
const envelope = await readEnvelope(response);
|
|
805
|
-
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
806
|
-
const sources = [];
|
|
807
|
-
for (const candidate of [envelope.data, envelope.document]) {
|
|
808
|
-
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
|
|
809
|
-
const record = candidate;
|
|
810
|
-
if (typeof record["data"] === "object" && record["data"] !== null && !Array.isArray(record["data"])) {
|
|
811
|
-
sources.push(record["data"]);
|
|
812
|
-
}
|
|
813
|
-
sources.push(record);
|
|
814
|
-
}
|
|
815
|
-
const numberAt = (source, key) => typeof source[key] === "number" ? source[key] : void 0;
|
|
816
|
-
let limit;
|
|
817
|
-
let used;
|
|
818
|
-
let resetTime;
|
|
819
|
-
for (const source of sources) {
|
|
820
|
-
const candidate = numberAt(source, "limitNum") ?? numberAt(source, "limit_num");
|
|
821
|
-
if (candidate === void 0) continue;
|
|
822
|
-
limit = candidate;
|
|
823
|
-
used = numberAt(source, "credit") ?? numberAt(source, "used_num");
|
|
824
|
-
if (typeof source["cycleResetTime"] === "string" && source["cycleResetTime"] !== "") {
|
|
825
|
-
resetTime = source["cycleResetTime"];
|
|
826
|
-
}
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
if (limit === void 0) {
|
|
830
|
-
throw new Error(`workbuddy enterprise billing response carried no recognised quota field (expected limitNum/limit_num + credit/used_num; received ${describeShape(envelope.document)})`);
|
|
831
|
-
}
|
|
832
|
-
if (limit === -1) {
|
|
833
|
-
return {
|
|
834
|
-
total: 0,
|
|
835
|
-
accounts: [{ packageName: enterprisePackageName, remain: 0, size: 0, unlimited: true }],
|
|
836
|
-
unlimited: true,
|
|
837
|
-
...resetTime === void 0 ? {} : { cycleResetTime: resetTime }
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
if (used === void 0) {
|
|
841
|
-
throw new Error(`workbuddy enterprise billing response carried a quota limit but no recognised usage field (expected credit/used_num alongside limitNum/limit_num; received ${describeShape(envelope.document)})`);
|
|
842
|
-
}
|
|
843
|
-
let remain = limit - used;
|
|
844
|
-
if (remain < 0) remain = 0;
|
|
845
|
-
return {
|
|
846
|
-
total: remain,
|
|
847
|
-
accounts: [{ packageName: enterprisePackageName, remain, size: limit }],
|
|
848
|
-
...resetTime === void 0 ? {} : { cycleResetTime: resetTime }
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* One probe request: a real streaming chat call carrying the effort under
|
|
853
|
-
* test.
|
|
854
|
-
*
|
|
855
|
-
* Shares `chatHeaders` with the normal chat path on purpose — the plan
|
|
856
|
-
* forbids probing through anything but the plugin's own credential handling,
|
|
857
|
-
* so a result describes what a real message would experience.
|
|
858
|
-
*
|
|
859
|
-
* The caller aborts as soon as a parseable event arrives; the body is never
|
|
860
|
-
* assembled into an answer. `reasoning_effort` is omitted entirely (rather
|
|
861
|
-
* than sent empty) when `effort` is undefined, so the baseline case is a
|
|
862
|
-
* genuinely bare request.
|
|
863
|
-
*
|
|
864
|
-
* Two international differences, both measured on 2026-09-11:
|
|
865
|
-
*
|
|
866
|
-
* - The gateway requires a leading `system` message (400/11128 otherwise), so
|
|
867
|
-
* one is prepended for the global region only.
|
|
868
|
-
* - `max_tokens: 1` is below some models' floor (the GPT-5.6 family rejects it
|
|
869
|
-
* with 400/11133 `integer_below_min_value`), so the international probe asks
|
|
870
|
-
* for a slightly larger minimum. This is a floor the plugin must clear, not
|
|
871
|
-
* evidence about any model's effort support: a model still refusing that
|
|
872
|
-
* minimum is reported as an incompatible request, never as "effort
|
|
873
|
-
* unsupported", and the ceiling is never raised further to force an answer.
|
|
874
|
-
*/
|
|
875
|
-
async probeEffort(credential, model, effort, signal) {
|
|
876
|
-
const international = regionOf(credential.domain) === "global";
|
|
877
|
-
let identity;
|
|
878
|
-
let userAgent;
|
|
879
|
-
try {
|
|
880
|
-
identity = await this.resolveChatIdentity(international ? "global" : "cn");
|
|
881
|
-
userAgent = chatUserAgent(identity, international ? "global" : "cn");
|
|
882
|
-
} catch {
|
|
883
|
-
identity = fallbackChatIdentity(international ? "global" : "cn");
|
|
884
|
-
userAgent = chatUserAgent(identity, international ? "global" : "cn");
|
|
885
|
-
}
|
|
886
|
-
const payload = {
|
|
887
|
-
model,
|
|
888
|
-
stream: true,
|
|
889
|
-
messages: [
|
|
890
|
-
...international ? [{ role: "system", content: INTERNATIONAL_SYSTEM_PROMPT }] : [],
|
|
891
|
-
{ role: "user", content: PROBE_PROMPT }
|
|
892
|
-
],
|
|
893
|
-
max_tokens: international ? INTERNATIONAL_PROBE_MAX_TOKENS : PROBE_MAX_TOKENS
|
|
894
|
-
};
|
|
895
|
-
if (effort !== void 0) payload["reasoning_effort"] = effort;
|
|
896
|
-
let response;
|
|
897
|
-
try {
|
|
898
|
-
response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
|
|
899
|
-
method: "POST",
|
|
900
|
-
headers: { ...chatHeaders(credential, userAgent, identity.clientVersion), "Authorization": `Bearer ${credential.accessToken}` },
|
|
901
|
-
body: JSON.stringify(payload),
|
|
902
|
-
signal
|
|
903
|
-
});
|
|
904
|
-
} catch (error) {
|
|
905
|
-
return { status: 0, streamed: false, detail: `transport error: ${String(error)}` };
|
|
906
|
-
}
|
|
907
|
-
if (!response.ok) {
|
|
908
|
-
const text = (await response.text()).slice(0, ERROR_BODY_LIMIT);
|
|
909
|
-
return { status: response.status, streamed: false, ...errorCodeOf(text) };
|
|
910
|
-
}
|
|
911
|
-
const streamed = await readFirstEvent(response);
|
|
912
|
-
return { status: response.status, streamed };
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
function errorCodeOf(text) {
|
|
916
|
-
try {
|
|
917
|
-
const parsed = JSON.parse(text);
|
|
918
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
919
|
-
const wrapped = parsed;
|
|
920
|
-
const extError = wrapped["extError"];
|
|
921
|
-
if (typeof extError === "object" && extError !== null && !Array.isArray(extError)) {
|
|
922
|
-
const code = extError["code"];
|
|
923
|
-
if (typeof code === "string") return { errorCode: code, detail: code };
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
} catch {
|
|
927
|
-
}
|
|
928
|
-
return { detail: text.slice(0, 200) };
|
|
929
|
-
}
|
|
930
|
-
async function readFirstEvent(response) {
|
|
931
|
-
const body = response.body;
|
|
932
|
-
if (body === null) return false;
|
|
933
|
-
const reader = body.getReader();
|
|
934
|
-
const decoder = new TextDecoder();
|
|
935
|
-
try {
|
|
936
|
-
for (; ; ) {
|
|
937
|
-
const { done, value } = await reader.read();
|
|
938
|
-
if (done) return false;
|
|
939
|
-
const text = decoder.decode(value, { stream: true });
|
|
940
|
-
if (text.includes("data:")) return true;
|
|
941
|
-
}
|
|
942
|
-
} catch {
|
|
943
|
-
return false;
|
|
944
|
-
} finally {
|
|
945
|
-
await reader.cancel().catch(() => {
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
function isObject(value) {
|
|
950
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
951
|
-
}
|
|
952
|
-
function positive(value) {
|
|
953
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
954
|
-
}
|
|
955
|
-
function parseModelCatalog(data, international = false, promoBadges) {
|
|
956
|
-
const rawModels = Array.isArray(data["models"]) ? data["models"] : [];
|
|
957
|
-
const agents = Array.isArray(data["agents"]) ? data["agents"] : [];
|
|
958
|
-
let cliIds;
|
|
959
|
-
for (const agent of agents) {
|
|
960
|
-
if (typeof agent === "object" && agent !== null) {
|
|
961
|
-
const wrapped = agent;
|
|
962
|
-
if (wrapped["name"] === "cli" && Array.isArray(wrapped["models"])) {
|
|
963
|
-
cliIds = wrapped["models"].filter((id) => typeof id === "string");
|
|
964
|
-
break;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
if (cliIds === void 0 || cliIds.length === 0) {
|
|
969
|
-
throw new Error("workbuddy model catalog lists no cli agent models");
|
|
970
|
-
}
|
|
971
|
-
const byId = /* @__PURE__ */ new Map();
|
|
972
|
-
for (const model of rawModels) {
|
|
973
|
-
if (typeof model !== "object" || model === null) continue;
|
|
974
|
-
const wrapped = model;
|
|
975
|
-
const id = typeof wrapped["id"] === "string" ? wrapped["id"] : "";
|
|
976
|
-
if (id === "" || wrapped["disabled"] === true) continue;
|
|
977
|
-
const input = typeof wrapped["maxInputTokens"] === "number" ? wrapped["maxInputTokens"] : 0;
|
|
978
|
-
const output = typeof wrapped["maxOutputTokens"] === "number" ? wrapped["maxOutputTokens"] : 0;
|
|
979
|
-
if (input <= 0 || output <= 0) continue;
|
|
980
|
-
byId.set(id, {
|
|
981
|
-
id,
|
|
982
|
-
name: typeof wrapped["name"] === "string" && wrapped["name"] !== "" ? wrapped["name"] : id,
|
|
983
|
-
contextWindow: international && isObject(wrapped["contextWindow"]) && positive(wrapped["contextWindow"]["defaultLength"]) ? wrapped["contextWindow"]["defaultLength"] : input,
|
|
984
|
-
...international ? {
|
|
985
|
-
...isObject(wrapped["contextWindow"]) && positive(wrapped["contextWindow"]["defaultLength"]) ? { defaultContextWindow: wrapped["contextWindow"]["defaultLength"] } : {},
|
|
986
|
-
maxInputTokens: input,
|
|
987
|
-
supportedContextWindows: isObject(wrapped["contextWindow"]) && Array.isArray(wrapped["contextWindow"]["supportedLengths"]) ? wrapped["contextWindow"]["supportedLengths"].filter(positive) : [],
|
|
988
|
-
promotions: parsePromotions(data["modelPromotions"], id)
|
|
989
|
-
} : {},
|
|
990
|
-
maxTokens: output,
|
|
991
|
-
supportsImages: wrapped["supportsImages"] === true && wrapped["disabledMultimodal"] !== true,
|
|
992
|
-
...resolveUpstreamReasoning(wrapped),
|
|
993
|
-
...resolveUpstreamBilling(wrapped, promoBadges?.get(id))
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
const models = cliIds.map((id) => byId.get(id)).filter((model) => model !== void 0);
|
|
997
|
-
if (models.length === 0) throw new Error("workbuddy model catalog resolved to an empty list");
|
|
998
|
-
return models;
|
|
999
|
-
}
|
|
1000
|
-
function parsePromotions(value, model) {
|
|
1001
|
-
if (!Array.isArray(value)) return [];
|
|
1002
|
-
return value.flatMap((item) => {
|
|
1003
|
-
if (!isObject(item) || item["enabled"] !== true) return [];
|
|
1004
|
-
const modelIds = item["modelIds"];
|
|
1005
|
-
if (!Array.isArray(modelIds) || !modelIds.includes(model)) return [];
|
|
1006
|
-
const schedule = item["schedule"];
|
|
1007
|
-
const discount = item["discount"];
|
|
1008
|
-
const badge = item["badge"];
|
|
1009
|
-
if (!isObject(schedule) || !isObject(discount) || !isObject(badge)) return [];
|
|
1010
|
-
if (discount["displayMode"] !== "replace") return [];
|
|
1011
|
-
const start = typeof schedule["validFrom"] === "string" ? Date.parse(schedule["validFrom"]) : Number.NaN;
|
|
1012
|
-
const end = typeof schedule["validUntil"] === "string" ? Date.parse(schedule["validUntil"]) : Number.NaN;
|
|
1013
|
-
const factor = discount["factor"];
|
|
1014
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return [];
|
|
1015
|
-
if (typeof factor !== "number" || !Number.isFinite(factor) || factor < 0) return [];
|
|
1016
|
-
return [{
|
|
1017
|
-
start,
|
|
1018
|
-
end,
|
|
1019
|
-
factor,
|
|
1020
|
-
label: typeof badge["label"] === "string" ? badge["label"] : "",
|
|
1021
|
-
priority: typeof item["priority"] === "number" && Number.isFinite(item["priority"]) ? item["priority"] : 0
|
|
1022
|
-
}];
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
function modelWithCurrentPromotion(model, now = Date.now()) {
|
|
1026
|
-
if (model.promotions === void 0 || model.promotions.length === 0) return model;
|
|
1027
|
-
const promotion = [...model.promotions].sort((a, b) => b.priority - a.priority).find((candidate) => now >= candidate.start && now < candidate.end);
|
|
1028
|
-
if (promotion === void 0) {
|
|
1029
|
-
const derivedFromPromotion = model.billing?.free === true || (model.billing?.badges?.length ?? 0) > 0 || model.promotions.some((candidate) => candidate.factor !== 1);
|
|
1030
|
-
if (!derivedFromPromotion) return model;
|
|
1031
|
-
return {
|
|
1032
|
-
...model,
|
|
1033
|
-
billing: {
|
|
1034
|
-
free: false,
|
|
1035
|
-
rateUnknown: true
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
const rate = normalizeCredits(model.billing?.credits);
|
|
1040
|
-
const original = rate !== void 0 && rate.startsWith("x") ? Number(rate.slice(1)) : Number.NaN;
|
|
1041
|
-
if (promotion.factor !== 0 && !Number.isFinite(original)) return model;
|
|
1042
|
-
const value = promotion.factor === 0 ? 0 : original * promotion.factor;
|
|
1043
|
-
return {
|
|
1044
|
-
...model,
|
|
1045
|
-
billing: {
|
|
1046
|
-
...model.billing,
|
|
1047
|
-
credits: `x${value.toFixed(2)}`,
|
|
1048
|
-
free: value === 0,
|
|
1049
|
-
badges: [
|
|
1050
|
-
...model.billing?.badges ?? [],
|
|
1051
|
-
...promotion.label === "" ? [] : [promotion.label]
|
|
1052
|
-
]
|
|
1053
|
-
}
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
function prepareCnChatBody(source) {
|
|
1057
|
-
let body;
|
|
1058
|
-
try {
|
|
1059
|
-
body = JSON.parse(source);
|
|
1060
|
-
} catch {
|
|
1061
|
-
return source;
|
|
1062
|
-
}
|
|
1063
|
-
if (!isObject(body)) return source;
|
|
1064
|
-
dropUnsupportedEffort(body);
|
|
1065
|
-
return JSON.stringify(body);
|
|
1066
|
-
}
|
|
1067
|
-
function prepareInternationalChatBody(source) {
|
|
1068
|
-
const prepared = prepareChatBody(source);
|
|
1069
|
-
let body;
|
|
1070
|
-
try {
|
|
1071
|
-
body = JSON.parse(prepared);
|
|
1072
|
-
} catch {
|
|
1073
|
-
return prepared;
|
|
1074
|
-
}
|
|
1075
|
-
if (!isObject(body)) return prepared;
|
|
1076
|
-
dropUnsupportedEffort(body);
|
|
1077
|
-
const messages = body["messages"];
|
|
1078
|
-
if (!Array.isArray(messages)) return JSON.stringify(body);
|
|
1079
|
-
const first = messages[0];
|
|
1080
|
-
if (isObject(first) && first["role"] === "system") return JSON.stringify(body);
|
|
1081
|
-
messages.unshift({ role: "system", content: INTERNATIONAL_SYSTEM_PROMPT });
|
|
1082
|
-
return JSON.stringify(body);
|
|
1083
|
-
}
|
|
1084
|
-
function dropUnsupportedEffort(obj) {
|
|
1085
|
-
if (obj["reasoning_effort"] === "off") delete obj["reasoning_effort"];
|
|
1086
|
-
}
|
|
1087
|
-
var INTERNATIONAL_SYSTEM_PROMPT = "You are a helpful assistant.";
|
|
1088
|
-
var INTERNATIONAL_PROBE_MAX_TOKENS = 16;
|
|
1089
|
-
|
|
1090
|
-
export {
|
|
1091
|
-
normalizeCredits,
|
|
1092
|
-
classifyUpstreamError,
|
|
1093
|
-
extractDisplayErrorMessage,
|
|
1094
|
-
regionOf,
|
|
1095
|
-
prepareChatBody,
|
|
1096
|
-
WorkBuddyUpstreamClient,
|
|
1097
|
-
parseModelCatalog,
|
|
1098
|
-
modelWithCurrentPromotion,
|
|
1099
|
-
prepareCnChatBody,
|
|
1100
|
-
prepareInternationalChatBody
|
|
1101
|
-
};
|
|
1102
|
-
//# sourceMappingURL=chunk-KWXLQJS5.js.map
|