dsh-qoder-connect 0.2.3-alpha.1 → 0.2.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.
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, T as QoderCatalogStore, V as qoderCredentialIdentity, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey, z as QoderCredentialStore } from "./variants-CU8HDUwi.js";
2
+ import { D as QoderCatalogStore, O as qoderCatalogPath, U as qoderCredentialIdentity, V as QoderCredentialStore, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey } from "./variants-BgysmR2W.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
package/lib/client.js CHANGED
@@ -422,10 +422,16 @@ window.__ModuleLoader__.load({
422
422
  const POLL_MIN_MS = 6e4;
423
423
  /** 10:00 UTC+8, the moment the upstream resets the daily campaign. */
424
424
  const CHECK_IN_MINUTE_DEFAULT = 600;
425
+ /**
426
+ * Floor the Host schema also enforces: the daily benefit window opens at 10:00,
427
+ * so an earlier moment can never claim anything. Mirrored here for immediate UI
428
+ * feedback, the same way `POLL_MIN_MS` mirrors the poll floor.
429
+ */
430
+ const EARLIEST_CHECK_IN_MINUTE = 600;
425
431
  /** Minutes past midnight (UTC+8), split for the two number fields. */
426
432
  function splitMinutes(minutes) {
427
433
  const safe = Number.isFinite(minutes) ? Math.trunc(minutes) : CHECK_IN_MINUTE_DEFAULT;
428
- const clamped = safe < 0 || safe > 1439 ? CHECK_IN_MINUTE_DEFAULT : safe;
434
+ const clamped = safe < EARLIEST_CHECK_IN_MINUTE || safe > 1439 ? CHECK_IN_MINUTE_DEFAULT : safe;
429
435
  return {
430
436
  hours: Math.floor(clamped / 60),
431
437
  minutes: clamped % 60
@@ -555,10 +561,11 @@ window.__ModuleLoader__.load({
555
561
  const parsedMinutes = Number.parseInt(minuteDraft, 10);
556
562
  const hours = Number.isFinite(parsedHours) ? Math.min(23, Math.max(0, parsedHours)) : split.hours;
557
563
  const minutes = Number.isFinite(parsedMinutes) ? Math.min(59, Math.max(0, parsedMinutes)) : split.minutes;
558
- const next = hours * 60 + minutes;
564
+ const next = Math.max(EARLIEST_CHECK_IN_MINUTE, hours * 60 + minutes);
559
565
  if (next === value) {
560
- setHourDraft(String(hours));
561
- setMinuteDraft(String(minutes).padStart(2, "0"));
566
+ const settled = splitMinutes(next);
567
+ setHourDraft(String(settled.hours));
568
+ setMinuteDraft(String(settled.minutes).padStart(2, "0"));
562
569
  return;
563
570
  }
564
571
  onPick(next);
@@ -1753,17 +1760,17 @@ window.__ModuleLoader__.load({
1753
1760
  height: 6,
1754
1761
  borderRadius: "50%",
1755
1762
  flexShrink: 0,
1756
- background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
1757
- } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
1763
+ background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" || log.status === "before-window" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
1764
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : log.status === "before-window" ? t("autoCheckInStatusBeforeWindow") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
1758
1765
  }),
1759
1766
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1760
1767
  style: {
1761
1768
  flex: 1,
1762
1769
  textAlign: "right",
1763
1770
  fontWeight: 600,
1764
- color: log.amount ? "var(--dsw-alias-brand-primary)" : "inherit"
1771
+ color: log.status === "claimed" ? "var(--dsw-alias-brand-primary)" : "inherit"
1765
1772
  },
1766
- children: log.amount ? `+${log.amount}` : "-"
1773
+ children: log.status === "claimed" ? `+${log.amount ?? 100}` : "-"
1767
1774
  })
1768
1775
  ]
1769
1776
  }, log.id))]
@@ -1943,6 +1950,7 @@ window.__ModuleLoader__.load({
1943
1950
  if (result.state === "claimed") setCheckInNotice(t("autoCheckInStatusClaimed", { amount: result.amount ?? 100 }));
1944
1951
  else if (result.state === "already-claimed") setCheckInNotice(t("autoCheckInStatusAlready"));
1945
1952
  else if (result.state === "no-campaign") setCheckInNotice(t("autoCheckInStatusNoCampaign"));
1953
+ else if (result.state === "before-window") setCheckInNotice(t("autoCheckInStatusBeforeWindow"));
1946
1954
  else if (result.reason) setCheckInNotice(t("autoCheckInStatusError", { message: result.reason }));
1947
1955
  } catch (error) {
1948
1956
  if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
@@ -3957,10 +3965,11 @@ window.__ModuleLoader__.load({
3957
3965
  autoCheckInHintGlobal: "Automatically check in daily to claim available perks for the Global account.",
3958
3966
  checkInTimeCN: "Check-in time (China)",
3959
3967
  checkInTimeGlobal: "Check-in time (Global)",
3960
- checkInTimeHint: "The moment the automatic check-in runs each day, in UTC+8.",
3968
+ checkInTimeHint: "The moment the automatic check-in runs each day, in UTC+8. Earliest 10:00 — that is when the daily benefit window opens.",
3961
3969
  autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
3962
3970
  autoCheckInStatusAlready: "Already checked in today",
3963
3971
  autoCheckInStatusNoCampaign: "No active benefit campaign today",
3972
+ autoCheckInStatusBeforeWindow: "Benefit window not open yet",
3964
3973
  autoCheckInStatusError: "Auto check-in error: {message}",
3965
3974
  quotaSignInRequired: "Save a PAT for this variant first to enable this card.",
3966
3975
  quotaPollLabel: "Refresh interval",
@@ -4128,10 +4137,11 @@ window.__ModuleLoader__.load({
4128
4137
  autoCheckInHintGlobal: "每天自动签到,为国际版账号领取可用福利额度。",
4129
4138
  checkInTimeCN: "签到时间(国内版)",
4130
4139
  checkInTimeGlobal: "签到时间(国际版)",
4131
- checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8)。",
4140
+ checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8)。最早 10:00,每日福利窗口在此时开启。",
4132
4141
  autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
4133
4142
  autoCheckInStatusAlready: "今日已完成签到",
4134
4143
  autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
4144
+ autoCheckInStatusBeforeWindow: "签到活动尚未开启",
4135
4145
  autoCheckInStatusError: "自动签到出错:{message}",
4136
4146
  quotaSignInRequired: "请先为该变体保存 PAT,再开启此开关。",
4137
4147
  quotaPollLabel: "刷新间隔",
package/lib/index.d.ts CHANGED
@@ -269,7 +269,7 @@ type QoderWebStatus = {
269
269
  checkIn?: {
270
270
  lastDate: string;
271
271
  lastAt: number;
272
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
272
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
273
273
  amount?: number | undefined;
274
274
  message?: string | undefined;
275
275
  /**
@@ -284,7 +284,7 @@ type QoderWebStatus = {
284
284
  id: string;
285
285
  date: string;
286
286
  timestamp: number;
287
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
287
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
288
288
  amount?: number | undefined;
289
289
  campaignKey?: string | undefined;
290
290
  message?: string | undefined;
@@ -747,7 +747,7 @@ interface QoderCheckInResult {
747
747
  variantId: string;
748
748
  date: string;
749
749
  timestamp: number;
750
- status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
750
+ status: 'claimed' | 'already-claimed' | 'before-window' | 'no-campaign' | 'error';
751
751
  amount?: number | undefined;
752
752
  campaignKey?: string | undefined;
753
753
  message?: string | undefined;
@@ -942,13 +942,14 @@ declare class QoderUpstreamClient {
942
942
  /**
943
943
  * One minimal reasoning-effort request, as the probe sweep needs it.
944
944
  *
945
- * The transport validates `reasoningEffort` against the model's advertised
946
- * efforts **locally** before any network call: a rejection therefore reads
947
- * as an attributable 400 with code `UNSUPPORTED_REASONING_EFFORT`, and an
948
- * accepted value costs exactly one streamed request, which this method ends
949
- * after the first chunk arrives. A probe on this upstream measures the
950
- * discovery catalog as much as the endpoint behind it — see the module docs
951
- * in `probe.ts`.
945
+ * A row that declares efforts is still checked locally, so an unlisted value
946
+ * costs no request. A row that declares none — the only kind detection is
947
+ * offered for — sends the value to the upstream and waits for its answer,
948
+ * because the endpoint is the only authority on spellings the catalog never
949
+ * lists. Either refusal is reported as an attributable 400 with code
950
+ * `UNSUPPORTED_REASONING_EFFORT`, and an accepted value costs exactly one
951
+ * streamed request, which this method ends after the first chunk arrives —
952
+ * see the module docs in `probe.ts`.
952
953
  */
953
954
  probeEffort(model: string, effort: string | undefined, signal: AbortSignal): Promise<ProbeAttempt>;
954
955
  }
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as QODER_GLOBAL_AUTH_PATH, B as patTail, C as probeModel, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, F as QODER_STATUS_PATH, G as qoderMachineIdPath, H as qoderOwnAuthPath, I as QODER_AUTH_FILENAME, K as qoderPluginDataDir, L as QODER_PAT_ENV_CN, M as QODER_GLOBAL_STATUS_PATH, N as QODER_PROBE_PATH, O as QoderCatalog, P as QODER_SETTINGS_FACE_PATH, R as QODER_PAT_ENV_GLOBAL, S as PROBE_EFFORT_CANDIDATES, T as QoderCatalogStore, U as QODER_DATA_DIR_ENV, V as qoderCredentialIdentity, W as QODER_DATA_DIR_NAME, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_GLOBAL_PROBE_PATH, k as QODER_AUTH_PATH, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderStateDir, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as randomSentinel, x as getMachineId, y as validateApiKey, z as QoderCredentialStore } from "./variants-CU8HDUwi.js";
1
+ import { A as QoderCatalog, B as QODER_PAT_ENV_GLOBAL, C as getMachineId, D as QoderCatalogStore, E as randomSentinel, F as QODER_PROBE_PATH, G as QODER_DATA_DIR_ENV, H as patTail, I as QODER_SETTINGS_FACE_PATH, J as qoderPluginDataDir, K as QODER_DATA_DIR_NAME, L as QODER_STATUS_PATH, M as QODER_GLOBAL_AUTH_PATH, N as QODER_GLOBAL_PROBE_PATH, O as qoderCatalogPath, P as QODER_GLOBAL_STATUS_PATH, R as QODER_AUTH_FILENAME, S as utc8WallClock, T as probeModel, U as qoderCredentialIdentity, V as QoderCredentialStore, W as qoderOwnAuthPath, Y as qoderStateDir, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_AUTH_PATH, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderMachineIdPath, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as PROBE_EFFORT_CANDIDATES, x as formatUtc8Date, y as validateApiKey, z as QODER_PAT_ENV_CN } from "./variants-BgysmR2W.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -1366,20 +1366,12 @@ var JsonFileCheckInStore = class {
1366
1366
  } catch {}
1367
1367
  }
1368
1368
  };
1369
- /**
1370
- * Returns the current date in YYYY-MM-DD standardized on UTC+8 (Beijing Time).
1371
- */
1372
- function getUtc8DateString(nowMs = Date.now()) {
1373
- const d = new Date(nowMs);
1374
- const utc8 = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
1375
- return `${utc8.getFullYear()}-${String(utc8.getMonth() + 1).padStart(2, "0")}-${String(utc8.getDate()).padStart(2, "0")}`;
1376
- }
1377
- /** Clamp any stored/typed value onto a real minute of the day. */
1369
+ /** Clamp any stored/typed value onto a minute the plugin can actually honour. */
1378
1370
  function normalizeCheckInMinute(value) {
1379
1371
  if (typeof value !== "number" || !Number.isFinite(value)) return 600;
1380
1372
  const whole = Math.trunc(value);
1381
1373
  if (whole < 0 || whole > 1439) return 600;
1382
- return whole;
1374
+ return Math.max(600, whole);
1383
1375
  }
1384
1376
  /**
1385
1377
  * Calculates milliseconds until the next occurrence of `minuteOfDay` (UTC+8).
@@ -1389,8 +1381,7 @@ function normalizeCheckInMinute(value) {
1389
1381
  */
1390
1382
  function msUntilNextCheckIn(minuteOfDay, nowMs = Date.now()) {
1391
1383
  const minute = normalizeCheckInMinute(minuteOfDay);
1392
- const d = new Date(nowMs);
1393
- const utc8Time = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
1384
+ const utc8Time = utc8WallClock(nowMs);
1394
1385
  const targetUtc8 = new Date(utc8Time.getTime());
1395
1386
  targetUtc8.setHours(Math.floor(minute / 60), minute % 60, 5, 0);
1396
1387
  let diff = targetUtc8.getTime() - utc8Time.getTime();
@@ -1405,8 +1396,7 @@ function msUntilNextCheckIn(minuteOfDay, nowMs = Date.now()) {
1405
1396
  */
1406
1397
  function isPastCheckInTime(minuteOfDay, nowMs = Date.now()) {
1407
1398
  const minute = normalizeCheckInMinute(minuteOfDay);
1408
- const d = new Date(nowMs);
1409
- const utc8 = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
1399
+ const utc8 = utc8WallClock(nowMs);
1410
1400
  return utc8.getHours() * 60 + utc8.getMinutes() >= minute;
1411
1401
  }
1412
1402
  var CheckInScheduler = class {
@@ -1502,7 +1492,7 @@ var CheckInScheduler = class {
1502
1492
  async sweepAll(isCatchUp, only) {
1503
1493
  if (this.disposed) return;
1504
1494
  const nowMs = this.now();
1505
- const today = getUtc8DateString(nowMs);
1495
+ const today = formatUtc8Date(nowMs);
1506
1496
  for (const target of this.targets) {
1507
1497
  if (only !== void 0 && target.variantId !== only) continue;
1508
1498
  if (!this.isEnabled(target.variantId)) continue;
@@ -1912,8 +1902,14 @@ const AUTO_CHECK_IN_FIELD = volatileField(z.boolean().default(false).description
1912
1902
  * itself rejects an impossible time: a browser time input yields 0–1439, and
1913
1903
  * anything outside that range fails Host validation instead of silently
1914
1904
  * scheduling a request at a moment that never arrives.
1905
+ *
1906
+ * The floor is the window's own opening rather than midnight, because a moment
1907
+ * before 10:00 is impossible in the same sense: the upstream has not opened the
1908
+ * day's benefit yet, so the run can only read the *previous* window's status
1909
+ * and settle a day that was never claimed. Refusing the value is the point — it
1910
+ * is not a time this plugin could honour.
1915
1911
  */
1916
- const CHECK_IN_MINUTE_FIELD = volatileField(z.number().default(600).min(0).max(1439).description("每日自动签到的时刻(自 UTC+8 午夜起的分钟数,600 = 10:00)"));
1912
+ const CHECK_IN_MINUTE_FIELD = volatileField(z.number().default(600).min(600).max(1439).description("每日自动签到的时刻(自 UTC+8 午夜起的分钟数,600 = 10:00;最早 10:00,即活动窗口开启时刻)"));
1917
1913
  /**
1918
1914
  * Quota poll interval: default 5 minutes, floor 1 minute. The status route
1919
1915
  * performs a live upstream billing call per request with no cache, so an
@@ -760,17 +760,14 @@ function randomSentinel() {
760
760
  return `probe_sentinel_${randomBytes(12).toString("hex")}`;
761
761
  }
762
762
  /**
763
- * The transport's rejection code for an effort the model does not advertise
764
- * (`src/qoder/transport/wire/serialize.ts`).
763
+ * The code a refused effort arrives under, wherever it was refused
764
+ * (`src/qoder/transport/wire/serialize.ts` for a declared row, `probeEffort`
765
+ * for a refusal the endpoint issued itself).
765
766
  *
766
- * With the Qoder upstream this rejection is raised *locally* against the
767
- * discovery catalog before any network call: a probe therefore measures the
768
- * catalog's declaration as much as the endpoint behind it. That is a weaker
769
- * finding than the WorkBuddy-era one, and it is what the plan's Stage D will
770
- * weigh; the sequence below still distinguishes "advertises and validates"
771
- * (per-level refusals after an accepted baseline) from "accepts anything the
772
- * catalog does not name" — the sentinel answers that question against the
773
- * same vocabulary the request path will later enforce.
767
+ * Only a row that declares efforts can be refused locally. An undeclared row —
768
+ * the only kind a sweep is offered for — sends every candidate to the wire and
769
+ * lets the sentinel measure the endpoint rather than this plugin's own reading
770
+ * of the catalog.
774
771
  */
775
772
  const INVALID_EFFORT_CODE = "UNSUPPORTED_REASONING_EFFORT";
776
773
  /** Whether an attempt is an attributable rejection of the effort value. */
@@ -2082,7 +2079,8 @@ function stableChatRecordId(model, messages, tools, maxTokens) {
2082
2079
  function validateQoderRequestShape(options, model) {
2083
2080
  if (options.reasoningEffort !== void 0) {
2084
2081
  const effort = String(options.reasoningEffort);
2085
- if (!model?.reasoningEfforts?.some((candidate) => candidate.id === effort)) throw new QoderLlmError(`Qoder model "${options.model}" does not advertise reasoning effort "${effort}".`, "UNSUPPORTED_REASONING_EFFORT");
2082
+ const declared = model?.reasoningEfforts;
2083
+ if (declared !== void 0 && declared.length > 0 && !declared.some((candidate) => candidate.id === effort)) throw new QoderLlmError(`Qoder model "${options.model}" does not advertise reasoning effort "${effort}".`, "UNSUPPORTED_REASONING_EFFORT");
2086
2084
  }
2087
2085
  if (options.messages.some((message) => contentHasImage(message.content)) && model?.supportsImages !== true) throw new QoderLlmError(`Qoder model "${options.model}" does not advertise image input.`, "UNSUPPORTED_CONTENT");
2088
2086
  validateMessageShapes(options.messages);
@@ -2720,9 +2718,38 @@ var QoderUsageReader = class {
2720
2718
  }
2721
2719
  }
2722
2720
  };
2721
+ /**
2722
+ * The UTC+8 wall clock of an instant, as a Date whose *local* fields hold it.
2723
+ *
2724
+ * Shifting by the host's own offset cancels the host timezone out, so the
2725
+ * result reads the same on a UTC+8 machine and on a UTC-5 one. Callers must
2726
+ * therefore use the local getters (`getHours`, `getFullYear`, …) on it.
2727
+ */
2728
+ function utc8WallClock(atMs) {
2729
+ const d = new Date(atMs);
2730
+ return new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
2731
+ }
2732
+ /**
2733
+ * Format an epoch-ms instant as its UTC+8 calendar date, `YYYY-MM-DD`.
2734
+ */
2735
+ function formatUtc8Date(atMs) {
2736
+ const utc8 = utc8WallClock(atMs);
2737
+ return `${utc8.getFullYear()}-${String(utc8.getMonth() + 1).padStart(2, "0")}-${String(utc8.getDate()).padStart(2, "0")}`;
2738
+ }
2723
2739
  //#endregion
2724
2740
  //#region src/qoder/transport/checkin.ts
2741
+ /** Qoder campaign inquiry and daily benefit check-in service. */
2725
2742
  const defaultCheckInTimeoutMs = 15e3;
2743
+ /**
2744
+ * The longest span still recognised as one daily benefit window.
2745
+ *
2746
+ * Qoder's daily window runs 10:00 → 09:59 the next morning (86_340 s). The
2747
+ * slack above 24h absorbs a clock or rounding difference; a genuinely longer
2748
+ * campaign is *not* a daily window, and its start day says nothing about which
2749
+ * day one claim of it belongs to, so it must fall back to the calendar day
2750
+ * rather than mislabel every day of its run.
2751
+ */
2752
+ const DAILY_WINDOW_MAX_MS = 864e5;
2726
2753
  var QoderCheckInService = class {
2727
2754
  authService;
2728
2755
  fetchImpl;
@@ -2770,14 +2797,37 @@ var QoderCheckInService = class {
2770
2797
  logCategory: "campaigns.claim"
2771
2798
  });
2772
2799
  }
2773
- getTodayDateString() {
2774
- const now = /* @__PURE__ */ new Date();
2775
- const utc8Time = new Date(now.getTime() + (now.getTimezoneOffset() + 480) * 6e4);
2776
- return `${utc8Time.getFullYear()}-${String(utc8Time.getMonth() + 1).padStart(2, "0")}-${String(utc8Time.getDate()).padStart(2, "0")}`;
2800
+ /**
2801
+ * Attribute the campaign the upstream is serving to a day, or say why it
2802
+ * cannot be attributed to today.
2803
+ *
2804
+ * The daily benefit window is 10:00 UTC+8 → 09:59 the next morning, so a
2805
+ * calendar date and the window's own date agree for only fourteen hours of
2806
+ * every twenty-four. Treating them as the same thing is what recorded a
2807
+ * pre-10:00 read of the *previous* window's `CLAIMED` against today.
2808
+ */
2809
+ classifyCampaignWindow(campaign, nowMs, today) {
2810
+ const startAt = campaign.startAt;
2811
+ if (typeof startAt !== "number" || !Number.isFinite(startAt) || startAt <= 0) return { kind: "unknown" };
2812
+ const endAt = campaign.endAt;
2813
+ if (typeof endAt === "number" && Number.isFinite(endAt)) {
2814
+ const spanMs = (endAt - startAt) * 1e3;
2815
+ if (spanMs <= 0 || spanMs > DAILY_WINDOW_MAX_MS) return { kind: "unknown" };
2816
+ }
2817
+ const startMs = startAt * 1e3;
2818
+ if (startMs > nowMs) return { kind: "not-open" };
2819
+ const date = formatUtc8Date(startMs);
2820
+ return date === today ? {
2821
+ kind: "open",
2822
+ date
2823
+ } : {
2824
+ kind: "earlier",
2825
+ date
2826
+ };
2777
2827
  }
2778
2828
  async checkIn(pat, signal) {
2779
- const today = this.getTodayDateString();
2780
2829
  const nowMs = Date.now();
2830
+ const today = formatUtc8Date(nowMs);
2781
2831
  if (!pat || typeof pat !== "string" || pat.trim() === "") return {
2782
2832
  variantId: this.variantId,
2783
2833
  date: today,
@@ -2794,10 +2844,20 @@ var QoderCheckInService = class {
2794
2844
  status: "no-campaign",
2795
2845
  message: "No claimable benefit campaign found for this region"
2796
2846
  };
2797
- if (benefitCampaign.claimStatus === "CLAIMED") return {
2847
+ const campaignWindow = this.classifyCampaignWindow(benefitCampaign, nowMs, today);
2848
+ if (campaignWindow.kind === "not-open" || campaignWindow.kind === "earlier") return {
2798
2849
  variantId: this.variantId,
2799
2850
  date: today,
2800
2851
  timestamp: nowMs,
2852
+ status: "before-window",
2853
+ campaignKey: benefitCampaign.campaignKey,
2854
+ message: campaignWindow.kind === "not-open" ? "Today's benefit window has not opened yet" : `Upstream is still serving the benefit window of ${campaignWindow.date}`
2855
+ };
2856
+ const windowDate = campaignWindow.kind === "open" ? campaignWindow.date : today;
2857
+ if (benefitCampaign.claimStatus === "CLAIMED") return {
2858
+ variantId: this.variantId,
2859
+ date: windowDate,
2860
+ timestamp: nowMs,
2801
2861
  status: "already-claimed",
2802
2862
  campaignKey: benefitCampaign.campaignKey,
2803
2863
  amount: benefitCampaign.benefit?.amount ?? 100,
@@ -2809,7 +2869,7 @@ var QoderCheckInService = class {
2809
2869
  const amount = claimResult.benefit?.amount ?? benefitCampaign.benefit?.amount ?? 100;
2810
2870
  if (isClaimed) return {
2811
2871
  variantId: this.variantId,
2812
- date: today,
2872
+ date: windowDate,
2813
2873
  timestamp: nowMs,
2814
2874
  status: replayed ? "already-claimed" : "claimed",
2815
2875
  amount,
@@ -4465,13 +4525,14 @@ var QoderUpstreamClient = class {
4465
4525
  /**
4466
4526
  * One minimal reasoning-effort request, as the probe sweep needs it.
4467
4527
  *
4468
- * The transport validates `reasoningEffort` against the model's advertised
4469
- * efforts **locally** before any network call: a rejection therefore reads
4470
- * as an attributable 400 with code `UNSUPPORTED_REASONING_EFFORT`, and an
4471
- * accepted value costs exactly one streamed request, which this method ends
4472
- * after the first chunk arrives. A probe on this upstream measures the
4473
- * discovery catalog as much as the endpoint behind it — see the module docs
4474
- * in `probe.ts`.
4528
+ * A row that declares efforts is still checked locally, so an unlisted value
4529
+ * costs no request. A row that declares none — the only kind detection is
4530
+ * offered for — sends the value to the upstream and waits for its answer,
4531
+ * because the endpoint is the only authority on spellings the catalog never
4532
+ * lists. Either refusal is reported as an attributable 400 with code
4533
+ * `UNSUPPORTED_REASONING_EFFORT`, and an accepted value costs exactly one
4534
+ * streamed request, which this method ends after the first chunk arrives —
4535
+ * see the module docs in `probe.ts`.
4475
4536
  */
4476
4537
  async probeEffort(model, effort, signal) {
4477
4538
  try {
@@ -4510,12 +4571,15 @@ var QoderUpstreamClient = class {
4510
4571
  await iterator.return?.();
4511
4572
  }
4512
4573
  } catch (error) {
4513
- if (error instanceof LlmError) return {
4514
- status: error.failure.status ?? defaultStatusForProbe(error.failure.code),
4515
- streamed: false,
4516
- errorCode: error.failure.code,
4517
- detail: error.failure.message.slice(0, 300)
4518
- };
4574
+ if (error instanceof LlmError) {
4575
+ const status = error.failure.status ?? defaultStatusForProbe(error.failure.code);
4576
+ return {
4577
+ status,
4578
+ streamed: false,
4579
+ errorCode: status === 400 && blamesReasoningEffort(error.failure.message) ? "UNSUPPORTED_REASONING_EFFORT" : error.failure.code,
4580
+ detail: error.failure.message.slice(0, 300)
4581
+ };
4582
+ }
4519
4583
  return {
4520
4584
  status: 0,
4521
4585
  streamed: false,
@@ -4524,6 +4588,17 @@ var QoderUpstreamClient = class {
4524
4588
  }
4525
4589
  }
4526
4590
  };
4591
+ /**
4592
+ * Whether an upstream 400 blames the `reasoning_effort` value it was sent.
4593
+ *
4594
+ * Qoder answers an impossible effort with a `provider_error` body whose nested
4595
+ * error names `invalid_parameter_error` and quotes `reasoning_effort`. Both
4596
+ * markers are required: a 400 naming only one of them is some other parameter
4597
+ * problem, and the sweep must not record it as a rejected level.
4598
+ */
4599
+ function blamesReasoningEffort(message) {
4600
+ return message.includes("invalid_parameter_error") && message.includes("reasoning_effort");
4601
+ }
4527
4602
  /** Statuses {@link probeEffort} reports for failures the transport raised pre-network. */
4528
4603
  function defaultStatusForProbe(code) {
4529
4604
  switch (code) {
@@ -4587,7 +4662,7 @@ const DEFAULT_MAX_TOKENS = 32768;
4587
4662
  const DEFAULT_CONTEXT_WINDOW = 18e4;
4588
4663
  //#endregion
4589
4664
  //#region src/version.ts
4590
- const QODER_CONNECT_VERSION = "0.2.3-alpha.1";
4665
+ const QODER_CONNECT_VERSION = "0.2.3";
4591
4666
  //#endregion
4592
4667
  //#region src/host-heartbeat.ts
4593
4668
  /**
@@ -4784,4 +4859,4 @@ function variantFor(id) {
4784
4859
  return QODER_VARIANTS.find((variant) => variant.id === id);
4785
4860
  }
4786
4861
  //#endregion
4787
- export { QODER_GLOBAL_AUTH_PATH as A, patTail as B, probeModel as C, FALLBACK_QODER_MODELS as D, qoderCatalogPath as E, QODER_STATUS_PATH as F, qoderMachineIdPath as G, qoderOwnAuthPath as H, QODER_AUTH_FILENAME as I, qoderPluginDataDir as K, QODER_PAT_ENV_CN as L, QODER_GLOBAL_STATUS_PATH as M, QODER_PROBE_PATH as N, QoderCatalog as O, QODER_SETTINGS_FACE_PATH as P, QODER_PAT_ENV_GLOBAL as R, PROBE_EFFORT_CANDIDATES as S, QoderCatalogStore as T, QODER_DATA_DIR_ENV as U, qoderCredentialIdentity as V, QODER_DATA_DIR_NAME as W, modelInfoOf as _, QODER_HOST_HEARTBEAT_FILENAME as a, createQoderTransport as b, processStartTimeMs as c, writeHostHeartbeat as d, QODER_CONNECT_VERSION as f, kindFromQoderFailure as g, classifyUpstreamError as h, variantFor as i, QODER_GLOBAL_PROBE_PATH as j, QODER_AUTH_PATH as k, qoderHostHeartbeatPath as l, QoderUpstreamClient as m, GLOBAL_VARIANT as n, clearHostHeartbeat as o, KIND_STATUS as p, qoderStateDir as q, QODER_VARIANTS as r, isHeartbeatProcessAlive as s, CHINA_VARIANT as t, readHostHeartbeat as u, normalizeCredits as v, randomSentinel as w, getMachineId as x, validateApiKey as y, QoderCredentialStore as z };
4862
+ export { QoderCatalog as A, QODER_PAT_ENV_GLOBAL as B, getMachineId as C, QoderCatalogStore as D, randomSentinel as E, QODER_PROBE_PATH as F, QODER_DATA_DIR_ENV as G, patTail as H, QODER_SETTINGS_FACE_PATH as I, qoderPluginDataDir as J, QODER_DATA_DIR_NAME as K, QODER_STATUS_PATH as L, QODER_GLOBAL_AUTH_PATH as M, QODER_GLOBAL_PROBE_PATH as N, qoderCatalogPath as O, QODER_GLOBAL_STATUS_PATH as P, QODER_AUTH_FILENAME as R, utc8WallClock as S, probeModel as T, qoderCredentialIdentity as U, QoderCredentialStore as V, qoderOwnAuthPath as W, qoderStateDir as Y, modelInfoOf as _, QODER_HOST_HEARTBEAT_FILENAME as a, createQoderTransport as b, processStartTimeMs as c, writeHostHeartbeat as d, QODER_CONNECT_VERSION as f, kindFromQoderFailure as g, classifyUpstreamError as h, variantFor as i, QODER_AUTH_PATH as j, FALLBACK_QODER_MODELS as k, qoderHostHeartbeatPath as l, QoderUpstreamClient as m, GLOBAL_VARIANT as n, clearHostHeartbeat as o, KIND_STATUS as p, qoderMachineIdPath as q, QODER_VARIANTS as r, isHeartbeatProcessAlive as s, CHINA_VARIANT as t, readHostHeartbeat as u, normalizeCredits as v, PROBE_EFFORT_CANDIDATES as w, formatUtc8Date as x, validateApiKey as y, QODER_PAT_ENV_CN as z };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-qoder-connect",
3
3
  "displayName": "DSH Qoder Connect",
4
4
  "description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
5
- "version": "0.2.3-alpha.1",
5
+ "version": "0.2.3",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",