dsh-qoder-connect 0.1.2 → 0.1.5

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/index.d.ts CHANGED
@@ -141,7 +141,7 @@ interface QoderProbeAction {
141
141
  * All are writes, which is why they share this route's in-process key
142
142
  * and loopback guards rather than the read-only status GET.
143
143
  */
144
- action: 'probe' | 'clear' | 'refresh' | 'set-maximum-context-window';
144
+ action: 'probe' | 'clear' | 'refresh' | 'set-maximum-context-window' | 'clear-checkin-logs' | 'checkin';
145
145
  /** Target model id; required for `probe`. */
146
146
  model?: string;
147
147
  /** Requested value for `set-maximum-context-window`. */
@@ -259,6 +259,25 @@ type QoderWebStatus = {
259
259
  * loopback guard); it is never persisted and rotates per process.
260
260
  */
261
261
  probeKey?: string;
262
+ /**
263
+ * Daily check-in status record for this variant.
264
+ */
265
+ checkIn?: {
266
+ lastDate: string;
267
+ lastAt: number;
268
+ status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
269
+ amount?: number | undefined;
270
+ message?: string | undefined;
271
+ logs?: readonly {
272
+ id: string;
273
+ date: string;
274
+ timestamp: number;
275
+ status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
276
+ amount?: number | undefined;
277
+ campaignKey?: string | undefined;
278
+ message?: string | undefined;
279
+ }[] | undefined;
280
+ };
262
281
  /**
263
282
  * In-process key authorizing PAT writes, including clearing. Travels with
264
283
  * the document for the same reason `probeKey` does.
@@ -692,6 +711,17 @@ interface QoderAccountInfo {
692
711
  updatedAt: string;
693
712
  }
694
713
  //#endregion
714
+ //#region src/qoder/transport/checkin.d.ts
715
+ interface QoderCheckInResult {
716
+ variantId: string;
717
+ date: string;
718
+ timestamp: number;
719
+ status: 'claimed' | 'already-claimed' | 'no-campaign' | 'error';
720
+ amount?: number | undefined;
721
+ campaignKey?: string | undefined;
722
+ message?: string | undefined;
723
+ }
724
+ //#endregion
695
725
  //#region src/qoder/transport/index.d.ts
696
726
  interface QoderTransport {
697
727
  stream(options: GenerateOptions, model?: QoderCatalogModel): AsyncIterable<StreamChunk>;
@@ -700,6 +730,7 @@ interface QoderTransport {
700
730
  force?: boolean | undefined;
701
731
  signal?: AbortSignal | undefined;
702
732
  }): Promise<QoderAccountInfo>;
733
+ checkIn(signal?: AbortSignal): Promise<QoderCheckInResult>;
703
734
  }
704
735
  //#endregion
705
736
  //#region src/upstream.d.ts
@@ -840,6 +871,10 @@ declare class QoderUpstreamClient {
840
871
  * account's "unlimited" state.
841
872
  */
842
873
  fetchCredits(signal?: AbortSignal): Promise<QoderCredits>;
874
+ /**
875
+ * Run daily benefit check-in for this client's variant.
876
+ */
877
+ checkIn(signal?: AbortSignal): Promise<QoderCheckInResult>;
843
878
  /**
844
879
  * Stream one translated chat-completions request.
845
880
  *
@@ -1345,6 +1380,10 @@ interface Config {
1345
1380
  sidebarQuotaCN?: boolean;
1346
1381
  /** Show the global variant's sidebar quota card. */
1347
1382
  sidebarQuotaGlobal?: boolean;
1383
+ /** Automatically check in daily at 10:00 (UTC+8) to claim credits for China variant. */
1384
+ autoCheckInCN?: boolean;
1385
+ /** Automatically check in daily at 10:00 (UTC+8) to claim credits for Global variant. */
1386
+ autoCheckInGlobal?: boolean;
1348
1387
  /**
1349
1388
  * Sidebar quota refresh interval in milliseconds. One shared value (both
1350
1389
  * cards poll on it) because the two widgets hit the same rate-limited
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as QODER_GLOBAL_AUTH_PATH, B as qoderCredentialIdentity, C as probeModel, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, F as QODER_AUTH_FILENAME, G as qoderPluginDataDir, H as QODER_DATA_DIR_ENV, I as QODER_PAT_ENV_CN, K as qoderStateDir, L as QODER_PAT_ENV_GLOBAL, M as QODER_GLOBAL_STATUS_PATH, N as QODER_PROBE_PATH, O as QoderCatalog, P as QODER_STATUS_PATH, R as QoderCredentialStore, S as PROBE_EFFORT_CANDIDATES, T as QoderCatalogStore, U as QODER_DATA_DIR_NAME, V as qoderOwnAuthPath, W as qoderMachineIdPath, _ 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, 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 patTail } from "./variants-C0iZD8AD.js";
1
+ import { A as QODER_GLOBAL_AUTH_PATH, B as qoderCredentialIdentity, C as probeModel, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, F as QODER_AUTH_FILENAME, G as qoderPluginDataDir, H as QODER_DATA_DIR_ENV, I as QODER_PAT_ENV_CN, K as qoderStateDir, L as QODER_PAT_ENV_GLOBAL, M as QODER_GLOBAL_STATUS_PATH, N as QODER_PROBE_PATH, O as QoderCatalog, P as QODER_STATUS_PATH, R as QoderCredentialStore, S as PROBE_EFFORT_CANDIDATES, T as QoderCatalogStore, U as QODER_DATA_DIR_NAME, V as qoderOwnAuthPath, W as qoderMachineIdPath, _ 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, 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 patTail } from "./variants-BEiQve5M.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";
@@ -1056,15 +1056,20 @@ async function qoderWebStatus(deps) {
1056
1056
  ...probed,
1057
1057
  jobTokenRefreshedAt: refreshedAt
1058
1058
  };
1059
+ const checkInRecord = deps.checkIn?.();
1060
+ const withCheckIn = checkInRecord === void 0 ? withRefreshNotice : {
1061
+ ...withRefreshNotice,
1062
+ checkIn: checkInRecord
1063
+ };
1059
1064
  try {
1060
1065
  const credits = await deps.client.fetchCredits();
1061
1066
  return {
1062
- ...withRefreshNotice,
1067
+ ...withCheckIn,
1063
1068
  credits
1064
1069
  };
1065
1070
  } catch (error) {
1066
1071
  return {
1067
- ...withRefreshNotice,
1072
+ ...withCheckIn,
1068
1073
  creditsError: safeMessage(error)
1069
1074
  };
1070
1075
  }
@@ -1169,6 +1174,8 @@ function parseAction(text) {
1169
1174
  const wrapped = parsed;
1170
1175
  const action = wrapped["action"];
1171
1176
  if (action === "clear") return { action: "clear" };
1177
+ if (action === "clear-checkin-logs") return { action: "clear-checkin-logs" };
1178
+ if (action === "checkin") return { action: "checkin" };
1172
1179
  if (action === "refresh") return { action: "refresh" };
1173
1180
  if (action === "set-maximum-context-window") return typeof wrapped["enabled"] === "boolean" ? {
1174
1181
  action: "set-maximum-context-window",
@@ -1212,6 +1219,19 @@ function qoderProbeHandler(deps, key) {
1212
1219
  return;
1213
1220
  }
1214
1221
  try {
1222
+ if (action.action === "clear-checkin-logs") {
1223
+ deps.clearCheckInLogs?.();
1224
+ json(res, 200, { state: "cleared" });
1225
+ return;
1226
+ }
1227
+ if (action.action === "checkin") {
1228
+ if (deps.checkIn === void 0) {
1229
+ json(res, 404, { error: "checkin-not-supported" });
1230
+ return;
1231
+ }
1232
+ json(res, 200, await deps.checkIn());
1233
+ return;
1234
+ }
1215
1235
  if (action.action === "clear") {
1216
1236
  deps.clear();
1217
1237
  json(res, 200, { state: "cleared" });
@@ -1254,6 +1274,162 @@ function registerQoderProbeRoute(ctx, deps, key) {
1254
1274
  }, "dsh-qoder-connect: probe control route");
1255
1275
  }
1256
1276
  //#endregion
1277
+ //#region src/checkin-scheduler.ts
1278
+ /** Scheduling and catch-up orchestration for daily 10:00 (UTC+8) check-in. */
1279
+ var JsonFileCheckInStore = class {
1280
+ filePath;
1281
+ constructor(filePath) {
1282
+ this.filePath = filePath ?? join(qoderPluginDataDir(), "checkin-status.json");
1283
+ }
1284
+ readAll() {
1285
+ try {
1286
+ if (!existsSync(this.filePath)) return {};
1287
+ const raw = readFileSync(this.filePath, "utf-8");
1288
+ return JSON.parse(raw);
1289
+ } catch {
1290
+ return {};
1291
+ }
1292
+ }
1293
+ read(variantId) {
1294
+ return this.readAll()[variantId];
1295
+ }
1296
+ clearLogs(variantId) {
1297
+ try {
1298
+ const all = this.readAll();
1299
+ if (all[variantId]) {
1300
+ all[variantId] = {
1301
+ ...all[variantId],
1302
+ logs: []
1303
+ };
1304
+ mkdirSync(dirname(this.filePath), { recursive: true });
1305
+ writeFileSync(this.filePath, JSON.stringify(all, null, 2), "utf-8");
1306
+ }
1307
+ } catch {}
1308
+ }
1309
+ write(variantId, record) {
1310
+ try {
1311
+ const all = this.readAll();
1312
+ const existingLogs = all[variantId]?.logs ?? [];
1313
+ const newLog = {
1314
+ id: `${record.lastDate}-${record.lastAt}`,
1315
+ date: record.lastDate,
1316
+ timestamp: record.lastAt,
1317
+ status: record.status,
1318
+ ...record.amount === void 0 ? {} : { amount: record.amount },
1319
+ ...record.message === void 0 ? {} : { message: record.message }
1320
+ };
1321
+ const updatedLogs = [newLog, ...existingLogs.filter((l) => l.id !== newLog.id)].slice(0, 30);
1322
+ all[variantId] = {
1323
+ ...record,
1324
+ logs: updatedLogs
1325
+ };
1326
+ mkdirSync(dirname(this.filePath), { recursive: true });
1327
+ writeFileSync(this.filePath, JSON.stringify(all, null, 2), "utf-8");
1328
+ } catch {}
1329
+ }
1330
+ };
1331
+ /**
1332
+ * Returns the current date in YYYY-MM-DD standardized on UTC+8 (Beijing Time).
1333
+ */
1334
+ function getUtc8DateString(nowMs = Date.now()) {
1335
+ const d = new Date(nowMs);
1336
+ const utc8 = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
1337
+ return `${utc8.getFullYear()}-${String(utc8.getMonth() + 1).padStart(2, "0")}-${String(utc8.getDate()).padStart(2, "0")}`;
1338
+ }
1339
+ /**
1340
+ * Calculates milliseconds until the next 10:00:05 AM in UTC+8.
1341
+ */
1342
+ function msUntilNext10amUtc8(nowMs = Date.now()) {
1343
+ const d = new Date(nowMs);
1344
+ const utc8Time = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
1345
+ const targetUtc8 = new Date(utc8Time.getTime());
1346
+ targetUtc8.setHours(10, 0, 5, 0);
1347
+ let diff = targetUtc8.getTime() - utc8Time.getTime();
1348
+ if (diff <= 0) {
1349
+ targetUtc8.setDate(targetUtc8.getDate() + 1);
1350
+ diff = targetUtc8.getTime() - utc8Time.getTime();
1351
+ }
1352
+ return diff;
1353
+ }
1354
+ /**
1355
+ * Checks whether catch-up is needed today:
1356
+ * Current time is past today's 10:00:00 AM (UTC+8) and today has not yet settled a check-in.
1357
+ */
1358
+ function shouldCatchUp(today, lastDate, nowMs = Date.now()) {
1359
+ if (lastDate === today) return false;
1360
+ const d = new Date(nowMs);
1361
+ return new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4).getHours() >= 10;
1362
+ }
1363
+ var CheckInScheduler = class {
1364
+ targets;
1365
+ isEnabled;
1366
+ store;
1367
+ onResult;
1368
+ now;
1369
+ timer;
1370
+ disposed = false;
1371
+ constructor(options) {
1372
+ this.targets = options.targets;
1373
+ this.isEnabled = options.isEnabled;
1374
+ this.store = options.store ?? new JsonFileCheckInStore();
1375
+ this.onResult = options.onResult;
1376
+ this.now = options.now ?? Date.now;
1377
+ }
1378
+ start() {
1379
+ if (this.disposed) return;
1380
+ this.sweepAll(true);
1381
+ this.armNextTimer();
1382
+ }
1383
+ dispose() {
1384
+ this.disposed = true;
1385
+ if (this.timer) {
1386
+ clearTimeout(this.timer);
1387
+ this.timer = void 0;
1388
+ }
1389
+ }
1390
+ armNextTimer() {
1391
+ if (this.disposed) return;
1392
+ const delay = msUntilNext10amUtc8(this.now());
1393
+ this.timer = setTimeout(() => {
1394
+ this.sweepAll(false);
1395
+ this.armNextTimer();
1396
+ }, delay);
1397
+ this.timer.unref?.();
1398
+ }
1399
+ async sweepAll(isCatchUp) {
1400
+ if (this.disposed) return;
1401
+ const nowMs = this.now();
1402
+ const today = getUtc8DateString(nowMs);
1403
+ for (const target of this.targets) {
1404
+ if (!this.isEnabled(target.variantId)) continue;
1405
+ const record = this.store.read(target.variantId);
1406
+ if (isCatchUp && !shouldCatchUp(today, record?.lastDate, nowMs)) continue;
1407
+ if (!isCatchUp && record?.lastDate === today && (record.status === "claimed" || record.status === "already-claimed")) continue;
1408
+ let pat;
1409
+ try {
1410
+ pat = await target.getPat();
1411
+ } catch {
1412
+ continue;
1413
+ }
1414
+ if (!pat) continue;
1415
+ try {
1416
+ const result = await target.service.checkIn(pat);
1417
+ if (result.status !== "error") {
1418
+ this.store.write(target.variantId, {
1419
+ lastDate: result.date,
1420
+ lastAt: result.timestamp,
1421
+ status: result.status,
1422
+ amount: result.amount,
1423
+ message: result.message
1424
+ });
1425
+ if (result.status === "claimed") target.onClaimed?.();
1426
+ }
1427
+ this.onResult?.(result);
1428
+ } catch {}
1429
+ }
1430
+ }
1431
+ };
1432
+ //#endregion
1257
1433
  //#region src/job-token-hint.ts
1258
1434
  /** The name recorded on the hint row (and therefore its displayed title). */
1259
1435
  const JOB_TOKEN_HINT_NAME = "qoder";
@@ -1290,16 +1466,42 @@ function emitJobTokenHint(at, text) {
1290
1466
  const agent = lastRunningAgent;
1291
1467
  if (agent?.session === void 0) return;
1292
1468
  lastRefreshAt.set(String(agent.id), at);
1469
+ appendHintRow(agent, "success", text);
1470
+ }
1471
+ /**
1472
+ * Print the failed-heal row into the conversation whose request triggered it.
1473
+ *
1474
+ * The mirror of {@link emitJobTokenHint}: the self-heal ran, exchanged a fresh
1475
+ * job token, retried, and the upstream still refused. Without this row that
1476
+ * outcome was silent — the user saw only the resulting authorization failure
1477
+ * and could not tell that recovery had already been attempted and lost.
1478
+ *
1479
+ * Rate limited by the transport (once per unresolved outage), not here: this
1480
+ * module stays a pure printer so the dedupe policy lives with the state that
1481
+ * knows when an outage ends.
1482
+ *
1483
+ * @param at - When the failed heal was recorded.
1484
+ * @param text - The row's summary line.
1485
+ */
1486
+ function emitJobTokenRefreshFailedHint(at, text) {
1487
+ const agent = lastRunningAgent;
1488
+ if (agent?.session === void 0) return;
1489
+ appendHintRow(agent, "error", text);
1490
+ }
1491
+ /** Append one log-only command pair carrying `text` as its outcome. */
1492
+ function appendHintRow(agent, kind, text) {
1493
+ const session = agent.session;
1494
+ if (session === void 0) return;
1293
1495
  const commandId = `cmd-qoder-hint-${Date.now().toString(36)}-${++hintSeq}`;
1294
1496
  try {
1295
- agent.session.append("command/run", {
1497
+ session.append("command/run", {
1296
1498
  commandId,
1297
1499
  name: JOB_TOKEN_HINT_NAME,
1298
1500
  source: { kind: "user" }
1299
1501
  });
1300
- agent.session.append("command/done", {
1502
+ session.append("command/done", {
1301
1503
  commandId,
1302
- kind: "success",
1504
+ kind,
1303
1505
  text
1304
1506
  });
1305
1507
  } catch {}
@@ -1397,6 +1599,8 @@ const MAXIMUM_CONTEXT_WINDOW_CN_FIELD = z.boolean().default(true).description("U
1397
1599
  const MODEL_CONTEXT_WINDOWS_FIELD = z.dict(z.number().min(1), z.string()).description("Per-model context-window overrides for Qoder Global (model id → tokens)");
1398
1600
  /** Sidebar quota toggle (one per variant; both live on the shared quota card). */
1399
1601
  const QUOTA_TOGGLE_FIELD = z.boolean().default(false).description("Show this variant’s remaining-credit card in the sidebar footer (off by default)");
1602
+ /** Automatic check-in toggle. */
1603
+ const AUTO_CHECK_IN_FIELD = z.boolean().default(false).description("每天 10:00 (UTC+8) 自动签到领取算力额度(默认关闭)");
1400
1604
  /**
1401
1605
  * Quota poll interval: default 5 minutes, floor 1 minute. The status route
1402
1606
  * performs a live upstream billing call per request with no cache, so an
@@ -1415,6 +1619,8 @@ const Config = z.object({
1415
1619
  modelContextWindowsCN: MODEL_CONTEXT_WINDOWS_FIELD,
1416
1620
  sidebarQuotaCN: QUOTA_TOGGLE_FIELD,
1417
1621
  sidebarQuotaGlobal: QUOTA_TOGGLE_FIELD,
1622
+ autoCheckInCN: AUTO_CHECK_IN_FIELD,
1623
+ autoCheckInGlobal: AUTO_CHECK_IN_FIELD,
1418
1624
  quotaPollMs: QUOTA_POLL_FIELD
1419
1625
  });
1420
1626
  /**
@@ -1446,6 +1652,8 @@ const GLOBAL_SECTION = z.object({
1446
1652
  const QUOTA_SECTION = z.object({
1447
1653
  sidebarQuotaCN: QUOTA_TOGGLE_FIELD,
1448
1654
  sidebarQuotaGlobal: QUOTA_TOGGLE_FIELD,
1655
+ autoCheckInCN: AUTO_CHECK_IN_FIELD,
1656
+ autoCheckInGlobal: AUTO_CHECK_IN_FIELD,
1449
1657
  quotaPollMs: QUOTA_POLL_FIELD
1450
1658
  });
1451
1659
  /** Stable identity key used by credentials, probe records, and catalog entries. */
@@ -1475,6 +1683,17 @@ function fallbackFor(_variant) {
1475
1683
  function jobTokenHintText(at) {
1476
1684
  return `jobToken 已自动刷新(${new Date(at).toLocaleTimeString("zh-CN", { hour12: false })})— 旧令牌被上游拒绝,已自动重换并恢复`;
1477
1685
  }
1686
+ /**
1687
+ * The failed-heal row's summary line.
1688
+ *
1689
+ * Deliberately does not name a cause: the upstream rejection that survived a
1690
+ * fresh token is not necessarily an authorization problem at all, and the real
1691
+ * reason travels in the failure message itself (see the SSE envelope body).
1692
+ * Claiming "quota" or "revoked" here would be a guess presented as a finding.
1693
+ */
1694
+ function jobTokenRefreshFailedHintText(at) {
1695
+ return `jobToken 已重换但仍被上游拒绝(${new Date(at).toLocaleTimeString("zh-CN", { hour12: false })})— 自愈未恢复,请查看上方错误详情`;
1696
+ }
1478
1697
  /** Build one variant's stores, transport, and probe state. */
1479
1698
  function createVariantRuntime(ctx, config, variant, current, identityOf) {
1480
1699
  const store = new QoderCredentialStore({
@@ -1490,6 +1709,10 @@ function createVariantRuntime(ctx, config, variant, current, identityOf) {
1490
1709
  jobTokenRefreshedAt = info.at;
1491
1710
  ctx.logger.warn(`dsh-qoder-connect: ${variant.displayName} job token was auto-refreshed after an upstream rejection`);
1492
1711
  emitJobTokenHint(info.at, jobTokenHintText(info.at));
1712
+ },
1713
+ onJobTokenRefreshFailed: (info) => {
1714
+ ctx.logger.warn(`dsh-qoder-connect: ${variant.displayName} job token refresh did not recover the chat (upstream status ${info.status ?? "unknown"})`);
1715
+ emitJobTokenRefreshFailedHint(info.at, jobTokenRefreshFailedHintText(info.at));
1493
1716
  }
1494
1717
  });
1495
1718
  const client = new QoderUpstreamClient({
@@ -1699,6 +1922,24 @@ function apply(ctx, config) {
1699
1922
  */
1700
1923
  const lastIdentities = /* @__PURE__ */ new Map();
1701
1924
  const runtimes = QODER_VARIANTS.map((variant) => createVariantRuntime(ctx, config, variant, () => current(), (id) => lastIdentities.get(id)));
1925
+ const checkInStore = new JsonFileCheckInStore();
1926
+ const checkInScheduler = new CheckInScheduler({
1927
+ targets: runtimes.map((runtime) => ({
1928
+ variantId: runtime.variant.id,
1929
+ service: runtime.transport.checkInService,
1930
+ getPat: async () => runtime.store.patPromise(),
1931
+ onClaimed: () => {
1932
+ runtime.client.fetchCredits().catch(() => void 0);
1933
+ }
1934
+ })),
1935
+ isEnabled: (variantId) => {
1936
+ const cfg = current();
1937
+ if (variantId === CHINA_VARIANT.id) return cfg.autoCheckInCN === true;
1938
+ return cfg.autoCheckInGlobal === true;
1939
+ },
1940
+ store: checkInStore
1941
+ });
1942
+ checkInScheduler.start();
1702
1943
  installJobTokenHint(ctx);
1703
1944
  const probeKey = createProbeKey();
1704
1945
  /**
@@ -1782,7 +2023,8 @@ function apply(ctx, config) {
1782
2023
  probeKey,
1783
2024
  authKey,
1784
2025
  ...runtime.variant.id === CHINA_VARIANT.id ? { useMaximumContextWindow: () => current().useMaximumContextWindowCN === true } : { useMaximumContextWindow: () => current().useMaximumContextWindow === true },
1785
- jobTokenRefreshedAt: runtime.jobTokenRefreshedAt
2026
+ jobTokenRefreshedAt: runtime.jobTokenRefreshedAt,
2027
+ checkIn: () => checkInStore.read(runtime.variant.id)
1786
2028
  });
1787
2029
  registerQoderAuthRoute(webCtx, {
1788
2030
  path: runtime.variant.authPath,
@@ -1845,6 +2087,36 @@ function apply(ctx, config) {
1845
2087
  reason: runtime.catalogError
1846
2088
  };
1847
2089
  },
2090
+ clearCheckInLogs: () => {
2091
+ checkInStore.clearLogs(runtime.variant.id);
2092
+ },
2093
+ checkIn: async () => {
2094
+ let pat;
2095
+ try {
2096
+ pat = await runtime.store.patPromise();
2097
+ } catch {
2098
+ return {
2099
+ state: "failed",
2100
+ reason: "No PAT available"
2101
+ };
2102
+ }
2103
+ const result = await runtime.transport.checkInService.checkIn(pat);
2104
+ if (result.status !== "error") {
2105
+ checkInStore.write(runtime.variant.id, {
2106
+ lastDate: result.date,
2107
+ lastAt: result.timestamp,
2108
+ status: result.status,
2109
+ ...result.amount === void 0 ? {} : { amount: result.amount },
2110
+ ...result.message === void 0 ? {} : { message: result.message }
2111
+ });
2112
+ if (result.status === "claimed") runtime.client.fetchCredits().catch(() => void 0);
2113
+ }
2114
+ return {
2115
+ state: result.status,
2116
+ ...result.amount === void 0 ? {} : { amount: result.amount },
2117
+ ...result.message === void 0 ? {} : { reason: result.message }
2118
+ };
2119
+ },
1848
2120
  ...runtime.variant.id === CHINA_VARIANT.id ? { setMaximumContextWindow: async (enabled) => {
1849
2121
  if (setMaximumContextWindowCN === void 0) return {
1850
2122
  state: "failed",
@@ -1925,6 +2197,7 @@ function apply(ctx, config) {
1925
2197
  });
1926
2198
  ctx.effect(() => () => {
1927
2199
  stopped = true;
2200
+ checkInScheduler.dispose();
1928
2201
  for (const timer of timers) clearInterval(timer);
1929
2202
  timers.length = 0;
1930
2203
  detach(ctx, clearHostHeartbeat(), "host heartbeat cleanup");