dsh-connect-minimaxcode 0.6.8 → 0.7.0

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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
2
3
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
4
5
  import { dirname, join } from "node:path";
@@ -733,6 +734,68 @@ function imageInputHooks(ctx) {
733
734
  };
734
735
  }
735
736
  //#endregion
737
+ //#region src/failure.ts
738
+ /**
739
+ * Match a shape of the upstream's message.
740
+ *
741
+ * The patterns name *facts* rather than identifiers: "no active subscription"
742
+ * is a fact about the account, "2062" is a number another system chose.
743
+ */
744
+ const SHAPES = [
745
+ {
746
+ kind: "subscription",
747
+ pattern: /no active (token plan|subscription)|not subscribed|no subscription/i
748
+ },
749
+ {
750
+ kind: "credentials",
751
+ pattern: /login fail|log ?in fail|unauthorized|invalid api[ _-]?key|api secret key|invalid credential|authentication (failed|error)/i
752
+ },
753
+ {
754
+ kind: "quota",
755
+ pattern: /insufficient|quota (exceed|exhaust)|balance (is )?(not enough|insufficient)|not enough (credit|balance)|credit (exceed|exhaust)|超出|余额不足/i
756
+ },
757
+ {
758
+ kind: "rate-limited",
759
+ pattern: /rate ?limit|too many requests|请求过于频繁|频率限制/i
760
+ },
761
+ {
762
+ kind: "parameters",
763
+ pattern: /invalid (parameter|argument|request|value)|parameter .* (invalid|must be|not allowed)|unsupported (parameter|value|model)|must be (a |an )?(string|number|array|object|boolean)|参数(错误|无效)|不合法/i
764
+ }
765
+ ];
766
+ /**
767
+ * Classify a failure by what the upstream said and how it said it.
768
+ *
769
+ * @param message - The upstream's message, or a description of what went
770
+ * wrong locally.
771
+ * @param status - The HTTP status the answer arrived with, when there was one.
772
+ * @returns The kind of failure. A failure this plugin does not recognise is
773
+ * `upstream` rather than a guess: the remedy for an unknown failure is to
774
+ * show the upstream's words, not to pretend to know better.
775
+ */
776
+ function classifyFailure(message, status) {
777
+ if (status === 401 || status === 403) return "credentials";
778
+ if (status === 429) return "rate-limited";
779
+ for (const shape of SHAPES) if (shape.pattern.test(message)) return shape.kind;
780
+ if (status === 400 || status === 422) return "parameters";
781
+ return "upstream";
782
+ }
783
+ /**
784
+ * How long to hold off before trying the same thing again.
785
+ *
786
+ * A failure that spent nothing has nothing to cool down: the caller fixed
787
+ * something and may try again at once. A failure that happened after the
788
+ * upstream accepted the work may already have been charged for, so a retry
789
+ * right away is another charge — that one waits.
790
+ *
791
+ * @returns The wait in milliseconds, or zero when no wait is warranted.
792
+ */
793
+ function cooldownFor(failure) {
794
+ return failure.charged ? COOLDOWN_MS : 0;
795
+ }
796
+ /** The wait after a failure that may already have been charged for. */
797
+ const COOLDOWN_MS = 3e4;
798
+ //#endregion
736
799
  //#region src/status-route.ts
737
800
  /** Route this plugin mounts, matching its settings card. */
738
801
  const MINIMAX_STATUS_PATH = "/plugins/dsh-connect-minimaxcode/status";
@@ -787,6 +850,8 @@ function statusDocument(deps) {
787
850
  ...deps.login === void 0 ? {} : { login: deps.login },
788
851
  ...deps.usageProfile === void 0 ? {} : { usageProfile: deps.usageProfile },
789
852
  ...deps.plan === void 0 ? {} : { plan: deps.plan },
853
+ ...deps.diagnostics === void 0 ? {} : { diagnostics: deps.diagnostics },
854
+ ...deps.showPill === void 0 ? {} : { showPill: deps.showPill },
790
855
  ...deps.capability === void 0 ? {} : { capability: deps.capability },
791
856
  ...deps.generation === void 0 ? {} : { generation: deps.generation }
792
857
  };
@@ -888,10 +953,27 @@ const VIDEO_DURATIONS = [
888
953
  var GenerationError = class extends Error {
889
954
  /** The upstream's own status code, when it gave one. */
890
955
  code;
891
- constructor(message, code) {
956
+ /**
957
+ * What kind of failure this is, and what it cost.
958
+ *
959
+ * The verdict is what a caller acts on: it says whether the remedy is to
960
+ * change something, to sign in again, or to wait, and whether the work may
961
+ * already have been charged for.
962
+ */
963
+ verdict;
964
+ constructor(message, code, verdict) {
892
965
  super(message);
893
966
  this.name = "GenerationError";
894
967
  this.code = code;
968
+ this.verdict = {
969
+ kind: verdict?.kind ?? classifyFailure(message, code),
970
+ charged: verdict?.charged ?? false,
971
+ upstream: verdict?.upstream ?? (code === void 0 ? void 0 : {
972
+ status: code,
973
+ code,
974
+ message
975
+ })
976
+ };
895
977
  }
896
978
  };
897
979
  /**
@@ -981,12 +1063,38 @@ async function callGeneration(apiKey, url, body, fetchImpl) {
981
1063
  try {
982
1064
  document = JSON.parse(text);
983
1065
  } catch {
984
- if (!response.ok) throw new GenerationError(`generation request failed: HTTP ${response.status}`, response.status);
985
- throw new GenerationError("generation response was not JSON");
1066
+ if (!response.ok) throw new GenerationError(`generation request failed: HTTP ${response.status}`, response.status, {
1067
+ charged: false,
1068
+ upstream: {
1069
+ status: response.status,
1070
+ message: `generation request failed: HTTP ${response.status}`
1071
+ }
1072
+ });
1073
+ throw new GenerationError("generation response was not JSON", void 0, { charged: false });
1074
+ }
1075
+ if (!response.ok) {
1076
+ const message = readErrorMessage(document) ?? `generation request failed: HTTP ${response.status}`;
1077
+ const code = readErrorCode(document);
1078
+ throw new GenerationError(message, code ?? response.status, {
1079
+ kind: classifyFailure(message, response.status),
1080
+ charged: false,
1081
+ upstream: {
1082
+ status: response.status,
1083
+ code,
1084
+ message
1085
+ }
1086
+ });
986
1087
  }
987
- if (!response.ok) throw new GenerationError(readErrorMessage(document) ?? `generation request failed: HTTP ${response.status}`, readErrorCode(document) ?? response.status);
988
1088
  const refusal = readBodyRefusal(document);
989
- if (refusal !== void 0) throw new GenerationError(refusal.message, refusal.code);
1089
+ if (refusal !== void 0) throw new GenerationError(refusal.message, refusal.code, {
1090
+ kind: classifyFailure(refusal.message, response.status),
1091
+ charged: false,
1092
+ upstream: {
1093
+ status: response.status,
1094
+ code: refusal.code,
1095
+ message: refusal.message
1096
+ }
1097
+ });
990
1098
  return document ?? {};
991
1099
  }
992
1100
  /**
@@ -1092,11 +1200,37 @@ async function queryVideoTask(options) {
1092
1200
  try {
1093
1201
  document = JSON.parse(text) ?? {};
1094
1202
  } catch {
1095
- throw new GenerationError(`video task query failed: HTTP ${response.status}`, response.status);
1203
+ throw new GenerationError(`video task query failed: HTTP ${response.status}`, response.status, {
1204
+ charged: true,
1205
+ upstream: {
1206
+ status: response.status,
1207
+ message: `video task query failed: HTTP ${response.status}`
1208
+ }
1209
+ });
1210
+ }
1211
+ if (!response.ok) {
1212
+ const message = readErrorMessage(document) ?? `video task query failed: HTTP ${response.status}`;
1213
+ const code = readErrorCode(document);
1214
+ throw new GenerationError(message, code ?? response.status, {
1215
+ kind: classifyFailure(message, response.status),
1216
+ charged: true,
1217
+ upstream: {
1218
+ status: response.status,
1219
+ code,
1220
+ message
1221
+ }
1222
+ });
1096
1223
  }
1097
- if (!response.ok) throw new GenerationError(readErrorMessage(document) ?? `video task query failed: HTTP ${response.status}`, readErrorCode(document) ?? response.status);
1098
1224
  const refusal = readBodyRefusal(document);
1099
- if (refusal !== void 0) throw new GenerationError(refusal.message, refusal.code);
1225
+ if (refusal !== void 0) throw new GenerationError(refusal.message, refusal.code, {
1226
+ kind: classifyFailure(refusal.message, response.status),
1227
+ charged: true,
1228
+ upstream: {
1229
+ status: response.status,
1230
+ code: refusal.code,
1231
+ message: refusal.message
1232
+ }
1233
+ });
1100
1234
  const task = document.task ?? document;
1101
1235
  const content = task.content;
1102
1236
  const usage = task.usage;
@@ -2642,6 +2776,16 @@ var UsageMeter = class {
2642
2776
  };
2643
2777
  //#endregion
2644
2778
  //#region src/index.ts
2779
+ /**
2780
+ * MiniMax Code models for DeepSeek Harness.
2781
+ *
2782
+ * Registers one provider, `minimaxcode`, that serves the models the signed-in
2783
+ * MiniMax Code desktop app has access to. Streaming, tool calls, and
2784
+ * permissions stay Harness-owned; this plugin only supplies the route and the
2785
+ * catalog.
2786
+ *
2787
+ * @module dsh-connect-minimaxcode
2788
+ */
2645
2789
  /** Loader entry name; also the id the bundle patch inserts. */
2646
2790
  /**
2647
2791
  * Whether a message says the server refused the credential itself.
@@ -2727,6 +2871,13 @@ function apply(ctx) {
2727
2871
  /** What the card last reported about what the host offered it. */
2728
2872
  let capability;
2729
2873
  /**
2874
+ * The most recent upstream failure, in its own words.
2875
+ *
2876
+ * It is kept verbatim and unedited: a report that carries it does not
2877
+ * depend on the reader describing it correctly.
2878
+ */
2879
+ let lastUpstreamError;
2880
+ /**
2730
2881
  * Give up on the plugin's own credential after the server refused it.
2731
2882
  *
2732
2883
  * A revoked token keeps its expiry date, so every later call would send the
@@ -3024,6 +3175,54 @@ function apply(ctx) {
3024
3175
  };
3025
3176
  };
3026
3177
  /**
3178
+ * This plugin's own version, read from the package it was installed as.
3179
+ *
3180
+ * It is read at runtime rather than written into the bundle, so the number
3181
+ * in a report is the number of the package that is actually installed — a
3182
+ * number baked in at build time would drift from it on the first release
3183
+ * that forgot to rebuild.
3184
+ *
3185
+ * A package root that cannot be read leaves the field out rather than
3186
+ * guessing: a wrong version in a report is worse than no version.
3187
+ */
3188
+ function readPluginVersion() {
3189
+ try {
3190
+ const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
3191
+ return typeof manifest.version === "string" ? manifest.version : void 0;
3192
+ } catch {
3193
+ return;
3194
+ }
3195
+ }
3196
+ /** This plugin's own version, read once when it loads. */
3197
+ const PLUGIN_VERSION = readPluginVersion();
3198
+ /**
3199
+ * The facts a report needs, gathered in one place.
3200
+ *
3201
+ * Every line is a fact about this installation and nothing here changes
3202
+ * anything: the section exists so that a person can copy it into a report
3203
+ * without having to work out what to include.
3204
+ *
3205
+ * The host's own version is not in here, because the host does not offer
3206
+ * it to a plugin — a line that had to be guessed would be worse than no
3207
+ * line at all.
3208
+ */
3209
+ const buildDiagnostics = async () => {
3210
+ const auth = await buildAuthStatus();
3211
+ return {
3212
+ pluginVersion: PLUGIN_VERSION,
3213
+ region,
3214
+ hosts: [.../* @__PURE__ */ new Set([
3215
+ MINIMAX_GATEWAYS[region],
3216
+ MINIMAX_ACCOUNT_HOSTS[region],
3217
+ MINIMAX_API_HOSTS[region]
3218
+ ])],
3219
+ authSource: auth.state === "signed-in" ? buildLoginStatus().source : auth.state,
3220
+ catalogSource: catalog.source,
3221
+ lastUpstreamError,
3222
+ ...capability === void 0 ? {} : { capability: capability.findings }
3223
+ };
3224
+ };
3225
+ /**
3027
3226
  * The plan the account holds, as the card reads it.
3028
3227
  *
3029
3228
  * It comes from the same membership answer as the balance, so it is free.
@@ -3188,6 +3387,8 @@ function apply(ctx) {
3188
3387
  usageProfile: buildUsageProfileStatus(),
3189
3388
  plan: buildPlanStatus(),
3190
3389
  capability,
3390
+ diagnostics: await buildDiagnostics(),
3391
+ showPill: pluginState.showPill ?? true,
3191
3392
  generation: {
3192
3393
  imageModels: [...IMAGE_MODELS],
3193
3394
  imageAspectRatios: [...IMAGE_ASPECT_RATIOS],
@@ -3299,6 +3500,22 @@ function apply(ctx) {
3299
3500
  state: result.state
3300
3501
  };
3301
3502
  }
3503
+ case "pillVisible": {
3504
+ const showPill = body.showPill;
3505
+ if (typeof showPill !== "boolean") return {
3506
+ ok: false,
3507
+ error: "showPill 必须是布尔值"
3508
+ };
3509
+ pluginState = {
3510
+ ...pluginState,
3511
+ showPill
3512
+ };
3513
+ await writePluginState(pluginState);
3514
+ return {
3515
+ ok: true,
3516
+ showPill
3517
+ };
3518
+ }
3302
3519
  case "settings": {
3303
3520
  const autoCheckin = body.autoCheckin;
3304
3521
  if (typeof autoCheckin !== "boolean") return {
@@ -3334,9 +3551,15 @@ function apply(ctx) {
3334
3551
  const resolvePlanApiKey = async () => {
3335
3552
  if (planApiKey !== void 0) return planApiKey;
3336
3553
  const account = await resolveAccountCredential();
3337
- if (account.state !== "signed-in") throw new GenerationError("尚未登录 MiniMax Code");
3554
+ if (account.state !== "signed-in") throw new GenerationError("尚未登录 MiniMax Code", void 0, {
3555
+ kind: "credentials",
3556
+ charged: false
3557
+ });
3338
3558
  const key = await fetchPlanApiKey(account.credential, region);
3339
- if (key === void 0) throw new GenerationError("账户没有开放平台密钥,无法调用生成接口");
3559
+ if (key === void 0) throw new GenerationError("账户没有开放平台密钥,无法调用生成接口", void 0, {
3560
+ kind: "credentials",
3561
+ charged: false
3562
+ });
3340
3563
  planApiKey = key;
3341
3564
  return key;
3342
3565
  };
@@ -3383,7 +3606,10 @@ function apply(ctx) {
3383
3606
  const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
3384
3607
  if (prompt.length === 0) return {
3385
3608
  ok: false,
3386
- error: "请填写提示词"
3609
+ error: "请填写提示词",
3610
+ kind: "local",
3611
+ charged: false,
3612
+ cooldownMs: 0
3387
3613
  };
3388
3614
  const apiKey = await resolvePlanApiKey();
3389
3615
  if (kind === "video") return {
@@ -3575,11 +3801,19 @@ function apply(ctx) {
3575
3801
  res.end(JSON.stringify(await runGeneration(body)));
3576
3802
  } catch (error) {
3577
3803
  const status = error instanceof GenerationError ? 400 : 500;
3804
+ if (error instanceof GenerationError) lastUpstreamError = error.verdict.upstream?.message ?? error.message;
3578
3805
  res.writeHead(status, { "Content-Type": "application/json" });
3806
+ const verdict = error instanceof GenerationError ? error.verdict : void 0;
3579
3807
  res.end(JSON.stringify({
3580
3808
  ok: false,
3581
3809
  error: error instanceof Error ? error.message : String(error),
3582
- ...error instanceof GenerationError && error.code !== void 0 ? { code: error.code } : {}
3810
+ ...error instanceof GenerationError && error.code !== void 0 ? { code: error.code } : {},
3811
+ ...verdict === void 0 ? {} : {
3812
+ kind: verdict.kind,
3813
+ charged: verdict.charged,
3814
+ cooldownMs: cooldownFor(verdict)
3815
+ },
3816
+ ...verdict?.upstream === void 0 ? {} : { upstream: verdict.upstream }
3583
3817
  }));
3584
3818
  }
3585
3819
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-minimaxcode",
3
3
  "displayName": "DSH Connect MiniMax Code",
4
4
  "description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
5
- "version": "0.6.8",
5
+ "version": "0.7.0",
6
6
  "author": "JeffTsuiCUG",
7
7
  "contributors": [
8
8
  {
@@ -39,7 +39,7 @@
39
39
  "type": "module",
40
40
  "engines": {
41
41
  "node": "^22.19.0 || >=24.0.0",
42
- "dsh": ">=0.2.1-rc.2 <0.3.0-0"
42
+ "dsh": ">=0.1.6-alpha.2 <0.3.0-0"
43
43
  },
44
44
  "icon": "./icon.svg",
45
45
  "main": "lib/index.js",
@@ -58,6 +58,7 @@
58
58
  "files": [
59
59
  "lib",
60
60
  "locale",
61
+ "docs",
61
62
  "icon.svg",
62
63
  "cordis.patch.yml",
63
64
  "README.md",
@@ -82,13 +83,13 @@
82
83
  ],
83
84
  "peerDependencies": {
84
85
  "@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
85
- "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.3.0-0",
86
- "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.3.0-0",
87
- "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.3.0-0",
88
- "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.3.0-0",
89
- "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.3.0-0",
90
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
91
- "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
86
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.6-alpha.2 <0.3.0-0",
87
+ "@deepseek-ai/dsh-attachment": ">=0.1.6-alpha.2 <0.3.0-0",
88
+ "@deepseek-ai/dsh-home-paths": ">=0.1.6-alpha.2 <0.3.0-0",
89
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.6-alpha.2 <0.3.0-0",
90
+ "@deepseek-ai/dsh-llm": ">=0.1.6-alpha.2 <0.3.0-0",
91
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.6-alpha.2 <0.3.0-0",
92
+ "@deepseek-ai/dsh-settings": ">=0.1.6-alpha.2 <0.3.0-0",
92
93
  "@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
93
94
  "@earendil-works/pi-ai": ">=0.85.1"
94
95
  },