@lynn123411/dsh-a6api 1.4.1 → 1.5.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.js CHANGED
@@ -810,11 +810,19 @@ async function fetchPriceFluctuation(userId, accessToken) {
810
810
  let unseen = unseenPick.value;
811
811
  const total = arr.length;
812
812
  if (!pendingPick.present && arr.length > 0) {
813
- const counted = arr.filter((n) => {
814
- const s = String(n.state || n.status || "").toLowerCase();
815
- return s === "open" || s === "pending" || n.pending === true;
816
- }).length;
817
- const hasState = arr.some((n) => n.state !== void 0 || n.status !== void 0);
813
+ const isPendingNotice = (n) => {
814
+ if (n?.pending === true) return true;
815
+ const rels = Array.isArray(n?.relations) ? n.relations : [];
816
+ if (rels.length > 0) {
817
+ return rels.some((r) => String(r?.state ?? "").toLowerCase() === "open");
818
+ }
819
+ const s = String(n?.state ?? n?.status ?? "").toLowerCase();
820
+ return s === "open" || s === "pending" || s === "effective";
821
+ };
822
+ const counted = arr.filter(isPendingNotice).length;
823
+ const hasState = arr.some(
824
+ (n) => n.state !== void 0 || n.status !== void 0 || Array.isArray(n.relations) && n.relations.length > 0
825
+ );
818
826
  if (hasState) pending = counted;
819
827
  }
820
828
  if (!unseenPick.present && arr.length > 0) {
@@ -1791,6 +1799,197 @@ function overlayPinsOnModels(models, pins, tokenId) {
1791
1799
  };
1792
1800
  });
1793
1801
  }
1802
+ function createUpstreamMemo(ttlMs, shouldCache) {
1803
+ let cache = /* @__PURE__ */ new Map();
1804
+ const inflight = /* @__PURE__ */ new Map();
1805
+ let epoch = 0;
1806
+ return {
1807
+ /**
1808
+ * 数据变更后调用:旧结果立即不可用。
1809
+ * 同时清空在途构建表:变更完成后才到达的请求不会「加入」变更前已开始的旧构建
1810
+ * (旧构建的写缓存仍有代际校验兜底),而是必然发起一次新构建拿到最新数据。
1811
+ */
1812
+ invalidate() {
1813
+ epoch += 1;
1814
+ cache.clear();
1815
+ inflight.clear();
1816
+ },
1817
+ async get(key, fetcher) {
1818
+ const hit = cache.get(key);
1819
+ if (hit && hit.epoch === epoch && Date.now() - hit.at < ttlMs) return hit.data;
1820
+ let pending = inflight.get(key);
1821
+ if (!pending) {
1822
+ const buildEpoch = epoch;
1823
+ const run = Promise.resolve().then(fetcher).then((data) => {
1824
+ if (buildEpoch === epoch && (!shouldCache || shouldCache(data))) {
1825
+ cache.set(key, { data, at: Date.now(), epoch: buildEpoch });
1826
+ }
1827
+ return data;
1828
+ }).finally(() => {
1829
+ if (inflight.get(key) === run) inflight.delete(key);
1830
+ });
1831
+ inflight.set(key, run);
1832
+ pending = run;
1833
+ }
1834
+ return pending;
1835
+ }
1836
+ };
1837
+ }
1838
+ var stateMemo = createUpstreamMemo(12e4);
1839
+ var priceCountsMemo = createUpstreamMemo(12e4, (counts) => !counts.authError);
1840
+ function stateCacheKeyOf(config) {
1841
+ const fingerprint = (s) => {
1842
+ let h = 0;
1843
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
1844
+ return (h >>> 0).toString(36);
1845
+ };
1846
+ return `${config.baseURL || ""}|${config.userId || ""}|${fingerprint(config.apiKey || "")}|${fingerprint(config.accessToken || "")}`;
1847
+ }
1848
+ async function getCachedStateResponse(config, configAccess) {
1849
+ return stateMemo.get(stateCacheKeyOf(config), () => buildStateResponse(config, configAccess));
1850
+ }
1851
+ async function buildStateResponse(config, configAccess) {
1852
+ const token = config.accessToken || "";
1853
+ const [balance, dshConfiguredModels, modelIdsRaw, allLogs, pins] = await Promise.all([
1854
+ fetchBalance(config.baseURL, config.apiKey, config.userId, token),
1855
+ configAccess.getDshConfiguredModels(),
1856
+ config.apiKey ? fetchTokenModels(config.baseURL, config.apiKey) : Promise.resolve([]),
1857
+ fetchRecentLogs(config.userId, token, 100),
1858
+ config.userId && token ? fetchMarketplacePins(config.userId, token).catch(() => []) : Promise.resolve([])
1859
+ ]);
1860
+ if (balance?.userId && String(balance.userId) !== config.userId) {
1861
+ tokenResolveCache = null;
1862
+ config.userId = String(balance.userId);
1863
+ await configAccess.writeConfig({ userId: config.userId });
1864
+ }
1865
+ let modelIds = modelIdsRaw;
1866
+ if (modelIds.length === 0) {
1867
+ modelIds = [
1868
+ .../* @__PURE__ */ new Set([
1869
+ ...dshConfiguredModels,
1870
+ "gpt-5.6-sol",
1871
+ "gpt-5.6-terra",
1872
+ "gpt-5.6-luna",
1873
+ "claude-fable-5",
1874
+ "claude-opus-5",
1875
+ "grok-4.6"
1876
+ ])
1877
+ ];
1878
+ }
1879
+ allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1880
+ if (config.userId || token) {
1881
+ const missing = modelIds.filter((m) => {
1882
+ const entry = merchantCardCache.get(m.toLowerCase());
1883
+ return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
1884
+ });
1885
+ if (missing.length > 0) {
1886
+ let found = {};
1887
+ try {
1888
+ found = await Promise.race([
1889
+ getKnownMerchantsFromLogs(config.userId, token, missing, allLogs),
1890
+ new Promise((resolve2) => setTimeout(() => resolve2({}), 1e4))
1891
+ ]);
1892
+ } catch {
1893
+ found = {};
1894
+ }
1895
+ for (const [mName, card] of Object.entries(found)) {
1896
+ merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
1897
+ }
1898
+ }
1899
+ }
1900
+ const lastRoutedMap = /* @__PURE__ */ new Map();
1901
+ for (const log of allLogs) {
1902
+ const mName = log.model_name;
1903
+ const chId = Number(log.channel);
1904
+ const ts = Number(log.created_at) || 0;
1905
+ if (mName && chId > 0 && ts > 0 && !lastRoutedMap.has(mName.toLowerCase())) {
1906
+ lastRoutedMap.set(mName.toLowerCase(), ts);
1907
+ }
1908
+ }
1909
+ const dshSet = new Set(dshConfiguredModels);
1910
+ let models = modelIds.map((mId) => {
1911
+ const meta = resolveModelMeta(mId);
1912
+ const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1913
+ const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
1914
+ const routedAt = lastRoutedMap.get(mId.toLowerCase());
1915
+ return {
1916
+ model_name: mId,
1917
+ brand: meta.brand,
1918
+ contextWindow: meta.contextWindow,
1919
+ maxTokens: meta.maxTokens,
1920
+ modalities: meta.modalities,
1921
+ hasReasoning: Boolean(meta.reasoningEfforts || meta.thinkingFormat),
1922
+ inDsh: dshSet.has(mId),
1923
+ merchant: cachedCard,
1924
+ probeStatus: cachedCard ? "success" : "idle",
1925
+ lastRoutedAt: routedAt,
1926
+ lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1927
+ };
1928
+ });
1929
+ const resolvedTokenId = pins.length > 0 ? await resolveTokenId(config) : null;
1930
+ models = overlayPinsOnModels(models, pins, resolvedTokenId);
1931
+ const rePointTargets = models.filter(
1932
+ (m) => m.pinStatus === "pin_elsewhere" && m.pinTokenMatched === true && m.pinnedChannelId && m.pinnedChannelId > 0
1933
+ ).map((m) => ({ modelName: m.model_name, channelId: m.pinnedChannelId }));
1934
+ if (rePointTargets.length > 0 && config.userId && token) {
1935
+ try {
1936
+ await Promise.race([
1937
+ (async () => {
1938
+ for (let i = 0; i < rePointTargets.length; i += 4) {
1939
+ const batch = rePointTargets.slice(i, i + 4);
1940
+ await Promise.all(
1941
+ batch.map(async ({ modelName, channelId }) => {
1942
+ try {
1943
+ const pinnedCard = await fetchChannelDetails(
1944
+ channelId,
1945
+ config.userId,
1946
+ token,
1947
+ modelName
1948
+ );
1949
+ if (pinnedCard && Number(pinnedCard.channel_id) === Number(channelId)) {
1950
+ merchantCardCache.set(modelName.toLowerCase(), { card: pinnedCard, at: Date.now() });
1951
+ }
1952
+ } catch {
1953
+ }
1954
+ })
1955
+ );
1956
+ }
1957
+ })(),
1958
+ new Promise((resolve2) => setTimeout(() => resolve2(), 1e4))
1959
+ ]);
1960
+ } catch {
1961
+ }
1962
+ models = models.map((m) => {
1963
+ if (m.pinStatus !== "pin_elsewhere" || m.pinTokenMatched !== true) return m;
1964
+ const entry = merchantCardCache.get(m.model_name.toLowerCase());
1965
+ const card = entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1966
+ if (card && Number(card.channel_id) === Number(m.pinnedChannelId)) {
1967
+ const pinnedLog = allLogs.find(
1968
+ (l) => l.model_name?.toLowerCase() === m.model_name.toLowerCase() && Number(l.channel) === m.pinnedChannelId
1969
+ );
1970
+ const pinnedAt = pinnedLog ? Number(pinnedLog.created_at) || 0 : void 0;
1971
+ return {
1972
+ ...m,
1973
+ merchant: card,
1974
+ pinStatus: "pin_here",
1975
+ probeStatus: "success",
1976
+ lastRoutedAt: pinnedAt,
1977
+ lastRoutedText: pinnedAt ? formatRelativeTime(pinnedAt) : void 0
1978
+ };
1979
+ }
1980
+ return m;
1981
+ });
1982
+ }
1983
+ const recentLogs = allLogs.slice(0, 20);
1984
+ return {
1985
+ config: maskConfig(config),
1986
+ balance,
1987
+ models,
1988
+ dshConfiguredModels,
1989
+ recentLogs,
1990
+ pins
1991
+ };
1992
+ }
1794
1993
  function apply(ctx) {
1795
1994
  const configAccess = createConfigAccess(ctx);
1796
1995
  void configAccess.ensureMigrated();
@@ -1813,146 +2012,7 @@ function apply(ctx) {
1813
2012
  try {
1814
2013
  if (pathname === "/state" && (req.method === "GET" || req.method === "HEAD")) {
1815
2014
  const config = await configAccess.readConfig();
1816
- const token = config.accessToken || "";
1817
- const [balance, dshConfiguredModels, modelIdsRaw, allLogs, pins] = await Promise.all([
1818
- fetchBalance(config.baseURL, config.apiKey, config.userId, token),
1819
- configAccess.getDshConfiguredModels(),
1820
- config.apiKey ? fetchTokenModels(config.baseURL, config.apiKey) : Promise.resolve([]),
1821
- fetchRecentLogs(config.userId, token, 100),
1822
- config.userId && token ? fetchMarketplacePins(config.userId, token).catch(() => []) : Promise.resolve([])
1823
- ]);
1824
- if (balance?.userId && String(balance.userId) !== config.userId) {
1825
- tokenResolveCache = null;
1826
- config.userId = String(balance.userId);
1827
- await configAccess.writeConfig({ userId: config.userId });
1828
- }
1829
- let modelIds = modelIdsRaw;
1830
- if (modelIds.length === 0) {
1831
- modelIds = [
1832
- .../* @__PURE__ */ new Set([
1833
- ...dshConfiguredModels,
1834
- "gpt-5.6-sol",
1835
- "gpt-5.6-terra",
1836
- "gpt-5.6-luna",
1837
- "claude-fable-5",
1838
- "claude-opus-5",
1839
- "grok-4.6"
1840
- ])
1841
- ];
1842
- }
1843
- allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1844
- if (config.userId || token) {
1845
- const missing = modelIds.filter((m) => {
1846
- const entry = merchantCardCache.get(m.toLowerCase());
1847
- return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
1848
- });
1849
- if (missing.length > 0) {
1850
- let found = {};
1851
- try {
1852
- found = await Promise.race([
1853
- getKnownMerchantsFromLogs(config.userId, token, missing, allLogs),
1854
- new Promise((resolve2) => setTimeout(() => resolve2({}), 1e4))
1855
- ]);
1856
- } catch {
1857
- found = {};
1858
- }
1859
- for (const [mName, card] of Object.entries(found)) {
1860
- merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
1861
- }
1862
- }
1863
- }
1864
- const lastRoutedMap = /* @__PURE__ */ new Map();
1865
- for (const log of allLogs) {
1866
- const mName = log.model_name;
1867
- const chId = Number(log.channel);
1868
- const ts = Number(log.created_at) || 0;
1869
- if (mName && chId > 0 && ts > 0 && !lastRoutedMap.has(mName.toLowerCase())) {
1870
- lastRoutedMap.set(mName.toLowerCase(), ts);
1871
- }
1872
- }
1873
- const dshSet = new Set(dshConfiguredModels);
1874
- let models = modelIds.map((mId) => {
1875
- const meta = resolveModelMeta(mId);
1876
- const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1877
- const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
1878
- const routedAt = lastRoutedMap.get(mId.toLowerCase());
1879
- return {
1880
- model_name: mId,
1881
- brand: meta.brand,
1882
- contextWindow: meta.contextWindow,
1883
- maxTokens: meta.maxTokens,
1884
- modalities: meta.modalities,
1885
- hasReasoning: Boolean(meta.reasoningEfforts || meta.thinkingFormat),
1886
- inDsh: dshSet.has(mId),
1887
- merchant: cachedCard,
1888
- probeStatus: cachedCard ? "success" : "idle",
1889
- lastRoutedAt: routedAt,
1890
- lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1891
- };
1892
- });
1893
- const resolvedTokenId = pins.length > 0 ? await resolveTokenId(config) : null;
1894
- models = overlayPinsOnModels(models, pins, resolvedTokenId);
1895
- const rePointTargets = models.filter(
1896
- (m) => m.pinStatus === "pin_elsewhere" && m.pinTokenMatched === true && m.pinnedChannelId && m.pinnedChannelId > 0
1897
- ).map((m) => ({ modelName: m.model_name, channelId: m.pinnedChannelId }));
1898
- if (rePointTargets.length > 0 && config.userId && token) {
1899
- try {
1900
- await Promise.race([
1901
- (async () => {
1902
- for (let i = 0; i < rePointTargets.length; i += 4) {
1903
- const batch = rePointTargets.slice(i, i + 4);
1904
- await Promise.all(
1905
- batch.map(async ({ modelName, channelId }) => {
1906
- try {
1907
- const pinnedCard = await fetchChannelDetails(
1908
- channelId,
1909
- config.userId,
1910
- token,
1911
- modelName
1912
- );
1913
- if (pinnedCard && Number(pinnedCard.channel_id) === Number(channelId)) {
1914
- merchantCardCache.set(modelName.toLowerCase(), { card: pinnedCard, at: Date.now() });
1915
- }
1916
- } catch {
1917
- }
1918
- })
1919
- );
1920
- }
1921
- })(),
1922
- new Promise((resolve2) => setTimeout(() => resolve2(), 1e4))
1923
- ]);
1924
- } catch {
1925
- }
1926
- models = models.map((m) => {
1927
- if (m.pinStatus !== "pin_elsewhere" || m.pinTokenMatched !== true) return m;
1928
- const entry = merchantCardCache.get(m.model_name.toLowerCase());
1929
- const card = entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1930
- if (card && Number(card.channel_id) === Number(m.pinnedChannelId)) {
1931
- const pinnedLog = allLogs.find(
1932
- (l) => l.model_name?.toLowerCase() === m.model_name.toLowerCase() && Number(l.channel) === m.pinnedChannelId
1933
- );
1934
- const pinnedAt = pinnedLog ? Number(pinnedLog.created_at) || 0 : void 0;
1935
- return {
1936
- ...m,
1937
- merchant: card,
1938
- pinStatus: "pin_here",
1939
- probeStatus: "success",
1940
- lastRoutedAt: pinnedAt,
1941
- lastRoutedText: pinnedAt ? formatRelativeTime(pinnedAt) : void 0
1942
- };
1943
- }
1944
- return m;
1945
- });
1946
- }
1947
- const recentLogs = allLogs.slice(0, 20);
1948
- const response = {
1949
- config: maskConfig(config),
1950
- balance,
1951
- models,
1952
- dshConfiguredModels,
1953
- recentLogs,
1954
- pins
1955
- };
2015
+ const response = await getCachedStateResponse(config, configAccess);
1956
2016
  return sendJson(res, 200, { ok: true, data: response });
1957
2017
  }
1958
2018
  if (pathname === "/config" && req.method === "POST") {
@@ -1985,6 +2045,8 @@ function apply(ctx) {
1985
2045
  if (updated.activeModels.length > 0) {
1986
2046
  await configAccess.syncModels(updated.baseURL, updated.activeModels);
1987
2047
  }
2048
+ stateMemo.invalidate();
2049
+ priceCountsMemo.invalidate();
1988
2050
  return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
1989
2051
  }
1990
2052
  if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2010,6 +2072,7 @@ function apply(ctx) {
2010
2072
  if (result.merchant) {
2011
2073
  merchantCardCache.set(modelName.toLowerCase(), { card: result.merchant, at: Date.now() });
2012
2074
  }
2075
+ stateMemo.invalidate();
2013
2076
  return sendJson(res, 200, { ok: true, result });
2014
2077
  }
2015
2078
  let modelIds = body.modelNames;
@@ -2027,6 +2090,7 @@ function apply(ctx) {
2027
2090
  }
2028
2091
  results.push(r);
2029
2092
  }
2093
+ stateMemo.invalidate();
2030
2094
  return sendJson(res, 200, { ok: true, results });
2031
2095
  }
2032
2096
  if (pathname === "/sync-models" && req.method === "POST") {
@@ -2037,6 +2101,7 @@ function apply(ctx) {
2037
2101
  const baseURL = body.baseURL || config.baseURL;
2038
2102
  await configAccess.syncModels(baseURL, modelIds);
2039
2103
  const dshConfiguredModels = await configAccess.getDshConfiguredModels();
2104
+ stateMemo.invalidate();
2040
2105
  return sendJson(res, 200, { ok: true, dshConfiguredModels });
2041
2106
  }
2042
2107
  if (pathname === "/pin" && req.method === "POST") {
@@ -2051,8 +2116,10 @@ function apply(ctx) {
2051
2116
  let card = cachedMerchantOf(modelName);
2052
2117
  let tokenId = await resolveTokenId(config);
2053
2118
  if ((!card || !tokenId) && config.apiKey) {
2119
+ let probedOk = false;
2054
2120
  try {
2055
2121
  const probe = await probeSingleModel(config.baseURL, config.apiKey, userId, token, modelName);
2122
+ probedOk = Boolean(probe && probe.success);
2056
2123
  if (!tokenId && probe.tokenId && Number(probe.tokenId) > 0) tokenId = Number(probe.tokenId);
2057
2124
  if (!card && probe.merchant) {
2058
2125
  card = probe.merchant;
@@ -2060,6 +2127,7 @@ function apply(ctx) {
2060
2127
  }
2061
2128
  } catch {
2062
2129
  }
2130
+ if (probedOk) stateMemo.invalidate();
2063
2131
  }
2064
2132
  if (!card) {
2065
2133
  return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
@@ -2086,6 +2154,7 @@ function apply(ctx) {
2086
2154
  at: Date.now()
2087
2155
  });
2088
2156
  const pinList = await fetchMarketplacePins(userId, token);
2157
+ stateMemo.invalidate();
2089
2158
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u56FA\u5B9A ${modelName} \u81F3\u5546\u6237 #${card.channel_id}`, pins: pinList, tokenId });
2090
2159
  }
2091
2160
  if (pathname === "/unpin" && req.method === "POST") {
@@ -2113,6 +2182,7 @@ function apply(ctx) {
2113
2182
  });
2114
2183
  }
2115
2184
  const pinList = await fetchMarketplacePins(userId, token);
2185
+ stateMemo.invalidate();
2116
2186
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u53D6\u6D88\u56FA\u5B9A ${modelName}`, pins: pinList, tokenId });
2117
2187
  }
2118
2188
  if (pathname === "/disable" && req.method === "POST") {
@@ -2122,14 +2192,17 @@ function apply(ctx) {
2122
2192
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2123
2193
  let card = cachedMerchantOf(modelName);
2124
2194
  if (!card && config.apiKey) {
2195
+ let probedOk = false;
2125
2196
  try {
2126
2197
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2198
+ probedOk = Boolean(probe && probe.success);
2127
2199
  if (probe.merchant) {
2128
2200
  card = probe.merchant;
2129
2201
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2130
2202
  }
2131
2203
  } catch {
2132
2204
  }
2205
+ if (probedOk) stateMemo.invalidate();
2133
2206
  }
2134
2207
  if (!card || !card.channel_id) {
2135
2208
  return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
@@ -2146,6 +2219,7 @@ function apply(ctx) {
2146
2219
  card: { ...card, user_channel_disabled: true },
2147
2220
  at: Date.now()
2148
2221
  });
2222
+ stateMemo.invalidate();
2149
2223
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u7981\u7528\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2150
2224
  }
2151
2225
  if (pathname === "/restore" && req.method === "POST") {
@@ -2155,14 +2229,17 @@ function apply(ctx) {
2155
2229
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2156
2230
  let card = cachedMerchantOf(modelName);
2157
2231
  if (!card && config.apiKey) {
2232
+ let probedOk = false;
2158
2233
  try {
2159
2234
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2235
+ probedOk = Boolean(probe && probe.success);
2160
2236
  if (probe.merchant) {
2161
2237
  card = probe.merchant;
2162
2238
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2163
2239
  }
2164
2240
  } catch {
2165
2241
  }
2242
+ if (probedOk) stateMemo.invalidate();
2166
2243
  }
2167
2244
  if (!card || !card.channel_id) {
2168
2245
  return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
@@ -2179,6 +2256,7 @@ function apply(ctx) {
2179
2256
  card: { ...card, user_channel_disabled: false },
2180
2257
  at: Date.now()
2181
2258
  });
2259
+ stateMemo.invalidate();
2182
2260
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u6062\u590D\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2183
2261
  }
2184
2262
  if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2187,8 +2265,11 @@ function apply(ctx) {
2187
2265
  if (!token || !config.userId) {
2188
2266
  return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
2189
2267
  }
2190
- const result = await fetchPriceFluctuation(config.userId, token);
2191
- const { notices, ...counts } = result;
2268
+ const counts = await priceCountsMemo.get(stateCacheKeyOf(config), async () => {
2269
+ const result = await fetchPriceFluctuation(config.userId, token);
2270
+ const { notices, ...rest } = result;
2271
+ return rest;
2272
+ });
2192
2273
  const hasAuth = !counts.authError;
2193
2274
  return sendJson(res, 200, { ok: true, data: { pendingCount: counts.pendingCount, unseenCount: counts.unseenCount, totalCount: counts.totalCount, hasAuth, authError: Boolean(counts.authError), updatedAt: Date.now() } });
2194
2275
  }
@@ -2211,6 +2292,7 @@ function apply(ctx) {
2211
2292
  }
2212
2293
  if (pathname === "/catalog/clear" && req.method === "POST") {
2213
2294
  await clearCatalog();
2295
+ stateMemo.invalidate();
2214
2296
  return sendJson(res, 200, { ok: true });
2215
2297
  }
2216
2298
  if (pathname === "/catalog/fetch-models" && req.method === "POST") {
@@ -2231,6 +2313,7 @@ function apply(ctx) {
2231
2313
  await upsertCatalogEntries(
2232
2314
  models.map((m) => ({ id: m.id, brand: m.brand, reasoningEfforts: m.reasoningEfforts }))
2233
2315
  );
2316
+ stateMemo.invalidate();
2234
2317
  return sendJson(res, 200, {
2235
2318
  ok: true,
2236
2319
  total: models.length,
@@ -2246,6 +2329,7 @@ function apply(ctx) {
2246
2329
  return sendJson(res, 400, { ok: false, error: "\u76EE\u5F55\u4E3A\u7A7A\uFF0C\u8BF7\u5148\u300C\u4ECE A6API \u83B7\u53D6\u5E02\u573A\u6A21\u578B\u300D" });
2247
2330
  }
2248
2331
  const result = await queryOpenRouter(modelIds);
2332
+ stateMemo.invalidate();
2249
2333
  return sendJson(res, 200, {
2250
2334
  ok: true,
2251
2335
  updated: result.updated.length,
@@ -2308,6 +2392,7 @@ function apply(ctx) {
2308
2392
  } catch (err) {
2309
2393
  console.warn("[dsh-a6api] catalog update: resync settings failed:", err?.message || err);
2310
2394
  }
2395
+ stateMemo.invalidate();
2311
2396
  return sendJson(res, 200, { ok: true, entry });
2312
2397
  }
2313
2398
  return sendJson(res, 404, { ok: false, error: "Not found" });