billion-context 0.1.7 → 0.1.9

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 CHANGED
@@ -212,6 +212,15 @@ The config file is a single JSON object. Example:
212
212
  | `providers` | *(none)* | Provider routes — see below |
213
213
  | `compress` | *(see defaults)* | `{ injectTool, injectNudge }` |
214
214
 
215
+ > **Choosing a `host`** (IPv6 / containers): the default `127.0.0.1` is
216
+ > IPv4-only and loopback-only. Use `--host ::` (or `"host": "::"`) to listen
217
+ > on **both** IPv4 and IPv6, which matters if your client resolves
218
+ > `localhost` to `::1` first (some `/etc/hosts` files list `::1` before
219
+ > `127.0.0.1`). Inside a **container**, `127.0.0.1` binds the container's own
220
+ > loopback and is unreachable through a published port — use
221
+ > `--host 0.0.0.0` there. ⚠️ `0.0.0.0` / `::` expose the proxy on **all**
222
+ > interfaces; ensure you're on a trusted network or behind a firewall.
223
+
215
224
  ### Providers (URL routing + per-model context)
216
225
 
217
226
  `providers` maps a route name to either a bare URL string (simple) or an
package/README.zh-CN.md CHANGED
@@ -196,6 +196,14 @@ bili --no-auto-update # 本次启动禁用自动更新
196
196
  | `providers` | *(无)* | Provider 路由 —— 见下文 |
197
197
  | `compress` | *(见默认值)* | `{ injectTool, injectNudge }` |
198
198
 
199
+ > **选择 `host`**(IPv6 / 容器):默认 `127.0.0.1` 只听 IPv4 且仅
200
+ > loopback。用 `--host ::`(或 `"host": "::"`)可同时听 IPv4 和 IPv6
201
+ > ——当你的客户端把 `localhost` 先解析成 `::1` 时(有些 `/etc/hosts`
202
+ > 把 `::1` 排在 `127.0.0.1` 前)这就很关键。在**容器**内,`127.0.0.1`
203
+ > 绑的是容器自己的 loopback,通过映射端口访问不到——这时用
204
+ > `--host 0.0.0.0`。⚠️ `0.0.0.0` / `::` 会把代理暴露到**所有**网卡;
205
+ > 确保你在可信网络或防火墙后面。
206
+
199
207
  ### Providers(URL 路由 + 按模型 context)
200
208
 
201
209
  `providers` 把路由名映射到一个纯 URL 字符串(简单)或一个带 `url` + 可选按模型 context 窗口的对象(推荐)。
package/dist/index.js CHANGED
@@ -41,7 +41,8 @@ function defaultLogFile() {
41
41
  // src/config.ts
42
42
  function safeReadJson(path6) {
43
43
  try {
44
- return JSON.parse(readFileSync(path6, "utf8"));
44
+ const raw = readFileSync(path6, "utf8").replace(/^\uFEFF/, "");
45
+ return JSON.parse(raw);
45
46
  } catch (e) {
46
47
  if (e.code !== "ENOENT") {
47
48
  console.error(`[acp-config] failed to parse ${path6}: ${String(e)}`);
@@ -148,6 +149,7 @@ function parseRouteEntry(v) {
148
149
  // src/server.ts
149
150
  import http from "http";
150
151
  import fs2 from "fs";
152
+ import { tmpdir as tmpdir2 } from "os";
151
153
  import { createCore, estimateTokensFast as estimateTokensFast3, renderNudgeText, deactivateBlock as deactivateBlock4 } from "acp-kernel";
152
154
 
153
155
  // src/fetch-util.ts
@@ -837,8 +839,18 @@ var SessionStore = class {
837
839
  }
838
840
  const tmp = this.tempPath(session.id);
839
841
  const data = JSON.stringify(record);
840
- await fs.writeFile(tmp, data, "utf8");
841
- await fs.rename(tmp, file);
842
+ try {
843
+ await fs.writeFile(tmp, data, "utf8");
844
+ await fs.rename(tmp, file);
845
+ } catch (e) {
846
+ try {
847
+ await fs.unlink(tmp).catch(() => {
848
+ });
849
+ } catch {
850
+ }
851
+ this.log("error", `[persist] write failed for ${session.id}: ${msg(e)}`);
852
+ throw e;
853
+ }
842
854
  }
843
855
  /** Synchronous flush for a single session. Used on memory eviction so a
844
856
  * dirty evicted session is not lost. Sync because eviction runs in the
@@ -1591,15 +1603,27 @@ import path3 from "path";
1591
1603
  var MAX_BYTES = 10 * 1024 * 1024;
1592
1604
  var stream;
1593
1605
  var logPath;
1606
+ var bytesWritten = 0;
1594
1607
  function open(pathStr) {
1595
1608
  mkdirSync3(path3.dirname(pathStr), { recursive: true });
1609
+ let existingSize = 0;
1596
1610
  try {
1597
- if (statSync(pathStr).size >= MAX_BYTES) {
1598
- renameSync2(pathStr, pathStr + ".old");
1611
+ existingSize = statSync(pathStr).size;
1612
+ if (existingSize >= MAX_BYTES) {
1613
+ rotate(pathStr);
1614
+ existingSize = 0;
1599
1615
  }
1600
1616
  } catch {
1601
1617
  }
1602
- return createWriteStream(pathStr, { flags: "a" });
1618
+ const s = createWriteStream(pathStr, { flags: "a" });
1619
+ bytesWritten = existingSize;
1620
+ return s;
1621
+ }
1622
+ function rotate(pathStr) {
1623
+ try {
1624
+ renameSync2(pathStr, pathStr + ".old");
1625
+ } catch {
1626
+ }
1603
1627
  }
1604
1628
  function configureLogger(file) {
1605
1629
  if (!file || file === "off") {
@@ -1616,13 +1640,20 @@ var log = (level, msg2) => {
1616
1640
  const line = `${ts} [${level}] ${msg2}
1617
1641
  `;
1618
1642
  process.stderr.write(line);
1619
- if (stream) {
1643
+ if (stream && logPath) {
1644
+ if (bytesWritten >= MAX_BYTES) {
1645
+ stream.end();
1646
+ rotate(logPath);
1647
+ stream = open(logPath);
1648
+ }
1620
1649
  stream.write(line);
1650
+ bytesWritten += Buffer.byteLength(line);
1621
1651
  }
1622
1652
  };
1623
1653
  function closeLogger() {
1624
1654
  stream?.end();
1625
1655
  stream = void 0;
1656
+ bytesWritten = 0;
1626
1657
  }
1627
1658
 
1628
1659
  // src/compress-loop.ts
@@ -2624,19 +2655,46 @@ async function startServer(opts) {
2624
2655
  `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}`)
2625
2656
  );
2626
2657
  });
2658
+ server.on("error", (err) => {
2659
+ const hint = err.code === "EADDRINUSE" ? ` \u2014 port ${opts.port} is already in use. Stop the other process or use --port <N>.` : err.code === "EACCES" ? ` \u2014 port ${opts.port} requires privileges. Use a port >= 1024.` : "";
2660
+ log2("error", `listen failed: ${err.code ?? ""} ${err.message}${hint}`);
2661
+ shuttingDown = true;
2662
+ server.close();
2663
+ void flushAllSessions().finally(() => {
2664
+ closeLogger();
2665
+ process.exit(1);
2666
+ });
2667
+ });
2668
+ process.on("uncaughtException", (err) => {
2669
+ log2("error", `uncaughtException: ${String(err?.stack ?? err)}`);
2670
+ });
2671
+ process.on("unhandledRejection", (reason) => {
2672
+ log2("error", `unhandledRejection: ${String(reason)}`);
2673
+ });
2627
2674
  let shuttingDown = false;
2628
2675
  const shutdown = (sig) => {
2629
2676
  if (shuttingDown) return;
2630
2677
  shuttingDown = true;
2631
2678
  log2("info", `${sig} received \u2014 flushing sessions\u2026`);
2632
- server.close();
2633
- void flushAllSessions().finally(() => {
2634
- closeLogger();
2635
- process.exit(0);
2679
+ server.close(() => {
2680
+ void flushAllSessions().finally(() => {
2681
+ closeLogger();
2682
+ process.exit(0);
2683
+ });
2636
2684
  });
2685
+ setTimeout(() => {
2686
+ log2("warn", "shutdown grace window elapsed; forcing exit");
2687
+ void flushAllSessions().finally(() => {
2688
+ closeLogger();
2689
+ process.exit(0);
2690
+ });
2691
+ }, 1e4).unref?.();
2637
2692
  };
2638
2693
  process.on("SIGTERM", () => shutdown("SIGTERM"));
2639
2694
  process.on("SIGINT", () => shutdown("SIGINT"));
2695
+ if (process.platform === "win32") {
2696
+ process.on("SIGBREAK", () => shutdown("SIGBREAK"));
2697
+ }
2640
2698
  return server;
2641
2699
  }
2642
2700
  async function handle(req, res, opts, core, config, log2) {
@@ -2940,7 +2998,7 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
2940
2998
  });
2941
2999
  log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m) => m.role === "system")?.content ?? "").length}`);
2942
3000
  if (process.env.ACP_DUMP_REQ === "1") {
2943
- const out = `/tmp/acp-proxy-debug-req-${Date.now()}.json`;
3001
+ const out = `${tmpdir2()}/acp-proxy-debug-req-${Date.now()}.json`;
2944
3002
  fs2.writeFileSync(out, body.slice(0, 5e4));
2945
3003
  log2("info", `[debug] forwarded body written to ${out}`);
2946
3004
  }