billion-context 0.1.23 → 0.1.24

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
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b2) => (typeof require !== "undefined" ? require : a)[b2]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/config.ts
4
10
  import { defaultConfig } from "acp-kernel";
@@ -99,13 +105,13 @@ function closeLogger() {
99
105
  }
100
106
 
101
107
  // src/config.ts
102
- function safeReadJson(path6) {
108
+ function safeReadJson(path7) {
103
109
  try {
104
- const raw = readFileSync(path6, "utf8").replace(/^\uFEFF/, "");
110
+ const raw = readFileSync(path7, "utf8").replace(/^\uFEFF/, "");
105
111
  return JSON.parse(raw);
106
112
  } catch (e) {
107
113
  if (e.code !== "ENOENT") {
108
- log("error", `[acp-config] failed to parse ${path6}: ${String(e)}`);
114
+ log("error", `[acp-config] failed to parse ${path7}: ${String(e)}`);
109
115
  }
110
116
  return void 0;
111
117
  }
@@ -134,17 +140,41 @@ function lookupContextLimit(model) {
134
140
  }
135
141
  return void 0;
136
142
  }
137
- function resolveContextLimit(routes, provider, model) {
138
- if (!model) return void 0;
139
- if (provider) {
140
- const route = routes[provider];
141
- if (route?.models) {
142
- const m2 = route.models[model];
143
- if (m2?.context && m2.context > 0) return m2.context;
143
+ function resolveContextLimit(routes, upstreamUrl, model) {
144
+ if (!model || !upstreamUrl) return lookupContextLimit(model);
145
+ let bestKey = "";
146
+ for (const key of Object.keys(routes)) {
147
+ if (upstreamUrl === key || upstreamUrl.startsWith(key + "/")) {
148
+ if (key.length > bestKey.length) bestKey = key;
144
149
  }
145
150
  }
151
+ if (bestKey) {
152
+ const m2 = routes[bestKey].models?.[model];
153
+ if (m2?.context && m2.context > 0) return m2.context;
154
+ }
146
155
  return lookupContextLimit(model);
147
156
  }
157
+ function loadRoutes(env = process.env) {
158
+ const fileConfig = loadConfigFile();
159
+ const routes = {};
160
+ const routesPath = env.ACP_PROVIDERS ?? fileConfig.providersPath ?? "";
161
+ if (routesPath) {
162
+ const parsed = safeReadJson(routesPath);
163
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
164
+ for (const [k2, v2] of Object.entries(parsed)) {
165
+ const route = parseRouteEntry(v2);
166
+ if (route) routes[normalizeUrlKey(k2)] = route;
167
+ }
168
+ }
169
+ }
170
+ if (fileConfig.providers) {
171
+ for (const [k2, v2] of Object.entries(fileConfig.providers)) {
172
+ const route = parseRouteEntry(v2);
173
+ if (route && !routes[normalizeUrlKey(k2)]) routes[normalizeUrlKey(k2)] = route;
174
+ }
175
+ }
176
+ return routes;
177
+ }
148
178
  function loadOptions(env = process.env) {
149
179
  const fileConfig = loadConfigFile();
150
180
  const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
@@ -157,14 +187,14 @@ function loadOptions(env = process.env) {
157
187
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
158
188
  for (const [k2, v2] of Object.entries(parsed)) {
159
189
  const route = parseRouteEntry(v2);
160
- if (route) routes[k2] = route;
190
+ if (route) routes[normalizeUrlKey(k2)] = route;
161
191
  }
162
192
  }
163
193
  }
164
194
  if (fileConfig.providers) {
165
195
  for (const [k2, v2] of Object.entries(fileConfig.providers)) {
166
196
  const route = parseRouteEntry(v2);
167
- if (route && !routes[k2]) routes[k2] = route;
197
+ if (route && !routes[normalizeUrlKey(k2)]) routes[normalizeUrlKey(k2)] = route;
168
198
  }
169
199
  }
170
200
  const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
@@ -211,14 +241,15 @@ function ensureConfigTemplate() {
211
241
  return false;
212
242
  }
213
243
  }
244
+ function normalizeUrlKey(key) {
245
+ return key.replace(/\/+$/, "");
246
+ }
214
247
  function parseRouteEntry(v2) {
215
- if (typeof v2 === "string" && v2.length > 0) {
216
- return { url: v2.replace(/\/$/, "") };
217
- }
218
- if (v2 && typeof v2 === "object" && !Array.isArray(v2) && typeof v2.url === "string" && v2.url.length > 0) {
248
+ if (v2 && typeof v2 === "object" && !Array.isArray(v2)) {
219
249
  const obj = v2;
220
- return { url: obj.url.replace(/\/$/, ""), models: obj.models };
250
+ return { models: obj.models };
221
251
  }
252
+ if (v2 === null) return {};
222
253
  return void 0;
223
254
  }
224
255
 
@@ -228,6 +259,126 @@ import fs2 from "fs";
228
259
  import { tmpdir as tmpdir2 } from "os";
229
260
  import { createCore, estimateTokensFast as estimateTokensFast3, renderNudgeText, deactivateBlock as deactivateBlock4 } from "acp-kernel";
230
261
 
262
+ // src/registry.ts
263
+ import { readFile, writeFile, mkdir } from "fs/promises";
264
+ import { existsSync as existsSync2 } from "fs";
265
+ import path3 from "path";
266
+ var REGISTRY_URL = "https://models.dev/models.json";
267
+ var CACHE_FILE = path3.join(cacheDir(), "models-dev.json");
268
+ var TTL_MS = 24 * 60 * 60 * 1e3;
269
+ var cache = null;
270
+ var loading = null;
271
+ function parse(raw) {
272
+ try {
273
+ const parsed = JSON.parse(raw);
274
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
275
+ return parsed;
276
+ }
277
+ } catch {
278
+ }
279
+ return null;
280
+ }
281
+ async function readDiskCache() {
282
+ try {
283
+ const raw = await readFile(CACHE_FILE, "utf8");
284
+ return parse(raw);
285
+ } catch {
286
+ return null;
287
+ }
288
+ }
289
+ async function writeDiskCache(data) {
290
+ try {
291
+ await mkdir(path3.dirname(CACHE_FILE), { recursive: true });
292
+ await writeFile(CACHE_FILE, JSON.stringify(data), "utf8");
293
+ } catch {
294
+ }
295
+ }
296
+ function diskCacheFresh() {
297
+ if (!existsSync2(CACHE_FILE)) return false;
298
+ try {
299
+ const { mtimeMs } = __require("fs").statSync(CACHE_FILE);
300
+ return Date.now() - mtimeMs < TTL_MS;
301
+ } catch {
302
+ return false;
303
+ }
304
+ }
305
+ async function fetchFresh() {
306
+ try {
307
+ const res = await fetch(REGISTRY_URL, {
308
+ signal: AbortSignal.timeout(15e3),
309
+ headers: { Accept: "application/json" }
310
+ });
311
+ if (!res.ok) return null;
312
+ const text = await res.text();
313
+ const parsed = parse(text);
314
+ if (parsed) await writeDiskCache(parsed);
315
+ return parsed;
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+ async function loadRegistry() {
321
+ if (cache) return cache;
322
+ if (diskCacheFresh()) {
323
+ const disk = await readDiskCache();
324
+ if (disk) {
325
+ cache = disk;
326
+ return cache;
327
+ }
328
+ }
329
+ if (loading) return loading;
330
+ loading = (async () => {
331
+ const fresh = await fetchFresh();
332
+ if (fresh) {
333
+ cache = fresh;
334
+ log("info", `[acp-registry] loaded models.dev (${Object.keys(fresh).length} models)`);
335
+ return fresh;
336
+ }
337
+ const disk = await readDiskCache();
338
+ if (disk) {
339
+ cache = disk;
340
+ log("info", `[acp-registry] using stale disk cache (${Object.keys(disk).length} models, fetch failed)`);
341
+ return disk;
342
+ }
343
+ log("warn", `[acp-registry] could not load models.dev registry (offline + no cache)`);
344
+ return null;
345
+ })();
346
+ const result = await loading;
347
+ loading = null;
348
+ return result;
349
+ }
350
+ var HOST_TO_PROVIDER = {
351
+ "api.anthropic.com": "anthropic",
352
+ "api.openai.com": "openai",
353
+ "open.bigmodel.cn": "zhipuai",
354
+ "open.bigmodel.com": "zhipuai",
355
+ "coding.dashscope.aliyuncs.com": "dashscope",
356
+ "api.deepseek.com": "deepseek",
357
+ "api.moonshot.cn": "moonshot",
358
+ "generativelanguage.googleapis.com": "google",
359
+ "ai.comfly.org": "comfly"
360
+ };
361
+ function providerFromHost(host) {
362
+ const lower = host.toLowerCase();
363
+ if (HOST_TO_PROVIDER[lower]) return HOST_TO_PROVIDER[lower];
364
+ for (const [h, p2] of Object.entries(HOST_TO_PROVIDER)) {
365
+ if (lower.endsWith(h) || h.endsWith(lower)) return p2;
366
+ }
367
+ return void 0;
368
+ }
369
+ async function contextFromRegistry(model, host) {
370
+ const reg = await loadRegistry();
371
+ if (!reg || !model) return void 0;
372
+ const provider = host ? providerFromHost(host) : void 0;
373
+ const candidates = provider ? [`${provider}/${model}`, model] : [model];
374
+ for (const key of candidates) {
375
+ const entry = reg[key];
376
+ const ctx = entry?.limit?.context;
377
+ if (typeof ctx === "number" && ctx > 0) return ctx;
378
+ }
379
+ return void 0;
380
+ }
381
+
231
382
  // src/fetch-util.ts
232
383
  var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
233
384
  var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -760,9 +911,9 @@ import { createInitialState as createInitialState2 } from "acp-kernel";
760
911
 
761
912
  // src/persist.ts
762
913
  import { promises as fs } from "fs";
763
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
914
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
764
915
  import { createHash as createHash2 } from "crypto";
765
- import * as path3 from "path";
916
+ import * as path4 from "path";
766
917
  import { createInitialState } from "acp-kernel";
767
918
  var PERSIST_VERSION = 2;
768
919
  function mergeState(parsed) {
@@ -788,7 +939,7 @@ function hostLabel(upstreamOrigin) {
788
939
  function relPathFor(id, protocol, upstreamOrigin) {
789
940
  const proto = protocol ?? "_unknown";
790
941
  const host = protocol ? hostLabel(upstreamOrigin) + "_" : "";
791
- return path3.join(proto, `${host}${createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24)}.json`);
942
+ return path4.join(proto, `${host}${createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24)}.json`);
792
943
  }
793
944
  function legacyFileNameFor(id) {
794
945
  return createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24) + ".json";
@@ -808,13 +959,13 @@ var SessionStore = class {
808
959
  this.log = opts?.log ?? defaultLogger;
809
960
  }
810
961
  filePath(id, protocol, upstreamOrigin) {
811
- return path3.join(this.dir, relPathFor(id, protocol, upstreamOrigin));
962
+ return path4.join(this.dir, relPathFor(id, protocol, upstreamOrigin));
812
963
  }
813
964
  /** A unique temp path per write (per process). Two overlapping writes for
814
965
  * the same session must not share a temp file, or one rename invalidates
815
966
  * the other. */
816
967
  tempPath(id) {
817
- return path3.join(this.dir, `.tmp-${legacyFileNameFor(id)}-${process.pid}-${this.tmpSeq++}`);
968
+ return path4.join(this.dir, `.tmp-${legacyFileNameFor(id)}-${process.pid}-${this.tmpSeq++}`);
818
969
  }
819
970
  /** Bulk-load every persisted session from disk into a map keyed by the
820
971
  * REAL session id (read from the file body, not the filename). Called once
@@ -838,7 +989,7 @@ var SessionStore = class {
838
989
  }
839
990
  for (const e of entries) {
840
991
  if (e.name.startsWith(".tmp-")) continue;
841
- const full = path3.join(dir, e.name);
992
+ const full = path4.join(dir, e.name);
842
993
  if (e.isDirectory()) {
843
994
  await walk(full);
844
995
  } else if (e.isFile() && e.name.endsWith(".json")) {
@@ -848,14 +999,14 @@ var SessionStore = class {
848
999
  };
849
1000
  await walk(this.dir);
850
1001
  for (const full of files) {
851
- const name = path3.basename(full);
1002
+ const name = path4.basename(full);
852
1003
  try {
853
1004
  const parsed = JSON.parse(await fs.readFile(full, "utf8"));
854
1005
  if (!isValidRecord(parsed)) continue;
855
1006
  const pm = parsed.meta ?? {};
856
1007
  const proto = pm.protocol ?? parsed.protocol;
857
1008
  const origin = pm.upstreamOrigin ?? parsed.upstreamOrigin;
858
- const expectedNamespaced = path3.basename(relPathFor(parsed.id, proto, origin));
1009
+ const expectedNamespaced = path4.basename(relPathFor(parsed.id, proto, origin));
859
1010
  const expectedLegacy = legacyFileNameFor(parsed.id);
860
1011
  if (name !== expectedNamespaced && name !== expectedLegacy) {
861
1012
  this.log("warn", `[persist] skipping ${full}: filename does not match body id (expected ${expectedNamespaced})`);
@@ -877,7 +1028,7 @@ var SessionStore = class {
877
1028
  const candidates = [this.filePath(id, meta?.protocol, meta?.upstreamOrigin)];
878
1029
  if (meta?.protocol) candidates.push(this.filePath(id));
879
1030
  for (const file of candidates) {
880
- if (!existsSync2(file)) continue;
1031
+ if (!existsSync3(file)) continue;
881
1032
  try {
882
1033
  const parsed = JSON.parse(readFileSync2(file, "utf8"));
883
1034
  if (!isValidRecord(parsed) || parsed.id !== id) continue;
@@ -910,7 +1061,7 @@ var SessionStore = class {
910
1061
  const record = buildRecord(session);
911
1062
  const file = this.filePath(session.id, session.meta.protocol, session.meta.upstreamOrigin);
912
1063
  try {
913
- await fs.mkdir(path3.dirname(file), { recursive: true });
1064
+ await fs.mkdir(path4.dirname(file), { recursive: true });
914
1065
  } catch (e) {
915
1066
  this.log("warn", `[persist] could not create session dir ${this.dir}: ${msg(e)}`);
916
1067
  }
@@ -944,7 +1095,7 @@ var SessionStore = class {
944
1095
  const record = buildRecord(session);
945
1096
  const file = this.filePath(session.id, session.meta.protocol, session.meta.upstreamOrigin);
946
1097
  try {
947
- mkdirSync3(path3.dirname(file), { recursive: true });
1098
+ mkdirSync3(path4.dirname(file), { recursive: true });
948
1099
  } catch (e) {
949
1100
  this.log("warn", `[persist] could not create session dir ${this.dir}: ${msg(e)}`);
950
1101
  }
@@ -1670,22 +1821,22 @@ async function handleConfigPut(req, res) {
1670
1821
  return;
1671
1822
  }
1672
1823
  const routes = {};
1673
- for (const [name, val] of Object.entries(body.providers)) {
1674
- if (!name || typeof name !== "string") {
1824
+ for (const [url, val] of Object.entries(body.providers)) {
1825
+ if (!url || typeof url !== "string") {
1675
1826
  res.writeHead(400, { "content-type": "application/json" });
1676
- res.end(JSON.stringify({ error: `invalid provider name: ${JSON.stringify(name)}` }));
1827
+ res.end(JSON.stringify({ error: `invalid provider key: ${JSON.stringify(url)}` }));
1677
1828
  return;
1678
1829
  }
1679
1830
  const route = parseRouteEntry(val);
1680
1831
  if (!route) {
1681
1832
  res.writeHead(400, { "content-type": "application/json" });
1682
- res.end(JSON.stringify({ error: `invalid provider "${name}": expected "url" or { url, models }` }));
1833
+ res.end(JSON.stringify({ error: `invalid provider "${url}": expected { "models": { ... } } or null (the key is the upstream URL)` }));
1683
1834
  return;
1684
1835
  }
1685
- routes[name] = route;
1836
+ routes[normalizeUrlKey(url)] = route;
1686
1837
  }
1687
1838
  const existing = safeReadJson(configFile()) ?? {};
1688
- existing.providers = body.providers;
1839
+ existing.providers = routes;
1689
1840
  try {
1690
1841
  mkdirSync4(dirname3(configFile()), { recursive: true });
1691
1842
  writeFileSync3(configFile(), JSON.stringify(existing, null, 2) + "\n", "utf8");
@@ -1694,7 +1845,7 @@ async function handleConfigPut(req, res) {
1694
1845
  res.end(JSON.stringify({ error: `failed to write: ${String(e)}` }));
1695
1846
  return;
1696
1847
  }
1697
- log("info", `[acp-web] providers updated via web UI (${Object.keys(routes).length} providers) \u2014 restart to apply`);
1848
+ log("info", `[acp-web] providers updated via web UI (${Object.keys(routes).length} upstream URL(s)) \u2014 restart to apply`);
1698
1849
  res.writeHead(200, { "content-type": "application/json" });
1699
1850
  res.end(JSON.stringify({ ok: true, count: Object.keys(routes).length, note: "restart bili to apply" }));
1700
1851
  }
@@ -1819,6 +1970,8 @@ input:focus, select:focus { outline: none; border-color: var(--accent); }
1819
1970
  .snippet .label b { color: var(--fg); }
1820
1971
  .snippet code { display: block; font-size: 13px; color: var(--ok); white-space: pre-wrap; word-break: break-all; }
1821
1972
  .snippet .copy { position: absolute; top: 8px; right: 8px; }
1973
+ .client-setup { margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border); }
1974
+ .client-head { font-size: 12px; color: var(--dim); margin-bottom: 8px; }
1822
1975
 
1823
1976
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
1824
1977
  th { text-align: left; padding: 8px 12px; color: var(--dim); font-weight: 500; border-bottom: 1px solid var(--border); }
@@ -1850,11 +2003,10 @@ select { cursor: pointer; }
1850
2003
  </header>
1851
2004
  <nav>
1852
2005
  <button class="active" onclick="showTab('providers')">Providers</button>
1853
- <button onclick="showTab('setup')">Client Setup</button>
1854
2006
  <button onclick="showTab('sessions')">Sessions</button>
1855
2007
  </nav>
1856
2008
  <main>
1857
- <!-- Providers tab -->
2009
+ <!-- Providers tab (URL + models + client config, all in one) -->
1858
2010
  <div id="tab-providers" class="tab active">
1859
2011
  <div id="restart-notice" class="notice" style="display:none"></div>
1860
2012
  <div id="providers-list"></div>
@@ -1862,19 +2014,11 @@ select { cursor: pointer; }
1862
2014
  <button class="btn" onclick="addProvider()">+ Add provider</button>
1863
2015
  </div>
1864
2016
  <div class="actions">
2017
+ <button class="btn" onclick="applyProviders()">Apply</button>
1865
2018
  <button class="btn primary" onclick="saveProviders()">Save</button>
1866
2019
  </div>
1867
2020
  </div>
1868
2021
 
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
2022
  <!-- Sessions tab -->
1879
2023
  <div id="tab-sessions" class="tab">
1880
2024
  <div class="row" style="justify-content:space-between;margin-bottom:12px">
@@ -1894,6 +2038,7 @@ var savedProviders = null;
1894
2038
  // \u2500\u2500 helpers \u2500\u2500
1895
2039
  function el(id) { return document.getElementById(id); }
1896
2040
  function esc(s) { return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); }
2041
+ function autosize(t) { t.style.height="auto"; t.style.height=t.scrollHeight+"px"; }
1897
2042
  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
2043
  function toast(msg, isErr) {
1899
2044
  var t = el("toast"); t.textContent = msg; t.className = "toast show" + (isErr ? " err" : "");
@@ -1905,16 +2050,14 @@ function showTab(name) {
1905
2050
  el("tab-"+name).classList.add("active");
1906
2051
  event.target.classList.add("active");
1907
2052
  if (name === "sessions") refreshSessions();
1908
- if (name === "setup") renderSetup();
1909
2053
  }
1910
2054
 
1911
2055
  // \u2500\u2500 load \u2500\u2500
1912
2056
  async function load() {
1913
2057
  try {
1914
- var r = await fetch("/__acp/config");
2058
+ var r = await fetch("/__bili/config");
1915
2059
  var d = await r.json();
1916
2060
  providers = entries(d.providers);
1917
- el("setup-provider").innerHTML = "";
1918
2061
  savedProviders = JSON.stringify(providers);
1919
2062
  renderProviders();
1920
2063
  } catch(e) {
@@ -1923,18 +2066,17 @@ async function load() {
1923
2066
  }
1924
2067
  function entries(obj) {
1925
2068
  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 };
2069
+ return Object.keys(obj).map(function(url){
2070
+ var v = obj[url];
2071
+ var models = [];
2072
+ if (v && typeof v === "object" && !Array.isArray(v)) models = entries_models(v.models);
2073
+ return { url: url, models: models };
1932
2074
  });
1933
2075
  }
1934
2076
  function entries_models(obj) {
1935
2077
  if (!obj || typeof obj !== "object") return [];
1936
2078
  return Object.keys(obj).map(function(name){
1937
- return { name: name, context: obj[name].context||0, output: obj[name].output||0 };
2079
+ return { name: name, context: obj[name].context||0 };
1938
2080
  });
1939
2081
  }
1940
2082
 
@@ -1942,17 +2084,19 @@ function entries_models(obj) {
1942
2084
  function renderProviders() {
1943
2085
  var html = "";
1944
2086
  if (providers.length === 0) html = '<div class="empty">No providers yet. Click "Add provider" below.</div>';
1945
- providers.forEach(function(p, i) {
2087
+ providers.forEach(function(p, i) {
1946
2088
  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>';
2089
+ html += '<div class="card-head" style="align-items:flex-start"><div class="name" style="flex:1;min-width:0;width:100%"><textarea class="mono url-edit" onchange="providers['+i+'].url=this.value" oninput="autosize(this)" placeholder="https://upstream/api/path" style="background:transparent;border:1px dashed var(--border);border-radius:4px;padding:6px 8px;width:100%;resize:none;overflow:hidden;font-weight:600;color:var(--accent);font-family:inherit;font-size:13px;line-height:1.4">'+esc(p.url)+'</textarea></div>';
1948
2090
  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>';
2091
+ html += '<div class="client-setup">';
2092
+ html += '<div class="client-head">Client base URL \u2014 copy this into Pi / OpenCode / Codex / Claude Code config:</div>';
2093
+ html += snippet("", ORIGIN + "/bili/" + p.url);
2094
+ html += '</div>';
1950
2095
  p.models.forEach(function(m, j) {
1951
2096
  html += '<div class="sub-card">';
1952
2097
  html += '<div class="model-head"><span class="mname mono">'+esc(m.name)+'</span>';
1953
2098
  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>';
2099
+ html += '<div class="model-row"><label>context</label><input type="number" value="'+m.context+'" onchange="providers['+i+'].models['+j+'].context=parseInt(this.value)||0" placeholder="optional"></div>';
1956
2100
  html += '<div class="model-row"><label>name</label><input value="'+esc(m.name)+'" onchange="providers['+i+'].models['+j+'].name=this.value"></div>';
1957
2101
  html += '</div>';
1958
2102
  });
@@ -1960,10 +2104,11 @@ function renderProviders() {
1960
2104
  html += '</div>';
1961
2105
  });
1962
2106
  el("providers-list").innerHTML = html;
2107
+ document.querySelectorAll(".url-edit").forEach(autosize);
1963
2108
  checkDirty();
1964
2109
  }
1965
2110
  function addProvider() {
1966
- providers.push({ name: "new-provider", url: "https://", models: [] });
2111
+ providers.push({ url: "https://api.example.com", models: [] });
1967
2112
  renderProviders();
1968
2113
  }
1969
2114
  function removeProvider(i) {
@@ -1971,7 +2116,7 @@ function removeProvider(i) {
1971
2116
  renderProviders();
1972
2117
  }
1973
2118
  function addModel(i) {
1974
- providers[i].models.push({ name: "model-name", context: 200000, output: 8192 });
2119
+ providers[i].models.push({ name: "model-name", context: 0 });
1975
2120
  renderProviders();
1976
2121
  }
1977
2122
  function removeModel(i, j) {
@@ -1981,54 +2126,56 @@ function removeModel(i, j) {
1981
2126
  function checkDirty() {
1982
2127
  var dirty = savedProviders !== null && JSON.stringify(providers) !== savedProviders;
1983
2128
  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."; }
2129
+ if (dirty) { n.style.display = "block"; n.textContent = "Unsaved changes \u2014 click Save to write to the config file, then click Apply to activate without restart."; }
1985
2130
  else { n.style.display = "none"; }
1986
2131
  }
1987
2132
 
2133
+ // \u2500\u2500 apply (hot-reload routes into running process) \u2500\u2500
2134
+ async function applyProviders() {
2135
+ // Apply always re-reads the config FILE, so require a Save first if dirty.
2136
+ if (savedProviders !== JSON.stringify(providers)) {
2137
+ toast("Save your changes first", true);
2138
+ return;
2139
+ }
2140
+ try {
2141
+ var r = await fetch("/__bili/config/reload", { method: "POST" });
2142
+ var d = await r.json();
2143
+ if (r.ok) { toast("Applied \u2014 " + d.count + " providers active (no restart needed)"); }
2144
+ else { toast("Apply failed: " + (d.error || "unknown"), true); }
2145
+ } catch(e) { toast("Apply failed: " + e, true); }
2146
+ }
2147
+
1988
2148
  // \u2500\u2500 save \u2500\u2500
1989
2149
  async function saveProviders() {
1990
2150
  var obj = {};
1991
2151
  providers.forEach(function(p) {
1992
- if (!p.name) return;
1993
- if (p.models.length === 0) { obj[p.name] = p.url; }
2152
+ if (!p.url) return;
2153
+ // Strip trailing slashes (keys must be prefix-matchable). Done without a
2154
+ // regex literal because the HTML is inlined into a template string and
2155
+ // backslash escaping there is brittle.
2156
+ var key = p.url;
2157
+ while (key.length > 1 && key.charAt(key.length - 1) === "/") key = key.slice(0, -1);
2158
+ if (!key) return;
2159
+ if (p.models.length === 0) { obj[key] = {}; }
1994
2160
  else {
1995
2161
  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 };
2162
+ p.models.forEach(function(m){ if(m.name) models[m.name] = { context: m.context }; });
2163
+ obj[key] = { models: models };
1998
2164
  }
1999
2165
  });
2000
2166
  try {
2001
- var r = await fetch("/__acp/config", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ providers: obj }) });
2167
+ var r = await fetch("/__bili/config", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ providers: obj }) });
2002
2168
  var d = await r.json();
2003
- if (r.ok) { savedProviders = JSON.stringify(providers); checkDirty(); toast("Saved " + d.count + " providers \u2014 restart bili to apply"); }
2169
+ if (r.ok) { savedProviders = JSON.stringify(providers); checkDirty(); toast("Saved " + d.count + " provider(s) \u2014 click Apply to activate"); }
2004
2170
  else { toast("Error: " + (d.error || "unknown"), true); }
2005
2171
  } catch(e) { toast("Save failed: " + e, true); }
2006
2172
  }
2007
2173
 
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
- }
2174
+ // \u2500\u2500 snippets (used inline in each provider card) \u2500\u2500
2029
2175
  function snippet(label, code) {
2030
2176
  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>';
2177
+ var labelHtml = label ? '<div class="label"><b>' + label + '</b></div>' : '';
2178
+ return '<div class="snippet">' + labelHtml + '<code>' + esc(code) + '</code><button class="btn small copy" onclick="copyText(this,\\''+c+'\\')">Copy</button></div>';
2032
2179
  }
2033
2180
  function copyText(btn, text) {
2034
2181
  var t = text.replace(/&quot;/g, '"');
@@ -2038,7 +2185,7 @@ function copyText(btn, text) {
2038
2185
  // \u2500\u2500 sessions \u2500\u2500
2039
2186
  async function refreshSessions() {
2040
2187
  try {
2041
- var r = await fetch("/__acp/stats");
2188
+ var r = await fetch("/__bili/stats");
2042
2189
  var d = await r.json();
2043
2190
  var ss = d.sessions || [];
2044
2191
  el("sess-total").textContent = ss.length + " session" + (ss.length !== 1 ? "s" : "");
@@ -3111,21 +3258,14 @@ var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
3111
3258
  // streamed from fetch, otherwise clients try to decompress plain bytes.
3112
3259
  "content-encoding"
3113
3260
  ]);
3114
- function resolveUpstream(opts, reqUrl) {
3115
- const names = Object.keys(opts.routes);
3116
- if (names.length === 0) return void 0;
3117
- const RESERVED = /* @__PURE__ */ new Set(["v1", "v2", "v4", "chat", "completions", "messages", "models", "api"]);
3118
- const sorted = [...names].sort((a, b2) => b2.length - a.length);
3119
- const segments = reqUrl.split("/");
3120
- for (const name of sorted) {
3121
- if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name)) continue;
3122
- if (RESERVED.has(name.toLowerCase())) continue;
3123
- const idx = segments.indexOf(name);
3124
- if (idx < 0) continue;
3125
- const base = opts.routes[name].url.replace(/\/$/, "");
3126
- const rest = [...segments.slice(0, idx), ...segments.slice(idx + 1)].join("/");
3127
- const rewrittenUrl = base + rest;
3128
- return { upstream: base, rewrittenUrl, provider: name };
3261
+ function resolveUpstream(_opts, reqUrl) {
3262
+ if (reqUrl.startsWith("/bili/http://") || reqUrl.startsWith("/bili/https://")) {
3263
+ const full = reqUrl.slice(6);
3264
+ try {
3265
+ const u2 = new URL(full);
3266
+ return { upstream: `${u2.protocol}//${u2.host}`, rewrittenUrl: full };
3267
+ } catch {
3268
+ }
3129
3269
  }
3130
3270
  return void 0;
3131
3271
  }
@@ -3139,6 +3279,7 @@ async function startServer(opts) {
3139
3279
  if (filePath) {
3140
3280
  log2("info", `[log] writing to ${filePath}`);
3141
3281
  }
3282
+ void loadRegistry();
3142
3283
  const server = http.createServer(async (req, res) => {
3143
3284
  try {
3144
3285
  await handle(req, res, opts, core, config, log2);
@@ -3156,9 +3297,10 @@ async function startServer(opts) {
3156
3297
  });
3157
3298
  server.listen(opts.port, opts.host, () => {
3158
3299
  const displayHost = opts.host === "0.0.0.0" ? "localhost" : opts.host;
3300
+ const nOverrides = Object.keys(opts.routes).length;
3159
3301
  log2(
3160
3302
  "info",
3161
- `acp-proxy listening on http://${displayHost}:${opts.port}` + (Object.keys(opts.routes).length ? ` \u2014 routes: ${Object.entries(opts.routes).map(([n, u2]) => `${n}=${typeof u2 === "string" ? u2 : u2.url}`).join(", ")}` : ` \u2192 ${opts.upstream}`) + ` \u2014 web UI: http://${displayHost}:${opts.port}/__acp/`
3303
+ `acp-proxy listening on http://${displayHost}:${opts.port} \u2014 web UI: http://${displayHost}:${opts.port}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${opts.port}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "")
3162
3304
  );
3163
3305
  });
3164
3306
  server.on("error", (err) => {
@@ -3204,20 +3346,32 @@ async function startServer(opts) {
3204
3346
  return server;
3205
3347
  }
3206
3348
  async function handle(req, res, opts, core, config, log2) {
3207
- if (req.method === "GET" && req.url === "/__acp/stats") return sendStats(res);
3208
- if (req.method === "GET" && (req.url === "/" || req.url === "/__acp/health")) {
3349
+ if (req.method === "GET" && req.url === "/__bili/stats") return sendStats(res);
3350
+ if (req.method === "GET" && req.url === "/") {
3351
+ const accept = req.headers.accept ?? "";
3352
+ if (accept.includes("text/html")) {
3353
+ res.writeHead(302, { location: "/__bili/" });
3354
+ res.end();
3355
+ return;
3356
+ }
3209
3357
  res.writeHead(200, { "content-type": "application/json" });
3210
3358
  res.end(JSON.stringify({ ok: true, upstream: opts.upstream }));
3211
3359
  return;
3212
3360
  }
3213
- if (req.method === "GET" && req.url === "/__acp/") {
3361
+ if (req.method === "GET" && req.url === "/__bili/health") {
3362
+ res.writeHead(200, { "content-type": "application/json" });
3363
+ res.end(JSON.stringify({ ok: true, upstream: opts.upstream }));
3364
+ return;
3365
+ }
3366
+ if (req.method === "GET" && req.url === "/__bili/") {
3214
3367
  const origin = `http://${opts.host === "0.0.0.0" ? "localhost" : opts.host}:${opts.port}`;
3215
3368
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
3216
3369
  res.end(renderUI(origin));
3217
3370
  return;
3218
3371
  }
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);
3372
+ if (req.method === "GET" && req.url === "/__bili/config") return handleConfigGet(res);
3373
+ if (req.method === "PUT" && req.url === "/__bili/config") return handleConfigPut(req, res);
3374
+ if (req.method === "POST" && req.url === "/__bili/config/reload") return handleConfigReload(opts, res, log2);
3221
3375
  let bodyBuffer;
3222
3376
  try {
3223
3377
  bodyBuffer = await readBody(req);
@@ -3250,7 +3404,18 @@ async function handle(req, res, opts, core, config, log2) {
3250
3404
  if (parsed && typeof parsed === "object") {
3251
3405
  const model = parsed.model;
3252
3406
  if (model) {
3253
- const limit = resolveContextLimit(opts.routes, route?.provider, model);
3407
+ const embeddedUrl = route?.rewrittenUrl;
3408
+ let limit = resolveContextLimit(opts.routes, embeddedUrl, model);
3409
+ if (!limit && embeddedUrl) {
3410
+ const host = (() => {
3411
+ try {
3412
+ return new URL(embeddedUrl).host;
3413
+ } catch {
3414
+ return void 0;
3415
+ }
3416
+ })();
3417
+ limit = await contextFromRegistry(model, host);
3418
+ }
3254
3419
  if (limit && limit !== config.modelContextLimit) {
3255
3420
  reqConfig = { ...config, modelContextLimit: limit };
3256
3421
  }
@@ -3505,7 +3670,7 @@ function injectResponsesTool(tools) {
3505
3670
  }
3506
3671
  async function forward(req, res, opts, body, prepared, core, config, log2, route, affinity) {
3507
3672
  const upstreamUrl = route ? route.rewrittenUrl : opts.upstream + (req.url ?? "");
3508
- log2("info", `forward ${req.method} ${req.url ?? ""} \u2192 ${upstreamUrl}${route ? ` (${route.provider})` : ""}`);
3673
+ log2("info", `forward ${req.method} \u2192 ${upstreamUrl}`);
3509
3674
  if (process.env.ACP_DEBUG && prepared) {
3510
3675
  const sid = prepared.session.id;
3511
3676
  const hdrKeys = Object.keys(req.headers);
@@ -3718,6 +3883,15 @@ function deriveTitle(messages) {
3718
3883
  }
3719
3884
  return void 0;
3720
3885
  }
3886
+ function handleConfigReload(opts, res, log2) {
3887
+ const fresh = loadRoutes();
3888
+ for (const k2 of Object.keys(opts.routes)) delete opts.routes[k2];
3889
+ Object.assign(opts.routes, fresh);
3890
+ const names = Object.keys(fresh);
3891
+ log2("info", `[acp-web] routes hot-reloaded (${names.length} providers): ${names.join(", ") || "(none)"}`);
3892
+ res.writeHead(200, { "content-type": "application/json" });
3893
+ res.end(JSON.stringify({ ok: true, count: names.length, routes: names }));
3894
+ }
3721
3895
  function sendStats(res) {
3722
3896
  const sessions2 = listSessions().map((s3) => ({
3723
3897
  id: s3.id,
@@ -3781,7 +3955,7 @@ function logMsg(opts, level, msg2) {
3781
3955
  }
3782
3956
 
3783
3957
  // src/update.ts
3784
- import { readFile, writeFile, mkdir, access, constants, rm, cp } from "fs/promises";
3958
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access, constants, rm, cp } from "fs/promises";
3785
3959
 
3786
3960
  // node_modules/tar/dist/esm/index.min.js
3787
3961
  import Qr from "events";
@@ -6753,12 +6927,12 @@ var To = (s3) => {
6753
6927
  };
6754
6928
 
6755
6929
  // src/update.ts
6756
- import path4 from "path";
6930
+ import path5 from "path";
6757
6931
  import { fileURLToPath as fileURLToPath2 } from "url";
6758
6932
  var REGISTRY_BASE = "https://registry.npmjs.org";
6759
6933
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
6760
- var THROTTLE_FILE = path4.join(cacheDir(), ".update-check");
6761
- var LOCK_FILE = path4.join(cacheDir(), ".update-lock");
6934
+ var THROTTLE_FILE = path5.join(cacheDir(), ".update-check");
6935
+ var LOCK_FILE = path5.join(cacheDir(), ".update-lock");
6762
6936
  var LOCK_STALE_MS = 2 * 60 * 1e3;
6763
6937
  var timer;
6764
6938
  var inFlight = false;
@@ -6779,7 +6953,7 @@ function isNewer(latest, current) {
6779
6953
  }
6780
6954
  async function readLastCheck() {
6781
6955
  try {
6782
- const data = await readFile(THROTTLE_FILE, "utf-8");
6956
+ const data = await readFile2(THROTTLE_FILE, "utf-8");
6783
6957
  return parseInt(data.trim(), 10) || 0;
6784
6958
  } catch {
6785
6959
  return 0;
@@ -6787,27 +6961,27 @@ async function readLastCheck() {
6787
6961
  }
6788
6962
  async function writeLastCheck(ts2) {
6789
6963
  try {
6790
- await mkdir(path4.dirname(THROTTLE_FILE), { recursive: true });
6791
- await writeFile(THROTTLE_FILE, String(ts2), "utf-8");
6964
+ await mkdir2(path5.dirname(THROTTLE_FILE), { recursive: true });
6965
+ await writeFile2(THROTTLE_FILE, String(ts2), "utf-8");
6792
6966
  } catch {
6793
6967
  }
6794
6968
  }
6795
6969
  async function findInstallDir(packageName) {
6796
- let dir = path4.dirname(fileURLToPath2(import.meta.url));
6970
+ let dir = path5.dirname(fileURLToPath2(import.meta.url));
6797
6971
  for (; ; ) {
6798
6972
  try {
6799
- const pkg = JSON.parse(await readFile(path4.join(dir, "package.json"), "utf-8"));
6973
+ const pkg = JSON.parse(await readFile2(path5.join(dir, "package.json"), "utf-8"));
6800
6974
  if (pkg.name === packageName) return dir;
6801
6975
  } catch {
6802
6976
  }
6803
- const parent = path4.dirname(dir);
6977
+ const parent = path5.dirname(dir);
6804
6978
  if (parent === dir) return void 0;
6805
6979
  dir = parent;
6806
6980
  }
6807
6981
  }
6808
6982
  async function readDiskVersion(installDir) {
6809
6983
  try {
6810
- const pkg = JSON.parse(await readFile(path4.join(installDir, "package.json"), "utf-8"));
6984
+ const pkg = JSON.parse(await readFile2(path5.join(installDir, "package.json"), "utf-8"));
6811
6985
  return pkg.version;
6812
6986
  } catch {
6813
6987
  return void 0;
@@ -6818,7 +6992,7 @@ async function tryAcquireLock() {
6818
6992
  const now = Date.now();
6819
6993
  async function readLock() {
6820
6994
  try {
6821
- const raw = await readFile(LOCK_FILE, "utf-8");
6995
+ const raw = await readFile2(LOCK_FILE, "utf-8");
6822
6996
  const data = JSON.parse(raw);
6823
6997
  if (typeof data.pid === "number" && typeof data.ts === "number") {
6824
6998
  return data;
@@ -6845,7 +7019,7 @@ async function tryAcquireLock() {
6845
7019
  log("info", `[update] stealing stale lock (pid=${existing.pid}, age=${Math.round(age / 1e3)}s, alive=${holderAlive})`);
6846
7020
  }
6847
7021
  try {
6848
- await writeFile(LOCK_FILE, JSON.stringify({ pid, ts: now }), { flag: "wx" });
7022
+ await writeFile2(LOCK_FILE, JSON.stringify({ pid, ts: now }), { flag: "wx" });
6849
7023
  } catch {
6850
7024
  const winner = await readLock();
6851
7025
  if (winner && winner.pid !== pid) {
@@ -6952,17 +7126,17 @@ async function installViaTarball(version, tarballUrl, installDir) {
6952
7126
  } catch (e) {
6953
7127
  return { ok: false, error: `tarball download failed: ${String(e)}` };
6954
7128
  }
6955
- const tmpFile = path4.join(cacheDir(), `.update-${version}.tgz`);
7129
+ const tmpFile = path5.join(cacheDir(), `.update-${version}.tgz`);
6956
7130
  try {
6957
- await mkdir(cacheDir(), { recursive: true });
6958
- await writeFile(tmpFile, tgzBuffer);
7131
+ await mkdir2(cacheDir(), { recursive: true });
7132
+ await writeFile2(tmpFile, tgzBuffer);
6959
7133
  } catch (e) {
6960
7134
  return { ok: false, error: `failed to write temp file ${tmpFile}: ${String(e)}` };
6961
7135
  }
6962
- const stagingDir = path4.join(cacheDir(), `.update-staging-${version}`);
7136
+ const stagingDir = path5.join(cacheDir(), `.update-staging-${version}`);
6963
7137
  try {
6964
7138
  await rm(stagingDir, { recursive: true, force: true });
6965
- await mkdir(stagingDir, { recursive: true });
7139
+ await mkdir2(stagingDir, { recursive: true });
6966
7140
  await So({
6967
7141
  file: tmpFile,
6968
7142
  cwd: stagingDir,
@@ -7004,11 +7178,11 @@ function startAutoUpdate(opts) {
7004
7178
  // src/cli.ts
7005
7179
  import { readFileSync as readFileSync4 } from "fs";
7006
7180
  import { fileURLToPath as fileURLToPath3 } from "url";
7007
- import path5 from "path";
7181
+ import path6 from "path";
7008
7182
  var VERSION = (() => {
7009
7183
  try {
7010
7184
  const here = fileURLToPath3(import.meta.url);
7011
- const pkg = path5.join(path5.dirname(here), "..", "package.json");
7185
+ const pkg = path6.join(path6.dirname(here), "..", "package.json");
7012
7186
  return JSON.parse(readFileSync4(pkg, "utf8")).version ?? "dev";
7013
7187
  } catch {
7014
7188
  return "dev";
@@ -7017,7 +7191,7 @@ var VERSION = (() => {
7017
7191
  var PACKAGE_NAME = (() => {
7018
7192
  try {
7019
7193
  const here = fileURLToPath3(import.meta.url);
7020
- const pkg = path5.join(path5.dirname(here), "..", "package.json");
7194
+ const pkg = path6.join(path6.dirname(here), "..", "package.json");
7021
7195
  return JSON.parse(readFileSync4(pkg, "utf8")).name ?? "billion-context";
7022
7196
  } catch {
7023
7197
  return "billion-context";