dsh-qoder-connect 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,7 +79,7 @@ dsh plugin --profile web add dsh-qoder-connect
79
79
 
80
80
  ### 生成 PAT
81
81
 
82
- - **Qoder(中国版)**:登录 qoder.com.cn → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.cn/account/integrations)
82
+ - **Qoder(国内版)**:登录 qoder.com.cn → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.cn/account/integrations)
83
83
  - **Qoder Global(国际版)**:登录 qoder.com → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.com/account/integrations)
84
84
 
85
85
  (卡片内也有同样指引:「请先在 qoder.com.cn 的账号设置(账号 → 个人访问令牌)中生成 PAT,再粘贴到下方输入框。」)
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { B as qoderCredentialIdentity, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, R as QoderCredentialStore, T as QoderCatalogStore, 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 } from "./variants-CQg-9veJ.js";
2
+ import { B as qoderCredentialIdentity, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, R as QoderCredentialStore, T as QoderCatalogStore, 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 } from "./variants-C0iZD8AD.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/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-CQg-9veJ.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-C0iZD8AD.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";
@@ -539,7 +539,13 @@ function createQoderShim(options) {
539
539
  return timingSafeEqual(a, b);
540
540
  }
541
541
  const server = createServer((req, res) => {
542
- handle(req, res);
542
+ handle(req, res).catch((error) => {
543
+ logger?.warn("dsh-qoder-connect: loopback request failed", error);
544
+ try {
545
+ if (!res.headersSent) writeOpenAIError(res, 500, "internal", "loopback request failed");
546
+ else res.end();
547
+ } catch {}
548
+ });
543
549
  });
544
550
  const ready = new Promise((resolve, reject) => {
545
551
  server.once("listening", () => resolve());
@@ -1582,6 +1588,21 @@ function probeSection(runtime, consent) {
1582
1588
  };
1583
1589
  }
1584
1590
  /**
1591
+ * Run a detached promise without letting its failure kill the host.
1592
+ *
1593
+ * Node terminates the whole process on an unhandled rejection (exit code 1),
1594
+ * which the desktop shell reports as the backend having "exited unexpectedly"
1595
+ * — so a failing heartbeat write, loopback close, or background sweep in this
1596
+ * plugin would take the entire Harness down with it. Every fire-and-forget
1597
+ * call therefore carries a handler; a failure is a diagnostic, never a reason
1598
+ * for the host to die.
1599
+ */
1600
+ function detach(ctx, work, what) {
1601
+ work.catch((error) => {
1602
+ ctx.logger.warn(`dsh-qoder-connect: ${what} failed`, error);
1603
+ });
1604
+ }
1605
+ /**
1585
1606
  * Start one variant: its loopback endpoint, provider registration, and
1586
1607
  * configuration-card wiring.
1587
1608
  *
@@ -1641,18 +1662,18 @@ async function startVariant(ctx, runtime) {
1641
1662
  ctx.effect(() => () => {
1642
1663
  releaseAdapter?.();
1643
1664
  releaseDirectory?.();
1644
- shim.close();
1665
+ detach(ctx, shim.close(), "loopback endpoint close");
1645
1666
  });
1646
1667
  } catch {
1647
1668
  releaseAdapter?.();
1648
1669
  releaseDirectory?.();
1649
- shim.close();
1670
+ detach(ctx, shim.close(), "loopback endpoint close");
1650
1671
  }
1651
1672
  runtime.registered = true;
1652
1673
  return true;
1653
1674
  } catch (error) {
1654
1675
  ctx.logger.error(`dsh-qoder-connect: ${variant.displayName} provider registration failed`, error);
1655
- shim.close();
1676
+ detach(ctx, shim.close(), "loopback endpoint close");
1656
1677
  return false;
1657
1678
  }
1658
1679
  }
@@ -1906,7 +1927,7 @@ function apply(ctx, config) {
1906
1927
  stopped = true;
1907
1928
  for (const timer of timers) clearInterval(timer);
1908
1929
  timers.length = 0;
1909
- clearHostHeartbeat();
1930
+ detach(ctx, clearHostHeartbeat(), "host heartbeat cleanup");
1910
1931
  });
1911
1932
  /**
1912
1933
  * Fetch one variant's catalog for the current credential.
@@ -1967,11 +1988,15 @@ function apply(ctx, config) {
1967
1988
  runtime.catalogSource = "live";
1968
1989
  runtime.catalogFetchedAtMs = runtime.client.lastCatalog?.fetchedAtMs ?? Date.now();
1969
1990
  runtime.catalogError = void 0;
1970
- if (lastIdentities.get(runtime.variant.id) === identity) runtime.savedCatalogs.set(identity, {
1971
- source: runtime.client.lastCatalog?.source ?? "unknown",
1972
- fetchedAtMs: runtime.client.lastCatalog?.fetchedAtMs ?? Date.now(),
1973
- models: [...models]
1974
- });
1991
+ if (lastIdentities.get(runtime.variant.id) === identity) try {
1992
+ runtime.savedCatalogs.set(identity, {
1993
+ source: runtime.client.lastCatalog?.source ?? "unknown",
1994
+ fetchedAtMs: runtime.client.lastCatalog?.fetchedAtMs ?? Date.now(),
1995
+ models: [...models]
1996
+ });
1997
+ } catch (error) {
1998
+ ctx.logger.warn(`dsh-qoder-connect: ${runtime.variant.displayName} catalog could not be saved for this account`, error);
1999
+ }
1975
2000
  runtime.invalidate();
1976
2001
  })().finally(() => {
1977
2002
  if (runtime.inflightFetch?.promise === run) runtime.inflightFetch = void 0;
@@ -2022,16 +2047,23 @@ function apply(ctx, config) {
2022
2047
  const syncAll = async () => {
2023
2048
  for (const runtime of runtimes) await syncVariant(runtime);
2024
2049
  };
2025
- Promise.all(runtimes.map(async (runtime) => startVariant(ctx, runtime))).then(() => {
2050
+ /**
2051
+ * Start both variants, then begin the credential sweep.
2052
+ *
2053
+ * The chain carries its own failure handler: without one, a rejection here
2054
+ * would be an unhandled rejection — which Node turns into process
2055
+ * termination, taking the whole Harness down over one plugin's startup.
2056
+ */
2057
+ detach(ctx, Promise.all(runtimes.map(async (runtime) => startVariant(ctx, runtime))).then(() => {
2026
2058
  if (stopped) return;
2027
- if (runtimes.some((runtime) => runtime.registered)) writeHostHeartbeat();
2028
- syncAll();
2059
+ if (runtimes.some((runtime) => runtime.registered)) detach(ctx, writeHostHeartbeat(), "host heartbeat write");
2060
+ detach(ctx, syncAll(), "credential sweep");
2029
2061
  const timer = setInterval(() => {
2030
- syncAll();
2062
+ detach(ctx, syncAll(), "credential sweep");
2031
2063
  }, credentialPollMs());
2032
2064
  timer.unref?.();
2033
2065
  timers.push(timer);
2034
- });
2066
+ }), "variant startup");
2035
2067
  }
2036
2068
  //#endregion
2037
2069
  export { CHINA_VARIANT, Config, FALLBACK_QODER_MODELS, GLOBAL_VARIANT, KIND_STATUS, PROBE_EFFORT_CANDIDATES, QODER_AUTH_FILENAME, QODER_AUTH_PATH, QODER_DATA_DIR_ENV, QODER_DATA_DIR_NAME, QODER_GLOBAL_AUTH_PATH, QODER_GLOBAL_PROBE_PATH, QODER_GLOBAL_SETTINGS_NS, QODER_GLOBAL_STATUS_PATH, QODER_HOST_HEARTBEAT_FILENAME, QODER_PAT_ENV_CN, QODER_PAT_ENV_GLOBAL, QODER_PROBE_FILENAME, QODER_PROBE_PATH, QODER_PROVIDER, QODER_QUOTA_SETTINGS_NS, QODER_SETTINGS_NS, QODER_STATUS_PATH, QODER_STREAM_IDLE_TIMEOUT_MS, QODER_VARIANTS, QUOTA_POLL_DEFAULT_MS, QUOTA_POLL_MIN_MS, QoderCatalog, QoderCatalogStore, QoderCredentialStore, QoderProbeService, QoderProbeStore, QoderUpstreamClient, apply, classifyUpstreamError, clearHostHeartbeat, createAuthKey, createQoderAdapter, createQoderShim, fingerprintModel, inject, isHeartbeatProcessAlive, kindFromQoderFailure, modelInfoOf, name, normalizeCredits, patTail, probeModel, processStartTimeMs, qoderAuthHandler, qoderCatalogPath, qoderCredentialIdentity, qoderHostHeartbeatPath, qoderOwnAuthPath, qoderPluginDataDir, qoderProbePath, randomSentinel, readHostHeartbeat, registerQoderAuthRoute, variantFor };
@@ -2415,27 +2415,38 @@ var QoderUsageReader = class {
2415
2415
  * Re-arranges standard Base64 chunks and translates characters against
2416
2416
  * a custom alphabet to satisfy Qoder upstream WAF requirements.
2417
2417
  *
2418
+ * The translation runs through a byte table applied to a Buffer instead of a
2419
+ * per-character string loop. The loop form (`out += char` plus
2420
+ * `alphabet.indexOf(char)`) costs ~800 ms on a 1 MB body and runs on the main
2421
+ * thread once per model request, which saturated the Harness event loop as
2422
+ * soon as a few large-context sessions were in flight. The table form is
2423
+ * byte-for-byte identical (the input is always Base64, i.e. ASCII) and about
2424
+ * 24x faster.
2425
+ *
2418
2426
  * @module dsh-provider-qoder/qoder/transport/wire/encoding
2419
2427
  */
2420
2428
  const qoderCustomAlphabet = "_doRTgHZBKcGVjlvpC,@aFSx#DPuNJme&i*MzLOEn)sUrthbf%Y^w.(kIQyXqWA!";
2421
2429
  const qoderStdAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2430
+ /**
2431
+ * Byte translation table: every byte maps to itself unless it is a Base64
2432
+ * alphabet character (→ the custom alphabet) or the `=` pad (→ `$`).
2433
+ */
2434
+ const encodeTable = (() => {
2435
+ const table = /* @__PURE__ */ new Uint8Array(256);
2436
+ for (let index = 0; index < 256; index++) table[index] = index;
2437
+ for (let index = 0; index < 64; index++) table[qoderStdAlphabet.charCodeAt(index)] = qoderCustomAlphabet.charCodeAt(index);
2438
+ table["=".charCodeAt(0)] = "$".charCodeAt(0);
2439
+ return table;
2440
+ })();
2422
2441
  function qoderEncodeBody(plaintext) {
2423
2442
  const std = Buffer.isBuffer(plaintext) ? plaintext.toString("base64") : Buffer.from(plaintext).toString("base64");
2424
2443
  const n = std.length;
2425
2444
  const a = Math.floor(n / 3);
2426
2445
  const rearranged = std.slice(n - a) + std.slice(a, n - a) + std.slice(0, a);
2427
- let out = "";
2428
- for (let i = 0; i < n; i++) {
2429
- const c = rearranged[i];
2430
- if (c === void 0) continue;
2431
- if (c === "=") out += "$";
2432
- else {
2433
- const idx = qoderStdAlphabet.indexOf(c);
2434
- if (idx >= 0) out += qoderCustomAlphabet[idx];
2435
- else out += c;
2436
- }
2437
- }
2438
- return out;
2446
+ const source = Buffer.from(rearranged, "latin1");
2447
+ const target = Buffer.allocUnsafe(n);
2448
+ for (let index = 0; index < n; index++) target[index] = encodeTable[source[index] ?? 0] ?? 0;
2449
+ return target.toString("latin1");
2439
2450
  }
2440
2451
  //#endregion
2441
2452
  //#region src/qoder/transport/wire/thinking.ts
@@ -4002,7 +4013,7 @@ const DEFAULT_MAX_TOKENS = 32768;
4002
4013
  const DEFAULT_CONTEXT_WINDOW = 18e4;
4003
4014
  //#endregion
4004
4015
  //#region src/version.ts
4005
- const QODER_CONNECT_VERSION = "0.1.1";
4016
+ const QODER_CONNECT_VERSION = "0.1.2";
4006
4017
  //#endregion
4007
4018
  //#region src/host-heartbeat.ts
4008
4019
  /**
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.1.1",
5
+ "version": "0.1.2",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",