dsh-qoder-connect 0.2.1 → 0.2.3-alpha.1

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
@@ -773,8 +773,15 @@ interface QoderTransport {
773
773
  */
774
774
  type QoderEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
775
775
  /** How the shim should present an upstream failure to its OpenAI client. */
776
- type UpstreamErrorKind = 'missing_credential' | 'auth' | 'soft_rate' | 'quota_exceeded' | 'server' | 'client';
777
- /** HTTP status the shim answers each failure kind with. */
776
+ type UpstreamErrorKind = 'missing_credential' | 'auth' | 'soft_rate' | 'quota_exceeded' | 'timeout' | 'server' | 'client';
777
+ /**
778
+ * HTTP status the shim answers each failure kind with.
779
+ *
780
+ * `timeout` is 408 and deliberately not 504: the harness's pi-ai classifier tests
781
+ * `/\b5\d\d\b/` *before* its `timeout` test, so a 5xx status would be read as
782
+ * SERVER no matter what the message says. 408 is also the honest answer for a
783
+ * request the upstream never completed.
784
+ */
778
785
  declare const KIND_STATUS: Readonly<Record<UpstreamErrorKind, number>>;
779
786
  /** The chat-completions answer: a streaming `Response`, or a classified failure. */
780
787
  type QoderChatResult = {
@@ -840,6 +847,12 @@ declare function classifyUpstreamError(status: number, body: string): UpstreamEr
840
847
  * `code: RATE_LIMIT, status: 403` (body carries 10605/isQueued/
841
848
  * retryAfterSeconds), and letting the status arm claim it reported a dead
842
849
  * credential — which the host never retries — instead of a throttle it would.
850
+ *
851
+ * `QUOTA` and the request family are tested before the auth family for the same
852
+ * reason: both keep the upstream's 401/403 (see `qoderHttpError`), and the status
853
+ * arm would call a spent daily window or a refused request a credential
854
+ * rejection. `status === 402` is tested before the request arm so that a 402 the
855
+ * transport coded `INVALID_REQUEST` (its 4xx fallback) still reads as a quota.
843
856
  */
844
857
  declare function kindFromQoderFailure(failure: {
845
858
  code: string;
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-CjWW4lVI.js";
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";
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";
@@ -855,6 +855,94 @@ async function probeModel(options) {
855
855
  };
856
856
  }
857
857
  //#endregion
858
+ //#region src/qoder/codes.ts
859
+ const table = (family, rows) => rows.map(([code, name]) => ({
860
+ code,
861
+ name,
862
+ family
863
+ }));
864
+ /**
865
+ * The documented code table.
866
+ *
867
+ * Source: Qoder's *Errors and error codes* reference
868
+ * (https://docs.qoder.com/cli/sdk/errors), "Result error codes" — the same
869
+ * reference also states the rule this module exists to serve: "Authentication,
870
+ * quota, policy, input, and configuration errors require a change before
871
+ * retrying."
872
+ *
873
+ * Scope note on evidence. That page documents the Agent SDK / qodercli surface,
874
+ * not the loopback HTTP endpoint this plugin talks to. Two of its codes are
875
+ * confirmed on our own path, with matching semantics:
876
+ *
877
+ * - `10605` — observed verbatim: `{"code":"10605","message":"{\"isQueued\":true,
878
+ * ...\"retryAfterSeconds\":30}"}` (see `qoderQueueSignal`).
879
+ * - `110` — reported in dsh-qoder-connect#10 and reproduced by injecting the
880
+ * reported body through this transport: `{"code":"110","message":"Billing
881
+ * daily count exceeded"}`.
882
+ *
883
+ * The rest are carried as the upstream's documented vocabulary. They are read
884
+ * only to identify the family; an undocumented code is never guessed at, so a
885
+ * body this table does not recognize keeps whatever routing the HTTP status
886
+ * already gives it.
887
+ */
888
+ const QODER_RESULT_CODES = Object.freeze([
889
+ ...table("auth", [["105", "Login or access token expired"]]),
890
+ ...table("quota", [
891
+ ["110", "Daily usage limit reached"],
892
+ ["113", "Usage quota exhausted"],
893
+ ["114", "Free-trial account limit reached"],
894
+ ["115", "Free-user quota reached"],
895
+ ["116", "Team administrator Credits exhausted"],
896
+ ["117", "Team member Credits exhausted"],
897
+ ["118", "Personal Credits exhausted"],
898
+ ["119", "Free usage limit for the selected model reached"],
899
+ ["122", "Billing-group Credits limit reached"]
900
+ ]),
901
+ ...table("request", [
902
+ ["406", "Request blocked because of sensitive content or model refusal"],
903
+ ["416", "Requested range or request shape is not satisfiable"],
904
+ ["430", "Requested capability is not supported"],
905
+ ["47902", "Maximum Agent turns reached"],
906
+ ["48716", "A Hook blocked Agent execution"],
907
+ ["80411", "Input content is too long"],
908
+ ["80412", "Too many images or documents"]
909
+ ]),
910
+ ...table("runtime", [
911
+ ["500", "Request or network failure"],
912
+ ["10408", "Request timed out"],
913
+ ["10500", "Model service internal error"],
914
+ ["10605", "Model request is queued"]
915
+ ])
916
+ ]);
917
+ const byCode = new Map(QODER_RESULT_CODES.map((entry) => [entry.code, entry]));
918
+ /**
919
+ * The documented result code a rejection body names, if it names one.
920
+ *
921
+ * Qoder nests: a queue answer arrives as `{"code":"10605","message":"{\"isQueued\":
922
+ * true,...}"}`, so the inner payload is escaped JSON and a marker can sit at any
923
+ * depth. Dropping the ASCII backslashes keeps every layer readable at once — the
924
+ * same trick `qoderQueueSignal` uses, and for the same reason; no marker this
925
+ * module looks for depends on a backslash.
926
+ *
927
+ * Only a *quoted* `code` key counts. Qoder also mirrors raw HTTP statuses into a
928
+ * `code` field (`"code":"429"`, `"code":403`), and a message that merely mentions
929
+ * a code in prose must not be read as one; both stay unrecognized because neither
930
+ * names a documented result code.
931
+ *
932
+ * @param body - the upstream error body, or the formatted message when the body
933
+ * was only embedded in it.
934
+ * @returns the matching table entry, or `undefined` when the body names no
935
+ * documented code — never a guess.
936
+ */
937
+ function qoderResultCode(body) {
938
+ if (body === void 0 || body === "") return void 0;
939
+ const text = body.replace(/\\/gu, "");
940
+ for (const match of text.matchAll(/"(?:error_)?code"\s*:\s*"?(\d+)"?/gu)) {
941
+ const entry = byCode.get(match[1]);
942
+ if (entry !== void 0) return entry;
943
+ }
944
+ }
945
+ //#endregion
858
946
  //#region src/qoder/errors.ts
859
947
  /**
860
948
  * DSH-compatible LLM error representation.
@@ -902,18 +990,25 @@ function qoderQueueSignal(body) {
902
990
  }
903
991
  /**
904
992
  * Replace status digits that read as authorization failure with a neutral
905
- * word, for messages the upstream used to announce a queue.
993
+ * word, for messages the upstream used to announce something other than a dead
994
+ * credential.
906
995
  *
907
996
  * `dsh-llm-pi-ai`'s error classifier is a text matcher whose first test is
908
- * `/\b(?:401|403)\b/` — ahead of its 429/rate-limit test — so any message
909
- * carrying those digits classifies AUTH no matter the structured code
910
- * travelling beside it. A queue answer quoting the upstream's 403 (in the
911
- * message prefix, the envelope status, or the body's `"code":"403"`) must not
912
- * reach that matcher as-is, or the user sees "API 密钥无效" for a throttle
913
- * (observed verbatim on 2026-09-26 23:30 and again at 23:40).
997
+ * `/\b(?:401|403)\b/` — ahead of its 429/rate-limit test and ahead of every
998
+ * quota test — so any message carrying those digits classifies AUTH no matter
999
+ * the structured code travelling beside it. An answer that is really a
1000
+ * throttle or an exhausted quota must not reach that matcher as-is, or the user
1001
+ * sees "API 密钥无效" for a limit that clears on its own (observed verbatim on
1002
+ * 2026-09-26 23:30 and again at 23:40 for the queue; reproduced 2026-10-08 for
1003
+ * the daily counter reported in #10).
1004
+ *
1005
+ * @param message - the message to neutralize.
1006
+ * @param replacement - the neutral word substituted for the digits. `throttled`
1007
+ * for the queue answer, whose wording users and tests already know; `limited`
1008
+ * for a quota answer, which is not a throttle.
914
1009
  */
915
- function deauthorizeDigits(message) {
916
- return message.replace(/\b(?:401|403)\b/gu, "throttled");
1010
+ function deauthorizeDigits(message, replacement = "throttled") {
1011
+ return message.replace(/\b(?:401|403)\b/gu, replacement);
917
1012
  }
918
1013
  function qoderHttpError(message, response, body) {
919
1014
  const { status } = response;
@@ -928,6 +1023,15 @@ function qoderHttpError(message, response, body) {
928
1023
  ...requestId === void 0 ? {} : { requestId }
929
1024
  });
930
1025
  }
1026
+ const documented = qoderResultCode(body ?? message);
1027
+ if (documented?.family === "quota") return new QoderLlmError(deauthorizeDigits(message, "limited"), "QUOTA", {
1028
+ status,
1029
+ ...requestId === void 0 ? {} : { requestId }
1030
+ });
1031
+ if (documented?.family === "request" && (status === 401 || status === 403)) return new QoderLlmError(deauthorizeDigits(message, "rejected"), "INVALID_REQUEST", {
1032
+ status,
1033
+ ...requestId === void 0 ? {} : { requestId }
1034
+ });
931
1035
  return new QoderLlmError(message, status === 401 || status === 403 ? "AUTH" : status === 408 ? "TIMEOUT" : status === 429 ? "RATE_LIMIT" : status >= 500 && status <= 599 ? "SERVER" : status >= 400 && status <= 499 ? "INVALID_REQUEST" : "PROVIDER_ERROR", {
932
1036
  status,
933
1037
  ...headerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: headerRetryAfterMs },
@@ -939,6 +1043,24 @@ function qoderRequestId(headers) {
939
1043
  return normalized ? ProviderRequestId(normalized) : void 0;
940
1044
  }
941
1045
  /**
1046
+ * Codes that keep the upstream's 401/403 status without being credential
1047
+ * rejections, so the status arm of {@link isQoderAuthRejection} must not claim
1048
+ * them.
1049
+ *
1050
+ * `RATE_LIMIT` is the queue answer (code 10605 / isQueued / retryAfterSeconds)
1051
+ * and `QUOTA`/`ACCOUNT_QUOTA` is a spent usage window (#10, e.g. code 110 with
1052
+ * `Billing daily count exceeded`). Both arrive as 401/403 and neither is fixed
1053
+ * by a fresh job token — the queue cannot be jumped and a quota cannot be
1054
+ * exchanged away — so healing either one spends an exchange and suppresses the
1055
+ * retry that would actually help (the queue) or reports a failure notice for a
1056
+ * condition that clears on its own (the quota).
1057
+ */
1058
+ const NON_AUTH_STATUS_CODES = [
1059
+ "RATE_LIMIT",
1060
+ "QUOTA",
1061
+ "ACCOUNT_QUOTA"
1062
+ ];
1063
+ /**
942
1064
  * Whether this failure is an upstream authorization rejection worth one
943
1065
  * re-auth retry.
944
1066
  *
@@ -949,16 +1071,16 @@ function qoderRequestId(headers) {
949
1071
  * exchange, so callers clear their credential cache and retry once before
950
1072
  * reporting `AUTH` to the user.
951
1073
  *
952
- * A `RATE_LIMIT` code is never a re-auth candidate, **even when its status is
953
- * 401/403**: that is the upstream's queue answer (code 10605 / isQueued /
954
- * retryAfterSeconds in the body), which keeps the status but says the
955
- * credential was not the problem. Healing it exchanged job tokens against a
956
- * queue no token can jump (observed 2026-09-26) and, by consuming the retry,
957
- * suppressed the rate-limit retry that would have ridden out the window.
1074
+ * A code in {@link NON_AUTH_STATUS_CODES} is never a re-auth candidate, **even
1075
+ * when its status is 401/403**: those answers keep the status while saying the
1076
+ * credential was not the problem. Healing one exchanged job tokens against a
1077
+ * condition no token can change (observed 2026-09-26 for the queue, and again
1078
+ * 2026-10-08 for the daily counter), and by consuming the retry it suppressed
1079
+ * the rate-limit retry that would have ridden out the window.
958
1080
  */
959
1081
  function isQoderAuthRejection(error) {
960
1082
  if (!(error instanceof LlmError)) return false;
961
- if (error.code === "RATE_LIMIT") return false;
1083
+ if (NON_AUTH_STATUS_CODES.includes(error.code)) return false;
962
1084
  return error.code === "AUTH" || error.failure.status === 401 || error.failure.status === 403;
963
1085
  }
964
1086
  function retryAfterMs(value, nowMs = Date.now()) {
@@ -3650,12 +3772,20 @@ function createQoderTransport(options) {
3650
3772
  *
3651
3773
  * @module dsh-qoder-connect/upstream
3652
3774
  */
3653
- /** HTTP status the shim answers each failure kind with. */
3775
+ /**
3776
+ * HTTP status the shim answers each failure kind with.
3777
+ *
3778
+ * `timeout` is 408 and deliberately not 504: the harness's pi-ai classifier tests
3779
+ * `/\b5\d\d\b/` *before* its `timeout` test, so a 5xx status would be read as
3780
+ * SERVER no matter what the message says. 408 is also the honest answer for a
3781
+ * request the upstream never completed.
3782
+ */
3654
3783
  const KIND_STATUS = {
3655
3784
  missing_credential: 401,
3656
3785
  auth: 401,
3657
3786
  soft_rate: 429,
3658
3787
  quota_exceeded: 402,
3788
+ timeout: 408,
3659
3789
  server: 502,
3660
3790
  client: 400
3661
3791
  };
@@ -3681,6 +3811,9 @@ function normalizeCredits(credits) {
3681
3811
  function classifyUpstreamError(status, body) {
3682
3812
  const lower = body.toLowerCase();
3683
3813
  if (qoderQueueSignal(body) !== void 0) return "soft_rate";
3814
+ const documented = qoderResultCode(body);
3815
+ if (documented?.family === "quota") return "quota_exceeded";
3816
+ if (documented?.family === "request") return "client";
3684
3817
  if (status === 402) return "quota_exceeded";
3685
3818
  if (status === 429) return "soft_rate";
3686
3819
  if (status === 401 || status === 403) return "auth";
@@ -3705,6 +3838,7 @@ function classifyUpstreamError(status, body) {
3705
3838
  "forbidden",
3706
3839
  "not authenticated"
3707
3840
  ]) if (lower.includes(marker)) return "auth";
3841
+ if (status === 408) return "timeout";
3708
3842
  if (status === 0 || status >= 500) return "server";
3709
3843
  return "client";
3710
3844
  }
@@ -3720,14 +3854,22 @@ function classifyUpstreamError(status, body) {
3720
3854
  * `code: RATE_LIMIT, status: 403` (body carries 10605/isQueued/
3721
3855
  * retryAfterSeconds), and letting the status arm claim it reported a dead
3722
3856
  * credential — which the host never retries — instead of a throttle it would.
3857
+ *
3858
+ * `QUOTA` and the request family are tested before the auth family for the same
3859
+ * reason: both keep the upstream's 401/403 (see `qoderHttpError`), and the status
3860
+ * arm would call a spent daily window or a refused request a credential
3861
+ * rejection. `status === 402` is tested before the request arm so that a 402 the
3862
+ * transport coded `INVALID_REQUEST` (its 4xx fallback) still reads as a quota.
3723
3863
  */
3724
3864
  function kindFromQoderFailure(failure) {
3725
3865
  if (failure.code === "MISSING_CREDENTIAL") return "missing_credential";
3726
3866
  if (failure.code === "RATE_LIMIT") return "soft_rate";
3867
+ if (failure.code === "QUOTA" || failure.code === "ACCOUNT_QUOTA") return "quota_exceeded";
3868
+ if (failure.status === 402) return "quota_exceeded";
3869
+ if (failure.code.startsWith("INVALID_") || failure.code.startsWith("UNSUPPORTED_") || failure.code === "ATTACHMENT") return "client";
3727
3870
  if (failure.code === "AUTH" || failure.status === 401 || failure.status === 403) return "auth";
3728
3871
  if (failure.status === 429) return "soft_rate";
3729
- if (failure.code === "QUOTA" || failure.status === 402) return "quota_exceeded";
3730
- if (failure.code.startsWith("INVALID_") || failure.code.startsWith("UNSUPPORTED_") || failure.code === "ATTACHMENT") return "client";
3872
+ if (failure.code === "TIMEOUT") return "timeout";
3731
3873
  return "server";
3732
3874
  }
3733
3875
  /** Whether a Personal Access Token works against one region's endpoints. */
@@ -4445,7 +4587,7 @@ const DEFAULT_MAX_TOKENS = 32768;
4445
4587
  const DEFAULT_CONTEXT_WINDOW = 18e4;
4446
4588
  //#endregion
4447
4589
  //#region src/version.ts
4448
- const QODER_CONNECT_VERSION = "0.2.1";
4590
+ const QODER_CONNECT_VERSION = "0.2.3-alpha.1";
4449
4591
  //#endregion
4450
4592
  //#region src/host-heartbeat.ts
4451
4593
  /**
package/package.json CHANGED
@@ -1,120 +1,120 @@
1
- {
2
- "name": "dsh-qoder-connect",
3
- "displayName": "DSH Qoder Connect",
4
- "description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
5
- "version": "0.2.1",
6
- "author": "corrinehu",
7
- "keywords": [
8
- "dsh-plugin",
9
- "deepseek-harness",
10
- "deepseek",
11
- "qoder",
12
- "model-provider"
13
- ],
14
- "type": "module",
15
- "engines": {
16
- "dsh": ">=0.1.7-rc.1",
17
- "node": "^22.19.0 || >=24.0.0"
18
- },
19
- "main": "lib/index.js",
20
- "types": "lib/index.d.ts",
21
- "bin": {
22
- "dsh-qoder-connect": "lib/bin.js"
23
- },
24
- "scripts": {
25
- "prepack": "npm run build",
26
- "build": "tsdown",
27
- "test": "node -r ./tests/sandbox-shim.cjs ./node_modules/vitest/vitest.mjs run",
28
- "test:qoder": "node --test --test-isolation=none --experimental-strip-types tests/qoder/*.test.ts",
29
- "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
30
- "check": "pnpm run typecheck && pnpm run test && pnpm run test:qoder && pnpm run build"
31
- },
32
- "exports": {
33
- ".": {
34
- "types": "./lib/index.d.ts",
35
- "default": "./lib/index.js"
36
- },
37
- "./client": "./lib/client.js",
38
- "./cordis.patch.yml": "./cordis.patch.yml",
39
- "./package.json": "./package.json",
40
- "./locale/*.json": "./locale/*.json"
41
- },
42
- "files": [
43
- "lib",
44
- "assets",
45
- "cordis.patch.yml",
46
- "README.md",
47
- "README.en.md",
48
- "LICENSE",
49
- "locale/*.json"
50
- ],
51
- "license": "MIT",
52
- "repository": {
53
- "type": "git",
54
- "url": "git+https://github.com/masknull/dsh-qoder-connect.git"
55
- },
56
- "homepage": "https://github.com/masknull/dsh-qoder-connect#readme",
57
- "bugs": "https://github.com/masknull/dsh-qoder-connect/issues",
58
- "dsh": {
59
- "bundle": {
60
- "patch": "./cordis.patch.yml"
61
- },
62
- "client": {
63
- "inject": [
64
- "@deepseek-ai/dsh-api-remotes",
65
- "@deepseek-ai/dsh-api-session-controller",
66
- "@deepseek-ai/dsh-client-ui-renderer",
67
- "@deepseek-ai/dsh-client-ui-settings-plugins",
68
- "@deepseek-ai/dsh-client-ui-settings",
69
- "@deepseek-ai/dsh-client-ui-conversation",
70
- "@deepseek-ai/dsh-client-ui-model-selection",
71
- "@deepseek-ai/dsh-client-ui-sidebar",
72
- "@deepseek-ai/dsh-client-locale"
73
- ],
74
- "platform": "web"
75
- }
76
- },
77
- "peerDependencies": {
78
- "@deepseek-ai/cordis": "^4.0.2",
79
- "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1",
80
- "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1",
81
- "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1",
82
- "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1",
83
- "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1",
84
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1",
85
- "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1",
86
- "@deepseek-ai/schemastery": "^3.18.2",
87
- "@earendil-works/pi-ai": "^0.85.1",
88
- "react": "^18.2.0"
89
- },
90
- "devDependencies": {
91
- "@deepseek-ai/cordis": "^4.0.2",
92
- "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1",
93
- "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1",
94
- "@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.1",
95
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.7-rc.1",
96
- "@deepseek-ai/dsh-client-ui-model-selection": ">=0.1.7-rc.1",
97
- "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-rc.1",
98
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.1",
99
- "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.7-rc.1",
100
- "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.7-rc.1",
101
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.1",
102
- "@deepseek-ai/dsh-credentials": ">=0.1.7-rc.1",
103
- "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1",
104
- "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1",
105
- "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1",
106
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1",
107
- "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1",
108
- "@deepseek-ai/schemastery": "^3.18.2",
109
- "@earendil-works/pi-ai": "^0.85.1",
110
- "@types/node": "^22.20.0",
111
- "@types/react": "~18.3.1",
112
- "@types/react-test-renderer": "^18.3.1",
113
- "react": "^18.2.0",
114
- "react-test-renderer": "18.3.1",
115
- "tsdown": "^0.22.2",
116
- "typescript": "^5.9.3",
117
- "vitest": "^4.1.8"
118
- },
119
- "icon": "./assets/icon.svg"
120
- }
1
+ {
2
+ "name": "dsh-qoder-connect",
3
+ "displayName": "DSH Qoder Connect",
4
+ "description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
5
+ "version": "0.2.3-alpha.1",
6
+ "author": "corrinehu",
7
+ "keywords": [
8
+ "dsh-plugin",
9
+ "deepseek-harness",
10
+ "deepseek",
11
+ "qoder",
12
+ "model-provider"
13
+ ],
14
+ "type": "module",
15
+ "engines": {
16
+ "dsh": ">=0.1.7-rc.1",
17
+ "node": "^22.19.0 || >=24.0.0"
18
+ },
19
+ "main": "lib/index.js",
20
+ "types": "lib/index.d.ts",
21
+ "bin": {
22
+ "dsh-qoder-connect": "lib/bin.js"
23
+ },
24
+ "scripts": {
25
+ "prepack": "npm run build",
26
+ "build": "tsdown",
27
+ "test": "node -r ./tests/sandbox-shim.cjs ./node_modules/vitest/vitest.mjs run",
28
+ "test:qoder": "node --test --test-isolation=none --experimental-strip-types tests/qoder/*.test.ts",
29
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
30
+ "check": "pnpm run typecheck && pnpm run test && pnpm run test:qoder && pnpm run build"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./lib/index.d.ts",
35
+ "default": "./lib/index.js"
36
+ },
37
+ "./client": "./lib/client.js",
38
+ "./cordis.patch.yml": "./cordis.patch.yml",
39
+ "./package.json": "./package.json",
40
+ "./locale/*.json": "./locale/*.json"
41
+ },
42
+ "files": [
43
+ "lib",
44
+ "assets",
45
+ "cordis.patch.yml",
46
+ "README.md",
47
+ "README.en.md",
48
+ "LICENSE",
49
+ "locale/*.json"
50
+ ],
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/masknull/dsh-qoder-connect.git"
55
+ },
56
+ "homepage": "https://github.com/masknull/dsh-qoder-connect#readme",
57
+ "bugs": "https://github.com/masknull/dsh-qoder-connect/issues",
58
+ "dsh": {
59
+ "bundle": {
60
+ "patch": "./cordis.patch.yml"
61
+ },
62
+ "client": {
63
+ "inject": [
64
+ "@deepseek-ai/dsh-api-remotes",
65
+ "@deepseek-ai/dsh-api-session-controller",
66
+ "@deepseek-ai/dsh-client-ui-renderer",
67
+ "@deepseek-ai/dsh-client-ui-settings-plugins",
68
+ "@deepseek-ai/dsh-client-ui-settings",
69
+ "@deepseek-ai/dsh-client-ui-conversation",
70
+ "@deepseek-ai/dsh-client-ui-model-selection",
71
+ "@deepseek-ai/dsh-client-ui-sidebar",
72
+ "@deepseek-ai/dsh-client-locale"
73
+ ],
74
+ "platform": "web"
75
+ }
76
+ },
77
+ "peerDependencies": {
78
+ "@deepseek-ai/cordis": "^4.0.2",
79
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1",
80
+ "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1",
81
+ "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1",
82
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1",
83
+ "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1",
84
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1",
85
+ "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1",
86
+ "@deepseek-ai/schemastery": "^3.18.2",
87
+ "@earendil-works/pi-ai": ">=0.85.1",
88
+ "react": "^18.2.0"
89
+ },
90
+ "devDependencies": {
91
+ "@deepseek-ai/cordis": "^4.0.2",
92
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1",
93
+ "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1",
94
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.1",
95
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.7-rc.1",
96
+ "@deepseek-ai/dsh-client-ui-model-selection": ">=0.1.7-rc.1",
97
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-rc.1",
98
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.1",
99
+ "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.7-rc.1",
100
+ "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.7-rc.1",
101
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.1",
102
+ "@deepseek-ai/dsh-credentials": ">=0.1.7-rc.1",
103
+ "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1",
104
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1",
105
+ "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1",
106
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1",
107
+ "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1",
108
+ "@deepseek-ai/schemastery": "^3.18.2",
109
+ "@earendil-works/pi-ai": ">=0.85.1",
110
+ "@types/node": "^22.20.0",
111
+ "@types/react": "~18.3.1",
112
+ "@types/react-test-renderer": "^18.3.1",
113
+ "react": "^18.2.0",
114
+ "react-test-renderer": "18.3.1",
115
+ "tsdown": "^0.22.2",
116
+ "typescript": "^5.9.3",
117
+ "vitest": "^4.1.8"
118
+ },
119
+ "icon": "./assets/icon.svg"
120
+ }