@lynn123411/dsh-a6api 1.0.0 → 1.1.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/README.md +3 -2
- package/lib/client.js +328 -49
- package/lib/client.js.map +3 -3
- package/lib/index.js +121 -27
- package/lib/index.js.map +2 -2
- package/lib/types/client/store.d.ts +6 -1
- package/lib/types/server/a6api-client.d.ts +8 -0
- package/lib/types/server/sync.d.ts +1 -1
- package/lib/types/types.d.ts +12 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -892,6 +892,66 @@ async function fetchChannelDetails(channelId, userId, sessionCookie, targetModel
|
|
|
892
892
|
}
|
|
893
893
|
return null;
|
|
894
894
|
}
|
|
895
|
+
async function fetchPriceFluctuation(userId, sessionCookie, accessToken) {
|
|
896
|
+
const token = (accessToken || sessionCookie || "").trim();
|
|
897
|
+
const uid = (userId || "").trim();
|
|
898
|
+
if (!uid && !token) {
|
|
899
|
+
return { pendingCount: 0, unseenCount: 0, totalCount: 0, authError: false };
|
|
900
|
+
}
|
|
901
|
+
const headers = buildWebHeaders(uid || void 0, token || void 0);
|
|
902
|
+
const url = "https://a6api.com/api/marketplace/price-notices";
|
|
903
|
+
try {
|
|
904
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(8e3) });
|
|
905
|
+
if (res.status === 401 || res.status === 403) {
|
|
906
|
+
console.warn("[dsh-a6api] fetchPriceFluctuation auth failed", res.status);
|
|
907
|
+
return { pendingCount: 0, unseenCount: 0, totalCount: 0, authError: true };
|
|
908
|
+
}
|
|
909
|
+
if (!res.ok) {
|
|
910
|
+
console.warn("[dsh-a6api] fetchPriceFluctuation HTTP", res.status);
|
|
911
|
+
return { pendingCount: 0, unseenCount: 0, totalCount: 0 };
|
|
912
|
+
}
|
|
913
|
+
const json = await res.json().catch(() => null);
|
|
914
|
+
if (!json) return { pendingCount: 0, unseenCount: 0, totalCount: 0 };
|
|
915
|
+
if (json.success === false) return { pendingCount: 0, unseenCount: 0, totalCount: 0 };
|
|
916
|
+
let arr = [];
|
|
917
|
+
if (Array.isArray(json)) arr = json;
|
|
918
|
+
else if (Array.isArray(json.data)) arr = json.data;
|
|
919
|
+
else if (Array.isArray(json.data?.notices)) arr = json.data.notices;
|
|
920
|
+
else if (Array.isArray(json.data?.items)) arr = json.data.items;
|
|
921
|
+
else if (Array.isArray(json.notices)) arr = json.notices;
|
|
922
|
+
else if (Array.isArray(json.items)) arr = json.items;
|
|
923
|
+
const pickWithPresent = (keys) => {
|
|
924
|
+
for (const k of keys) {
|
|
925
|
+
const v = json?.data?.[k] ?? json?.[k];
|
|
926
|
+
if (v !== void 0 && v !== null) {
|
|
927
|
+
const n = Number(v);
|
|
928
|
+
if (!Number.isNaN(n)) return { value: n, present: true };
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return { value: 0, present: false };
|
|
932
|
+
};
|
|
933
|
+
const pendingPick = pickWithPresent(["pendingCount", "pending_count", "pending", "openCount"]);
|
|
934
|
+
const unseenPick = pickWithPresent(["unseenCount", "unseen_count", "unseen", "has_unseen_count"]);
|
|
935
|
+
let pending = pendingPick.value;
|
|
936
|
+
let unseen = unseenPick.value;
|
|
937
|
+
const total = arr.length;
|
|
938
|
+
if (!pendingPick.present && arr.length > 0) {
|
|
939
|
+
const counted = arr.filter((n) => {
|
|
940
|
+
const s = String(n.state || n.status || "").toLowerCase();
|
|
941
|
+
return s === "open" || s === "pending" || n.pending === true;
|
|
942
|
+
}).length;
|
|
943
|
+
const hasState = arr.some((n) => n.state !== void 0 || n.status !== void 0);
|
|
944
|
+
if (hasState) pending = counted;
|
|
945
|
+
}
|
|
946
|
+
if (!unseenPick.present && arr.length > 0) {
|
|
947
|
+
unseen = arr.filter((n) => n.has_unseen === true || n.hasUnseen === true || n.unseen === true || n.is_unread === true).length;
|
|
948
|
+
}
|
|
949
|
+
return { pendingCount: pending, unseenCount: unseen, totalCount: total, notices: arr };
|
|
950
|
+
} catch (err) {
|
|
951
|
+
console.warn("[dsh-a6api] fetchPriceFluctuation error", err);
|
|
952
|
+
return { pendingCount: 0, unseenCount: 0, totalCount: 0 };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
895
955
|
|
|
896
956
|
// src/server/probe.ts
|
|
897
957
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -916,7 +976,8 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
|
|
|
916
976
|
messages: [{ role: "user", content: "1" }],
|
|
917
977
|
max_tokens: 1
|
|
918
978
|
}),
|
|
919
|
-
|
|
979
|
+
// 推理模型(如 grok-4.6)实测单次响应可达 40-90s+,阈值过短会被频繁掐断导致探测失败
|
|
980
|
+
signal: AbortSignal.timeout(18e4)
|
|
920
981
|
});
|
|
921
982
|
if (res.ok) {
|
|
922
983
|
requestOk = true;
|
|
@@ -925,11 +986,16 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
|
|
|
925
986
|
requestError = `HTTP ${res.status}: ${errText.slice(0, 150)}`;
|
|
926
987
|
}
|
|
927
988
|
} catch (err) {
|
|
928
|
-
|
|
989
|
+
const raw = err?.message || String(err);
|
|
990
|
+
if (raw.includes("aborted due to timeout") || err?.name === "TimeoutError") {
|
|
991
|
+
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";
|
|
992
|
+
} else {
|
|
993
|
+
requestError = raw;
|
|
994
|
+
}
|
|
929
995
|
}
|
|
930
996
|
const durationMs = Date.now() - startTime;
|
|
931
997
|
if (requestOk && (userId || accessToken)) {
|
|
932
|
-
await sleep(
|
|
998
|
+
await sleep(1200);
|
|
933
999
|
try {
|
|
934
1000
|
const logs = await fetchRecentLogs(userId, accessToken, 15);
|
|
935
1001
|
const minTimestamp = Math.floor(startTime / 1e3) - 10;
|
|
@@ -1019,6 +1085,13 @@ import * as fsp from "node:fs/promises";
|
|
|
1019
1085
|
import * as path from "node:path";
|
|
1020
1086
|
import * as os from "node:os";
|
|
1021
1087
|
var A6API_CRED_REF = "A6API_API_KEY";
|
|
1088
|
+
async function atomicWriteFile(filePath, content, mode = 384) {
|
|
1089
|
+
const dir = path.dirname(filePath);
|
|
1090
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
1091
|
+
const tmpPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
1092
|
+
await fsp.writeFile(tmpPath, content, { mode });
|
|
1093
|
+
await fsp.rename(tmpPath, filePath);
|
|
1094
|
+
}
|
|
1022
1095
|
function dshHome() {
|
|
1023
1096
|
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
1024
1097
|
}
|
|
@@ -1070,8 +1143,8 @@ async function readPluginConfig() {
|
|
|
1070
1143
|
}
|
|
1071
1144
|
async function savePluginConfig(config) {
|
|
1072
1145
|
const filePath = configFile();
|
|
1073
|
-
|
|
1074
|
-
await
|
|
1146
|
+
const { apiKey: _apiKey, ...safeConfig } = config;
|
|
1147
|
+
await atomicWriteFile(filePath, JSON.stringify(safeConfig, null, 2));
|
|
1075
1148
|
if (config.apiKey && config.apiKey.trim()) {
|
|
1076
1149
|
await writeCredentialKey(A6API_CRED_REF, config.apiKey.trim());
|
|
1077
1150
|
}
|
|
@@ -1144,8 +1217,7 @@ async function writeCredentialKey(refKey, value) {
|
|
|
1144
1217
|
lines.push("refs:", ` ${refKey}: ${JSON.stringify(value)}`);
|
|
1145
1218
|
}
|
|
1146
1219
|
}
|
|
1147
|
-
await
|
|
1148
|
-
await fsp.writeFile(cFile, lines.join("\n"), "utf8");
|
|
1220
|
+
await atomicWriteFile(cFile, lines.join("\n"), 384);
|
|
1149
1221
|
}
|
|
1150
1222
|
async function syncToDshSettings(baseURL, modelIds) {
|
|
1151
1223
|
const sFile = settingsFile();
|
|
@@ -1238,8 +1310,7 @@ async function syncToDshSettings(baseURL, modelIds) {
|
|
|
1238
1310
|
} else {
|
|
1239
1311
|
lines.push(`llm-pi-ai:`, ` providers:`, ...a6apiBlockLines);
|
|
1240
1312
|
}
|
|
1241
|
-
await
|
|
1242
|
-
await fsp.writeFile(sFile, lines.join("\n"), "utf8");
|
|
1313
|
+
await atomicWriteFile(sFile, lines.join("\n"), 420);
|
|
1243
1314
|
}
|
|
1244
1315
|
async function getDshConfiguredModels() {
|
|
1245
1316
|
try {
|
|
@@ -1279,13 +1350,23 @@ async function getDshConfiguredModels() {
|
|
|
1279
1350
|
var name = "@lynn123411/dsh-a6api";
|
|
1280
1351
|
var inject = ["webServer"];
|
|
1281
1352
|
var PREFIX = "/api/dsh-a6api";
|
|
1353
|
+
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
1354
|
+
function maskConfig(c) {
|
|
1355
|
+
return {
|
|
1356
|
+
...c,
|
|
1357
|
+
apiKey: c.apiKey ? MASK : "",
|
|
1358
|
+
accessToken: c.accessToken || c.sessionCookie ? MASK : "",
|
|
1359
|
+
sessionCookie: "",
|
|
1360
|
+
userId: c.userId ? MASK : "",
|
|
1361
|
+
hasApiKey: Boolean(c.apiKey),
|
|
1362
|
+
hasToken: Boolean(c.accessToken || c.sessionCookie)
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1282
1365
|
function sendJson(res, status, body) {
|
|
1283
1366
|
res.writeHead(status, {
|
|
1284
1367
|
"content-type": "application/json; charset=utf-8",
|
|
1285
|
-
"cache-control": "no-cache"
|
|
1286
|
-
|
|
1287
|
-
"access-control-allow-headers": "*",
|
|
1288
|
-
"access-control-allow-methods": "GET, POST, OPTIONS"
|
|
1368
|
+
"cache-control": "no-cache"
|
|
1369
|
+
// 不设 access-control-allow-origin:仅允许同源调用,阻断跨站读取与 CSRF 预检
|
|
1289
1370
|
});
|
|
1290
1371
|
res.end(JSON.stringify(body));
|
|
1291
1372
|
}
|
|
@@ -1313,6 +1394,7 @@ async function parseJsonBody(req) {
|
|
|
1313
1394
|
}
|
|
1314
1395
|
}
|
|
1315
1396
|
var merchantCardCache = /* @__PURE__ */ new Map();
|
|
1397
|
+
var MERCHANT_CARD_TTL_MS = 15 * 60 * 1e3;
|
|
1316
1398
|
function apply(ctx) {
|
|
1317
1399
|
const webServer = ctx.webServer || (ctx.get ? ctx.get("webServer") : null);
|
|
1318
1400
|
if (webServer && typeof webServer.register === "function") {
|
|
@@ -1324,11 +1406,7 @@ function apply(ctx) {
|
|
|
1324
1406
|
const url = new URL(req.url || "/", "http://localhost");
|
|
1325
1407
|
const pathname = url.pathname.replace(PREFIX, "") || "/";
|
|
1326
1408
|
if (req.method === "OPTIONS") {
|
|
1327
|
-
res.writeHead(204
|
|
1328
|
-
"access-control-allow-origin": "*",
|
|
1329
|
-
"access-control-allow-headers": "*",
|
|
1330
|
-
"access-control-allow-methods": "GET, POST, OPTIONS"
|
|
1331
|
-
});
|
|
1409
|
+
res.writeHead(204);
|
|
1332
1410
|
return res.end();
|
|
1333
1411
|
}
|
|
1334
1412
|
try {
|
|
@@ -1360,18 +1438,22 @@ function apply(ctx) {
|
|
|
1360
1438
|
];
|
|
1361
1439
|
}
|
|
1362
1440
|
if (config.userId || token) {
|
|
1363
|
-
const missing = modelIds.filter((m) =>
|
|
1441
|
+
const missing = modelIds.filter((m) => {
|
|
1442
|
+
const entry = merchantCardCache.get(m.toLowerCase());
|
|
1443
|
+
return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
|
|
1444
|
+
});
|
|
1364
1445
|
if (missing.length > 0) {
|
|
1365
1446
|
const found = await getKnownMerchantsFromLogs(config.userId, token, missing);
|
|
1366
1447
|
for (const [mName, card] of Object.entries(found)) {
|
|
1367
|
-
merchantCardCache.set(mName.toLowerCase(), card);
|
|
1448
|
+
merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
|
|
1368
1449
|
}
|
|
1369
1450
|
}
|
|
1370
1451
|
}
|
|
1371
1452
|
const dshSet = new Set(dshConfiguredModels);
|
|
1372
1453
|
const models = modelIds.map((mId) => {
|
|
1373
1454
|
const meta = resolveModelMeta(mId);
|
|
1374
|
-
const
|
|
1455
|
+
const cacheEntry = merchantCardCache.get(mId.toLowerCase());
|
|
1456
|
+
const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
|
|
1375
1457
|
return {
|
|
1376
1458
|
model_name: mId,
|
|
1377
1459
|
brand: meta.brand,
|
|
@@ -1386,7 +1468,7 @@ function apply(ctx) {
|
|
|
1386
1468
|
});
|
|
1387
1469
|
const recentLogs = await fetchRecentLogs(config.userId, token, 20);
|
|
1388
1470
|
const response = {
|
|
1389
|
-
config,
|
|
1471
|
+
config: maskConfig(config),
|
|
1390
1472
|
balance,
|
|
1391
1473
|
models,
|
|
1392
1474
|
dshConfiguredModels,
|
|
@@ -1397,10 +1479,11 @@ function apply(ctx) {
|
|
|
1397
1479
|
if (pathname === "/config" && req.method === "POST") {
|
|
1398
1480
|
const body = await parseJsonBody(req);
|
|
1399
1481
|
const current = await readPluginConfig();
|
|
1400
|
-
const rawToken = body.accessToken !== void 0 ? body.accessToken : body.sessionCookie !== void 0 ? body.sessionCookie : current.accessToken || current.sessionCookie || "";
|
|
1482
|
+
const rawToken = body.accessToken !== void 0 && body.accessToken !== MASK ? body.accessToken : body.sessionCookie !== void 0 && body.sessionCookie !== MASK ? body.sessionCookie : current.accessToken || current.sessionCookie || "";
|
|
1483
|
+
const newApiKey = body.apiKey !== void 0 && body.apiKey !== MASK ? body.apiKey : current.apiKey;
|
|
1401
1484
|
const updated = {
|
|
1402
1485
|
baseURL: body.baseURL !== void 0 ? body.baseURL : current.baseURL,
|
|
1403
|
-
apiKey:
|
|
1486
|
+
apiKey: newApiKey,
|
|
1404
1487
|
accessToken: rawToken,
|
|
1405
1488
|
sessionCookie: rawToken,
|
|
1406
1489
|
userId: body.userId !== void 0 ? body.userId : current.userId,
|
|
@@ -1415,7 +1498,7 @@ function apply(ctx) {
|
|
|
1415
1498
|
if (updated.activeModels.length > 0) {
|
|
1416
1499
|
await syncToDshSettings(updated.baseURL, updated.activeModels);
|
|
1417
1500
|
}
|
|
1418
|
-
return sendJson(res, 200, { ok: true, config: updated, balance });
|
|
1501
|
+
return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
|
|
1419
1502
|
}
|
|
1420
1503
|
if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
|
|
1421
1504
|
const config = await readPluginConfig();
|
|
@@ -1438,7 +1521,7 @@ function apply(ctx) {
|
|
|
1438
1521
|
if (modelName && modelName !== "all") {
|
|
1439
1522
|
const result = await probeSingleModel(config.baseURL, config.apiKey, config.userId, token, modelName);
|
|
1440
1523
|
if (result.merchant) {
|
|
1441
|
-
merchantCardCache.set(modelName.toLowerCase(), result.merchant);
|
|
1524
|
+
merchantCardCache.set(modelName.toLowerCase(), { card: result.merchant, at: Date.now() });
|
|
1442
1525
|
}
|
|
1443
1526
|
return sendJson(res, 200, { ok: true, result });
|
|
1444
1527
|
}
|
|
@@ -1453,7 +1536,7 @@ function apply(ctx) {
|
|
|
1453
1536
|
for (const m of modelIds) {
|
|
1454
1537
|
const r = await probeSingleModel(config.baseURL, config.apiKey, config.userId, token, m);
|
|
1455
1538
|
if (r.merchant) {
|
|
1456
|
-
merchantCardCache.set(m.toLowerCase(), r.merchant);
|
|
1539
|
+
merchantCardCache.set(m.toLowerCase(), { card: r.merchant, at: Date.now() });
|
|
1457
1540
|
}
|
|
1458
1541
|
results.push(r);
|
|
1459
1542
|
}
|
|
@@ -1471,6 +1554,17 @@ function apply(ctx) {
|
|
|
1471
1554
|
const dshConfiguredModels = await getDshConfiguredModels();
|
|
1472
1555
|
return sendJson(res, 200, { ok: true, dshConfiguredModels });
|
|
1473
1556
|
}
|
|
1557
|
+
if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
|
|
1558
|
+
const config = await readPluginConfig();
|
|
1559
|
+
const token = config.accessToken || config.sessionCookie || "";
|
|
1560
|
+
if (!token || !config.userId) {
|
|
1561
|
+
return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
|
|
1562
|
+
}
|
|
1563
|
+
const result = await fetchPriceFluctuation(config.userId, token, token);
|
|
1564
|
+
const { notices, ...counts } = result;
|
|
1565
|
+
const hasAuth = !counts.authError;
|
|
1566
|
+
return sendJson(res, 200, { ok: true, data: { pendingCount: counts.pendingCount, unseenCount: counts.unseenCount, totalCount: counts.totalCount, hasAuth, authError: Boolean(counts.authError), updatedAt: Date.now() } });
|
|
1567
|
+
}
|
|
1474
1568
|
return sendJson(res, 404, { ok: false, error: "Not found" });
|
|
1475
1569
|
} catch (err) {
|
|
1476
1570
|
console.error("[dsh-a6api] API error:", err);
|