dsh-multi-router 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-DNQPZMPF.js";
5
5
  import {
6
6
  RouteError
7
- } from "./chunk-XV7O2LRF.js";
7
+ } from "./chunk-XA3V2DGT.js";
8
8
  import {
9
9
  REASONING_EFFORTS
10
- } from "./chunk-GAQ77CH4.js";
10
+ } from "./chunk-U7J4ABAM.js";
11
11
 
12
12
  // src/shim.mjs
13
13
  import { createServer } from "node:http";
@@ -113,4 +113,4 @@ function createShim(router, { modelPrefix = "" } = {}) {
113
113
  export {
114
114
  createShim
115
115
  };
116
- //# sourceMappingURL=chunk-U2YTBWLW.js.map
116
+ //# sourceMappingURL=chunk-JYVJJ7OX.js.map
@@ -12,7 +12,11 @@ import {
12
12
  import {
13
13
  RouteError,
14
14
  abortable
15
- } from "./chunk-XV7O2LRF.js";
15
+ } from "./chunk-XA3V2DGT.js";
16
+ import {
17
+ beijingDay,
18
+ writePrivateJson
19
+ } from "./chunk-U7J4ABAM.js";
16
20
  import {
17
21
  WorkBuddyUpstreamClient,
18
22
  modelWithCurrentPromotion,
@@ -20,7 +24,8 @@ import {
20
24
  } from "./chunk-TEZ2FMEO.js";
21
25
 
22
26
  // src/clients.mjs
23
- import { readFile } from "node:fs/promises";
27
+ import { readFile as readFile2 } from "node:fs/promises";
28
+ import { setTimeout as delay2 } from "node:timers/promises";
24
29
 
25
30
  // upstream/trae/src/usage.ts
26
31
  function asNumber(value) {
@@ -2425,26 +2430,26 @@ async function validateAndTranslateMessages(messages, systemPrompt, attachments,
2425
2430
 
2426
2431
  // upstream/qoder/src/qoder/transport/wire/serialize.ts
2427
2432
  function stableHash(prefix, ...inputs) {
2428
- const hash = crypto3.createHash("sha256");
2429
- hash.update(prefix);
2433
+ const hash2 = crypto3.createHash("sha256");
2434
+ hash2.update(prefix);
2430
2435
  for (const input of inputs) {
2431
- hash.update("\0");
2432
- hash.update(input);
2436
+ hash2.update("\0");
2437
+ hash2.update(input);
2433
2438
  }
2434
- return hash.digest("hex").slice(0, 16);
2439
+ return hash2.digest("hex").slice(0, 16);
2435
2440
  }
2436
2441
  function stableChatRecordId(model, messages, tools, maxTokens) {
2437
- const hash = crypto3.createHash("sha256");
2438
- hash.update("qoder-record");
2439
- hash.update("\0");
2440
- hash.update(model);
2441
- hash.update("\0");
2442
- hash.update(JSON.stringify(messages));
2443
- hash.update("\0");
2444
- hash.update(JSON.stringify(tools));
2445
- hash.update("\0");
2446
- hash.update(`mt=${maxTokens}`);
2447
- return hash.digest("hex").slice(0, 16);
2442
+ const hash2 = crypto3.createHash("sha256");
2443
+ hash2.update("qoder-record");
2444
+ hash2.update("\0");
2445
+ hash2.update(model);
2446
+ hash2.update("\0");
2447
+ hash2.update(JSON.stringify(messages));
2448
+ hash2.update("\0");
2449
+ hash2.update(JSON.stringify(tools));
2450
+ hash2.update("\0");
2451
+ hash2.update(`mt=${maxTokens}`);
2452
+ return hash2.digest("hex").slice(0, 16);
2448
2453
  }
2449
2454
  function validateQoderRequestShape(options, model) {
2450
2455
  if (options.reasoningEffort !== void 0) {
@@ -2691,20 +2696,20 @@ var QoderImageUploader = class {
2691
2696
  * are included because a center object is never shared across either.
2692
2697
  */
2693
2698
  cacheKey(image, credentials) {
2694
- const hash = crypto4.createHash("sha256");
2695
- hash.update(getQoderImageUploadUrl(this.region));
2696
- hash.update("\0");
2697
- hash.update(credentials.userID);
2698
- hash.update("\0");
2699
- hash.update(image.mediaType);
2700
- hash.update("\0");
2699
+ const hash2 = crypto4.createHash("sha256");
2700
+ hash2.update(getQoderImageUploadUrl(this.region));
2701
+ hash2.update("\0");
2702
+ hash2.update(credentials.userID);
2703
+ hash2.update("\0");
2704
+ hash2.update(image.mediaType);
2705
+ hash2.update("\0");
2701
2706
  const variantId = typeof image.variantId === "string" ? image.variantId : "";
2702
2707
  if (variantId.length > 0) {
2703
- hash.update(variantId);
2708
+ hash2.update(variantId);
2704
2709
  } else {
2705
- hash.update(crypto4.createHash("sha256").update(Buffer.from(image.data)).digest("hex"));
2710
+ hash2.update(crypto4.createHash("sha256").update(Buffer.from(image.data)).digest("hex"));
2706
2711
  }
2707
- return hash.digest("hex");
2712
+ return hash2.digest("hex");
2708
2713
  }
2709
2714
  remember(key, url) {
2710
2715
  this.cache.delete(key);
@@ -4787,6 +4792,224 @@ function modelInfoOf(model) {
4787
4792
  var DEFAULT_MAX_TOKENS = 32768;
4788
4793
  var DEFAULT_CONTEXT_WINDOW = 18e4;
4789
4794
 
4795
+ // src/checkins.mjs
4796
+ import { createHash as createHash2 } from "node:crypto";
4797
+ import { readFile } from "node:fs/promises";
4798
+ import { setTimeout as delay } from "node:timers/promises";
4799
+ import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
4800
+ var supportsCheckin = (provider) => ["workbuddy", "trae", "qoder", "qoder-global"].includes(provider);
4801
+ var hash = (value) => createHash2("sha256").update(value).digest("hex");
4802
+ var nextDay = (now) => Date.parse(`${beijingDay(now)}T00:00:00+08:00`) + 864e5;
4803
+ var evening = (now) => Date.parse(`${beijingDay(now)}T20:05:00+08:00`);
4804
+ function checkinFailure(error, submitted = false) {
4805
+ const status = error?.status ?? error?.failure?.status ?? Number(/\bhttp\s+(401|403|429)\b/i.exec(error?.message || "")?.[1]);
4806
+ if (status === 401) return { status: "auth-invalid" };
4807
+ if ([403, 429].includes(status)) return { status: "paused" };
4808
+ if (/no valid refresh token is available|no refresh token is stored/.test(error?.message || "")) return { status: "auth-invalid" };
4809
+ return { status: submitted ? "uncertain" : "retryable" };
4810
+ }
4811
+ var canRetry = (record2, now) => ["retryable", "before-window"].includes(record2.status) && record2.retryAt <= now && (record2.attempts || 1) < 2;
4812
+ var checkinMessages = {
4813
+ claimed: "\u7B7E\u5230\u6210\u529F",
4814
+ "already-claimed": "\u4ECA\u65E5\u5DF2\u9886\u53D6",
4815
+ "device-used": "\u6B64\u8BBE\u5907\u4ECA\u65E5\u5DF2\u53C2\u4E0E\u7B7E\u5230\uFF0C\u4E0D\u518D\u5207\u6362\u5176\u4ED6\u8D26\u53F7\u9886\u53D6",
4816
+ "no-campaign": "\u5E73\u53F0\u672A\u5F00\u653E\u53EF\u9886\u53D6\u6D3B\u52A8",
4817
+ "before-window": "\u7B7E\u5230\u7A97\u53E3\u5C1A\u672A\u5F00\u542F",
4818
+ paused: "\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u6B64\u5E73\u53F0\u7B7E\u5230\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u5904\u7406",
4819
+ uncertain: "\u8BF7\u6C42\u7ED3\u679C\u5F85\u786E\u8BA4\uFF0C\u4ECA\u65E5\u4E0D\u518D\u81EA\u52A8\u63D0\u4EA4\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u6838\u5BF9",
4820
+ "auth-invalid": "\u4EE4\u724C\u5931\u6548\uFF0C\u8BF7\u5230\u8D26\u53F7\u4E0E\u767B\u5F55\u66F4\u65B0\u51ED\u8BC1",
4821
+ retryable: "\u67E5\u8BE2\u5931\u8D25\uFF0C\u9886\u53D6\u5C1A\u672A\u63D0\u4EA4",
4822
+ error: "\u7B7E\u5230\u672A\u5B8C\u6210\uFF0C\u4ECA\u65E5\u4E0D\u518D\u91CD\u590D\u5C1D\u8BD5\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u67E5\u770B",
4823
+ unsupported: "\u6682\u672A\u63A5\u5165\u53EF\u6838\u9A8C\u7684\u7B7E\u5230\u63A5\u53E3\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF"
4824
+ };
4825
+ var Checkins = class {
4826
+ constructor(file, manager, { now = Date.now, spacingMs = 1e4 } = {}) {
4827
+ this.file = file;
4828
+ this.manager = manager;
4829
+ this.now = now;
4830
+ this.spacingMs = spacingMs;
4831
+ this.data = { records: {}, devices: {}, pauses: {} };
4832
+ this.abort = new AbortController();
4833
+ }
4834
+ async read() {
4835
+ try {
4836
+ const raw = JSON.parse(await readFile(this.file, "utf8"));
4837
+ const object = (value) => value && typeof value === "object" && !Array.isArray(value);
4838
+ if (!object(raw) || !["records", "devices", "pauses"].every((k) => object(raw[k])) || Object.values(raw.records).some((r) => !object(r) || !Array.isArray(r.ids) || !Number.isFinite(r.at) || typeof r.day !== "string")) throw Error("Invalid check-in journal");
4839
+ this.data = raw;
4840
+ } catch (e) {
4841
+ if (e.code !== "ENOENT") throw e;
4842
+ }
4843
+ }
4844
+ async load() {
4845
+ try {
4846
+ await this.read();
4847
+ } catch {
4848
+ this.failed = true;
4849
+ }
4850
+ }
4851
+ state() {
4852
+ return {
4853
+ running: !!this.flight,
4854
+ current: this.current,
4855
+ error: this.failed ? "\u7B7E\u5230\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u5DF2\u505C\u6B62\u7B7E\u5230\uFF1B\u8BF7\u4FEE\u590D\u8BB0\u5F55\u6587\u4EF6\u540E\u91CD\u542F\u63D2\u4EF6\u3002" : void 0,
4856
+ schedule: "\u5317\u4EAC\u65F6\u95F4 10:05 \u9996\u6B21\u7B7E\u5230\uFF0C20:05 \u4EC5\u8865\u67E5\u53EF\u91CD\u8BD5\u7684\u5931\u8D25\uFF1BDSH \u8FD0\u884C\u65F6\u6267\u884C\uFF0C\u542F\u52A8\u65F6\u8865\u67E5",
4857
+ report: this.data.report,
4858
+ accounts: this.manager.config.accounts.map((a) => {
4859
+ const rows = Object.values(this.data.records).filter((r) => r.ids?.includes(a.id));
4860
+ const last = rows.sort((a2, b) => b.at - a2.at)[0];
4861
+ const pause = this.data.pauses[a.provider];
4862
+ return {
4863
+ id: a.id,
4864
+ supported: supportsCheckin(a.provider),
4865
+ auto: a.autoCheckin === true,
4866
+ paused: !!pause,
4867
+ last: last ? {
4868
+ day: last.day,
4869
+ at: last.at,
4870
+ status: last.status,
4871
+ amount: last.amount,
4872
+ activity: last.activity,
4873
+ retryAt: last.retryAt,
4874
+ retryReady: canRetry(last, this.now()),
4875
+ message: checkinMessages[last.status] || checkinMessages.error
4876
+ } : void 0
4877
+ };
4878
+ })
4879
+ };
4880
+ }
4881
+ start(ids, automatic = false) {
4882
+ if (this.failed || this.abort.signal.aborted || this.flight) return;
4883
+ if (!automatic && (!Array.isArray(ids) || !ids.length || ids.length > 200 || ids.some((id) => !this.manager.config.accounts.some((a) => a.id === id)))) throw Error("\u8BF7\u9009\u62E9\u6709\u6548\u7684\u7B7E\u5230\u8D26\u53F7");
4884
+ const selected = this.manager.config.accounts.filter((a) => supportsCheckin(a.provider) && a.enabled !== false && (automatic ? a.autoCheckin === true : ids.includes(a.id))).map((a) => a.id);
4885
+ if (!selected.length) return;
4886
+ this.flight = (async () => {
4887
+ let requested = false;
4888
+ const counts = {};
4889
+ for (const id of selected) {
4890
+ if (this.abort.signal.aborted) break;
4891
+ const account = this.manager.config.accounts.find((a) => a.id === id && a.enabled !== false && (!automatic || a.autoCheckin === true));
4892
+ if (!account) continue;
4893
+ if (requested) {
4894
+ await delay(this.spacingMs, void 0, { signal: this.abort.signal });
4895
+ requested = false;
4896
+ }
4897
+ this.current = id;
4898
+ requested = await this.run(account, automatic);
4899
+ if (requested) {
4900
+ const last = this.state().accounts.find((a) => a.id === id)?.last;
4901
+ if (last) counts[last.status] = (counts[last.status] || 0) + 1;
4902
+ }
4903
+ }
4904
+ if (Object.keys(counts).length) await withFileLock(this.file, async () => {
4905
+ await this.read();
4906
+ this.data.report = { at: this.now(), counts };
4907
+ await writePrivateJson(this.file, this.data);
4908
+ });
4909
+ })().catch(() => {
4910
+ if (!this.abort.signal.aborted) this.failed = true;
4911
+ }).finally(() => {
4912
+ this.flight = void 0;
4913
+ this.current = void 0;
4914
+ });
4915
+ }
4916
+ async resume(provider) {
4917
+ if (!supportsCheckin(provider) || this.failed || this.flight) throw Error("\u8BF7\u7B49\u5F85\u7B7E\u5230\u5B8C\u6210\uFF0C\u6216\u5148\u4FEE\u590D\u7B7E\u5230\u8BB0\u5F55");
4918
+ await withFileLock(this.file, async () => {
4919
+ await this.read();
4920
+ delete this.data.pauses[provider];
4921
+ await writePrivateJson(this.file, this.data);
4922
+ });
4923
+ }
4924
+ async credentialsUpdated(id) {
4925
+ await withFileLock(this.file, async () => {
4926
+ await this.read();
4927
+ for (const [key, row] of Object.entries(this.data.records)) if (row.status === "auth-invalid" && row.ids.includes(id)) delete this.data.records[key];
4928
+ await writePrivateJson(this.file, this.data);
4929
+ }, { waitMs: 3e4 });
4930
+ }
4931
+ async run(account, automatic = false) {
4932
+ const now = this.now(), day = beijingDay(now);
4933
+ if (now < Date.parse(`${day}T10:05:00+08:00`)) return false;
4934
+ return withFileLock(this.file, async () => {
4935
+ await this.read();
4936
+ if (this.abort.signal.aborted || this.data.pauses[account.provider]) return false;
4937
+ const previousEntry = Object.entries(this.data.records).find(([, r]) => r.day === day && r.ids?.includes(account.id));
4938
+ const previous = previousEntry?.[1];
4939
+ if (previous && !canRetry(previous, now)) return false;
4940
+ if (this.data.lastRequestAt && now - this.data.lastRequestAt < this.spacingMs) return false;
4941
+ this.data.lastRequestAt = now;
4942
+ await writePrivateJson(this.file, this.data);
4943
+ const pendingKey = hash(`${account.provider}:local:${account.id}`);
4944
+ const pending = {
4945
+ day,
4946
+ at: now,
4947
+ status: "retryable",
4948
+ ids: previous?.ids || [account.id],
4949
+ attempts: (previous?.attempts || (previous ? 1 : 0)) + 1,
4950
+ ...now < evening(now) ? { retryAt: evening(now) } : {}
4951
+ };
4952
+ if (previousEntry) delete this.data.records[previousEntry[0]];
4953
+ this.data.records[pendingKey] = pending;
4954
+ await writePrivateJson(this.file, this.data);
4955
+ let identity, client;
4956
+ try {
4957
+ client = await this.manager.router.client(account);
4958
+ if (!client.checkin) throw Error("Unsupported check-in");
4959
+ identity = await client.checkin.identity(AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]));
4960
+ if (!identity?.account || account.provider === "trae" && !identity.device) throw Error("Missing check-in identity");
4961
+ } catch (error) {
4962
+ pending.status = checkinFailure(error).status;
4963
+ if (pending.status === "paused") this.data.pauses[account.provider] = { at: now };
4964
+ await writePrivateJson(this.file, this.data);
4965
+ return true;
4966
+ }
4967
+ if (this.abort.signal.aborted || !this.manager.config.accounts.some((a) => a.id === account.id && a.enabled !== false && a.credentialFile === account.credentialFile && (!automatic || a.autoCheckin === true))) return true;
4968
+ const key = hash(`${account.provider}:${identity.account}`), device = identity.device ? hash(`${account.provider}:${identity.device}`) : void 0;
4969
+ const old = this.data.records[key];
4970
+ delete this.data.records[pendingKey];
4971
+ if (old?.day === day && !canRetry(old, now)) {
4972
+ if (!old.ids.includes(account.id)) {
4973
+ old.ids.push(account.id);
4974
+ await writePrivateJson(this.file, this.data);
4975
+ } else await writePrivateJson(this.file, this.data);
4976
+ return false;
4977
+ }
4978
+ const record2 = {
4979
+ ...pending,
4980
+ status: "uncertain",
4981
+ ids: [.../* @__PURE__ */ new Set([...pending.ids, ...old?.ids || [], account.id])],
4982
+ attempts: Math.max(pending.attempts, old?.day === day ? (old.attempts || 1) + 1 : 1)
4983
+ };
4984
+ this.data.records[key] = record2;
4985
+ for (const [k, row] of Object.entries(this.data.records)) if (row.at < now - 30 * 864e5) delete this.data.records[k];
4986
+ for (const [k, row] of Object.entries(this.data.devices)) if (row.until <= now) delete this.data.devices[k];
4987
+ if (device && this.data.devices[device]?.until > now) {
4988
+ record2.status = "device-used";
4989
+ await writePrivateJson(this.file, this.data);
4990
+ return true;
4991
+ }
4992
+ if (device) this.data.devices[device] = { until: nextDay(now) };
4993
+ await writePrivateJson(this.file, this.data);
4994
+ let result;
4995
+ try {
4996
+ result = await client.checkin.claim(AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]));
4997
+ } catch {
4998
+ result = { status: "uncertain" };
4999
+ }
5000
+ record2.status = Object.hasOwn(checkinMessages, result?.status) ? result.status : "uncertain";
5001
+ if (device && ["already-claimed", "no-campaign", "retryable", "before-window", "auth-invalid"].includes(record2.status)) delete this.data.devices[device];
5002
+ if (record2.attempts >= 2 || !["retryable", "before-window"].includes(record2.status)) delete record2.retryAt;
5003
+ if (["claimed", "already-claimed"].includes(record2.status) && Number.isFinite(result.amount) && result.amount >= 0) record2.amount = result.amount;
5004
+ if (result?.activity) record2.activity = Object.fromEntries(["streakDays", "totalCredits", "nextStreakDay", "endsAt"].filter((k) => Number.isFinite(result.activity[k]) && result.activity[k] >= 0).map((k) => [k, result.activity[k]]));
5005
+ if (record2.status === "paused" || result?.paused === true) this.data.pauses[account.provider] = { at: now };
5006
+ await writePrivateJson(this.file, this.data);
5007
+ if (record2.status === "claimed") await this.manager.monitorAccount(account);
5008
+ return true;
5009
+ }, { waitMs: 1e3 });
5010
+ }
5011
+ };
5012
+
4790
5013
  // src/stream.mjs
4791
5014
  async function preflightStream(response, signal) {
4792
5015
  if (!response.body) return { ok: false, status: 502, kind: "empty_stream" };
@@ -4889,7 +5112,7 @@ var expiryMs = (value) => {
4889
5112
  };
4890
5113
  async function jsonCredential(file) {
4891
5114
  try {
4892
- return JSON.parse(await readFile(file, "utf8"));
5115
+ return JSON.parse(await readFile2(file, "utf8"));
4893
5116
  } catch {
4894
5117
  throw new RouteError(401, "credential_unreadable");
4895
5118
  }
@@ -4917,7 +5140,7 @@ async function createAccountClient(account, config, attachments) {
4917
5140
  variant,
4918
5141
  ownPath: account.credentialFile,
4919
5142
  desktopPath: `${account.credentialFile}.no-desktop`,
4920
- refresh: (c) => client.refreshToken(c)
5143
+ refresh: (c) => account.provider === "workbuddy" ? refreshWorkBuddyCheckinCredential(c) : client.refreshToken(c)
4921
5144
  });
4922
5145
  const credential = async () => {
4923
5146
  try {
@@ -4948,6 +5171,7 @@ async function createAccountClient(account, config, attachments) {
4948
5171
  };
4949
5172
  };
4950
5173
  chat = async (input, signal) => client.chatStream(await credential(), prepareChatBody(JSON.stringify(input)), signal);
5174
+ if (account.provider === "workbuddy") checkin = createWorkBuddyCheckin(() => store.resolve());
4951
5175
  } else if (account.provider.startsWith("trae")) {
4952
5176
  const snapshot = await jsonCredential(account.credentialFile);
4953
5177
  const saved = snapshot.credential;
@@ -4975,8 +5199,9 @@ async function createAccountClient(account, config, attachments) {
4975
5199
  };
4976
5200
  const usage = new TraeUsageClient({ credential, deviceId: async () => (await identity()).deviceId });
4977
5201
  if (account.provider === "trae") {
5202
+ const checkinCredential = () => store.resolve();
4978
5203
  const checkinUsage = new TraeUsageClient({
4979
- credential,
5204
+ credential: checkinCredential,
4980
5205
  deviceId: async () => (await identity()).deviceId,
4981
5206
  fetchImpl: async (url, options) => {
4982
5207
  const response = await fetch(url, options);
@@ -4985,11 +5210,12 @@ async function createAccountClient(account, config, attachments) {
4985
5210
  const body = await response.clone().json();
4986
5211
  if (body.code !== void 0 && body.code !== 0 || typeof body.checked_in !== "boolean" || typeof body.did_checked_in !== "boolean") throw Object.assign(Error("checkin_status_rejected"), { status: 403 });
4987
5212
  }
5213
+ if (String(url).endsWith("/claim") && !Number.isFinite((await response.clone().json()).code)) throw Error("checkin_claim_invalid");
4988
5214
  return response;
4989
5215
  }
4990
5216
  });
4991
5217
  checkin = {
4992
- identity: async () => ({ account: (await credential()).userId, device: (await identity()).deviceId }),
5218
+ identity: async () => ({ account: (await checkinCredential()).userId, device: (await identity()).deviceId }),
4993
5219
  claim: (signal) => claimTraeCheckin(checkinUsage, signal)
4994
5220
  };
4995
5221
  }
@@ -5052,27 +5278,7 @@ async function createAccountClient(account, config, attachments) {
5052
5278
  transport,
5053
5279
  ...attachments ? { attachments } : {}
5054
5280
  });
5055
- const checkinTransport = createQoderTransport({
5056
- region,
5057
- resolvePat: getPat,
5058
- resolveMachineId: () => getMachineId(),
5059
- fetch: async (url, options) => {
5060
- const response = await fetch(url, options);
5061
- if (!response.ok) {
5062
- const restricted = [401, 403, 429].includes(response.status);
5063
- throw new QoderLlmError(`checkin_http_${response.status}`, restricted ? "QUOTA" : "PROVIDER_ERROR", { status: response.status });
5064
- }
5065
- return response;
5066
- }
5067
- });
5068
- checkin = {
5069
- identity: async (signal) => ({ account: (await checkinTransport.readAccount({ signal })).profile.id }),
5070
- claim: async (signal) => {
5071
- const result = await checkinTransport.checkIn(signal);
5072
- if (result.status !== "error") return { status: result.status };
5073
- return { status: /401|403|429|rate.?limit|throttl|captcha|verify|verification|风控|验证|频繁|forbidden|unauthor|AUTH/i.test(result.message || "") ? "paused" : "uncertain" };
5074
- }
5075
- };
5281
+ checkin = createQoderCheckin(region, getPat);
5076
5282
  accountInfo = async (signal) => {
5077
5283
  const result = await transport.readAccount({ force: true, signal });
5078
5284
  const buckets = [["\u4E2A\u4EBA\u5957\u9910", result.usage?.userQuota], ["\u9644\u52A0\u989D\u5EA6", result.usage?.addOnQuota], ["\u7EC4\u7EC7\u989D\u5EA6", result.usage?.orgResourcePackage]].filter(([, p]) => p).map(([name, p]) => ({ ...p, name }));
@@ -5101,25 +5307,173 @@ async function createAccountClient(account, config, attachments) {
5101
5307
  };
5102
5308
  }
5103
5309
  async function claimTraeCheckin(usage, signal) {
5310
+ let submitted = false;
5104
5311
  try {
5105
5312
  const status = await usage.checkinStatus(signal);
5106
5313
  if (status.checkedIn) return { status: "already-claimed" };
5107
5314
  if (status.didCheckedIn) return { status: "device-used" };
5108
5315
  if (!status.enabled) return { status: "no-campaign" };
5316
+ submitted = true;
5109
5317
  const claim = await usage.claimCheckin(signal);
5110
5318
  if (claim.code === 9095) return { status: "device-used" };
5111
5319
  if (!claim.claimed) return { status: "paused" };
5112
5320
  return { status: "claimed", ...Number.isFinite(status.credits) ? { amount: status.credits + (status.extraCredits || 0) } : {} };
5113
5321
  } catch (e) {
5114
- return { status: [401, 403, 429].includes(e.status) ? "paused" : "uncertain" };
5322
+ return checkinFailure(e, submitted);
5323
+ }
5324
+ }
5325
+ function createQoderCheckin(region, getPat, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
5326
+ const guardedFetch = async (url, options) => {
5327
+ try {
5328
+ const response = await fetchImpl(url, options);
5329
+ if (!response.ok) throw new QoderLlmError("checkin_http_error", "PROVIDER_ERROR", { status: response.status });
5330
+ if (String(url).endsWith("/campaigns") && !Array.isArray((await response.clone().json()).campaigns)) throw Error("checkin_campaigns_invalid");
5331
+ return response;
5332
+ } catch (error) {
5333
+ if (error instanceof QoderLlmError) throw error;
5334
+ throw new QoderLlmError("checkin_network_error", "PROVIDER_ERROR");
5335
+ }
5336
+ };
5337
+ const auth = new QoderAuthService({ region, fetch: guardedFetch, resolveMachineId: () => getMachineId() });
5338
+ const campaigns = new QoderCheckInService({ region, authService: auth, fetch: guardedFetch });
5339
+ const credentials = async (signal) => auth.getCredentials(await getPat(), signal);
5340
+ return {
5341
+ identity: async (signal) => ({ account: (await credentials(signal)).userID }),
5342
+ claim: async (signal) => {
5343
+ let submitted = false;
5344
+ try {
5345
+ const token = (await credentials(signal)).authToken;
5346
+ const rows = await campaigns.fetchCampaigns(token, signal), time = now();
5347
+ const benefits = rows.filter((c) => c.actionType === "CLAIM_BENEFIT");
5348
+ const active = benefits.filter((c) => (!Number.isFinite(c.startAt) || c.startAt * 1e3 <= time) && (!Number.isFinite(c.endAt) || c.endAt * 1e3 > time));
5349
+ const target = active.find((c) => c.claimStatus === "CLAIMABLE" && typeof c.campaignId === "string" && c.campaignId);
5350
+ if (!target) {
5351
+ if (active.some((c) => c.claimStatus === "CLAIMED")) return { status: "already-claimed" };
5352
+ return { status: benefits.some((c) => c.startAt * 1e3 > time || c.endAt * 1e3 <= time) ? "before-window" : "no-campaign" };
5353
+ }
5354
+ submitted = true;
5355
+ const result = await campaigns.claimCampaign(token, target.campaignId, signal);
5356
+ if (result.status !== "CLAIMED") return { status: "paused" };
5357
+ return {
5358
+ status: result.replayed ? "already-claimed" : "claimed",
5359
+ ...Number.isFinite(result.benefit?.amount) && result.benefit.amount >= 0 ? { amount: result.benefit.amount } : {}
5360
+ };
5361
+ } catch (error) {
5362
+ return checkinFailure(error, submitted);
5363
+ }
5364
+ }
5365
+ };
5366
+ }
5367
+ function workbuddyToken(token) {
5368
+ try {
5369
+ const [head, body, signature, extra] = token.split(".");
5370
+ const header = JSON.parse(Buffer.from(head, "base64url")), payload = JSON.parse(Buffer.from(body, "base64url"));
5371
+ if (extra || !signature || header.alg !== "RS256" || payload.iss !== "https://www.codebuddy.cn/auth/realms/copilot" || typeof payload.sub !== "string" || !payload.sub) throw Error();
5372
+ return payload;
5373
+ } catch {
5374
+ throw Object.assign(Error("HTTP 401 invalid_workbuddy_cn_token"), { status: 401 });
5115
5375
  }
5116
5376
  }
5377
+ async function workbuddyPost(url, headers, signal, fetchImpl) {
5378
+ let response;
5379
+ try {
5380
+ response = await fetchImpl(url, { method: "POST", headers: { "Content-Type": "application/json", "Accept-Encoding": "identity", ...headers }, body: "{}", signal });
5381
+ } catch {
5382
+ throw Object.assign(Error("workbuddy_checkin_network"), { transient: !signal?.aborted });
5383
+ }
5384
+ if (!response.ok) throw Object.assign(Error(`HTTP ${response.status} workbuddy_checkin_http`), { status: response.status, transient: response.status >= 500 });
5385
+ const body = JSON.parse(await readLimitedText(response, 256 * 1024, "Check-in"));
5386
+ if (body?.code === 401 || body?.code === 40100) throw Object.assign(Error("HTTP 401 workbuddy_checkin_auth"), { status: 401 });
5387
+ if ([403, 429].includes(body?.code)) throw Object.assign(Error(`HTTP ${body.code} workbuddy_checkin_restricted`), { status: body.code });
5388
+ return body;
5389
+ }
5390
+ async function refreshWorkBuddyCheckinCredential(credential, { fetchImpl = (...args) => fetch(...args), signal = AbortSignal.timeout(3e4) } = {}) {
5391
+ const body = await workbuddyPost("https://copilot.tencent.com/v2/plugin/auth/token/refresh", {
5392
+ "X-Refresh-Token": credential.refreshToken,
5393
+ "X-Auth-Refresh-Source": "plugin"
5394
+ }, signal, fetchImpl);
5395
+ if (body.code !== 0 || typeof body.data?.accessToken !== "string") throw Error("workbuddy_refresh_rejected");
5396
+ const token = workbuddyToken(body.data.accessToken);
5397
+ if (token.sub !== credential.uid || !(token.exp * 1e3 > Date.now())) throw Object.assign(Error("HTTP 401 workbuddy_refresh_identity"), { status: 401 });
5398
+ return {
5399
+ accessToken: body.data.accessToken,
5400
+ expiresInSec: (token.exp * 1e3 - Date.now()) / 1e3,
5401
+ ...typeof body.data.refreshToken === "string" && body.data.refreshToken ? { refreshToken: body.data.refreshToken } : {}
5402
+ };
5403
+ }
5404
+ function createWorkBuddyCheckin(credential, { fetchImpl = (...args) => fetch(...args), sleep = delay2, now = Date.now } = {}) {
5405
+ const login = async () => {
5406
+ const c = await credential(), token = workbuddyToken(c.accessToken);
5407
+ if (c.uid !== token.sub) throw Object.assign(Error("workbuddy_uid_mismatch"), { status: 401 });
5408
+ return { ...c, uid: token.sub };
5409
+ };
5410
+ const post = (path, c, signal) => workbuddyPost(`https://www.workbuddy.cn/v2/billing/meter/${path}`, {
5411
+ Authorization: `Bearer ${c.accessToken}`,
5412
+ "X-User-Id": c.uid,
5413
+ Origin: "https://www.workbuddy.cn",
5414
+ Referer: "https://www.workbuddy.cn/profile/growth-center",
5415
+ "User-Agent": "Mozilla/5.0"
5416
+ }, signal, fetchImpl);
5417
+ const activity = (data) => {
5418
+ const rawEnd = data.end_time;
5419
+ const endsAt = typeof rawEnd === "string" ? Date.parse(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(rawEnd) ? `${rawEnd.replace(" ", "T")}+08:00` : rawEnd) : void 0;
5420
+ return { streakDays: data.streak_days, totalCredits: data.total_credits, nextStreakDay: data.next_streak_day, endsAt };
5421
+ };
5422
+ return {
5423
+ identity: async () => ({ account: (await login()).uid }),
5424
+ claim: async (signal) => {
5425
+ let submitted = false;
5426
+ try {
5427
+ const c = await login();
5428
+ let state;
5429
+ for (let attempt = 0; ; attempt++) {
5430
+ try {
5431
+ state = await post("checkin-activity-status", c, signal);
5432
+ break;
5433
+ } catch (error) {
5434
+ if (!error.transient || attempt >= 2 || signal?.aborted) throw error;
5435
+ await sleep((attempt + 1) * 2e3, void 0, { signal });
5436
+ }
5437
+ }
5438
+ if (![0, 200].includes(state?.code) || !state.data || typeof state.data !== "object") throw Error("workbuddy_status_invalid");
5439
+ const stats = activity(state.data);
5440
+ if (state.data.today_checked_in === true) return { status: "already-claimed", activity: stats };
5441
+ if (state.data.active === false || stats.endsAt <= now()) return { status: "no-campaign", activity: stats };
5442
+ if (state.data.today_checked_in !== false) throw Error("workbuddy_status_unknown");
5443
+ submitted = true;
5444
+ const result = await post("daily-checkin", c, signal);
5445
+ const claimed = [0, 200].includes(result?.code), already = /今天已签到|今日已签到|already\s+(?:checked|signed)/i.test(result?.msg || "");
5446
+ if (!claimed && !already) return { status: Number.isFinite(result?.code) ? "paused" : "uncertain" };
5447
+ let updated = stats, paused = false;
5448
+ try {
5449
+ const after = await post("checkin-activity-status", c, signal);
5450
+ if ([0, 200].includes(after?.code) && after.data) updated = activity(after.data);
5451
+ } catch (error) {
5452
+ paused = checkinFailure(error).status === "paused";
5453
+ }
5454
+ return {
5455
+ status: already ? "already-claimed" : "claimed",
5456
+ activity: updated,
5457
+ ...paused ? { paused: true } : {},
5458
+ ...claimed && Number.isFinite(result.data?.credit) && result.data.credit >= 0 ? { amount: result.data.credit } : {}
5459
+ };
5460
+ } catch (error) {
5461
+ return checkinFailure(error, submitted);
5462
+ }
5463
+ }
5464
+ };
5465
+ }
5117
5466
 
5118
5467
  export {
5119
5468
  qoderPluginDataDir,
5469
+ supportsCheckin,
5470
+ Checkins,
5120
5471
  jsonCredential,
5121
5472
  cachedModels,
5122
5473
  createAccountClient,
5123
- claimTraeCheckin
5474
+ claimTraeCheckin,
5475
+ createQoderCheckin,
5476
+ refreshWorkBuddyCheckinCredential,
5477
+ createWorkBuddyCheckin
5124
5478
  };
5125
- //# sourceMappingURL=chunk-YUP77Q3A.js.map
5479
+ //# sourceMappingURL=chunk-RPW6DI6O.js.map