@s-berdia/opencode-router 1.0.1 → 1.1.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/dist/index.js CHANGED
@@ -923,10 +923,13 @@ function globalConfigDir() {
923
923
  const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".config");
924
924
  return path.join(base, "opencode");
925
925
  }
926
- function catalogCacheFile() {
926
+ function opencodeCacheDir() {
927
927
  const xdg = process.env.XDG_CACHE_HOME;
928
928
  const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".cache");
929
- return path.join(base, "opencode", "models.json");
929
+ return path.join(base, "opencode");
930
+ }
931
+ function catalogCacheFile() {
932
+ return path.join(opencodeCacheDir(), "models.json");
930
933
  }
931
934
  function globalRouterConfigFile() {
932
935
  return path.join(globalConfigDir(), "router.config.json");
@@ -961,6 +964,10 @@ function readJsonFile(file) {
961
964
  if (parsed.error !== void 0) return { status: "invalid", error: parsed.error };
962
965
  return { status: "ok", value: parsed.value };
963
966
  }
967
+ function readJson(file) {
968
+ const result = readJsonFile(file);
969
+ return result.status === "ok" ? result.value : void 0;
970
+ }
964
971
  function isPlainObject(value) {
965
972
  return typeof value === "object" && value !== null && !Array.isArray(value);
966
973
  }
@@ -1222,6 +1229,7 @@ function sanitizeUserConfig(raw, file, notes) {
1222
1229
  if (threshold !== void 0 && !(typeof threshold === "number" && threshold > 0 && threshold <= 1)) {
1223
1230
  drop("softThreshold", "must be a number above 0 and at most 1");
1224
1231
  }
1232
+ if (config.updateCheck !== void 0 && typeof config.updateCheck !== "boolean") drop("updateCheck", "must be true or false");
1225
1233
  for (const key of ["denyModels", "allowModels"]) {
1226
1234
  const list = config[key];
1227
1235
  if (list !== void 0 && !(Array.isArray(list) && list.every((item) => typeof item === "string"))) {
@@ -1816,7 +1824,8 @@ var SLASH_COMMANDS = [
1816
1824
  { name: "refresh-routing", description: "Recompute and apply OpenCode model routing", args: "refresh" },
1817
1825
  { name: "routing-status", description: "Show the model in use per role and its 5-hour quota use", args: "status" },
1818
1826
  { name: "routing-plan", description: "Switch the routing plan (go | go-plus | free) and rebuild", args: "plan $1" },
1819
- { name: "routing-eval", description: "Run the router scoring stress test", args: "eval" }
1827
+ { name: "routing-eval", description: "Run the router scoring stress test", args: "eval" },
1828
+ { name: "routing-update", description: "Update opencode-router to the newest version", args: "update" }
1820
1829
  ];
1821
1830
  var OUTPUT_BEGIN = "---BEGIN ROUTER OUTPUT---";
1822
1831
  function isRouterCommandPrompt(parts) {
@@ -1852,6 +1861,9 @@ function ledgerFile() {
1852
1861
  function routingTableFile() {
1853
1862
  return path3.join(stateDir(), "routing-table.json");
1854
1863
  }
1864
+ function updateCheckFile() {
1865
+ return path3.join(stateDir(), "update-check.json");
1866
+ }
1855
1867
  function applyMarkerFile() {
1856
1868
  return path3.join(stateDir(), "last-apply.json");
1857
1869
  }
@@ -1862,6 +1874,66 @@ function clearApplyMarker() {
1862
1874
  fs4.rmSync(applyMarkerFile(), { force: true });
1863
1875
  }
1864
1876
 
1877
+ // src/update.ts
1878
+ import fs5 from "fs";
1879
+ import path4 from "path";
1880
+ import { fileURLToPath as fileURLToPath2 } from "url";
1881
+ var PACKAGE_NAME = "@s-berdia/opencode-router";
1882
+ var LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME.replace("/", "%2f")}/latest`;
1883
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1884
+ var TIMEOUT_MS = 3e3;
1885
+ function parseVersion(version) {
1886
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
1887
+ return match ? match.slice(1).map(Number) : null;
1888
+ }
1889
+ function isNewerVersion(a, b) {
1890
+ const left = parseVersion(a);
1891
+ const right = parseVersion(b);
1892
+ if (!left || !right) return false;
1893
+ for (let i = 0; i < 3; i++) {
1894
+ if (left[i] !== right[i]) return left[i] > right[i];
1895
+ }
1896
+ return false;
1897
+ }
1898
+ function installedVersion() {
1899
+ const file = fileURLToPath2(new URL("../package.json", import.meta.url));
1900
+ return readJson(file)?.version ?? "0.0.0";
1901
+ }
1902
+ async function fetchLatestVersion() {
1903
+ const controller = new AbortController();
1904
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
1905
+ try {
1906
+ const res = await fetch(LATEST_URL, { signal: controller.signal, headers: { accept: "application/json" } });
1907
+ if (!res.ok) return void 0;
1908
+ const body = await res.json();
1909
+ return typeof body.version === "string" ? body.version : void 0;
1910
+ } catch {
1911
+ return void 0;
1912
+ } finally {
1913
+ clearTimeout(timeout);
1914
+ }
1915
+ }
1916
+ async function checkForUpdate(opts = {}) {
1917
+ try {
1918
+ if (loadUserConfig(opts.projectDir).config.updateCheck === false) return null;
1919
+ const current = opts.current ?? installedVersion();
1920
+ const now = opts.now ?? Date.now();
1921
+ const state = readJson(updateCheckFile());
1922
+ let latest = state?.latest;
1923
+ const fresh = state !== void 0 && now >= state.checkedAt && now - state.checkedAt < CHECK_INTERVAL_MS;
1924
+ if (!fresh) {
1925
+ latest = await fetchLatestVersion() ?? latest;
1926
+ writeJson(updateCheckFile(), { checkedAt: now, latest });
1927
+ }
1928
+ return latest && isNewerVersion(latest, current) ? { current, latest } : null;
1929
+ } catch {
1930
+ return null;
1931
+ }
1932
+ }
1933
+ function updateNotice(update) {
1934
+ return `opencode-router ${update.latest} is available (installed: ${update.current}). To update, type /routing-update in OpenCode.`;
1935
+ }
1936
+
1865
1937
  // src/index.ts
1866
1938
  var RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1e3;
1867
1939
  var AUTH_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
@@ -1887,6 +1959,8 @@ var OpencodeRouter = async (input) => {
1887
1959
  const ledger = new LedgerStore(ledgerFile(), () => Date.now());
1888
1960
  const recordedMessages = /* @__PURE__ */ new Set();
1889
1961
  const sessionModels = /* @__PURE__ */ new Map();
1962
+ const pendingUpdate = checkForUpdate({ projectDir: dir });
1963
+ let updateToastShown = false;
1890
1964
  let cached = null;
1891
1965
  let cachedAt = 0;
1892
1966
  let blendedById = /* @__PURE__ */ new Map();
@@ -1992,6 +2066,15 @@ var OpencodeRouter = async (input) => {
1992
2066
  ledger.record(id, cost, tokens);
1993
2067
  if (isRateLimitError(info.error)) ledger.cooldown(id, RATE_LIMIT_COOLDOWN_MS, "rate limit / quota");
1994
2068
  else if (isAuthError(info.error)) ledger.cooldown(id, AUTH_COOLDOWN_MS, "auth error");
2069
+ } else if (event.type === "session.created" && !updateToastShown) {
2070
+ updateToastShown = true;
2071
+ void pendingUpdate.then((update) => {
2072
+ if (!update) return;
2073
+ return input.client.tui.showToast({
2074
+ body: { title: "opencode-router", message: updateNotice(update), variant: "info", duration: 1e4 }
2075
+ });
2076
+ }).catch(() => {
2077
+ });
1995
2078
  } else if (event.type === "session.error") {
1996
2079
  const { sessionID, error } = event.properties;
1997
2080
  const id = sessionID ? sessionModels.get(sessionID) : void 0;