dsh-multi-router 0.6.12 → 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.
Files changed (56) hide show
  1. package/README.md +2 -430
  2. package/package.json +5 -81
  3. package/CHANGELOG.md +0 -243
  4. package/LICENSE +0 -21
  5. package/THIRD_PARTY_NOTICES.md +0 -171
  6. package/cordis.patch.yml +0 -3
  7. package/lib/accounts.js +0 -19
  8. package/lib/accounts.js.map +0 -7
  9. package/lib/api-service.js +0 -11
  10. package/lib/api-service.js.map +0 -7
  11. package/lib/chunk-2UM45OXY.js +0 -181
  12. package/lib/chunk-2UM45OXY.js.map +0 -7
  13. package/lib/chunk-4L2CBGXN.js +0 -674
  14. package/lib/chunk-4L2CBGXN.js.map +0 -7
  15. package/lib/chunk-4V52V5GO.js +0 -105
  16. package/lib/chunk-4V52V5GO.js.map +0 -7
  17. package/lib/chunk-DNQPZMPF.js +0 -33
  18. package/lib/chunk-DNQPZMPF.js.map +0 -7
  19. package/lib/chunk-FLL3DUVP.js +0 -2017
  20. package/lib/chunk-FLL3DUVP.js.map +0 -7
  21. package/lib/chunk-GEBZMIMA.js +0 -287
  22. package/lib/chunk-GEBZMIMA.js.map +0 -7
  23. package/lib/chunk-KWXLQJS5.js +0 -1102
  24. package/lib/chunk-KWXLQJS5.js.map +0 -7
  25. package/lib/chunk-NZF36O5I.js +0 -5421
  26. package/lib/chunk-NZF36O5I.js.map +0 -7
  27. package/lib/chunk-S5KMFAQM.js +0 -50
  28. package/lib/chunk-S5KMFAQM.js.map +0 -7
  29. package/lib/chunk-TM5XDMGY.js +0 -332
  30. package/lib/chunk-TM5XDMGY.js.map +0 -7
  31. package/lib/chunk-V37VG7MN.js +0 -2101
  32. package/lib/chunk-V37VG7MN.js.map +0 -7
  33. package/lib/chunk-WDIT4E2W.js +0 -120
  34. package/lib/chunk-WDIT4E2W.js.map +0 -7
  35. package/lib/cli.js +0 -103
  36. package/lib/cli.js.map +0 -7
  37. package/lib/client.js +0 -2805
  38. package/lib/client.js.map +0 -7
  39. package/lib/clients.js +0 -30
  40. package/lib/clients.js.map +0 -7
  41. package/lib/host-v0.6.12.js +0 -414
  42. package/lib/host.js +0 -414
  43. package/lib/index.js +0 -414
  44. package/lib/index.js.map +0 -7
  45. package/lib/manager.js +0 -27
  46. package/lib/manager.js.map +0 -7
  47. package/lib/region-DO3KAV7V.js +0 -15
  48. package/lib/region-DO3KAV7V.js.map +0 -7
  49. package/lib/router.js +0 -14
  50. package/lib/router.js.map +0 -7
  51. package/lib/shim.js +0 -10
  52. package/lib/shim.js.map +0 -7
  53. package/lib/upstream-VP6J7LU2.js +0 -25
  54. package/lib/upstream-VP6J7LU2.js.map +0 -7
  55. package/router.example.json +0 -289
  56. package/upstream-lock.json +0 -17
@@ -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