billion-context 0.1.18 → 0.1.20

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
@@ -2,7 +2,8 @@
2
2
 
3
3
  // src/config.ts
4
4
  import { defaultConfig } from "acp-kernel";
5
- import { readFileSync } from "fs";
5
+ import { readFileSync, existsSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
6
+ import { dirname } from "path";
6
7
 
7
8
  // src/paths.ts
8
9
  import { homedir } from "os";
@@ -194,6 +195,22 @@ function loadConfigFile() {
194
195
  }
195
196
  return {};
196
197
  }
198
+ var TEMPLATE_CONFIG = `{
199
+ "providers": {
200
+ }
201
+ }`;
202
+ function ensureConfigTemplate() {
203
+ const p = configFile();
204
+ if (existsSync(p)) return false;
205
+ try {
206
+ mkdirSync2(dirname(p), { recursive: true });
207
+ writeFileSync(p, TEMPLATE_CONFIG + "\n", "utf8");
208
+ log("info", `[acp-config] created empty config at ${p} \u2014 add your providers (see README Quickstart), then restart`);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
197
214
  function parseRouteEntry(v) {
198
215
  if (typeof v === "string" && v.length > 0) {
199
216
  return { url: v.replace(/\/$/, "") };
@@ -743,7 +760,7 @@ import { createInitialState as createInitialState2 } from "acp-kernel";
743
760
 
744
761
  // src/persist.ts
745
762
  import { promises as fs } from "fs";
746
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
763
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
747
764
  import { createHash as createHash2 } from "crypto";
748
765
  import * as path3 from "path";
749
766
  import { createInitialState } from "acp-kernel";
@@ -859,7 +876,7 @@ var SessionStore = class {
859
876
  const candidates = [this.filePath(id, meta?.protocol, meta?.upstreamOrigin)];
860
877
  if (meta?.protocol) candidates.push(this.filePath(id));
861
878
  for (const file of candidates) {
862
- if (!existsSync(file)) continue;
879
+ if (!existsSync2(file)) continue;
863
880
  try {
864
881
  const parsed = JSON.parse(readFileSync2(file, "utf8"));
865
882
  if (!isValidRecord(parsed) || parsed.id !== id) continue;
@@ -926,13 +943,13 @@ var SessionStore = class {
926
943
  const record = buildRecord(session);
927
944
  const file = this.filePath(session.id, session.protocol, session.upstreamOrigin);
928
945
  try {
929
- mkdirSync2(path3.dirname(file), { recursive: true });
946
+ mkdirSync3(path3.dirname(file), { recursive: true });
930
947
  } catch (e) {
931
948
  this.log("warn", `[persist] could not create session dir ${this.dir}: ${msg(e)}`);
932
949
  }
933
950
  const tmp = this.tempPath(session.id);
934
951
  try {
935
- writeFileSync(tmp, JSON.stringify(record), "utf8");
952
+ writeFileSync2(tmp, JSON.stringify(record), "utf8");
936
953
  renameSync2(tmp, file);
937
954
  return true;
938
955
  } catch (e) {
@@ -1608,8 +1625,8 @@ import {
1608
1625
  collectBlockContent as collectBlockContent2,
1609
1626
  deactivateBlock
1610
1627
  } from "acp-kernel";
1611
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
1612
- import { dirname as dirname2, join as join2 } from "path";
1628
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
1629
+ import { dirname as dirname3, join as join2 } from "path";
1613
1630
  import { tmpdir } from "os";
1614
1631
  function resolveDecompress(args, ctx) {
1615
1632
  const rawBlockId = args.blockId;
@@ -1641,8 +1658,8 @@ function resolveDecompress(args, ctx) {
1641
1658
  const outPath = body.length > 1e4 ? join2(tmpdir(), `acp-decompress-${safeBlockId}-${Date.now()}.txt`) : null;
1642
1659
  if (outPath) {
1643
1660
  try {
1644
- mkdirSync3(dirname2(outPath), { recursive: true });
1645
- writeFileSync2(outPath, body, "utf8");
1661
+ mkdirSync4(dirname3(outPath), { recursive: true });
1662
+ writeFileSync3(outPath, body, "utf8");
1646
1663
  return `${header}
1647
1664
  Content (${body.length} chars) written to: ${outPath}
1648
1665
  Use the read tool to access it.`;
@@ -3161,10 +3178,10 @@ async function pipeThrough(stream2, res) {
3161
3178
  }
3162
3179
  }
3163
3180
  async function dumpStreamToFile(stream2, dir, name) {
3164
- const { mkdirSync: mkdirSync4, createWriteStream: createWriteStream2 } = await import("fs");
3181
+ const { mkdirSync: mkdirSync5, createWriteStream: createWriteStream2 } = await import("fs");
3165
3182
  const { join: join3 } = await import("path");
3166
3183
  try {
3167
- mkdirSync4(dir, { recursive: true });
3184
+ mkdirSync5(dir, { recursive: true });
3168
3185
  const ws = createWriteStream2(join3(dir, name));
3169
3186
  const reader = stream2.getReader();
3170
3187
  try {
@@ -3234,16 +3251,18 @@ function logMsg(opts, level, msg2) {
3234
3251
  }
3235
3252
 
3236
3253
  // src/update.ts
3237
- import { readFile, writeFile, mkdir } from "fs/promises";
3254
+ import { readFile, writeFile, mkdir, access, constants, rm } from "fs/promises";
3238
3255
  import { execFile } from "child_process";
3239
3256
  import path4 from "path";
3257
+ import { fileURLToPath } from "url";
3240
3258
  var REGISTRY_BASE = "https://registry.npmjs.org";
3241
3259
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
3242
3260
  var THROTTLE_FILE = path4.join(cacheDir(), ".update-check");
3243
- var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
3261
+ var LOCK_FILE = path4.join(cacheDir(), ".update-lock");
3262
+ var LOCK_STALE_MS = 2 * 60 * 1e3;
3244
3263
  var timer;
3245
3264
  var inFlight = false;
3246
- var notified = /* @__PURE__ */ new Set();
3265
+ var firstCheckDone = false;
3247
3266
  function parseVersion(v) {
3248
3267
  return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
3249
3268
  }
@@ -3273,22 +3292,98 @@ async function writeLastCheck(ts) {
3273
3292
  } catch {
3274
3293
  }
3275
3294
  }
3295
+ async function findInstallDir(packageName) {
3296
+ let dir = path4.dirname(fileURLToPath(import.meta.url));
3297
+ for (; ; ) {
3298
+ try {
3299
+ const pkg = JSON.parse(await readFile(path4.join(dir, "package.json"), "utf-8"));
3300
+ if (pkg.name === packageName) return dir;
3301
+ } catch {
3302
+ }
3303
+ const parent = path4.dirname(dir);
3304
+ if (parent === dir) return void 0;
3305
+ dir = parent;
3306
+ }
3307
+ }
3308
+ async function readDiskVersion(installDir) {
3309
+ try {
3310
+ const pkg = JSON.parse(await readFile(path4.join(installDir, "package.json"), "utf-8"));
3311
+ return pkg.version;
3312
+ } catch {
3313
+ return void 0;
3314
+ }
3315
+ }
3316
+ async function tryAcquireLock() {
3317
+ const pid = process.pid;
3318
+ const now = Date.now();
3319
+ async function readLock() {
3320
+ try {
3321
+ const raw = await readFile(LOCK_FILE, "utf-8");
3322
+ const data = JSON.parse(raw);
3323
+ if (typeof data.pid === "number" && typeof data.ts === "number") {
3324
+ return data;
3325
+ }
3326
+ } catch {
3327
+ }
3328
+ return null;
3329
+ }
3330
+ function isAlive(checkPid) {
3331
+ try {
3332
+ process.kill(checkPid, 0);
3333
+ return true;
3334
+ } catch {
3335
+ return false;
3336
+ }
3337
+ }
3338
+ const existing = await readLock();
3339
+ if (existing) {
3340
+ const age = now - existing.ts;
3341
+ const holderAlive = isAlive(existing.pid);
3342
+ if (holderAlive && age < LOCK_STALE_MS) {
3343
+ return null;
3344
+ }
3345
+ log("info", `[update] stealing stale lock (pid=${existing.pid}, age=${Math.round(age / 1e3)}s, alive=${holderAlive})`);
3346
+ }
3347
+ try {
3348
+ await writeFile(LOCK_FILE, JSON.stringify({ pid, ts: now }), { flag: "wx" });
3349
+ } catch {
3350
+ const winner = await readLock();
3351
+ if (winner && winner.pid !== pid) {
3352
+ log("info", `[update] lost lock race to pid=${winner.pid}, skipping update`);
3353
+ return null;
3354
+ }
3355
+ }
3356
+ const confirmed = await readLock();
3357
+ if (!confirmed || confirmed.pid !== pid) {
3358
+ log("info", `[update] lock held by pid=${confirmed?.pid}, skipping update`);
3359
+ return null;
3360
+ }
3361
+ return {
3362
+ release: async () => {
3363
+ const current = await readLock();
3364
+ if (current?.pid === pid) {
3365
+ await rm(LOCK_FILE, { force: true }).catch(() => {
3366
+ });
3367
+ }
3368
+ }
3369
+ };
3370
+ }
3276
3371
  async function checkForUpdate(opts, force = false) {
3277
3372
  if (!opts.autoUpdate && !force) return;
3278
3373
  if (inFlight) return;
3279
3374
  inFlight = true;
3280
- const firstInProcess = !notified.has("__first_check_done__");
3281
3375
  try {
3282
3376
  const now = Date.now();
3283
3377
  const lastCheck = await readLastCheck();
3284
3378
  const sinceLastSec = lastCheck ? (now - lastCheck) / 1e3 | 0 : -1;
3285
- if (!force && !firstInProcess && now - lastCheck < CHECK_INTERVAL_MS) {
3379
+ if (!force && firstCheckDone && now - lastCheck < CHECK_INTERVAL_MS) {
3286
3380
  const retryIn = (CHECK_INTERVAL_MS - (now - lastCheck)) / 1e3 | 0;
3287
3381
  log("info", `[update] throttled \u2014 last checked ${sinceLastSec}s ago, retry in ${retryIn}s`);
3288
3382
  return;
3289
3383
  }
3290
3384
  await writeLastCheck(now);
3291
- log("info", `[update] checking npm registry for ${opts.packageName}${firstInProcess ? " (startup check)" : sinceLastSec < 0 ? "" : ` (last check ${sinceLastSec}s ago)`}\u2026`);
3385
+ firstCheckDone = true;
3386
+ log("info", `[update] checking npm registry for ${opts.packageName}${sinceLastSec < 0 ? " (startup check)" : sinceLastSec === 0 ? "" : ` (last check ${sinceLastSec}s ago)`}\u2026`);
3292
3387
  const url = `${REGISTRY_BASE}/${opts.packageName}/latest`;
3293
3388
  const res = await fetch(url, {
3294
3389
  signal: AbortSignal.timeout(5e3),
@@ -3304,44 +3399,124 @@ async function checkForUpdate(opts, force = false) {
3304
3399
  log("warn", `[update] registry response had no version, skipping`);
3305
3400
  return;
3306
3401
  }
3307
- if (!isNewer(latest, opts.currentVersion)) {
3308
- log("info", `[update] current=${opts.currentVersion} latest=${latest} (up to date)`);
3402
+ const installDir = await findInstallDir(opts.packageName);
3403
+ const diskVersion = installDir ? await readDiskVersion(installDir) : void 0;
3404
+ const currentVersion = diskVersion ?? opts.currentVersion;
3405
+ if (!isNewer(latest, currentVersion)) {
3406
+ log("info", `[update] current=${currentVersion} latest=${latest} (up to date)`);
3309
3407
  return;
3310
3408
  }
3311
- if (notified.has(latest)) {
3312
- log("info", `[update] latest=${latest} already installed this run, awaiting restart`);
3409
+ const tarballUrl = data.dist?.tarball;
3410
+ if (!tarballUrl) {
3411
+ log("warn", `[update] registry response for ${latest} had no tarball URL`);
3313
3412
  return;
3314
3413
  }
3315
- log("info", `[update] new version found: ${opts.currentVersion} \u2192 ${latest}, installing\u2026`);
3316
- const installed = await installLatest(opts.packageName, latest);
3317
- if (installed) {
3318
- notified.add(latest);
3319
- log("info", `[update] installed ${opts.packageName} ${opts.currentVersion} \u2192 ${latest}. Restart to finish.`);
3320
- } else {
3321
- log("warn", `[update] install failed; will retry next cycle. Or: npm install -g ${opts.packageName}@${latest}`);
3414
+ log("info", `[update] new version found: ${currentVersion} \u2192 ${latest}, downloading\u2026`);
3415
+ const lock = await tryAcquireLock();
3416
+ if (!lock) {
3417
+ log("info", `[update] another process is updating, will check next cycle`);
3418
+ return;
3419
+ }
3420
+ try {
3421
+ const result = await installViaTarball(latest, tarballUrl, installDir);
3422
+ if (result.ok) {
3423
+ log("info", `[update] installed ${currentVersion} \u2192 ${latest}. Restart to finish.`);
3424
+ } else {
3425
+ log("warn", `[update] install failed: ${result.error}. Will retry next cycle.`);
3426
+ }
3427
+ } finally {
3428
+ await lock.release();
3322
3429
  }
3323
3430
  } catch (e) {
3324
3431
  log("warn", `[update] check failed: ${String(e)}`);
3325
3432
  } finally {
3326
3433
  inFlight = false;
3327
- notified.add("__first_check_done__");
3328
3434
  }
3329
3435
  }
3330
- async function installLatest(packageName, latest) {
3331
- if (!SEMVER_RE.test(latest)) return false;
3436
+ async function installViaTarball(version, tarballUrl, installDir) {
3437
+ if (!installDir) {
3438
+ return { ok: false, error: "cannot determine install directory (package.json not found walking up from running binary)" };
3439
+ }
3440
+ try {
3441
+ await access(installDir, constants.W_OK);
3442
+ } catch {
3443
+ return { ok: false, error: `install dir not writable: ${installDir}` };
3444
+ }
3445
+ let tgzBuffer;
3446
+ try {
3447
+ const tgzRes = await fetch(tarballUrl, { signal: AbortSignal.timeout(6e4) });
3448
+ if (!tgzRes.ok) {
3449
+ return { ok: false, error: `tarball download failed: HTTP ${tgzRes.status} ${tgzRes.statusText}` };
3450
+ }
3451
+ tgzBuffer = Buffer.from(await tgzRes.arrayBuffer());
3452
+ } catch (e) {
3453
+ return { ok: false, error: `tarball download failed: ${String(e)}` };
3454
+ }
3455
+ const tmpFile = path4.join(cacheDir(), `.update-${version}.tgz`);
3456
+ try {
3457
+ await mkdir(cacheDir(), { recursive: true });
3458
+ await writeFile(tmpFile, tgzBuffer);
3459
+ } catch (e) {
3460
+ return { ok: false, error: `failed to write temp file ${tmpFile}: ${String(e)}` };
3461
+ }
3462
+ const stagingDir = path4.join(cacheDir(), `.update-staging-${version}`);
3463
+ try {
3464
+ await rm(stagingDir, { recursive: true, force: true });
3465
+ await mkdir(stagingDir, { recursive: true });
3466
+ const { code, stderr } = await new Promise((resolve) => {
3467
+ execFile(
3468
+ "tar",
3469
+ ["xzf", tmpFile, "-C", stagingDir, "--strip-components=1"],
3470
+ { timeout: 3e4 },
3471
+ (err, _stdout, stderr2) => {
3472
+ if (err) {
3473
+ resolve({ code: 1, stderr: stderr2 || err.message });
3474
+ } else {
3475
+ resolve({ code: 0, stderr: stderr2 || "" });
3476
+ }
3477
+ }
3478
+ );
3479
+ });
3480
+ if (code !== 0) {
3481
+ return { ok: false, error: `extraction failed (tar exit ${code})${stderr.trim() ? `: ${stderr.trim()}` : ""}` };
3482
+ }
3483
+ const stagingVersion = await readDiskVersion(stagingDir);
3484
+ if (stagingVersion !== version) {
3485
+ return { ok: false, error: `staging verification failed: version is ${stagingVersion ?? "missing"}, expected ${version}` };
3486
+ }
3487
+ } catch (e) {
3488
+ return { ok: false, error: `extraction failed: ${String(e)}` };
3489
+ } finally {
3490
+ await rm(tmpFile, { force: true });
3491
+ }
3332
3492
  try {
3333
- const code = await new Promise((resolve) => {
3493
+ const { code, stderr } = await new Promise((resolve) => {
3334
3494
  execFile(
3335
- "npm",
3336
- ["install", "-g", `${packageName}@${latest}`, "--silent", "--no-audit", "--no-fund"],
3337
- { timeout: 12e4, shell: process.platform === "win32" },
3338
- (err) => resolve(err ? 1 : 0)
3495
+ "cp",
3496
+ ["-r", stagingDir + "/.", installDir + "/"],
3497
+ { timeout: 3e4 },
3498
+ (err, _stdout, stderr2) => {
3499
+ if (err) {
3500
+ resolve({ code: 1, stderr: stderr2 || err.message });
3501
+ } else {
3502
+ resolve({ code: 0, stderr: stderr2 || "" });
3503
+ }
3504
+ }
3339
3505
  );
3340
3506
  });
3341
- return code === 0;
3342
- } catch {
3343
- return false;
3507
+ if (code !== 0) {
3508
+ return { ok: false, error: `failed to copy to install dir (cp exit ${code})${stderr.trim() ? `: ${stderr.trim()}` : ""}` };
3509
+ }
3510
+ } catch (e) {
3511
+ return { ok: false, error: `failed to copy to install dir: ${String(e)}` };
3512
+ } finally {
3513
+ await rm(stagingDir, { recursive: true, force: true });
3344
3514
  }
3515
+ const newVersion = await readDiskVersion(installDir);
3516
+ if (newVersion !== version) {
3517
+ return { ok: false, error: `post-install verification failed: package.json version is ${newVersion ?? "missing"}, expected ${version}` };
3518
+ }
3519
+ return { ok: true };
3345
3520
  }
3346
3521
  function startAutoUpdate(opts) {
3347
3522
  log("info", `[update] auto-update enabled (checking every ${CHECK_INTERVAL_MS / 1e3 | 0}s)`);
@@ -3356,11 +3531,11 @@ function startAutoUpdate(opts) {
3356
3531
 
3357
3532
  // src/cli.ts
3358
3533
  import { readFileSync as readFileSync3 } from "fs";
3359
- import { fileURLToPath } from "url";
3534
+ import { fileURLToPath as fileURLToPath2 } from "url";
3360
3535
  import path5 from "path";
3361
3536
  var VERSION = (() => {
3362
3537
  try {
3363
- const here = fileURLToPath(import.meta.url);
3538
+ const here = fileURLToPath2(import.meta.url);
3364
3539
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3365
3540
  return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "dev";
3366
3541
  } catch {
@@ -3369,7 +3544,7 @@ var VERSION = (() => {
3369
3544
  })();
3370
3545
  var PACKAGE_NAME = (() => {
3371
3546
  try {
3372
- const here = fileURLToPath(import.meta.url);
3547
+ const here = fileURLToPath2(import.meta.url);
3373
3548
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3374
3549
  return JSON.parse(readFileSync3(pkg, "utf8")).name ?? "billion-context";
3375
3550
  } catch {
@@ -3483,6 +3658,7 @@ async function main() {
3483
3658
  for (const [k, v] of Object.entries(overrides)) {
3484
3659
  if (v !== void 0) process.env[k] = v;
3485
3660
  }
3661
+ ensureConfigTemplate();
3486
3662
  const opts = loadOptions();
3487
3663
  await startServer(opts);
3488
3664
  if (opts.autoUpdate) {