@lynn123411/dsh-a6api 1.5.0 → 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
@@ -1799,6 +1799,197 @@ function overlayPinsOnModels(models, pins, tokenId) {
1799
1799
  };
1800
1800
  });
1801
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
+ }
1802
1993
  function apply(ctx) {
1803
1994
  const configAccess = createConfigAccess(ctx);
1804
1995
  void configAccess.ensureMigrated();
@@ -1821,146 +2012,7 @@ function apply(ctx) {
1821
2012
  try {
1822
2013
  if (pathname === "/state" && (req.method === "GET" || req.method === "HEAD")) {
1823
2014
  const config = await configAccess.readConfig();
1824
- const token = config.accessToken || "";
1825
- const [balance, dshConfiguredModels, modelIdsRaw, allLogs, pins] = await Promise.all([
1826
- fetchBalance(config.baseURL, config.apiKey, config.userId, token),
1827
- configAccess.getDshConfiguredModels(),
1828
- config.apiKey ? fetchTokenModels(config.baseURL, config.apiKey) : Promise.resolve([]),
1829
- fetchRecentLogs(config.userId, token, 100),
1830
- config.userId && token ? fetchMarketplacePins(config.userId, token).catch(() => []) : Promise.resolve([])
1831
- ]);
1832
- if (balance?.userId && String(balance.userId) !== config.userId) {
1833
- tokenResolveCache = null;
1834
- config.userId = String(balance.userId);
1835
- await configAccess.writeConfig({ userId: config.userId });
1836
- }
1837
- let modelIds = modelIdsRaw;
1838
- if (modelIds.length === 0) {
1839
- modelIds = [
1840
- .../* @__PURE__ */ new Set([
1841
- ...dshConfiguredModels,
1842
- "gpt-5.6-sol",
1843
- "gpt-5.6-terra",
1844
- "gpt-5.6-luna",
1845
- "claude-fable-5",
1846
- "claude-opus-5",
1847
- "grok-4.6"
1848
- ])
1849
- ];
1850
- }
1851
- allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1852
- if (config.userId || token) {
1853
- const missing = modelIds.filter((m) => {
1854
- const entry = merchantCardCache.get(m.toLowerCase());
1855
- return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
1856
- });
1857
- if (missing.length > 0) {
1858
- let found = {};
1859
- try {
1860
- found = await Promise.race([
1861
- getKnownMerchantsFromLogs(config.userId, token, missing, allLogs),
1862
- new Promise((resolve2) => setTimeout(() => resolve2({}), 1e4))
1863
- ]);
1864
- } catch {
1865
- found = {};
1866
- }
1867
- for (const [mName, card] of Object.entries(found)) {
1868
- merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
1869
- }
1870
- }
1871
- }
1872
- const lastRoutedMap = /* @__PURE__ */ new Map();
1873
- for (const log of allLogs) {
1874
- const mName = log.model_name;
1875
- const chId = Number(log.channel);
1876
- const ts = Number(log.created_at) || 0;
1877
- if (mName && chId > 0 && ts > 0 && !lastRoutedMap.has(mName.toLowerCase())) {
1878
- lastRoutedMap.set(mName.toLowerCase(), ts);
1879
- }
1880
- }
1881
- const dshSet = new Set(dshConfiguredModels);
1882
- let models = modelIds.map((mId) => {
1883
- const meta = resolveModelMeta(mId);
1884
- const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1885
- const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
1886
- const routedAt = lastRoutedMap.get(mId.toLowerCase());
1887
- return {
1888
- model_name: mId,
1889
- brand: meta.brand,
1890
- contextWindow: meta.contextWindow,
1891
- maxTokens: meta.maxTokens,
1892
- modalities: meta.modalities,
1893
- hasReasoning: Boolean(meta.reasoningEfforts || meta.thinkingFormat),
1894
- inDsh: dshSet.has(mId),
1895
- merchant: cachedCard,
1896
- probeStatus: cachedCard ? "success" : "idle",
1897
- lastRoutedAt: routedAt,
1898
- lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1899
- };
1900
- });
1901
- const resolvedTokenId = pins.length > 0 ? await resolveTokenId(config) : null;
1902
- models = overlayPinsOnModels(models, pins, resolvedTokenId);
1903
- const rePointTargets = models.filter(
1904
- (m) => m.pinStatus === "pin_elsewhere" && m.pinTokenMatched === true && m.pinnedChannelId && m.pinnedChannelId > 0
1905
- ).map((m) => ({ modelName: m.model_name, channelId: m.pinnedChannelId }));
1906
- if (rePointTargets.length > 0 && config.userId && token) {
1907
- try {
1908
- await Promise.race([
1909
- (async () => {
1910
- for (let i = 0; i < rePointTargets.length; i += 4) {
1911
- const batch = rePointTargets.slice(i, i + 4);
1912
- await Promise.all(
1913
- batch.map(async ({ modelName, channelId }) => {
1914
- try {
1915
- const pinnedCard = await fetchChannelDetails(
1916
- channelId,
1917
- config.userId,
1918
- token,
1919
- modelName
1920
- );
1921
- if (pinnedCard && Number(pinnedCard.channel_id) === Number(channelId)) {
1922
- merchantCardCache.set(modelName.toLowerCase(), { card: pinnedCard, at: Date.now() });
1923
- }
1924
- } catch {
1925
- }
1926
- })
1927
- );
1928
- }
1929
- })(),
1930
- new Promise((resolve2) => setTimeout(() => resolve2(), 1e4))
1931
- ]);
1932
- } catch {
1933
- }
1934
- models = models.map((m) => {
1935
- if (m.pinStatus !== "pin_elsewhere" || m.pinTokenMatched !== true) return m;
1936
- const entry = merchantCardCache.get(m.model_name.toLowerCase());
1937
- const card = entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1938
- if (card && Number(card.channel_id) === Number(m.pinnedChannelId)) {
1939
- const pinnedLog = allLogs.find(
1940
- (l) => l.model_name?.toLowerCase() === m.model_name.toLowerCase() && Number(l.channel) === m.pinnedChannelId
1941
- );
1942
- const pinnedAt = pinnedLog ? Number(pinnedLog.created_at) || 0 : void 0;
1943
- return {
1944
- ...m,
1945
- merchant: card,
1946
- pinStatus: "pin_here",
1947
- probeStatus: "success",
1948
- lastRoutedAt: pinnedAt,
1949
- lastRoutedText: pinnedAt ? formatRelativeTime(pinnedAt) : void 0
1950
- };
1951
- }
1952
- return m;
1953
- });
1954
- }
1955
- const recentLogs = allLogs.slice(0, 20);
1956
- const response = {
1957
- config: maskConfig(config),
1958
- balance,
1959
- models,
1960
- dshConfiguredModels,
1961
- recentLogs,
1962
- pins
1963
- };
2015
+ const response = await getCachedStateResponse(config, configAccess);
1964
2016
  return sendJson(res, 200, { ok: true, data: response });
1965
2017
  }
1966
2018
  if (pathname === "/config" && req.method === "POST") {
@@ -1993,6 +2045,8 @@ function apply(ctx) {
1993
2045
  if (updated.activeModels.length > 0) {
1994
2046
  await configAccess.syncModels(updated.baseURL, updated.activeModels);
1995
2047
  }
2048
+ stateMemo.invalidate();
2049
+ priceCountsMemo.invalidate();
1996
2050
  return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
1997
2051
  }
1998
2052
  if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2018,6 +2072,7 @@ function apply(ctx) {
2018
2072
  if (result.merchant) {
2019
2073
  merchantCardCache.set(modelName.toLowerCase(), { card: result.merchant, at: Date.now() });
2020
2074
  }
2075
+ stateMemo.invalidate();
2021
2076
  return sendJson(res, 200, { ok: true, result });
2022
2077
  }
2023
2078
  let modelIds = body.modelNames;
@@ -2035,6 +2090,7 @@ function apply(ctx) {
2035
2090
  }
2036
2091
  results.push(r);
2037
2092
  }
2093
+ stateMemo.invalidate();
2038
2094
  return sendJson(res, 200, { ok: true, results });
2039
2095
  }
2040
2096
  if (pathname === "/sync-models" && req.method === "POST") {
@@ -2045,6 +2101,7 @@ function apply(ctx) {
2045
2101
  const baseURL = body.baseURL || config.baseURL;
2046
2102
  await configAccess.syncModels(baseURL, modelIds);
2047
2103
  const dshConfiguredModels = await configAccess.getDshConfiguredModels();
2104
+ stateMemo.invalidate();
2048
2105
  return sendJson(res, 200, { ok: true, dshConfiguredModels });
2049
2106
  }
2050
2107
  if (pathname === "/pin" && req.method === "POST") {
@@ -2059,8 +2116,10 @@ function apply(ctx) {
2059
2116
  let card = cachedMerchantOf(modelName);
2060
2117
  let tokenId = await resolveTokenId(config);
2061
2118
  if ((!card || !tokenId) && config.apiKey) {
2119
+ let probedOk = false;
2062
2120
  try {
2063
2121
  const probe = await probeSingleModel(config.baseURL, config.apiKey, userId, token, modelName);
2122
+ probedOk = Boolean(probe && probe.success);
2064
2123
  if (!tokenId && probe.tokenId && Number(probe.tokenId) > 0) tokenId = Number(probe.tokenId);
2065
2124
  if (!card && probe.merchant) {
2066
2125
  card = probe.merchant;
@@ -2068,6 +2127,7 @@ function apply(ctx) {
2068
2127
  }
2069
2128
  } catch {
2070
2129
  }
2130
+ if (probedOk) stateMemo.invalidate();
2071
2131
  }
2072
2132
  if (!card) {
2073
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" });
@@ -2094,6 +2154,7 @@ function apply(ctx) {
2094
2154
  at: Date.now()
2095
2155
  });
2096
2156
  const pinList = await fetchMarketplacePins(userId, token);
2157
+ stateMemo.invalidate();
2097
2158
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u56FA\u5B9A ${modelName} \u81F3\u5546\u6237 #${card.channel_id}`, pins: pinList, tokenId });
2098
2159
  }
2099
2160
  if (pathname === "/unpin" && req.method === "POST") {
@@ -2121,6 +2182,7 @@ function apply(ctx) {
2121
2182
  });
2122
2183
  }
2123
2184
  const pinList = await fetchMarketplacePins(userId, token);
2185
+ stateMemo.invalidate();
2124
2186
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u53D6\u6D88\u56FA\u5B9A ${modelName}`, pins: pinList, tokenId });
2125
2187
  }
2126
2188
  if (pathname === "/disable" && req.method === "POST") {
@@ -2130,14 +2192,17 @@ function apply(ctx) {
2130
2192
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2131
2193
  let card = cachedMerchantOf(modelName);
2132
2194
  if (!card && config.apiKey) {
2195
+ let probedOk = false;
2133
2196
  try {
2134
2197
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2198
+ probedOk = Boolean(probe && probe.success);
2135
2199
  if (probe.merchant) {
2136
2200
  card = probe.merchant;
2137
2201
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2138
2202
  }
2139
2203
  } catch {
2140
2204
  }
2205
+ if (probedOk) stateMemo.invalidate();
2141
2206
  }
2142
2207
  if (!card || !card.channel_id) {
2143
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" });
@@ -2154,6 +2219,7 @@ function apply(ctx) {
2154
2219
  card: { ...card, user_channel_disabled: true },
2155
2220
  at: Date.now()
2156
2221
  });
2222
+ stateMemo.invalidate();
2157
2223
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u7981\u7528\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2158
2224
  }
2159
2225
  if (pathname === "/restore" && req.method === "POST") {
@@ -2163,14 +2229,17 @@ function apply(ctx) {
2163
2229
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2164
2230
  let card = cachedMerchantOf(modelName);
2165
2231
  if (!card && config.apiKey) {
2232
+ let probedOk = false;
2166
2233
  try {
2167
2234
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2235
+ probedOk = Boolean(probe && probe.success);
2168
2236
  if (probe.merchant) {
2169
2237
  card = probe.merchant;
2170
2238
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2171
2239
  }
2172
2240
  } catch {
2173
2241
  }
2242
+ if (probedOk) stateMemo.invalidate();
2174
2243
  }
2175
2244
  if (!card || !card.channel_id) {
2176
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" });
@@ -2187,6 +2256,7 @@ function apply(ctx) {
2187
2256
  card: { ...card, user_channel_disabled: false },
2188
2257
  at: Date.now()
2189
2258
  });
2259
+ stateMemo.invalidate();
2190
2260
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u6062\u590D\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2191
2261
  }
2192
2262
  if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2195,8 +2265,11 @@ function apply(ctx) {
2195
2265
  if (!token || !config.userId) {
2196
2266
  return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
2197
2267
  }
2198
- const result = await fetchPriceFluctuation(config.userId, token);
2199
- 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
+ });
2200
2273
  const hasAuth = !counts.authError;
2201
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() } });
2202
2275
  }
@@ -2219,6 +2292,7 @@ function apply(ctx) {
2219
2292
  }
2220
2293
  if (pathname === "/catalog/clear" && req.method === "POST") {
2221
2294
  await clearCatalog();
2295
+ stateMemo.invalidate();
2222
2296
  return sendJson(res, 200, { ok: true });
2223
2297
  }
2224
2298
  if (pathname === "/catalog/fetch-models" && req.method === "POST") {
@@ -2239,6 +2313,7 @@ function apply(ctx) {
2239
2313
  await upsertCatalogEntries(
2240
2314
  models.map((m) => ({ id: m.id, brand: m.brand, reasoningEfforts: m.reasoningEfforts }))
2241
2315
  );
2316
+ stateMemo.invalidate();
2242
2317
  return sendJson(res, 200, {
2243
2318
  ok: true,
2244
2319
  total: models.length,
@@ -2254,6 +2329,7 @@ function apply(ctx) {
2254
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" });
2255
2330
  }
2256
2331
  const result = await queryOpenRouter(modelIds);
2332
+ stateMemo.invalidate();
2257
2333
  return sendJson(res, 200, {
2258
2334
  ok: true,
2259
2335
  updated: result.updated.length,
@@ -2316,6 +2392,7 @@ function apply(ctx) {
2316
2392
  } catch (err) {
2317
2393
  console.warn("[dsh-a6api] catalog update: resync settings failed:", err?.message || err);
2318
2394
  }
2395
+ stateMemo.invalidate();
2319
2396
  return sendJson(res, 200, { ok: true, entry });
2320
2397
  }
2321
2398
  return sendJson(res, 404, { ok: false, error: "Not found" });