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/README.md +163 -65
- package/README.zh-CN.md +153 -63
- package/dist/index.js +218 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1612
|
-
import { dirname as
|
|
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
|
-
|
|
1645
|
-
|
|
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:
|
|
3181
|
+
const { mkdirSync: mkdirSync5, createWriteStream: createWriteStream2 } = await import("fs");
|
|
3165
3182
|
const { join: join3 } = await import("path");
|
|
3166
3183
|
try {
|
|
3167
|
-
|
|
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
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
3308
|
-
|
|
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
|
-
|
|
3312
|
-
|
|
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: ${
|
|
3316
|
-
const
|
|
3317
|
-
if (
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
}
|
|
3321
|
-
|
|
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
|
|
3331
|
-
if (!
|
|
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
|
-
"
|
|
3336
|
-
["
|
|
3337
|
-
{ timeout:
|
|
3338
|
-
(err) =>
|
|
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
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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 =
|
|
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 =
|
|
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) {
|