@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/README.md +34 -1
- package/USAGE.md +92 -8
- package/dist/bin.js +159 -11
- package/dist/bin.js.map +1 -1
- package/dist/index.js +86 -3
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
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
|
|
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"
|
|
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;
|