@lynn123411/dsh-a6api 1.5.5 → 1.5.7

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
@@ -26,6 +26,41 @@ function dshHomePath(...segments) {
26
26
  return join(resolveDshHome(), ...segments);
27
27
  }
28
28
 
29
+ // src/server/net.ts
30
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
31
+ function isFlakyNetworkError(err) {
32
+ if (err?.name === "TimeoutError" || err?.name === "AbortError") return false;
33
+ const cause = err?.cause;
34
+ const raw = `${err?.message || ""} ${cause?.code || ""} ${cause?.message || ""}`;
35
+ return /fetch failed|ECONNRESET|ECONNREFUSED|EHOSTUNREACH|ENETUNREACH|ENOTFOUND|EPIPE|socket hang up|other side closed|UND_ERR_SOCKET|UND_ERR_CONNECT_TIMEOUT|SSL_ERROR|ERR_SSL/i.test(
36
+ raw
37
+ );
38
+ }
39
+ async function fetchWithNetRetry(url, init = {}, opts) {
40
+ const retries = opts?.retries ?? 1;
41
+ const delayMs = opts?.delayMs ?? 800;
42
+ const { timeoutMs, ...rest } = init;
43
+ let lastErr;
44
+ for (let attempt = 0; attempt <= retries; attempt++) {
45
+ try {
46
+ const signal = timeoutMs ? AbortSignal.timeout(timeoutMs) : rest.signal;
47
+ return await fetch(url, { ...rest, signal });
48
+ } catch (err) {
49
+ lastErr = err;
50
+ if (attempt < retries && isFlakyNetworkError(err)) {
51
+ console.warn(
52
+ `[dsh-a6api] \u4E0A\u6E38\u8FDE\u63A5\u77AC\u65AD(${err?.cause?.code || err?.message || "network"})\uFF0C${delayMs}ms \u540E\u91CD\u8BD5:`,
53
+ url.split("?")[0]
54
+ );
55
+ await sleep(delayMs);
56
+ continue;
57
+ }
58
+ throw err;
59
+ }
60
+ }
61
+ throw lastErr;
62
+ }
63
+
29
64
  // src/server/catalog.ts
30
65
  var CATALOG_VERSION = 1;
31
66
  function catalogFile() {
@@ -234,9 +269,9 @@ async function fetchMarketplaceModels(userId, accessToken) {
234
269
  }
235
270
  const headers = buildWebHeaders(userId, accessToken);
236
271
  const first = await (async () => {
237
- const res = await fetch(`${MARKET_SEARCH}?view=list&page=1&page_size=${PAGE_SIZE}`, {
272
+ const res = await fetchWithNetRetry(`${MARKET_SEARCH}?view=list&page=1&page_size=${PAGE_SIZE}`, {
238
273
  headers,
239
- signal: AbortSignal.timeout(15e3)
274
+ timeoutMs: 15e3
240
275
  });
241
276
  if (!res.ok) throw new Error(`A6API \u5E02\u573A\u63A5\u53E3 HTTP ${res.status}`);
242
277
  return res.json();
@@ -250,9 +285,9 @@ async function fetchMarketplaceModels(userId, accessToken) {
250
285
  while (idx <= pages) {
251
286
  const p = idx++;
252
287
  try {
253
- const res = await fetch(`${MARKET_SEARCH}?view=list&page=${p}&page_size=${PAGE_SIZE}`, {
288
+ const res = await fetchWithNetRetry(`${MARKET_SEARCH}?view=list&page=${p}&page_size=${PAGE_SIZE}`, {
254
289
  headers,
255
- signal: AbortSignal.timeout(15e3)
290
+ timeoutMs: 15e3
256
291
  });
257
292
  const j = await res.json();
258
293
  all.push(...j?.data?.items || []);
@@ -285,9 +320,9 @@ var orCache = null;
285
320
  async function getOpenRouterModels() {
286
321
  if (orCache && Date.now() - orCache.at < OR_TTL_MS) return orCache.models;
287
322
  try {
288
- const res = await fetch(OPENROUTER_URL, {
323
+ const res = await fetchWithNetRetry(OPENROUTER_URL, {
289
324
  headers: { Accept: "application/json", "User-Agent": "Mozilla/5.0" },
290
- signal: AbortSignal.timeout(2e4)
325
+ timeoutMs: 2e4
291
326
  });
292
327
  if (!res.ok) throw new Error(`OpenRouter HTTP ${res.status}`);
293
328
  const j = await res.json();
@@ -434,9 +469,9 @@ async function fetchBalance(baseURL, apiKey, userId, accessToken) {
434
469
  const uniqueCandidates = [...new Set(candidates)];
435
470
  for (const url of uniqueCandidates) {
436
471
  try {
437
- const res = await fetch(url, {
472
+ const res = await fetchWithNetRetry(url, {
438
473
  headers: buildWebHeaders2(userId, accessToken),
439
- signal: AbortSignal.timeout(6e3)
474
+ timeoutMs: 6e3
440
475
  });
441
476
  if (res.ok) {
442
477
  const json = await res.json();
@@ -463,12 +498,12 @@ async function fetchBalance(baseURL, apiKey, userId, accessToken) {
463
498
  }
464
499
  if (!hasAccountAuth && apiKey && apiKey.trim()) {
465
500
  try {
466
- const usageRes = await fetch(`${cleanUrl}/v1/dashboard/billing/usage?start_date=2024-01-01&end_date=2030-12-31`, {
501
+ const usageRes = await fetchWithNetRetry(`${cleanUrl}/v1/dashboard/billing/usage?start_date=2024-01-01&end_date=2030-12-31`, {
467
502
  headers: {
468
503
  Authorization: `Bearer ${apiKey.trim()}`,
469
504
  Accept: "application/json"
470
505
  },
471
- signal: AbortSignal.timeout(6e3)
506
+ timeoutMs: 6e3
472
507
  }).catch(() => null);
473
508
  if (usageRes && usageRes.ok) {
474
509
  const usageJson = await usageRes.json();
@@ -501,12 +536,12 @@ async function fetchTokenModels(baseURL, apiKey) {
501
536
  let lastErr = null;
502
537
  for (const url of endpoints) {
503
538
  try {
504
- const res = await fetch(url, {
539
+ const res = await fetchWithNetRetry(url, {
505
540
  headers: {
506
541
  Authorization: `Bearer ${apiKey.trim()}`,
507
542
  Accept: "application/json"
508
543
  },
509
- signal: AbortSignal.timeout(8e3)
544
+ timeoutMs: 8e3
510
545
  });
511
546
  if (res.ok) {
512
547
  const json = await res.json();
@@ -526,9 +561,9 @@ async function fetchTokenModels(baseURL, apiKey) {
526
561
  async function fetchRecentLogs(userId, accessToken, limit = 30) {
527
562
  if (!userId && !accessToken) return [];
528
563
  try {
529
- const res = await fetch(`https://a6api.com/api/log/self?p=1&page_size=${limit}&type=0`, {
564
+ const res = await fetchWithNetRetry(`https://a6api.com/api/log/self?p=1&page_size=${limit}&type=0`, {
530
565
  headers: buildWebHeaders2(userId, accessToken),
531
- signal: AbortSignal.timeout(8e3)
566
+ timeoutMs: 8e3
532
567
  });
533
568
  if (res.ok) {
534
569
  const json = await res.json();
@@ -587,11 +622,11 @@ async function fetchChannelDetails(channelId, userId, accessToken, targetModelNa
587
622
  const targetName = targetModelName || "";
588
623
  const meta = resolveModelMeta(targetName);
589
624
  try {
590
- const res = await fetch(
625
+ const res = await fetchWithNetRetry(
591
626
  `https://a6api.com/api/marketplace/channels/search?channel_id=${channelId}&view=list&page=1&page_size=20`,
592
627
  {
593
628
  headers: buildWebHeaders2(userId, accessToken),
594
- signal: AbortSignal.timeout(8e3)
629
+ timeoutMs: 8e3
595
630
  }
596
631
  );
597
632
  if (res.ok) {
@@ -789,7 +824,7 @@ async function fetchPriceFluctuation(userId, accessToken) {
789
824
  const headers = buildWebHeaders2(uid || void 0, token || void 0);
790
825
  const url = "https://a6api.com/api/marketplace/price-notices";
791
826
  try {
792
- const res = await fetch(url, { headers, signal: AbortSignal.timeout(8e3) });
827
+ const res = await fetchWithNetRetry(url, { headers, timeoutMs: 8e3 });
793
828
  if (res.status === 401 || res.status === 403) {
794
829
  console.warn("[dsh-a6api] fetchPriceFluctuation auth failed", res.status);
795
830
  return { pendingCount: 0, unseenCount: 0, totalCount: 0, authError: true };
@@ -852,10 +887,17 @@ function parseMarketplaceResult(json) {
852
887
  if (!json) return { ok: false, message: "\u7A7A\u54CD\u5E94" };
853
888
  const top = json.success === false ? json : null;
854
889
  const inner = json.data && json.data.success === false ? json.data : null;
855
- if (top) return { ok: false, message: top.message || "\u64CD\u4F5C\u5931\u8D25" };
856
- if (inner) return { ok: false, message: inner.message || "\u64CD\u4F5C\u5931\u8D25" };
890
+ if (top) return { ok: false, message: friendlyMarketMessage(top.message) };
891
+ if (inner) return { ok: false, message: friendlyMarketMessage(inner.message) };
857
892
  return { ok: true, data: json.data };
858
893
  }
894
+ function friendlyMarketMessage(msg) {
895
+ const s = String(msg || "");
896
+ if (s === "invalid_request") {
897
+ return "\u4E0A\u6E38\u62D2\u7EDD\u4E86\u8BE5\u64CD\u4F5C(invalid_request)\uFF1A\u53C2\u6570\u672A\u901A\u8FC7\u5E73\u53F0\u6821\u9A8C\uFF08\u53EF\u80FD\u662F\u5E73\u53F0\u63A5\u53E3\u8C03\u6574\u6216\u56FA\u5B9A\u8BB0\u5F55\u5DF2\u53D8\u5316\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u5230\u5B98\u7F51\u5904\u7406";
898
+ }
899
+ return s || "\u64CD\u4F5C\u5931\u8D25";
900
+ }
859
901
  function extractArray(json) {
860
902
  if (!json) return [];
861
903
  if (Array.isArray(json)) return json;
@@ -868,9 +910,9 @@ function extractArray(json) {
868
910
  async function fetchMarketplacePins(userId, accessToken) {
869
911
  if (!userId && !accessToken) return [];
870
912
  try {
871
- const res = await fetch("https://a6api.com/api/marketplace/pins", {
913
+ const res = await fetchWithNetRetry("https://a6api.com/api/marketplace/pins", {
872
914
  headers: buildWebHeaders2(userId, accessToken),
873
- signal: AbortSignal.timeout(8e3)
915
+ timeoutMs: 8e3
874
916
  });
875
917
  if (!res.ok) {
876
918
  console.warn("[dsh-a6api] fetchMarketplacePins HTTP", res.status);
@@ -899,9 +941,9 @@ async function fetchMarketplacePins(userId, accessToken) {
899
941
  async function fetchTokens(userId, accessToken) {
900
942
  if (!userId && !accessToken) return [];
901
943
  try {
902
- const res = await fetch("https://a6api.com/api/token/?p=1&size=100", {
944
+ const res = await fetchWithNetRetry("https://a6api.com/api/token/?p=1&size=100", {
903
945
  headers: buildWebHeaders2(userId, accessToken),
904
- signal: AbortSignal.timeout(8e3)
946
+ timeoutMs: 8e3
905
947
  });
906
948
  if (!res.ok) return [];
907
949
  const json = await res.json().catch(() => null);
@@ -923,18 +965,21 @@ function friendlyActionError(err) {
923
965
  if (raw.includes("aborted due to timeout") || err?.name === "TimeoutError") {
924
966
  return "\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5";
925
967
  }
968
+ if (isFlakyNetworkError(err)) {
969
+ return "\u4E0E\u4E0A\u6E38\u8FDE\u63A5\u4E0D\u7A33\u5B9A\uFF08\u5DF2\u81EA\u52A8\u91CD\u8BD5\u4ECD\u5931\u8D25\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
970
+ }
926
971
  return raw;
927
972
  }
928
973
  async function marketplacePin(userId, accessToken, payload) {
929
974
  try {
930
- const res = await fetch("https://a6api.com/api/marketplace/pin", {
975
+ const res = await fetchWithNetRetry("https://a6api.com/api/marketplace/pin", {
931
976
  method: "POST",
932
977
  headers: {
933
978
  "Content-Type": "application/json",
934
979
  ...buildWebHeaders2(userId, accessToken)
935
980
  },
936
981
  body: JSON.stringify(payload),
937
- signal: AbortSignal.timeout(1e4)
982
+ timeoutMs: 1e4
938
983
  });
939
984
  const json = await res.json().catch(() => null);
940
985
  const result = parseMarketplaceResult(json);
@@ -947,14 +992,14 @@ async function marketplacePin(userId, accessToken, payload) {
947
992
  }
948
993
  async function marketplaceUnpin(userId, accessToken, payload) {
949
994
  try {
950
- const res = await fetch("https://a6api.com/api/marketplace/unpin", {
995
+ const res = await fetchWithNetRetry("https://a6api.com/api/marketplace/unpin", {
951
996
  method: "POST",
952
997
  headers: {
953
998
  "Content-Type": "application/json",
954
999
  ...buildWebHeaders2(userId, accessToken)
955
1000
  },
956
1001
  body: JSON.stringify(payload),
957
- signal: AbortSignal.timeout(1e4)
1002
+ timeoutMs: 1e4
958
1003
  });
959
1004
  const json = await res.json().catch(() => null);
960
1005
  if (!res.ok && !json) return { ok: false, message: `HTTP ${res.status}` };
@@ -966,12 +1011,12 @@ async function marketplaceUnpin(userId, accessToken, payload) {
966
1011
  }
967
1012
  async function marketplaceDisableChannel(userId, accessToken, channelId, model) {
968
1013
  try {
969
- const res = await fetch(
1014
+ const res = await fetchWithNetRetry(
970
1015
  `https://a6api.com/api/marketplace/channels/${channelId}/disable?model=${encodeURIComponent(model)}`,
971
1016
  {
972
1017
  method: "POST",
973
1018
  headers: buildWebHeaders2(userId, accessToken),
974
- signal: AbortSignal.timeout(1e4)
1019
+ timeoutMs: 1e4
975
1020
  }
976
1021
  );
977
1022
  const json = await res.json().catch(() => null);
@@ -984,12 +1029,12 @@ async function marketplaceDisableChannel(userId, accessToken, channelId, model)
984
1029
  }
985
1030
  async function marketplaceRestoreChannel(userId, accessToken, channelId, model) {
986
1031
  try {
987
- const res = await fetch(
1032
+ const res = await fetchWithNetRetry(
988
1033
  `https://a6api.com/api/marketplace/channels/${channelId}/restore?model=${encodeURIComponent(model)}`,
989
1034
  {
990
1035
  method: "POST",
991
1036
  headers: buildWebHeaders2(userId, accessToken),
992
- signal: AbortSignal.timeout(1e4)
1037
+ timeoutMs: 1e4
993
1038
  }
994
1039
  );
995
1040
  const json = await res.json().catch(() => null);
@@ -1002,7 +1047,7 @@ async function marketplaceRestoreChannel(userId, accessToken, channelId, model)
1002
1047
  }
1003
1048
 
1004
1049
  // src/server/probe.ts
1005
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1050
+ var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1006
1051
  async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName) {
1007
1052
  const targetModel = modelName || "";
1008
1053
  const cleanUrl = cleanBaseUrl(baseURL);
@@ -1013,7 +1058,7 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
1013
1058
  let requestOk = false;
1014
1059
  let requestError = "";
1015
1060
  try {
1016
- const res = await fetch(`${cleanUrl}/v1/chat/completions`, {
1061
+ const res = await fetchWithNetRetry(`${cleanUrl}/v1/chat/completions`, {
1017
1062
  method: "POST",
1018
1063
  headers: {
1019
1064
  "Content-Type": "application/json",
@@ -1024,8 +1069,9 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
1024
1069
  messages: [{ role: "user", content: "1" }],
1025
1070
  max_tokens: 1
1026
1071
  }),
1027
- // 推理模型(如 grok-4.6)实测单次响应可达 40-90s+,阈值过短会被频繁掐断导致探测失败
1028
- signal: AbortSignal.timeout(18e4)
1072
+ // 推理模型(如 grok-4.6)实测单次响应可达 40-90s+,阈值过短会被频繁掐断导致探测失败;
1073
+ // 走 fetchWithNetRetry:每次尝试独立 180s 窗口,传输层瞬断(ECONNRESET)自动重试一次
1074
+ timeoutMs: 18e4
1029
1075
  });
1030
1076
  if (res.ok) {
1031
1077
  requestOk = true;
@@ -1037,13 +1083,15 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
1037
1083
  const raw = err?.message || String(err);
1038
1084
  if (raw.includes("aborted due to timeout") || err?.name === "TimeoutError") {
1039
1085
  requestError = "\u63A2\u6D4B\u8D85\u65F6(\u9608\u503C180\u79D2) \u2014 \u63A8\u7406\u6A21\u578B\u54CD\u5E94\u8F83\u6162,\u5DF2\u4FDD\u7559\u4E0A\u6B21\u5546\u6237\u6570\u636E,\u8BF7\u7A0D\u540E\u91CD\u8BD5";
1086
+ } else if (isFlakyNetworkError(err)) {
1087
+ requestError = "\u4E0E\u4E0A\u6E38\u8FDE\u63A5\u4E0D\u7A33\u5B9A(fetch failed\uFF0C\u5DF2\u81EA\u52A8\u91CD\u8BD5\u4ECD\u5931\u8D25)\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
1040
1088
  } else {
1041
1089
  requestError = raw;
1042
1090
  }
1043
1091
  }
1044
1092
  const durationMs = Date.now() - startTime;
1045
1093
  if (requestOk && (userId || accessToken)) {
1046
- await sleep(1200);
1094
+ await sleep2(1200);
1047
1095
  try {
1048
1096
  const logs = await fetchRecentLogs(userId, accessToken, 15);
1049
1097
  const minTimestamp = Math.floor(startTime / 1e3) - 10;
@@ -2188,7 +2236,15 @@ function apply(ctx) {
2188
2236
  if (!tokenId) {
2189
2237
  return sendJson(res, 400, { ok: false, error: "\u65E0\u6CD5\u89E3\u6790 API Key \u5BF9\u5E94\u7684\u4EE4\u724C ID\uFF08\u591A\u4EE4\u724C\u8D26\u53F7\u9700\u4FDD\u8BC1\u4EE4\u724C\u5217\u8868\u53EF\u8BFB\uFF09\uFF1B\u53EF\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D\u4E00\u6B21\u540E\u91CD\u8BD5\uFF0C\u6216\u5230\u5B98\u7F51\u624B\u52A8\u53D6\u6D88" });
2190
2238
  }
2191
- const unpinResult = await marketplaceUnpin(userId, token, { token_id: tokenId, model_name: modelName });
2239
+ let channelId = Number(body.channelId) > 0 ? Number(body.channelId) : 0;
2240
+ if (!channelId) {
2241
+ const card2 = cachedMerchantOf(modelName);
2242
+ if (card2?.channel_id) channelId = Number(card2.channel_id);
2243
+ }
2244
+ if (!channelId) {
2245
+ return sendJson(res, 400, { ok: false, error: "\u672A\u80FD\u786E\u5B9A\u8BE5\u6A21\u578B\u56FA\u5B9A\u6240\u5C5E\u7684\u5546\u5BB6\u6E20\u9053\uFF08\u4E0A\u6E38\u53D6\u6D88\u56FA\u5B9A\u9700\u8981\u6E20\u9053 ID\uFF09\uFF1B\u8BF7\u5148\u5237\u65B0\u5217\u8868\u6216\u300C\u63A2\u6D4B\u5546\u5BB6\u300D\u4E00\u6B21\u540E\u91CD\u8BD5\uFF0C\u6216\u5230\u5B98\u7F51\u624B\u52A8\u53D6\u6D88" });
2246
+ }
2247
+ const unpinResult = await marketplaceUnpin(userId, token, { token_id: tokenId, channel_id: channelId, model_name: modelName });
2192
2248
  if (!unpinResult.ok) {
2193
2249
  return sendJson(res, 400, { ok: false, error: unpinResult.message || "\u53D6\u6D88\u56FA\u5B9A\u5931\u8D25" });
2194
2250
  }