billion-context 0.1.20 → 0.1.21

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
@@ -764,7 +764,7 @@ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as rea
764
764
  import { createHash as createHash2 } from "crypto";
765
765
  import * as path3 from "path";
766
766
  import { createInitialState } from "acp-kernel";
767
- var PERSIST_VERSION = 1;
767
+ var PERSIST_VERSION = 2;
768
768
  function mergeState(parsed) {
769
769
  const fresh = createInitialState();
770
770
  return {
@@ -852,8 +852,9 @@ var SessionStore = class {
852
852
  try {
853
853
  const parsed = JSON.parse(await fs.readFile(full, "utf8"));
854
854
  if (!isValidRecord(parsed)) continue;
855
- const proto = parsed.protocol;
856
- const origin = parsed.upstreamOrigin;
855
+ const pm = parsed.meta ?? {};
856
+ const proto = pm.protocol ?? parsed.protocol;
857
+ const origin = pm.upstreamOrigin ?? parsed.upstreamOrigin;
857
858
  const expectedNamespaced = path3.basename(relPathFor(parsed.id, proto, origin));
858
859
  const expectedLegacy = legacyFileNameFor(parsed.id);
859
860
  if (name !== expectedNamespaced && name !== expectedLegacy) {
@@ -907,7 +908,7 @@ var SessionStore = class {
907
908
  async writeNow(session) {
908
909
  if (!this.enabled) return;
909
910
  const record = buildRecord(session);
910
- const file = this.filePath(session.id, session.protocol, session.upstreamOrigin);
911
+ const file = this.filePath(session.id, session.meta.protocol, session.meta.upstreamOrigin);
911
912
  try {
912
913
  await fs.mkdir(path3.dirname(file), { recursive: true });
913
914
  } catch (e) {
@@ -941,7 +942,7 @@ var SessionStore = class {
941
942
  this.timers.delete(session.id);
942
943
  }
943
944
  const record = buildRecord(session);
944
- const file = this.filePath(session.id, session.protocol, session.upstreamOrigin);
945
+ const file = this.filePath(session.id, session.meta.protocol, session.meta.upstreamOrigin);
945
946
  try {
946
947
  mkdirSync3(path3.dirname(file), { recursive: true });
947
948
  } catch (e) {
@@ -995,13 +996,12 @@ function buildRecord(session) {
995
996
  version: PERSIST_VERSION,
996
997
  savedAt: Date.now(),
997
998
  id: session.id,
998
- protocol: session.protocol,
999
- upstreamOrigin: session.upstreamOrigin,
1000
- createdAt: session.createdAt,
1001
- requests: session.requests,
1002
- tokensSaved: session.tokensSaved,
999
+ meta: { ...session.meta },
1000
+ stats: { ...session.stats },
1001
+ metadata: { ...session.metadata },
1003
1002
  state: session.state,
1004
- blockContents: Object.fromEntries(session.blockContents)
1003
+ blockContents: Object.fromEntries(session.blockContents),
1004
+ createdAt: session.createdAt
1005
1005
  };
1006
1006
  }
1007
1007
  function buildSession(parsed) {
@@ -1009,15 +1009,29 @@ function buildSession(parsed) {
1009
1009
  for (const [bid, content] of Object.entries(parsed.blockContents ?? {})) {
1010
1010
  if (content && typeof content === "object") blockContents.set(bid, content);
1011
1011
  }
1012
+ const meta = parsed.meta ?? {};
1013
+ const stats = parsed.stats ?? {};
1012
1014
  return {
1013
1015
  id: parsed.id,
1014
- protocol: parsed.protocol,
1015
- upstreamOrigin: parsed.upstreamOrigin,
1016
+ meta: {
1017
+ protocol: meta.protocol ?? parsed.protocol,
1018
+ upstreamOrigin: meta.upstreamOrigin ?? parsed.upstreamOrigin,
1019
+ label: meta.label ?? parsed.label,
1020
+ title: meta.title
1021
+ },
1022
+ stats: {
1023
+ requests: stats.requests ?? parsed.requests ?? 0,
1024
+ tokensSaved: stats.tokensSaved ?? parsed.tokensSaved ?? 0,
1025
+ inputTokens: stats.inputTokens ?? parsed.inputTokens ?? 0,
1026
+ cachedTokens: stats.cachedTokens ?? parsed.cachedTokens ?? 0,
1027
+ outputTokens: stats.outputTokens ?? parsed.outputTokens ?? 0,
1028
+ cacheSamples: stats.cacheSamples ?? parsed.cacheSamples ?? 0,
1029
+ contextTokens: stats.contextTokens ?? parsed.contextTokens ?? 0
1030
+ },
1031
+ metadata: parsed.metadata ?? {},
1016
1032
  state: mergeState(parsed.state),
1017
1033
  createdAt: parsed.createdAt ?? Date.now(),
1018
1034
  lastSeen: Date.now(),
1019
- requests: parsed.requests ?? 0,
1020
- tokensSaved: parsed.tokensSaved ?? 0,
1021
1035
  blockContents,
1022
1036
  inFlight: 0,
1023
1037
  persisted: true
@@ -1082,8 +1096,9 @@ function getSession(id, meta) {
1082
1096
  const existing = sessions.get(id);
1083
1097
  if (existing) {
1084
1098
  existing.lastSeen = Date.now();
1085
- if (meta?.protocol && !existing.protocol) existing.protocol = meta.protocol;
1086
- if (meta?.upstreamOrigin && !existing.upstreamOrigin) existing.upstreamOrigin = meta.upstreamOrigin;
1099
+ if (meta?.protocol && !existing.meta.protocol) existing.meta.protocol = meta.protocol;
1100
+ if (meta?.upstreamOrigin && !existing.meta.upstreamOrigin) existing.meta.upstreamOrigin = meta.upstreamOrigin;
1101
+ if (meta?.label && !existing.meta.label) existing.meta.label = meta.label;
1087
1102
  return existing;
1088
1103
  }
1089
1104
  const store = getStore();
@@ -1097,13 +1112,12 @@ function getSession(id, meta) {
1097
1112
  if (sessions.size >= MAX_SESSIONS) evictOldest();
1098
1113
  const session = {
1099
1114
  id,
1100
- protocol: meta?.protocol,
1101
- upstreamOrigin: meta?.upstreamOrigin,
1115
+ meta: { protocol: meta?.protocol, upstreamOrigin: meta?.upstreamOrigin, label: meta?.label },
1116
+ stats: { requests: 0, tokensSaved: 0, inputTokens: 0, cachedTokens: 0, outputTokens: 0, cacheSamples: 0, contextTokens: 0 },
1117
+ metadata: {},
1102
1118
  state: createInitialState2(),
1103
1119
  createdAt: Date.now(),
1104
1120
  lastSeen: Date.now(),
1105
- requests: 0,
1106
- tokensSaved: 0,
1107
1121
  blockContents: /* @__PURE__ */ new Map(),
1108
1122
  inFlight: 0,
1109
1123
  persisted: false
@@ -1457,8 +1471,31 @@ function routeEvent(ev, blocks, ctx, markConverted, markRealToolUse, getConverte
1457
1471
  return emitEvent(ev);
1458
1472
  }
1459
1473
  if (t === "message_delta") {
1474
+ if (!getConverted()) {
1475
+ const u = d.usage;
1476
+ if (u) {
1477
+ const out = u.output_tokens;
1478
+ if (typeof out === "number") ctx.session.stats.outputTokens += out;
1479
+ }
1480
+ }
1460
1481
  return getConverted() ? NOOP : emitEvent(ev);
1461
1482
  }
1483
+ if (t === "message_start" && !getConverted()) {
1484
+ const u = d.message?.usage;
1485
+ if (u) {
1486
+ const inp = u.input_tokens;
1487
+ const cc = u.cache_creation_input_tokens;
1488
+ const cr = u.cache_read_input_tokens;
1489
+ if (typeof inp === "number") ctx.session.stats.inputTokens += inp;
1490
+ if (typeof cr === "number") {
1491
+ ctx.session.stats.cachedTokens += cr;
1492
+ ctx.session.stats.cacheSamples += 1;
1493
+ } else if (typeof inp === "number") {
1494
+ ctx.session.stats.cacheSamples += 1;
1495
+ }
1496
+ if (typeof cc === "number") ctx.session.stats.inputTokens += cc;
1497
+ }
1498
+ }
1462
1499
  if (t === "message_stop") {
1463
1500
  return getConverted() ? NOOP : emitEvent(ev);
1464
1501
  }
@@ -1588,6 +1625,447 @@ function rewriteJsonResponse(body, ctx) {
1588
1625
  return body;
1589
1626
  }
1590
1627
 
1628
+ // src/web.ts
1629
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1630
+ import { dirname as dirname3, join as join2 } from "path";
1631
+ import { fileURLToPath } from "url";
1632
+ function getVersion() {
1633
+ try {
1634
+ const here = fileURLToPath(import.meta.url);
1635
+ const pkg = join2(dirname3(here), "..", "package.json");
1636
+ return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "dev";
1637
+ } catch {
1638
+ return "dev";
1639
+ }
1640
+ }
1641
+ function readProviders() {
1642
+ const parsed = safeReadJson(configFile());
1643
+ const routes = {};
1644
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1645
+ const providers = parsed.providers;
1646
+ if (providers) {
1647
+ for (const [k, v] of Object.entries(providers)) {
1648
+ const route = parseRouteEntry(v);
1649
+ if (route) routes[k] = route;
1650
+ }
1651
+ }
1652
+ }
1653
+ return routes;
1654
+ }
1655
+ async function handleConfigGet(res) {
1656
+ res.writeHead(200, { "content-type": "application/json" });
1657
+ res.end(JSON.stringify({ path: configFile(), providers: readProviders() }, null, 2));
1658
+ }
1659
+ async function handleConfigPut(req, res) {
1660
+ const raw = await readJsonBody(req);
1661
+ if (!raw || typeof raw !== "object") {
1662
+ res.writeHead(400, { "content-type": "application/json" });
1663
+ res.end(JSON.stringify({ error: "expected JSON body" }));
1664
+ return;
1665
+ }
1666
+ const body = raw;
1667
+ if (!body.providers || typeof body.providers !== "object" || Array.isArray(body.providers)) {
1668
+ res.writeHead(400, { "content-type": "application/json" });
1669
+ res.end(JSON.stringify({ error: 'expected JSON: { "providers": { ... } }' }));
1670
+ return;
1671
+ }
1672
+ const routes = {};
1673
+ for (const [name, val] of Object.entries(body.providers)) {
1674
+ if (!name || typeof name !== "string") {
1675
+ res.writeHead(400, { "content-type": "application/json" });
1676
+ res.end(JSON.stringify({ error: `invalid provider name: ${JSON.stringify(name)}` }));
1677
+ return;
1678
+ }
1679
+ const route = parseRouteEntry(val);
1680
+ if (!route) {
1681
+ res.writeHead(400, { "content-type": "application/json" });
1682
+ res.end(JSON.stringify({ error: `invalid provider "${name}": expected "url" or { url, models }` }));
1683
+ return;
1684
+ }
1685
+ routes[name] = route;
1686
+ }
1687
+ const existing = safeReadJson(configFile()) ?? {};
1688
+ existing.providers = body.providers;
1689
+ try {
1690
+ mkdirSync4(dirname3(configFile()), { recursive: true });
1691
+ writeFileSync3(configFile(), JSON.stringify(existing, null, 2) + "\n", "utf8");
1692
+ } catch (e) {
1693
+ res.writeHead(500, { "content-type": "application/json" });
1694
+ res.end(JSON.stringify({ error: `failed to write: ${String(e)}` }));
1695
+ return;
1696
+ }
1697
+ log("info", `[acp-web] providers updated via web UI (${Object.keys(routes).length} providers) \u2014 restart to apply`);
1698
+ res.writeHead(200, { "content-type": "application/json" });
1699
+ res.end(JSON.stringify({ ok: true, count: Object.keys(routes).length, note: "restart bili to apply" }));
1700
+ }
1701
+ function readJsonBody(req) {
1702
+ return new Promise((resolve) => {
1703
+ const chunks = [];
1704
+ let size = 0;
1705
+ req.on("data", (c) => {
1706
+ size += c.length;
1707
+ if (size > 256 * 1024) {
1708
+ req.destroy();
1709
+ resolve(void 0);
1710
+ return;
1711
+ }
1712
+ chunks.push(c);
1713
+ });
1714
+ req.on("end", () => {
1715
+ try {
1716
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
1717
+ } catch {
1718
+ resolve(void 0);
1719
+ }
1720
+ });
1721
+ req.on("error", () => resolve(void 0));
1722
+ });
1723
+ }
1724
+ function renderUI(origin) {
1725
+ return HTML_UI.replace(/__ORIGIN__/g, origin).replace(/__VERSION__/g, getVersion());
1726
+ }
1727
+ var HTML_UI = `<!DOCTYPE html>
1728
+ <html lang="en">
1729
+ <head>
1730
+ <meta charset="utf-8">
1731
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1732
+ <title>billion-context</title>
1733
+ <style>
1734
+ :root {
1735
+ --bg: #1a1b26; --bg2: #24283b; --bg3: #2f334d;
1736
+ --fg: #c0caf5; --dim: #565f89; --accent: #7aa2f7; --accent2: #bb9af7;
1737
+ --ok: #9ece6a; --warn: #e0af68; --err: #f7768e;
1738
+ --border: #3b4261; --radius: 8px;
1739
+ }
1740
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1741
+ body {
1742
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1743
+ background: var(--bg); color: var(--fg); line-height: 1.5; min-height: 100vh;
1744
+ }
1745
+ .mono { font-family: "SF Mono", "Cascadia Code", Consolas, monospace; }
1746
+ header {
1747
+ display: flex; align-items: center; gap: 16px;
1748
+ padding: 16px 24px; border-bottom: 1px solid var(--border);
1749
+ }
1750
+ header a { color: inherit; text-decoration: none; }
1751
+ header .logo { font-size: 18px; font-weight: 600; cursor: pointer; }
1752
+ header .logo:hover { opacity: 0.85; }
1753
+ header .logo span { color: var(--accent); }
1754
+ header .meta { font-size: 12px; color: var(--dim); }
1755
+ header .meta b { color: var(--fg); }
1756
+ header .gh {
1757
+ margin-left: auto; font-size: 13px; color: var(--dim); text-decoration: none;
1758
+ display: flex; align-items: center; gap: 6px; padding: 6px 12px;
1759
+ border: 1px solid var(--border); border-radius: var(--radius); transition: color .15s, border-color .15s;
1760
+ }
1761
+ header .gh:hover { color: var(--accent); border-color: var(--accent); }
1762
+ nav { display: flex; gap: 4px; padding: 0 24px; border-bottom: 1px solid var(--border); }
1763
+ nav button {
1764
+ background: none; border: none; color: var(--dim); cursor: pointer;
1765
+ padding: 12px 16px; font-size: 14px; border-bottom: 2px solid transparent;
1766
+ font-family: inherit; transition: color .15s;
1767
+ }
1768
+ nav button:hover { color: var(--fg); }
1769
+ nav button.active { color: var(--accent); border-bottom-color: var(--accent); }
1770
+ main { max-width: 800px; margin: 0 auto; padding: 24px; }
1771
+ .tab { display: none; }
1772
+ .tab.active { display: block; }
1773
+
1774
+ .card {
1775
+ background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
1776
+ padding: 16px; margin-bottom: 12px;
1777
+ }
1778
+ .card-head {
1779
+ display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;
1780
+ }
1781
+ .card-head .name { font-weight: 600; color: var(--accent); }
1782
+ .card-head .name input { font-weight: 600; color: var(--accent); }
1783
+ input, select {
1784
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 4px;
1785
+ color: var(--fg); padding: 6px 10px; font-size: 13px; font-family: inherit; width: 100%;
1786
+ }
1787
+ input:focus, select:focus { outline: none; border-color: var(--accent); }
1788
+ .row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
1789
+ .row label { font-size: 12px; color: var(--dim); min-width: 80px; }
1790
+ .row input { flex: 1; }
1791
+ .sub-card {
1792
+ background: var(--bg3); border-radius: 4px; padding: 10px 12px; margin: 8px 0 8px 20px;
1793
+ border-left: 2px solid var(--accent2);
1794
+ }
1795
+ .model-head { display: flex; justify-content: space-between; align-items: center; }
1796
+ .model-head .mname { color: var(--accent2); font-size: 13px; font-weight: 500; }
1797
+ .model-row { display: flex; gap: 8px; align-items: center; margin-top: 6px; }
1798
+ .model-row label { font-size: 11px; color: var(--dim); min-width: 60px; }
1799
+ .model-row input { flex: 1; font-family: "SF Mono", monospace; font-size: 12px; }
1800
+ .btn {
1801
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 4px;
1802
+ color: var(--fg); padding: 8px 14px; font-size: 13px; cursor: pointer; font-family: inherit;
1803
+ transition: background .15s, border-color .15s;
1804
+ }
1805
+ .btn:hover { background: var(--border); }
1806
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 500; }
1807
+ .btn.primary:hover { background: var(--accent2); border-color: var(--accent2); }
1808
+ .btn.danger { color: var(--err); }
1809
+ .btn.danger:hover { background: rgba(247,118,142,.1); }
1810
+ .btn.small { padding: 4px 8px; font-size: 12px; }
1811
+ .add-bar { display: flex; gap: 8px; margin: 12px 0; }
1812
+ .actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
1813
+
1814
+ .snippet {
1815
+ background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
1816
+ padding: 12px; margin-bottom: 12px; position: relative;
1817
+ }
1818
+ .snippet .label { font-size: 12px; color: var(--dim); margin-bottom: 4px; }
1819
+ .snippet .label b { color: var(--fg); }
1820
+ .snippet code { display: block; font-size: 13px; color: var(--ok); white-space: pre-wrap; word-break: break-all; }
1821
+ .snippet .copy { position: absolute; top: 8px; right: 8px; }
1822
+
1823
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
1824
+ th { text-align: left; padding: 8px 12px; color: var(--dim); font-weight: 500; border-bottom: 1px solid var(--border); }
1825
+ td { padding: 8px 12px; border-bottom: 1px solid var(--border); }
1826
+ td.mono { color: var(--accent); font-size: 12px; }
1827
+ .empty { text-align: center; padding: 32px; color: var(--dim); font-size: 14px; }
1828
+ .toast {
1829
+ position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
1830
+ background: var(--ok); color: var(--bg); padding: 10px 20px; border-radius: var(--radius);
1831
+ font-size: 14px; font-weight: 500; opacity: 0; transition: opacity .3s; pointer-events: none;
1832
+ }
1833
+ .toast.show { opacity: 1; }
1834
+ .toast.err { background: var(--err); color: var(--bg); }
1835
+ .notice {
1836
+ background: rgba(224,175,104,.1); border: 1px solid var(--warn); border-radius: var(--radius);
1837
+ color: var(--warn); padding: 10px 14px; margin-bottom: 16px; font-size: 13px;
1838
+ }
1839
+ select { cursor: pointer; }
1840
+ </style>
1841
+ </head>
1842
+ <body>
1843
+ <header>
1844
+ <a class="logo" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener" title="GitHub repo">billion<span>-context</span></a>
1845
+ <div class="meta">v<b>__VERSION__</b> &middot; <b>__ORIGIN__</b></div>
1846
+ <a class="gh" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener">
1847
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
1848
+ GitHub
1849
+ </a>
1850
+ </header>
1851
+ <nav>
1852
+ <button class="active" onclick="showTab('providers')">Providers</button>
1853
+ <button onclick="showTab('setup')">Client Setup</button>
1854
+ <button onclick="showTab('sessions')">Sessions</button>
1855
+ </nav>
1856
+ <main>
1857
+ <!-- Providers tab -->
1858
+ <div id="tab-providers" class="tab active">
1859
+ <div id="restart-notice" class="notice" style="display:none"></div>
1860
+ <div id="providers-list"></div>
1861
+ <div class="add-bar">
1862
+ <button class="btn" onclick="addProvider()">+ Add provider</button>
1863
+ </div>
1864
+ <div class="actions">
1865
+ <button class="btn primary" onclick="saveProviders()">Save</button>
1866
+ </div>
1867
+ </div>
1868
+
1869
+ <!-- Client Setup tab -->
1870
+ <div id="tab-setup" class="tab">
1871
+ <div class="row" style="margin-bottom:16px">
1872
+ <label>Provider</label>
1873
+ <select id="setup-provider" onchange="renderSetup()"></select>
1874
+ </div>
1875
+ <div id="setup-snippets"></div>
1876
+ </div>
1877
+
1878
+ <!-- Sessions tab -->
1879
+ <div id="tab-sessions" class="tab">
1880
+ <div class="row" style="justify-content:space-between;margin-bottom:12px">
1881
+ <span style="font-size:13px;color:var(--dim)">Auto-refreshes every 5s</span>
1882
+ <span id="sess-total" style="font-size:13px;color:var(--dim)"></span>
1883
+ </div>
1884
+ <div id="sessions-table"></div>
1885
+ </div>
1886
+ </main>
1887
+ <div id="toast" class="toast"></div>
1888
+
1889
+ <script>
1890
+ var ORIGIN = "__ORIGIN__";
1891
+ var providers = [];
1892
+ var savedProviders = null;
1893
+
1894
+ // \u2500\u2500 helpers \u2500\u2500
1895
+ function el(id) { return document.getElementById(id); }
1896
+ function esc(s) { return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); }
1897
+ function fmtTok(n) { if (n >= 1000000) return (n/1000000).toFixed(1)+"M"; if (n >= 1000) return (n/1000).toFixed(1)+"K"; return String(n); }
1898
+ function toast(msg, isErr) {
1899
+ var t = el("toast"); t.textContent = msg; t.className = "toast show" + (isErr ? " err" : "");
1900
+ setTimeout(function(){ t.className = "toast" + (isErr ? " err" : ""); }, 2500);
1901
+ }
1902
+ function showTab(name) {
1903
+ document.querySelectorAll(".tab").forEach(function(t){ t.classList.remove("active"); });
1904
+ document.querySelectorAll("nav button").forEach(function(b){ b.classList.remove("active"); });
1905
+ el("tab-"+name).classList.add("active");
1906
+ event.target.classList.add("active");
1907
+ if (name === "sessions") refreshSessions();
1908
+ if (name === "setup") renderSetup();
1909
+ }
1910
+
1911
+ // \u2500\u2500 load \u2500\u2500
1912
+ async function load() {
1913
+ try {
1914
+ var r = await fetch("/__acp/config");
1915
+ var d = await r.json();
1916
+ providers = entries(d.providers);
1917
+ el("setup-provider").innerHTML = "";
1918
+ savedProviders = JSON.stringify(providers);
1919
+ renderProviders();
1920
+ } catch(e) {
1921
+ el("providers-list").innerHTML = '<div class="empty">Failed to load config: ' + esc(e) + "</div>";
1922
+ }
1923
+ }
1924
+ function entries(obj) {
1925
+ if (!obj || typeof obj !== "object") return [];
1926
+ return Object.keys(obj).map(function(name){
1927
+ var v = obj[name];
1928
+ var url, models = [];
1929
+ if (typeof v === "string") { url = v; }
1930
+ else { url = v.url || ""; models = entries_models(v.models); }
1931
+ return { name: name, url: url, models: models };
1932
+ });
1933
+ }
1934
+ function entries_models(obj) {
1935
+ if (!obj || typeof obj !== "object") return [];
1936
+ return Object.keys(obj).map(function(name){
1937
+ return { name: name, context: obj[name].context||0, output: obj[name].output||0 };
1938
+ });
1939
+ }
1940
+
1941
+ // \u2500\u2500 providers editor \u2500\u2500
1942
+ function renderProviders() {
1943
+ var html = "";
1944
+ if (providers.length === 0) html = '<div class="empty">No providers yet. Click "Add provider" below.</div>';
1945
+ providers.forEach(function(p, i) {
1946
+ html += '<div class="card">';
1947
+ html += '<div class="card-head"><div class="name"><input value="'+esc(p.name)+'" onchange="providers['+i+'].name=this.value" style="background:transparent;border:none;padding:0;width:auto"></div>';
1948
+ html += '<button class="btn danger small" onclick="removeProvider('+i+')">Remove</button></div>';
1949
+ html += '<div class="row"><label>URL</label><input class="mono" value="'+esc(p.url)+'" onchange="providers['+i+'].url=this.value"></div>';
1950
+ p.models.forEach(function(m, j) {
1951
+ html += '<div class="sub-card">';
1952
+ html += '<div class="model-head"><span class="mname mono">'+esc(m.name)+'</span>';
1953
+ html += '<button class="btn danger small" onclick="removeModel('+i+','+j+')">Remove</button></div>';
1954
+ html += '<div class="model-row"><label>context</label><input type="number" value="'+m.context+'" onchange="providers['+i+'].models['+j+'].context=parseInt(this.value)||0"></div>';
1955
+ html += '<div class="model-row"><label>output</label><input type="number" value="'+m.output+'" onchange="providers['+i+'].models['+j+'].output=parseInt(this.value)||0"></div>';
1956
+ html += '<div class="model-row"><label>name</label><input value="'+esc(m.name)+'" onchange="providers['+i+'].models['+j+'].name=this.value"></div>';
1957
+ html += '</div>';
1958
+ });
1959
+ html += '<button class="btn small" onclick="addModel('+i+')">+ Add model</button>';
1960
+ html += '</div>';
1961
+ });
1962
+ el("providers-list").innerHTML = html;
1963
+ checkDirty();
1964
+ }
1965
+ function addProvider() {
1966
+ providers.push({ name: "new-provider", url: "https://", models: [] });
1967
+ renderProviders();
1968
+ }
1969
+ function removeProvider(i) {
1970
+ providers.splice(i, 1);
1971
+ renderProviders();
1972
+ }
1973
+ function addModel(i) {
1974
+ providers[i].models.push({ name: "model-name", context: 200000, output: 8192 });
1975
+ renderProviders();
1976
+ }
1977
+ function removeModel(i, j) {
1978
+ providers[i].models.splice(j, 1);
1979
+ renderProviders();
1980
+ }
1981
+ function checkDirty() {
1982
+ var dirty = savedProviders !== null && JSON.stringify(providers) !== savedProviders;
1983
+ var n = el("restart-notice");
1984
+ if (dirty) { n.style.display = "block"; n.textContent = "Unsaved changes \u2014 click Save to write to the config file, then restart bili to apply."; }
1985
+ else { n.style.display = "none"; }
1986
+ }
1987
+
1988
+ // \u2500\u2500 save \u2500\u2500
1989
+ async function saveProviders() {
1990
+ var obj = {};
1991
+ providers.forEach(function(p) {
1992
+ if (!p.name) return;
1993
+ if (p.models.length === 0) { obj[p.name] = p.url; }
1994
+ else {
1995
+ var models = {};
1996
+ p.models.forEach(function(m){ if(m.name) models[m.name] = { context: m.context, output: m.output }; });
1997
+ obj[p.name] = { url: p.url, models: models };
1998
+ }
1999
+ });
2000
+ try {
2001
+ var r = await fetch("/__acp/config", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ providers: obj }) });
2002
+ var d = await r.json();
2003
+ if (r.ok) { savedProviders = JSON.stringify(providers); checkDirty(); toast("Saved " + d.count + " providers \u2014 restart bili to apply"); }
2004
+ else { toast("Error: " + (d.error || "unknown"), true); }
2005
+ } catch(e) { toast("Save failed: " + e, true); }
2006
+ }
2007
+
2008
+ // \u2500\u2500 client setup \u2500\u2500
2009
+ function renderSetup() {
2010
+ var sel = el("setup-provider");
2011
+ if (sel.options.length === 0 && providers.length > 0) {
2012
+ providers.forEach(function(p){ sel.options.add(new Option(p.name, p.name)); });
2013
+ }
2014
+ var name = sel.value || (providers[0] && providers[0].name) || "";
2015
+ var p = providers.find(function(x){ return x.name === name; });
2016
+ var box = el("setup-snippets");
2017
+ if (!p) { box.innerHTML = '<div class="empty">Add a provider first (Providers tab).</div>'; return; }
2018
+ var base = ORIGIN + "/" + p.name;
2019
+ // Pi: the original upstream URL minus host \u2192 keep the tail
2020
+ var tail = p.url.replace(/^https?:\\/\\/[^/]+/, "");
2021
+ var full = tail ? base + tail : base;
2022
+ var h = "";
2023
+ h += snippet("Pi (~/.pi/agent/models.json)", '"baseUrl": "' + full + '"');
2024
+ h += snippet("OpenCode (opencode.json)", '"baseURL": "' + full + '"');
2025
+ h += snippet("Codex (config.toml)", 'base_url = "' + full + '"');
2026
+ h += snippet("Full path (any client)", full);
2027
+ box.innerHTML = h;
2028
+ }
2029
+ function snippet(label, code) {
2030
+ var c = code.replace(/"/g, "&quot;");
2031
+ return '<div class="snippet"><div class="label"><b>' + label + '</b></div><code>' + esc(code) + '</code><button class="btn small copy" onclick="copyText(this,\\''+c+'\\')">Copy</button></div>';
2032
+ }
2033
+ function copyText(btn, text) {
2034
+ var t = text.replace(/&quot;/g, '"');
2035
+ navigator.clipboard.writeText(t).then(function(){ toast("Copied"); });
2036
+ }
2037
+
2038
+ // \u2500\u2500 sessions \u2500\u2500
2039
+ async function refreshSessions() {
2040
+ try {
2041
+ var r = await fetch("/__acp/stats");
2042
+ var d = await r.json();
2043
+ var ss = d.sessions || [];
2044
+ el("sess-total").textContent = ss.length + " session" + (ss.length !== 1 ? "s" : "");
2045
+ if (ss.length === 0) { el("sessions-table").innerHTML = '<div class="empty">No sessions yet. Send a request through the proxy.</div>'; return; }
2046
+ var h = '<table><tr><th>ID</th><th>Requests</th><th>Tokens saved</th><th>Last seen</th></tr>';
2047
+ var h = '<table><tr><th>Title</th><th>Protocol</th><th>Label</th><th>Requests</th><th>Context</th><th>Cache hit</th><th>Input</th><th>Output</th><th>Last seen</th></tr>';
2048
+ ss.forEach(function(s) {
2049
+ var title = s.title ? esc(s.title) : "<span class='dim'>\u2014</span>";
2050
+ var proto = s.protocol ? esc(s.protocol) : "<span class='dim'>?</span>";
2051
+ var label = s.label ? "<span class=\\"mono\\">"+esc(s.label.slice(0,24))+"</span>" : "<span class='dim'>\u2014</span>";
2052
+ var ctx = s.contextTokens ? fmtTok(s.contextTokens) : "0";
2053
+ var ch = (s.cacheHitPct !== null && s.cacheHitPct !== undefined) ? s.cacheHitPct + "%" : "<span class='dim'>\u2014</span>";
2054
+ var inp = s.inputTokens ? fmtTok(s.inputTokens) : "0";
2055
+ var out = s.outputTokens ? fmtTok(s.outputTokens) : "0";
2056
+ h += "<tr><td>"+title+"</td><td>"+proto+"</td><td>"+label+"</td><td>"+s.requests+"</td><td>"+ctx+"</td><td>"+ch+"</td><td>"+inp+"</td><td>"+out+"</td><td>"+esc(s.lastSeen)+"</td></tr>";
2057
+ });
2058
+ h += "</table>";
2059
+ el("sessions-table").innerHTML = h;
2060
+ } catch(e) { el("sessions-table").innerHTML = '<div class="empty">Failed to load: ' + esc(e) + "</div>"; }
2061
+ }
2062
+ setInterval(function(){ if (el("tab-sessions").classList.contains("active")) refreshSessions(); }, 5000);
2063
+
2064
+ load();
2065
+ </script>
2066
+ </body>
2067
+ </html>`;
2068
+
1591
2069
  // src/orphan-gc.ts
1592
2070
  var ORPHAN_THRESHOLD = 3;
1593
2071
  var orphanStreaks = /* @__PURE__ */ new WeakMap();
@@ -1625,8 +2103,8 @@ import {
1625
2103
  collectBlockContent as collectBlockContent2,
1626
2104
  deactivateBlock
1627
2105
  } from "acp-kernel";
1628
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
1629
- import { dirname as dirname3, join as join2 } from "path";
2106
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
2107
+ import { dirname as dirname4, join as join3 } from "path";
1630
2108
  import { tmpdir } from "os";
1631
2109
  function resolveDecompress(args, ctx) {
1632
2110
  const rawBlockId = args.blockId;
@@ -1655,11 +2133,11 @@ function resolveDecompress(args, ctx) {
1655
2133
  }
1656
2134
  const header = `[Restored block ${blockId} \u2014 ${count} item(s)${full ? ", full" : ""}]`;
1657
2135
  const safeBlockId = blockId.replace(/[^a-zA-Z0-9_-]/g, "-");
1658
- const outPath = body.length > 1e4 ? join2(tmpdir(), `acp-decompress-${safeBlockId}-${Date.now()}.txt`) : null;
2136
+ const outPath = body.length > 1e4 ? join3(tmpdir(), `acp-decompress-${safeBlockId}-${Date.now()}.txt`) : null;
1659
2137
  if (outPath) {
1660
2138
  try {
1661
- mkdirSync4(dirname3(outPath), { recursive: true });
1662
- writeFileSync3(outPath, body, "utf8");
2139
+ mkdirSync5(dirname4(outPath), { recursive: true });
2140
+ writeFileSync4(outPath, body, "utf8");
1663
2141
  return `${header}
1664
2142
  Content (${body.length} chars) written to: ${outPath}
1665
2143
  Use the read tool to access it.`;
@@ -1942,6 +2420,10 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
1942
2420
  if (typeof prompt === "number") {
1943
2421
  const ch = typeof cached === "number" ? cached : 0;
1944
2422
  log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${typeof cached === "number" ? cached : "?"} output=${out ?? "?"}${ch > 0 ? ` (cache hit ${Math.round(ch / prompt * 100)}%)` : ""}`);
2423
+ ctx.session.stats.inputTokens += prompt;
2424
+ if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
2425
+ if (typeof out === "number") ctx.session.stats.outputTokens += out;
2426
+ ctx.session.stats.cacheSamples += 1;
1945
2427
  }
1946
2428
  }
1947
2429
  if (!hasOnlyProxy) {
@@ -2342,6 +2824,12 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
2342
2824
  const cached = inDet?.cached_tokens ?? prDet?.cached_tokens ?? "?";
2343
2825
  const out = usage.output_tokens ?? "?";
2344
2826
  log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${cached} output=${out}${cached !== "?" && cached !== 0 && prompt !== "?" ? ` (cache hit ${Math.round(Number(cached) / Number(prompt) * 100)}%)` : ""}`);
2827
+ if (typeof prompt === "number") {
2828
+ ctx.session.stats.inputTokens += prompt;
2829
+ if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
2830
+ if (typeof out === "number") ctx.session.stats.outputTokens += out;
2831
+ ctx.session.stats.cacheSamples += 1;
2832
+ }
2345
2833
  }
2346
2834
  }
2347
2835
  }
@@ -2667,9 +3155,10 @@ async function startServer(opts) {
2667
3155
  }
2668
3156
  });
2669
3157
  server.listen(opts.port, opts.host, () => {
3158
+ const displayHost = opts.host === "0.0.0.0" ? "localhost" : opts.host;
2670
3159
  log2(
2671
3160
  "info",
2672
- `acp-proxy listening on http://${opts.host}:${opts.port}` + (Object.keys(opts.routes).length ? ` \u2014 routes: ${Object.entries(opts.routes).map(([n, u]) => `${n}=${typeof u === "string" ? u : u.url}`).join(", ")}` : ` \u2192 ${opts.upstream}`)
3161
+ `acp-proxy listening on http://${displayHost}:${opts.port}` + (Object.keys(opts.routes).length ? ` \u2014 routes: ${Object.entries(opts.routes).map(([n, u]) => `${n}=${typeof u === "string" ? u : u.url}`).join(", ")}` : ` \u2192 ${opts.upstream}`) + ` \u2014 web UI: http://${displayHost}:${opts.port}/__acp/`
2673
3162
  );
2674
3163
  });
2675
3164
  server.on("error", (err) => {
@@ -2721,6 +3210,14 @@ async function handle(req, res, opts, core, config, log2) {
2721
3210
  res.end(JSON.stringify({ ok: true, upstream: opts.upstream }));
2722
3211
  return;
2723
3212
  }
3213
+ if (req.method === "GET" && req.url === "/__acp/") {
3214
+ const origin = `http://${opts.host === "0.0.0.0" ? "localhost" : opts.host}:${opts.port}`;
3215
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
3216
+ res.end(renderUI(origin));
3217
+ return;
3218
+ }
3219
+ if (req.method === "GET" && req.url === "/__acp/config") return handleConfigGet(res);
3220
+ if (req.method === "PUT" && req.url === "/__acp/config") return handleConfigPut(req, res);
2724
3221
  let bodyBuffer;
2725
3222
  try {
2726
3223
  bodyBuffer = await readBody(req);
@@ -2764,8 +3261,9 @@ async function handle(req, res, opts, core, config, log2) {
2764
3261
  const sessionHeader = headerValue(req, opts.sessionHeader);
2765
3262
  const conversation = protocol === "anthropic" ? conversationSignalAnthropic(parsed, sessionHeader) : protocol === "openai" ? conversationSignalOpenai(parsed, sessionHeader) : conversationSignalResponses(parsed, sessionHeader);
2766
3263
  const sessionId = deriveSessionId(req.headers, protocol, upstreamOrigin, conversation);
2767
- const session = getSession(sessionId, { protocol, upstreamOrigin });
2768
3264
  const affinity = affinityToken(req.headers, conversation);
3265
+ const clientLabel = clientConversationHeader(req.headers);
3266
+ const session = getSession(sessionId, { protocol, upstreamOrigin, label: clientLabel ?? void 0 });
2769
3267
  await withSessionLock(session, async () => {
2770
3268
  prepared = protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, log2, session) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, log2, session) : prepareResponses(parsed, req, opts, core, reqConfig, log2, session);
2771
3269
  acquireInFlight(session);
@@ -2811,7 +3309,7 @@ function diagNudge(turn, sessionId, tokenCount, limit) {
2811
3309
  function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
2812
3310
  const sessionId = session.id;
2813
3311
  const stream2 = parsed.stream === true;
2814
- session.requests++;
3312
+ ++session.stats.requests;
2815
3313
  let processedMessages = [];
2816
3314
  let rebuiltMessages = parsed.messages;
2817
3315
  let systemOut = parsed.system;
@@ -2821,6 +3319,11 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
2821
3319
  const tokenCount = estimateTokensFast3(msgs.map((m) => m.text ?? "").join("\n"));
2822
3320
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
2823
3321
  session.state = turn.state;
3322
+ session.stats.contextTokens = tokenCount;
3323
+ if (!session.meta.title) {
3324
+ const t = deriveTitle(msgs);
3325
+ if (t) session.meta.title = t;
3326
+ }
2824
3327
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
2825
3328
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
2826
3329
  processedMessages = turn.messages;
@@ -2850,7 +3353,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
2850
3353
  function prepareOpenai(parsed, req, opts, core, config, log2, session) {
2851
3354
  const sessionId = session.id;
2852
3355
  const stream2 = parsed.stream === true;
2853
- session.requests++;
3356
+ ++session.stats.requests;
2854
3357
  let processedMessages = [];
2855
3358
  let rebuiltMessages = parsed.messages;
2856
3359
  let toolsOut = parsed.tools;
@@ -2862,6 +3365,11 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
2862
3365
  const tokenCount = estimateTokensFast3(msgs.map((m) => m.text ?? "").join("\n"));
2863
3366
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
2864
3367
  session.state = turn.state;
3368
+ session.stats.contextTokens = tokenCount;
3369
+ if (!session.meta.title) {
3370
+ const t = deriveTitle(msgs);
3371
+ if (t) session.meta.title = t;
3372
+ }
2865
3373
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
2866
3374
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
2867
3375
  processedMessages = turn.messages;
@@ -2893,7 +3401,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
2893
3401
  function prepareResponses(parsed, req, opts, core, config, log2, session) {
2894
3402
  const sessionId = session.id;
2895
3403
  const stream2 = parsed.stream === true;
2896
- session.requests++;
3404
+ ++session.stats.requests;
2897
3405
  let processedMessages = [];
2898
3406
  let rebuiltInput = parsed.input;
2899
3407
  let toolsOut = parsed.tools;
@@ -2906,6 +3414,11 @@ function prepareResponses(parsed, req, opts, core, config, log2, session) {
2906
3414
  const tokenCount = estimateTokensFast3(msgs.map((m) => m.text ?? "").join("\n"));
2907
3415
  const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: process.env.ACP_RENDER_NONE ? "none" : "text-only" });
2908
3416
  session.state = turn.state;
3417
+ session.stats.contextTokens = tokenCount;
3418
+ if (!session.meta.title) {
3419
+ const t = deriveTitle(msgs);
3420
+ if (t) session.meta.title = t;
3421
+ }
2909
3422
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
2910
3423
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit));
2911
3424
  processedMessages = turn.messages;
@@ -3178,11 +3691,11 @@ async function pipeThrough(stream2, res) {
3178
3691
  }
3179
3692
  }
3180
3693
  async function dumpStreamToFile(stream2, dir, name) {
3181
- const { mkdirSync: mkdirSync5, createWriteStream: createWriteStream2 } = await import("fs");
3182
- const { join: join3 } = await import("path");
3694
+ const { mkdirSync: mkdirSync6, createWriteStream: createWriteStream2 } = await import("fs");
3695
+ const { join: join4 } = await import("path");
3183
3696
  try {
3184
- mkdirSync5(dir, { recursive: true });
3185
- const ws = createWriteStream2(join3(dir, name));
3697
+ mkdirSync6(dir, { recursive: true });
3698
+ const ws = createWriteStream2(join4(dir, name));
3186
3699
  const reader = stream2.getReader();
3187
3700
  try {
3188
3701
  for (; ; ) {
@@ -3197,11 +3710,28 @@ async function dumpStreamToFile(stream2, dir, name) {
3197
3710
  } catch {
3198
3711
  }
3199
3712
  }
3713
+ function deriveTitle(messages) {
3714
+ for (const m of messages) {
3715
+ if (m.role !== "user" || m.contentType !== "text") continue;
3716
+ const clean = (m.text ?? "").replace(/\s+/g, " ").trim();
3717
+ if (clean) return clean.length > 60 ? clean.slice(0, 57) + "\u2026" : clean;
3718
+ }
3719
+ return void 0;
3720
+ }
3200
3721
  function sendStats(res) {
3201
3722
  const sessions2 = listSessions().map((s) => ({
3202
3723
  id: s.id,
3203
- requests: s.requests,
3204
- tokensSaved: s.tokensSaved,
3724
+ protocol: s.meta.protocol,
3725
+ upstream: s.meta.upstreamOrigin,
3726
+ label: s.meta.label,
3727
+ title: s.meta.title,
3728
+ requests: s.stats.requests,
3729
+ contextTokens: s.stats.contextTokens,
3730
+ inputTokens: s.stats.inputTokens,
3731
+ cachedTokens: s.stats.cachedTokens,
3732
+ outputTokens: s.stats.outputTokens,
3733
+ cacheSamples: s.stats.cacheSamples,
3734
+ cacheHitPct: s.stats.cacheSamples > 0 ? Math.round(s.stats.cachedTokens / s.stats.inputTokens * 100) : null,
3205
3735
  lastSeen: new Date(s.lastSeen).toISOString()
3206
3736
  }));
3207
3737
  res.writeHead(200, { "content-type": "application/json" });
@@ -3254,7 +3784,7 @@ function logMsg(opts, level, msg2) {
3254
3784
  import { readFile, writeFile, mkdir, access, constants, rm } from "fs/promises";
3255
3785
  import { execFile } from "child_process";
3256
3786
  import path4 from "path";
3257
- import { fileURLToPath } from "url";
3787
+ import { fileURLToPath as fileURLToPath2 } from "url";
3258
3788
  var REGISTRY_BASE = "https://registry.npmjs.org";
3259
3789
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
3260
3790
  var THROTTLE_FILE = path4.join(cacheDir(), ".update-check");
@@ -3293,7 +3823,7 @@ async function writeLastCheck(ts) {
3293
3823
  }
3294
3824
  }
3295
3825
  async function findInstallDir(packageName) {
3296
- let dir = path4.dirname(fileURLToPath(import.meta.url));
3826
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
3297
3827
  for (; ; ) {
3298
3828
  try {
3299
3829
  const pkg = JSON.parse(await readFile(path4.join(dir, "package.json"), "utf-8"));
@@ -3530,23 +4060,23 @@ function startAutoUpdate(opts) {
3530
4060
  }
3531
4061
 
3532
4062
  // src/cli.ts
3533
- import { readFileSync as readFileSync3 } from "fs";
3534
- import { fileURLToPath as fileURLToPath2 } from "url";
4063
+ import { readFileSync as readFileSync4 } from "fs";
4064
+ import { fileURLToPath as fileURLToPath3 } from "url";
3535
4065
  import path5 from "path";
3536
4066
  var VERSION = (() => {
3537
4067
  try {
3538
- const here = fileURLToPath2(import.meta.url);
4068
+ const here = fileURLToPath3(import.meta.url);
3539
4069
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3540
- return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "dev";
4070
+ return JSON.parse(readFileSync4(pkg, "utf8")).version ?? "dev";
3541
4071
  } catch {
3542
4072
  return "dev";
3543
4073
  }
3544
4074
  })();
3545
4075
  var PACKAGE_NAME = (() => {
3546
4076
  try {
3547
- const here = fileURLToPath2(import.meta.url);
4077
+ const here = fileURLToPath3(import.meta.url);
3548
4078
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3549
- return JSON.parse(readFileSync3(pkg, "utf8")).name ?? "billion-context";
4079
+ return JSON.parse(readFileSync4(pkg, "utf8")).name ?? "billion-context";
3550
4080
  } catch {
3551
4081
  return "billion-context";
3552
4082
  }