@lynn123411/dsh-a6api 1.5.0 → 1.5.2

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
@@ -386,6 +386,17 @@ function formatCnyPrice(micros, exchangeRate = 6.7209) {
386
386
  if (cny < 1) return `\xA5${cny.toFixed(4)}`;
387
387
  return `\xA5${cny.toFixed(3)}`;
388
388
  }
389
+ var BLENDED_OUT_SHARE = 35e-4;
390
+ function computeBlendedPrice100m(inMicros, cacheReadMicros, outMicros, cacheHitRatePct, exchangeRate) {
391
+ const inY = inMicros / 1e6 * exchangeRate;
392
+ const hitY = cacheReadMicros / 1e6 * exchangeRate;
393
+ const outY = outMicros / 1e6 * exchangeRate;
394
+ if (inY <= 0 && hitY <= 0 && outY <= 0) return void 0;
395
+ const h = Math.min(1, Math.max(0, cacheHitRatePct / 100));
396
+ const inShare = 1 - BLENDED_OUT_SHARE;
397
+ const per1M = h * inShare * hitY + (1 - h) * inShare * inY + BLENDED_OUT_SHARE * outY;
398
+ return per1M * 100;
399
+ }
389
400
  function buildWebHeaders2(userId, accessToken) {
390
401
  const headers = {
391
402
  Accept: "application/json",
@@ -648,6 +659,7 @@ async function fetchChannelDetails(channelId, userId, accessToken, targetModelNa
648
659
  const recentSuccessRate = item.recent_success_rate !== void 0 ? Number(item.recent_success_rate) / 100 : 100;
649
660
  const cacheHitRate = item.cache_hit_rate_24h !== void 0 ? Number(item.cache_hit_rate_24h) / 100 : 72;
650
661
  const lastSuccessAt = Number(item.last_success_at || item.last_test_time || 0);
662
+ const blended100m = computeBlendedPrice100m(inMicros, cacheReadMicros, outMicros, cacheHitRate, rate);
651
663
  const ratioCny = Number(item.realtime_ratio_cny || inMicros / 1e6 * rate || 0.0341);
652
664
  const ratioFormatted = ratioCny.toFixed(4);
653
665
  return {
@@ -686,6 +698,7 @@ async function fetchChannelDetails(channelId, userId, accessToken, targetModelNa
686
698
  p50_ttft_ms: Number(item.p50_ttft_ms || 2273),
687
699
  recent_p50_ms: Number(item.recent_p50_ms || item.last_test_response_ms || 2340),
688
700
  cache_hit_rate_pct: cacheHitRate,
701
+ blended_price_100m_cny: blended100m,
689
702
  labels,
690
703
  last_success_at: lastSuccessAt,
691
704
  last_success_text: formatRelativeTime(lastSuccessAt),
@@ -756,6 +769,7 @@ async function fetchChannelDetails(channelId, userId, accessToken, targetModelNa
756
769
  recent_p50_ms: Number(logSnapshot.use_time ? logSnapshot.use_time * 1e3 : 2340),
757
770
  p50_ttft_ms: 2273,
758
771
  cache_hit_rate_pct: 72,
772
+ blended_price_100m_cny: computeBlendedPrice100m(inMicros, cacheReadMicros, outMicros, 72, rate),
759
773
  labels: ["\u7A33\u5B9A", "\u4F4E\u4EF7", "\u9AD8\u901F", "\u9AD8\u8D28"],
760
774
  last_success_at: Math.floor(Date.now() / 1e3),
761
775
  last_success_text: "\u521A\u521A",
@@ -1799,6 +1813,197 @@ function overlayPinsOnModels(models, pins, tokenId) {
1799
1813
  };
1800
1814
  });
1801
1815
  }
1816
+ function createUpstreamMemo(ttlMs, shouldCache) {
1817
+ let cache = /* @__PURE__ */ new Map();
1818
+ const inflight = /* @__PURE__ */ new Map();
1819
+ let epoch = 0;
1820
+ return {
1821
+ /**
1822
+ * 数据变更后调用:旧结果立即不可用。
1823
+ * 同时清空在途构建表:变更完成后才到达的请求不会「加入」变更前已开始的旧构建
1824
+ * (旧构建的写缓存仍有代际校验兜底),而是必然发起一次新构建拿到最新数据。
1825
+ */
1826
+ invalidate() {
1827
+ epoch += 1;
1828
+ cache.clear();
1829
+ inflight.clear();
1830
+ },
1831
+ async get(key, fetcher) {
1832
+ const hit = cache.get(key);
1833
+ if (hit && hit.epoch === epoch && Date.now() - hit.at < ttlMs) return hit.data;
1834
+ let pending = inflight.get(key);
1835
+ if (!pending) {
1836
+ const buildEpoch = epoch;
1837
+ const run = Promise.resolve().then(fetcher).then((data) => {
1838
+ if (buildEpoch === epoch && (!shouldCache || shouldCache(data))) {
1839
+ cache.set(key, { data, at: Date.now(), epoch: buildEpoch });
1840
+ }
1841
+ return data;
1842
+ }).finally(() => {
1843
+ if (inflight.get(key) === run) inflight.delete(key);
1844
+ });
1845
+ inflight.set(key, run);
1846
+ pending = run;
1847
+ }
1848
+ return pending;
1849
+ }
1850
+ };
1851
+ }
1852
+ var stateMemo = createUpstreamMemo(12e4);
1853
+ var priceCountsMemo = createUpstreamMemo(12e4, (counts) => !counts.authError);
1854
+ function stateCacheKeyOf(config) {
1855
+ const fingerprint = (s) => {
1856
+ let h = 0;
1857
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
1858
+ return (h >>> 0).toString(36);
1859
+ };
1860
+ return `${config.baseURL || ""}|${config.userId || ""}|${fingerprint(config.apiKey || "")}|${fingerprint(config.accessToken || "")}`;
1861
+ }
1862
+ async function getCachedStateResponse(config, configAccess) {
1863
+ return stateMemo.get(stateCacheKeyOf(config), () => buildStateResponse(config, configAccess));
1864
+ }
1865
+ async function buildStateResponse(config, configAccess) {
1866
+ const token = config.accessToken || "";
1867
+ const [balance, dshConfiguredModels, modelIdsRaw, allLogs, pins] = await Promise.all([
1868
+ fetchBalance(config.baseURL, config.apiKey, config.userId, token),
1869
+ configAccess.getDshConfiguredModels(),
1870
+ config.apiKey ? fetchTokenModels(config.baseURL, config.apiKey) : Promise.resolve([]),
1871
+ fetchRecentLogs(config.userId, token, 100),
1872
+ config.userId && token ? fetchMarketplacePins(config.userId, token).catch(() => []) : Promise.resolve([])
1873
+ ]);
1874
+ if (balance?.userId && String(balance.userId) !== config.userId) {
1875
+ tokenResolveCache = null;
1876
+ config.userId = String(balance.userId);
1877
+ await configAccess.writeConfig({ userId: config.userId });
1878
+ }
1879
+ let modelIds = modelIdsRaw;
1880
+ if (modelIds.length === 0) {
1881
+ modelIds = [
1882
+ .../* @__PURE__ */ new Set([
1883
+ ...dshConfiguredModels,
1884
+ "gpt-5.6-sol",
1885
+ "gpt-5.6-terra",
1886
+ "gpt-5.6-luna",
1887
+ "claude-fable-5",
1888
+ "claude-opus-5",
1889
+ "grok-4.6"
1890
+ ])
1891
+ ];
1892
+ }
1893
+ allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1894
+ if (config.userId || token) {
1895
+ const missing = modelIds.filter((m) => {
1896
+ const entry = merchantCardCache.get(m.toLowerCase());
1897
+ return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
1898
+ });
1899
+ if (missing.length > 0) {
1900
+ let found = {};
1901
+ try {
1902
+ found = await Promise.race([
1903
+ getKnownMerchantsFromLogs(config.userId, token, missing, allLogs),
1904
+ new Promise((resolve2) => setTimeout(() => resolve2({}), 1e4))
1905
+ ]);
1906
+ } catch {
1907
+ found = {};
1908
+ }
1909
+ for (const [mName, card] of Object.entries(found)) {
1910
+ merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
1911
+ }
1912
+ }
1913
+ }
1914
+ const lastRoutedMap = /* @__PURE__ */ new Map();
1915
+ for (const log of allLogs) {
1916
+ const mName = log.model_name;
1917
+ const chId = Number(log.channel);
1918
+ const ts = Number(log.created_at) || 0;
1919
+ if (mName && chId > 0 && ts > 0 && !lastRoutedMap.has(mName.toLowerCase())) {
1920
+ lastRoutedMap.set(mName.toLowerCase(), ts);
1921
+ }
1922
+ }
1923
+ const dshSet = new Set(dshConfiguredModels);
1924
+ let models = modelIds.map((mId) => {
1925
+ const meta = resolveModelMeta(mId);
1926
+ const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1927
+ const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
1928
+ const routedAt = lastRoutedMap.get(mId.toLowerCase());
1929
+ return {
1930
+ model_name: mId,
1931
+ brand: meta.brand,
1932
+ contextWindow: meta.contextWindow,
1933
+ maxTokens: meta.maxTokens,
1934
+ modalities: meta.modalities,
1935
+ hasReasoning: Boolean(meta.reasoningEfforts || meta.thinkingFormat),
1936
+ inDsh: dshSet.has(mId),
1937
+ merchant: cachedCard,
1938
+ probeStatus: cachedCard ? "success" : "idle",
1939
+ lastRoutedAt: routedAt,
1940
+ lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1941
+ };
1942
+ });
1943
+ const resolvedTokenId = pins.length > 0 ? await resolveTokenId(config) : null;
1944
+ models = overlayPinsOnModels(models, pins, resolvedTokenId);
1945
+ const rePointTargets = models.filter(
1946
+ (m) => m.pinStatus === "pin_elsewhere" && m.pinTokenMatched === true && m.pinnedChannelId && m.pinnedChannelId > 0
1947
+ ).map((m) => ({ modelName: m.model_name, channelId: m.pinnedChannelId }));
1948
+ if (rePointTargets.length > 0 && config.userId && token) {
1949
+ try {
1950
+ await Promise.race([
1951
+ (async () => {
1952
+ for (let i = 0; i < rePointTargets.length; i += 4) {
1953
+ const batch = rePointTargets.slice(i, i + 4);
1954
+ await Promise.all(
1955
+ batch.map(async ({ modelName, channelId }) => {
1956
+ try {
1957
+ const pinnedCard = await fetchChannelDetails(
1958
+ channelId,
1959
+ config.userId,
1960
+ token,
1961
+ modelName
1962
+ );
1963
+ if (pinnedCard && Number(pinnedCard.channel_id) === Number(channelId)) {
1964
+ merchantCardCache.set(modelName.toLowerCase(), { card: pinnedCard, at: Date.now() });
1965
+ }
1966
+ } catch {
1967
+ }
1968
+ })
1969
+ );
1970
+ }
1971
+ })(),
1972
+ new Promise((resolve2) => setTimeout(() => resolve2(), 1e4))
1973
+ ]);
1974
+ } catch {
1975
+ }
1976
+ models = models.map((m) => {
1977
+ if (m.pinStatus !== "pin_elsewhere" || m.pinTokenMatched !== true) return m;
1978
+ const entry = merchantCardCache.get(m.model_name.toLowerCase());
1979
+ const card = entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1980
+ if (card && Number(card.channel_id) === Number(m.pinnedChannelId)) {
1981
+ const pinnedLog = allLogs.find(
1982
+ (l) => l.model_name?.toLowerCase() === m.model_name.toLowerCase() && Number(l.channel) === m.pinnedChannelId
1983
+ );
1984
+ const pinnedAt = pinnedLog ? Number(pinnedLog.created_at) || 0 : void 0;
1985
+ return {
1986
+ ...m,
1987
+ merchant: card,
1988
+ pinStatus: "pin_here",
1989
+ probeStatus: "success",
1990
+ lastRoutedAt: pinnedAt,
1991
+ lastRoutedText: pinnedAt ? formatRelativeTime(pinnedAt) : void 0
1992
+ };
1993
+ }
1994
+ return m;
1995
+ });
1996
+ }
1997
+ const recentLogs = allLogs.slice(0, 20);
1998
+ return {
1999
+ config: maskConfig(config),
2000
+ balance,
2001
+ models,
2002
+ dshConfiguredModels,
2003
+ recentLogs,
2004
+ pins
2005
+ };
2006
+ }
1802
2007
  function apply(ctx) {
1803
2008
  const configAccess = createConfigAccess(ctx);
1804
2009
  void configAccess.ensureMigrated();
@@ -1821,146 +2026,7 @@ function apply(ctx) {
1821
2026
  try {
1822
2027
  if (pathname === "/state" && (req.method === "GET" || req.method === "HEAD")) {
1823
2028
  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
- };
2029
+ const response = await getCachedStateResponse(config, configAccess);
1964
2030
  return sendJson(res, 200, { ok: true, data: response });
1965
2031
  }
1966
2032
  if (pathname === "/config" && req.method === "POST") {
@@ -1993,6 +2059,8 @@ function apply(ctx) {
1993
2059
  if (updated.activeModels.length > 0) {
1994
2060
  await configAccess.syncModels(updated.baseURL, updated.activeModels);
1995
2061
  }
2062
+ stateMemo.invalidate();
2063
+ priceCountsMemo.invalidate();
1996
2064
  return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
1997
2065
  }
1998
2066
  if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2018,6 +2086,7 @@ function apply(ctx) {
2018
2086
  if (result.merchant) {
2019
2087
  merchantCardCache.set(modelName.toLowerCase(), { card: result.merchant, at: Date.now() });
2020
2088
  }
2089
+ stateMemo.invalidate();
2021
2090
  return sendJson(res, 200, { ok: true, result });
2022
2091
  }
2023
2092
  let modelIds = body.modelNames;
@@ -2035,6 +2104,7 @@ function apply(ctx) {
2035
2104
  }
2036
2105
  results.push(r);
2037
2106
  }
2107
+ stateMemo.invalidate();
2038
2108
  return sendJson(res, 200, { ok: true, results });
2039
2109
  }
2040
2110
  if (pathname === "/sync-models" && req.method === "POST") {
@@ -2045,6 +2115,7 @@ function apply(ctx) {
2045
2115
  const baseURL = body.baseURL || config.baseURL;
2046
2116
  await configAccess.syncModels(baseURL, modelIds);
2047
2117
  const dshConfiguredModels = await configAccess.getDshConfiguredModels();
2118
+ stateMemo.invalidate();
2048
2119
  return sendJson(res, 200, { ok: true, dshConfiguredModels });
2049
2120
  }
2050
2121
  if (pathname === "/pin" && req.method === "POST") {
@@ -2059,8 +2130,10 @@ function apply(ctx) {
2059
2130
  let card = cachedMerchantOf(modelName);
2060
2131
  let tokenId = await resolveTokenId(config);
2061
2132
  if ((!card || !tokenId) && config.apiKey) {
2133
+ let probedOk = false;
2062
2134
  try {
2063
2135
  const probe = await probeSingleModel(config.baseURL, config.apiKey, userId, token, modelName);
2136
+ probedOk = Boolean(probe && probe.success);
2064
2137
  if (!tokenId && probe.tokenId && Number(probe.tokenId) > 0) tokenId = Number(probe.tokenId);
2065
2138
  if (!card && probe.merchant) {
2066
2139
  card = probe.merchant;
@@ -2068,6 +2141,7 @@ function apply(ctx) {
2068
2141
  }
2069
2142
  } catch {
2070
2143
  }
2144
+ if (probedOk) stateMemo.invalidate();
2071
2145
  }
2072
2146
  if (!card) {
2073
2147
  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 +2168,7 @@ function apply(ctx) {
2094
2168
  at: Date.now()
2095
2169
  });
2096
2170
  const pinList = await fetchMarketplacePins(userId, token);
2171
+ stateMemo.invalidate();
2097
2172
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u56FA\u5B9A ${modelName} \u81F3\u5546\u6237 #${card.channel_id}`, pins: pinList, tokenId });
2098
2173
  }
2099
2174
  if (pathname === "/unpin" && req.method === "POST") {
@@ -2121,6 +2196,7 @@ function apply(ctx) {
2121
2196
  });
2122
2197
  }
2123
2198
  const pinList = await fetchMarketplacePins(userId, token);
2199
+ stateMemo.invalidate();
2124
2200
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u53D6\u6D88\u56FA\u5B9A ${modelName}`, pins: pinList, tokenId });
2125
2201
  }
2126
2202
  if (pathname === "/disable" && req.method === "POST") {
@@ -2130,14 +2206,17 @@ function apply(ctx) {
2130
2206
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2131
2207
  let card = cachedMerchantOf(modelName);
2132
2208
  if (!card && config.apiKey) {
2209
+ let probedOk = false;
2133
2210
  try {
2134
2211
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2212
+ probedOk = Boolean(probe && probe.success);
2135
2213
  if (probe.merchant) {
2136
2214
  card = probe.merchant;
2137
2215
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2138
2216
  }
2139
2217
  } catch {
2140
2218
  }
2219
+ if (probedOk) stateMemo.invalidate();
2141
2220
  }
2142
2221
  if (!card || !card.channel_id) {
2143
2222
  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 +2233,7 @@ function apply(ctx) {
2154
2233
  card: { ...card, user_channel_disabled: true },
2155
2234
  at: Date.now()
2156
2235
  });
2236
+ stateMemo.invalidate();
2157
2237
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u7981\u7528\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2158
2238
  }
2159
2239
  if (pathname === "/restore" && req.method === "POST") {
@@ -2163,14 +2243,17 @@ function apply(ctx) {
2163
2243
  if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2164
2244
  let card = cachedMerchantOf(modelName);
2165
2245
  if (!card && config.apiKey) {
2246
+ let probedOk = false;
2166
2247
  try {
2167
2248
  const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2249
+ probedOk = Boolean(probe && probe.success);
2168
2250
  if (probe.merchant) {
2169
2251
  card = probe.merchant;
2170
2252
  merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2171
2253
  }
2172
2254
  } catch {
2173
2255
  }
2256
+ if (probedOk) stateMemo.invalidate();
2174
2257
  }
2175
2258
  if (!card || !card.channel_id) {
2176
2259
  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 +2270,7 @@ function apply(ctx) {
2187
2270
  card: { ...card, user_channel_disabled: false },
2188
2271
  at: Date.now()
2189
2272
  });
2273
+ stateMemo.invalidate();
2190
2274
  return sendJson(res, 200, { ok: true, message: `\u5DF2\u6062\u590D\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2191
2275
  }
2192
2276
  if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
@@ -2195,8 +2279,11 @@ function apply(ctx) {
2195
2279
  if (!token || !config.userId) {
2196
2280
  return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
2197
2281
  }
2198
- const result = await fetchPriceFluctuation(config.userId, token);
2199
- const { notices, ...counts } = result;
2282
+ const counts = await priceCountsMemo.get(stateCacheKeyOf(config), async () => {
2283
+ const result = await fetchPriceFluctuation(config.userId, token);
2284
+ const { notices, ...rest } = result;
2285
+ return rest;
2286
+ });
2200
2287
  const hasAuth = !counts.authError;
2201
2288
  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
2289
  }
@@ -2219,6 +2306,7 @@ function apply(ctx) {
2219
2306
  }
2220
2307
  if (pathname === "/catalog/clear" && req.method === "POST") {
2221
2308
  await clearCatalog();
2309
+ stateMemo.invalidate();
2222
2310
  return sendJson(res, 200, { ok: true });
2223
2311
  }
2224
2312
  if (pathname === "/catalog/fetch-models" && req.method === "POST") {
@@ -2239,6 +2327,7 @@ function apply(ctx) {
2239
2327
  await upsertCatalogEntries(
2240
2328
  models.map((m) => ({ id: m.id, brand: m.brand, reasoningEfforts: m.reasoningEfforts }))
2241
2329
  );
2330
+ stateMemo.invalidate();
2242
2331
  return sendJson(res, 200, {
2243
2332
  ok: true,
2244
2333
  total: models.length,
@@ -2254,6 +2343,7 @@ function apply(ctx) {
2254
2343
  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
2344
  }
2256
2345
  const result = await queryOpenRouter(modelIds);
2346
+ stateMemo.invalidate();
2257
2347
  return sendJson(res, 200, {
2258
2348
  ok: true,
2259
2349
  updated: result.updated.length,
@@ -2316,6 +2406,7 @@ function apply(ctx) {
2316
2406
  } catch (err) {
2317
2407
  console.warn("[dsh-a6api] catalog update: resync settings failed:", err?.message || err);
2318
2408
  }
2409
+ stateMemo.invalidate();
2319
2410
  return sendJson(res, 200, { ok: true, entry });
2320
2411
  }
2321
2412
  return sendJson(res, 404, { ok: false, error: "Not found" });