@cortexkit/aft-bridge 0.18.5 → 0.19.0
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/active-logger.js +44 -0
- package/dist/active-logger.js.map +1 -0
- package/dist/bridge.js +653 -0
- package/dist/bridge.js.map +1 -0
- package/dist/downloader.js +193 -0
- package/dist/downloader.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -1387
- package/dist/index.js.map +1 -0
- package/dist/logger.js +2 -0
- package/dist/logger.js.map +1 -0
- package/dist/onnx-runtime.js +631 -0
- package/dist/onnx-runtime.js.map +1 -0
- package/dist/platform.js +31 -0
- package/dist/platform.js.map +1 -0
- package/dist/pool.js +159 -0
- package/dist/pool.js.map +1 -0
- package/dist/protocol.js +10 -0
- package/dist/protocol.js.map +1 -0
- package/dist/resolver.js +186 -0
- package/dist/resolver.js.map +1 -0
- package/dist/url-fetch.d.ts +33 -0
- package/dist/url-fetch.d.ts.map +1 -0
- package/dist/url-fetch.js +387 -0
- package/dist/url-fetch.js.map +1 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,1387 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function error(message, meta) {
|
|
27
|
-
if (active) {
|
|
28
|
-
active.error(message, meta);
|
|
29
|
-
} else {
|
|
30
|
-
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function sessionWarn(sessionId, message) {
|
|
34
|
-
warn(message, sessionId ? { sessionId } : undefined);
|
|
35
|
-
}
|
|
36
|
-
// src/bridge.ts
|
|
37
|
-
import { spawn } from "node:child_process";
|
|
38
|
-
import { homedir } from "node:os";
|
|
39
|
-
import { join } from "node:path";
|
|
40
|
-
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
41
|
-
var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
|
|
42
|
-
var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
|
|
43
|
-
function compareSemver(a, b) {
|
|
44
|
-
const [aMain, aPre] = a.split("-", 2);
|
|
45
|
-
const [bMain, bPre] = b.split("-", 2);
|
|
46
|
-
const aParts = aMain.split(".").map(Number);
|
|
47
|
-
const bParts = bMain.split(".").map(Number);
|
|
48
|
-
for (let i = 0;i < 3; i++) {
|
|
49
|
-
if (aParts[i] !== bParts[i])
|
|
50
|
-
return (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
51
|
-
}
|
|
52
|
-
if (!aPre && !bPre)
|
|
53
|
-
return 0;
|
|
54
|
-
if (!aPre)
|
|
55
|
-
return 1;
|
|
56
|
-
if (!bPre)
|
|
57
|
-
return -1;
|
|
58
|
-
const aIds = aPre.split(".");
|
|
59
|
-
const bIds = bPre.split(".");
|
|
60
|
-
for (let i = 0;i < Math.max(aIds.length, bIds.length); i++) {
|
|
61
|
-
const ai = aIds[i];
|
|
62
|
-
const bi = bIds[i];
|
|
63
|
-
if (ai === undefined)
|
|
64
|
-
return -1;
|
|
65
|
-
if (bi === undefined)
|
|
66
|
-
return 1;
|
|
67
|
-
const aNum = /^\d+$/.test(ai);
|
|
68
|
-
const bNum = /^\d+$/.test(bi);
|
|
69
|
-
if (aNum && bNum) {
|
|
70
|
-
const diff = Number.parseInt(ai, 10) - Number.parseInt(bi, 10);
|
|
71
|
-
if (diff !== 0)
|
|
72
|
-
return diff;
|
|
73
|
-
} else if (aNum) {
|
|
74
|
-
return -1;
|
|
75
|
-
} else if (bNum) {
|
|
76
|
-
return 1;
|
|
77
|
-
} else {
|
|
78
|
-
const cmp = ai.localeCompare(bi);
|
|
79
|
-
if (cmp !== 0)
|
|
80
|
-
return cmp;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
|
|
86
|
-
const semantic = configOverrides.semantic;
|
|
87
|
-
if (!semantic || typeof semantic !== "object" || Array.isArray(semantic)) {
|
|
88
|
-
return configOverrides;
|
|
89
|
-
}
|
|
90
|
-
const timeoutMs = semantic.timeout_ms;
|
|
91
|
-
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
92
|
-
return configOverrides;
|
|
93
|
-
}
|
|
94
|
-
const maxSemanticTimeoutMs = bridgeTimeoutMs > SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS ? bridgeTimeoutMs - SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS : Math.max(1, bridgeTimeoutMs - 1);
|
|
95
|
-
if (timeoutMs <= maxSemanticTimeoutMs) {
|
|
96
|
-
return configOverrides;
|
|
97
|
-
}
|
|
98
|
-
warn(`semantic.timeout_ms=${timeoutMs} exceeds bridge timeout budget; clamping to ${maxSemanticTimeoutMs}ms (bridge timeout: ${bridgeTimeoutMs}ms)`);
|
|
99
|
-
return {
|
|
100
|
-
...configOverrides,
|
|
101
|
-
semantic: {
|
|
102
|
-
...semantic,
|
|
103
|
-
timeout_ms: maxSemanticTimeoutMs
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
class BinaryBridge {
|
|
109
|
-
static RESTART_RESET_MS = 5 * 60 * 1000;
|
|
110
|
-
static STDERR_TAIL_MAX = 20;
|
|
111
|
-
binaryPath;
|
|
112
|
-
cwd;
|
|
113
|
-
process = null;
|
|
114
|
-
pending = new Map;
|
|
115
|
-
nextId = 1;
|
|
116
|
-
stdoutBuffer = "";
|
|
117
|
-
stderrTail = [];
|
|
118
|
-
_restartCount = 0;
|
|
119
|
-
_shuttingDown = false;
|
|
120
|
-
timeoutMs;
|
|
121
|
-
maxRestarts;
|
|
122
|
-
configured = false;
|
|
123
|
-
_configurePromise = null;
|
|
124
|
-
configOverrides;
|
|
125
|
-
minVersion;
|
|
126
|
-
onVersionMismatch;
|
|
127
|
-
onConfigureWarnings;
|
|
128
|
-
onBashCompletion;
|
|
129
|
-
configureWarningClients = new Map;
|
|
130
|
-
restartResetTimer = null;
|
|
131
|
-
errorPrefix;
|
|
132
|
-
constructor(binaryPath, cwd, options, configOverrides) {
|
|
133
|
-
this.binaryPath = binaryPath;
|
|
134
|
-
this.cwd = cwd;
|
|
135
|
-
this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
|
|
136
|
-
this.maxRestarts = options?.maxRestarts ?? 3;
|
|
137
|
-
this.configOverrides = clampSemanticTimeout(configOverrides ?? {}, this.timeoutMs);
|
|
138
|
-
this.minVersion = options?.minVersion;
|
|
139
|
-
this.onVersionMismatch = options?.onVersionMismatch;
|
|
140
|
-
this.onConfigureWarnings = options?.onConfigureWarnings;
|
|
141
|
-
this.onBashCompletion = options?.onBashCompletion;
|
|
142
|
-
this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
|
|
143
|
-
}
|
|
144
|
-
get restartCount() {
|
|
145
|
-
return this._restartCount;
|
|
146
|
-
}
|
|
147
|
-
isAlive() {
|
|
148
|
-
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
149
|
-
}
|
|
150
|
-
hasPendingRequests() {
|
|
151
|
-
return this.pending.size > 0;
|
|
152
|
-
}
|
|
153
|
-
async send(command, params = {}, options) {
|
|
154
|
-
if (this._shuttingDown) {
|
|
155
|
-
throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
|
|
156
|
-
}
|
|
157
|
-
if (Object.hasOwn(params, "id")) {
|
|
158
|
-
throw new Error("params cannot contain reserved key 'id'");
|
|
159
|
-
}
|
|
160
|
-
this.ensureSpawned();
|
|
161
|
-
const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
|
|
162
|
-
if (requestSessionId && options?.configureWarningClient !== undefined) {
|
|
163
|
-
this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
|
|
164
|
-
}
|
|
165
|
-
if (!this.configured) {
|
|
166
|
-
if (command !== "configure" && command !== "version") {
|
|
167
|
-
if (!this._configurePromise) {
|
|
168
|
-
const sessionIdForConfigure = typeof params["session_id"] === "string" ? params["session_id"] : undefined;
|
|
169
|
-
this._configurePromise = (async () => {
|
|
170
|
-
try {
|
|
171
|
-
const configResult = await this.send("configure", {
|
|
172
|
-
project_root: this.cwd,
|
|
173
|
-
...this.configOverrides,
|
|
174
|
-
...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
|
|
175
|
-
});
|
|
176
|
-
if (configResult.success === false) {
|
|
177
|
-
throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
|
|
178
|
-
}
|
|
179
|
-
await this.deliverConfigureWarnings(configResult, params, options);
|
|
180
|
-
await this.checkVersion();
|
|
181
|
-
if (!this.isAlive()) {
|
|
182
|
-
throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${getLogFilePath()}`);
|
|
183
|
-
}
|
|
184
|
-
this.configured = true;
|
|
185
|
-
} finally {
|
|
186
|
-
this._configurePromise = null;
|
|
187
|
-
}
|
|
188
|
-
})();
|
|
189
|
-
}
|
|
190
|
-
await this._configurePromise;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const id = String(this.nextId++);
|
|
194
|
-
let request;
|
|
195
|
-
if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
196
|
-
const nested = { ...params };
|
|
197
|
-
const reserved = {};
|
|
198
|
-
for (const key of ["session_id", "lsp_hints"]) {
|
|
199
|
-
if (Object.hasOwn(nested, key)) {
|
|
200
|
-
reserved[key] = nested[key];
|
|
201
|
-
delete nested[key];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
request = { id, command, ...reserved, params: nested };
|
|
205
|
-
} else {
|
|
206
|
-
request = { id, command, ...params };
|
|
207
|
-
}
|
|
208
|
-
const line = `${JSON.stringify(request)}
|
|
209
|
-
`;
|
|
210
|
-
const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
|
|
211
|
-
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
212
|
-
return new Promise((resolve, reject) => {
|
|
213
|
-
const timer = setTimeout(() => {
|
|
214
|
-
this.pending.delete(id);
|
|
215
|
-
const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
|
|
216
|
-
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
217
|
-
if (requestSessionId) {
|
|
218
|
-
sessionWarn(requestSessionId, timeoutMsg);
|
|
219
|
-
} else {
|
|
220
|
-
warn(timeoutMsg);
|
|
221
|
-
}
|
|
222
|
-
reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
223
|
-
if (!keepBridgeOnTimeout) {
|
|
224
|
-
this.handleTimeout();
|
|
225
|
-
}
|
|
226
|
-
}, effectiveTimeoutMs);
|
|
227
|
-
this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
|
|
228
|
-
if (!this.process?.stdin?.writable) {
|
|
229
|
-
this.pending.delete(id);
|
|
230
|
-
clearTimeout(timer);
|
|
231
|
-
reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
this.process.stdin.write(line, (err) => {
|
|
235
|
-
if (err) {
|
|
236
|
-
const entry = this.pending.get(id);
|
|
237
|
-
if (entry) {
|
|
238
|
-
this.pending.delete(id);
|
|
239
|
-
clearTimeout(entry.timer);
|
|
240
|
-
entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
async deliverConfigureWarnings(configResult, params, options) {
|
|
247
|
-
if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
|
|
248
|
-
return;
|
|
249
|
-
if (configResult.warnings.length === 0)
|
|
250
|
-
return;
|
|
251
|
-
try {
|
|
252
|
-
const sessionId = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
253
|
-
await this.onConfigureWarnings({
|
|
254
|
-
projectRoot: this.cwd,
|
|
255
|
-
sessionId,
|
|
256
|
-
client: options?.configureWarningClient ?? (sessionId ? this.configureWarningClients.get(sessionId) : undefined),
|
|
257
|
-
warnings: configResult.warnings
|
|
258
|
-
});
|
|
259
|
-
} catch (err) {
|
|
260
|
-
warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
async handleConfigureWarningsFrame(frame) {
|
|
264
|
-
if (!this.onConfigureWarnings)
|
|
265
|
-
return;
|
|
266
|
-
const warnings = frame.warnings;
|
|
267
|
-
if (!Array.isArray(warnings) || warnings.length === 0)
|
|
268
|
-
return;
|
|
269
|
-
const projectRoot = typeof frame.project_root === "string" ? frame.project_root : this.cwd;
|
|
270
|
-
const rawSessionId = frame.session_id;
|
|
271
|
-
const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : null;
|
|
272
|
-
await this.onConfigureWarnings({
|
|
273
|
-
projectRoot,
|
|
274
|
-
sessionId,
|
|
275
|
-
client: sessionId ? this.configureWarningClients.get(sessionId) : undefined,
|
|
276
|
-
warnings
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
async shutdown() {
|
|
280
|
-
this._shuttingDown = true;
|
|
281
|
-
this.clearRestartResetTimer();
|
|
282
|
-
this.rejectAllPending(new Error(`${this.errorPrefix} Bridge shutting down`));
|
|
283
|
-
if (this.process) {
|
|
284
|
-
const proc = this.process;
|
|
285
|
-
this.process = null;
|
|
286
|
-
return new Promise((resolve) => {
|
|
287
|
-
const forceKillTimer = setTimeout(() => {
|
|
288
|
-
proc.kill("SIGKILL");
|
|
289
|
-
resolve();
|
|
290
|
-
}, 5000);
|
|
291
|
-
proc.once("exit", () => {
|
|
292
|
-
clearTimeout(forceKillTimer);
|
|
293
|
-
log("Process exited during shutdown");
|
|
294
|
-
resolve();
|
|
295
|
-
});
|
|
296
|
-
proc.kill("SIGTERM");
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
async checkVersion() {
|
|
301
|
-
if (!this.minVersion)
|
|
302
|
-
return;
|
|
303
|
-
try {
|
|
304
|
-
const resp = await this.send("version");
|
|
305
|
-
const binaryVersion = resp.version;
|
|
306
|
-
if (!binaryVersion) {
|
|
307
|
-
log("Binary did not report a version — skipping version check");
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
log(`Binary version: ${binaryVersion}`);
|
|
311
|
-
if (compareSemver(binaryVersion, this.minVersion) < 0) {
|
|
312
|
-
warn(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
|
|
313
|
-
this.onVersionMismatch?.(binaryVersion, this.minVersion);
|
|
314
|
-
}
|
|
315
|
-
} catch (err) {
|
|
316
|
-
warn(`Version check failed: ${err.message}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
ensureSpawned() {
|
|
320
|
-
if (this.isAlive())
|
|
321
|
-
return;
|
|
322
|
-
this.spawnProcess();
|
|
323
|
-
}
|
|
324
|
-
spawnProcess() {
|
|
325
|
-
log(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
326
|
-
const semantic = this.configOverrides.semantic;
|
|
327
|
-
const semanticBackend = (() => {
|
|
328
|
-
if (semantic && typeof semantic === "object" && !Array.isArray(semantic)) {
|
|
329
|
-
const candidate = semantic.backend;
|
|
330
|
-
return typeof candidate === "string" ? candidate : undefined;
|
|
331
|
-
}
|
|
332
|
-
return;
|
|
333
|
-
})();
|
|
334
|
-
const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
|
|
335
|
-
const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
|
|
336
|
-
const ortLibraryPath = ortDir == null ? null : join(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
|
|
337
|
-
const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
|
|
338
|
-
const env = {
|
|
339
|
-
...process.env,
|
|
340
|
-
...envPath ? { PATH: envPath } : {}
|
|
341
|
-
};
|
|
342
|
-
if (useFastembedBackend) {
|
|
343
|
-
env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join(this.configOverrides.storage_dir, "semantic", "models") : join(homedir() || "", ".cache", "fastembed"));
|
|
344
|
-
if (ortLibraryPath) {
|
|
345
|
-
env.ORT_DYLIB_PATH = ortLibraryPath;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
const child = spawn(this.binaryPath, [], {
|
|
349
|
-
cwd: this.cwd,
|
|
350
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
351
|
-
env
|
|
352
|
-
});
|
|
353
|
-
const currentChild = child;
|
|
354
|
-
child.stdout?.on("data", (chunk) => {
|
|
355
|
-
this.onStdoutData(chunk.toString("utf-8"));
|
|
356
|
-
});
|
|
357
|
-
child.stderr?.on("data", (chunk) => {
|
|
358
|
-
const lines = chunk.toString("utf-8").trimEnd().split(`
|
|
359
|
-
`);
|
|
360
|
-
for (const line of lines) {
|
|
361
|
-
if (!line)
|
|
362
|
-
continue;
|
|
363
|
-
const stripped = line.replace(/^\[aft\]\s*/, "");
|
|
364
|
-
log(`[aft] ${stripped}`);
|
|
365
|
-
this.pushStderrLine(stripped);
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
child.on("error", (err) => {
|
|
369
|
-
if (this.process !== currentChild)
|
|
370
|
-
return;
|
|
371
|
-
error(`Process error: ${err.message}${this.formatStderrTail()}`);
|
|
372
|
-
this.handleCrash();
|
|
373
|
-
});
|
|
374
|
-
child.on("exit", (code, signal) => {
|
|
375
|
-
if (this.process !== currentChild)
|
|
376
|
-
return;
|
|
377
|
-
if (this._shuttingDown)
|
|
378
|
-
return;
|
|
379
|
-
log(`Process exited: code=${code}, signal=${signal}`);
|
|
380
|
-
if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
|
|
381
|
-
this.process = null;
|
|
382
|
-
this.configured = false;
|
|
383
|
-
this.clearRestartResetTimer();
|
|
384
|
-
this.rejectAllPending(new Error(`${this.errorPrefix} Binary killed by ${signal}`));
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
this.handleCrash();
|
|
388
|
-
});
|
|
389
|
-
this.process = child;
|
|
390
|
-
this.stdoutBuffer = "";
|
|
391
|
-
this.stderrTail = [];
|
|
392
|
-
}
|
|
393
|
-
pushStderrLine(line) {
|
|
394
|
-
this.stderrTail.push(line);
|
|
395
|
-
if (this.stderrTail.length > BinaryBridge.STDERR_TAIL_MAX) {
|
|
396
|
-
this.stderrTail.shift();
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
formatStderrTail() {
|
|
400
|
-
if (this.stderrTail.length === 0)
|
|
401
|
-
return "";
|
|
402
|
-
const tail = this.stderrTail.join(`
|
|
403
|
-
`);
|
|
404
|
-
return `
|
|
405
|
-
--- last ${this.stderrTail.length} stderr lines ---
|
|
406
|
-
${tail}`;
|
|
407
|
-
}
|
|
408
|
-
onStdoutData(data) {
|
|
409
|
-
this.stdoutBuffer += data;
|
|
410
|
-
if (this.stdoutBuffer.length > MAX_STDOUT_BUFFER) {
|
|
411
|
-
this.handleCrash(new Error(`aft bridge stdout buffer exceeded ${MAX_STDOUT_BUFFER} bytes — killing bridge`));
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
let newlineIdx;
|
|
415
|
-
while ((newlineIdx = this.stdoutBuffer.indexOf(`
|
|
416
|
-
`)) !== -1) {
|
|
417
|
-
const line = this.stdoutBuffer.slice(0, newlineIdx).trim();
|
|
418
|
-
this.stdoutBuffer = this.stdoutBuffer.slice(newlineIdx + 1);
|
|
419
|
-
if (!line)
|
|
420
|
-
continue;
|
|
421
|
-
try {
|
|
422
|
-
const response = JSON.parse(line);
|
|
423
|
-
if (response.type === "progress") {
|
|
424
|
-
const requestId = response.request_id;
|
|
425
|
-
const entry = requestId ? this.pending.get(requestId) : undefined;
|
|
426
|
-
const kind = response.kind === "stderr" ? "stderr" : "stdout";
|
|
427
|
-
const text = typeof response.chunk === "string" ? response.chunk : "";
|
|
428
|
-
entry?.onProgress?.({ kind, text });
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
if (response.type === "permission_ask") {
|
|
432
|
-
const requestId = response.request_id;
|
|
433
|
-
const entry = requestId ? this.pending.get(requestId) : undefined;
|
|
434
|
-
if (requestId && entry) {
|
|
435
|
-
this.pending.delete(requestId);
|
|
436
|
-
clearTimeout(entry.timer);
|
|
437
|
-
entry.resolve({
|
|
438
|
-
success: false,
|
|
439
|
-
code: "permission_required",
|
|
440
|
-
message: "bash command requires permission",
|
|
441
|
-
asks: response.asks
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
if (response.type === "bash_completed") {
|
|
447
|
-
this.onBashCompletion?.(response, this);
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (response.type === "configure_warnings") {
|
|
451
|
-
this.handleConfigureWarningsFrame(response).catch((err) => {
|
|
452
|
-
warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
453
|
-
});
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
const id = response.id;
|
|
457
|
-
if (id && this.pending.has(id)) {
|
|
458
|
-
const entry = this.pending.get(id);
|
|
459
|
-
if (!entry)
|
|
460
|
-
continue;
|
|
461
|
-
this.pending.delete(id);
|
|
462
|
-
clearTimeout(entry.timer);
|
|
463
|
-
this.scheduleRestartCountReset();
|
|
464
|
-
entry.resolve(response);
|
|
465
|
-
} else if (typeof response.type === "string") {
|
|
466
|
-
log(`Ignoring unknown stdout push frame type: ${response.type}`);
|
|
467
|
-
}
|
|
468
|
-
} catch (_err) {
|
|
469
|
-
warn(`Failed to parse stdout line: ${line}`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
handleTimeout() {
|
|
474
|
-
if (this.process) {
|
|
475
|
-
this.process.kill("SIGKILL");
|
|
476
|
-
this.process = null;
|
|
477
|
-
}
|
|
478
|
-
this.clearRestartResetTimer();
|
|
479
|
-
this.configured = false;
|
|
480
|
-
const tail = this.formatStderrTail();
|
|
481
|
-
this.stderrTail = [];
|
|
482
|
-
if (tail) {
|
|
483
|
-
error(`Bridge killed after timeout.${tail}`);
|
|
484
|
-
} else {
|
|
485
|
-
warn(`Bridge killed after timeout (see ${getLogFilePath()})`);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
handleCrash(cause) {
|
|
489
|
-
const proc = this.process;
|
|
490
|
-
this.process = null;
|
|
491
|
-
if (proc && proc.exitCode === null && !proc.killed) {
|
|
492
|
-
proc.kill("SIGKILL");
|
|
493
|
-
}
|
|
494
|
-
this.clearRestartResetTimer();
|
|
495
|
-
this.configured = false;
|
|
496
|
-
const tail = this.formatStderrTail();
|
|
497
|
-
if (tail) {
|
|
498
|
-
error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
|
|
499
|
-
}
|
|
500
|
-
this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${getLogFilePath()})`));
|
|
501
|
-
if (this._restartCount < this.maxRestarts) {
|
|
502
|
-
const delay = 100 * 2 ** this._restartCount;
|
|
503
|
-
this._restartCount++;
|
|
504
|
-
log(`Auto-restart #${this._restartCount} in ${delay}ms`);
|
|
505
|
-
setTimeout(() => {
|
|
506
|
-
if (!this._shuttingDown && !this.isAlive()) {
|
|
507
|
-
try {
|
|
508
|
-
this.spawnProcess();
|
|
509
|
-
} catch (err) {
|
|
510
|
-
error(`Failed to restart: ${err.message}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}, delay);
|
|
514
|
-
this.scheduleRestartCountReset();
|
|
515
|
-
} else {
|
|
516
|
-
error(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
|
|
517
|
-
this.scheduleRestartCountReset();
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
rejectAllPending(error2) {
|
|
521
|
-
for (const [_id, entry] of this.pending) {
|
|
522
|
-
clearTimeout(entry.timer);
|
|
523
|
-
entry.reject(error2);
|
|
524
|
-
}
|
|
525
|
-
this.pending.clear();
|
|
526
|
-
}
|
|
527
|
-
scheduleRestartCountReset() {
|
|
528
|
-
this.clearRestartResetTimer();
|
|
529
|
-
this.restartResetTimer = setTimeout(() => {
|
|
530
|
-
this._restartCount = 0;
|
|
531
|
-
this.restartResetTimer = null;
|
|
532
|
-
}, BinaryBridge.RESTART_RESET_MS);
|
|
533
|
-
}
|
|
534
|
-
clearRestartResetTimer() {
|
|
535
|
-
if (this.restartResetTimer) {
|
|
536
|
-
clearTimeout(this.restartResetTimer);
|
|
537
|
-
this.restartResetTimer = null;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
// src/downloader.ts
|
|
542
|
-
import { chmodSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
543
|
-
import { homedir as homedir2 } from "node:os";
|
|
544
|
-
import { join as join2 } from "node:path";
|
|
545
|
-
|
|
546
|
-
// src/platform.ts
|
|
547
|
-
var PLATFORM_ARCH_MAP = {
|
|
548
|
-
darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
|
|
549
|
-
linux: { arm64: "linux-arm64", x64: "linux-x64" },
|
|
550
|
-
win32: { x64: "win32-x64" }
|
|
551
|
-
};
|
|
552
|
-
var PLATFORM_ASSET_MAP = {
|
|
553
|
-
"darwin-arm64": "aft-darwin-arm64",
|
|
554
|
-
"darwin-x64": "aft-darwin-x64",
|
|
555
|
-
"linux-arm64": "aft-linux-arm64",
|
|
556
|
-
"linux-x64": "aft-linux-x64",
|
|
557
|
-
"win32-x64": "aft-win32-x64.exe"
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
// src/downloader.ts
|
|
561
|
-
var REPO = "cortexkit/aft";
|
|
562
|
-
function getCacheDir() {
|
|
563
|
-
if (process.platform === "win32") {
|
|
564
|
-
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
565
|
-
const base2 = localAppData || join2(homedir2(), "AppData", "Local");
|
|
566
|
-
return join2(base2, "aft", "bin");
|
|
567
|
-
}
|
|
568
|
-
const base = process.env.XDG_CACHE_HOME || join2(homedir2(), ".cache");
|
|
569
|
-
return join2(base, "aft", "bin");
|
|
570
|
-
}
|
|
571
|
-
function getBinaryName() {
|
|
572
|
-
return process.platform === "win32" ? "aft.exe" : "aft";
|
|
573
|
-
}
|
|
574
|
-
function getCachedBinaryPath(version) {
|
|
575
|
-
if (!version)
|
|
576
|
-
return null;
|
|
577
|
-
const binaryPath = join2(getCacheDir(), version, getBinaryName());
|
|
578
|
-
return existsSync(binaryPath) ? binaryPath : null;
|
|
579
|
-
}
|
|
580
|
-
async function downloadBinary(version) {
|
|
581
|
-
const platformKey = `${process.platform}-${process.arch}`;
|
|
582
|
-
const assetName = PLATFORM_ASSET_MAP[platformKey];
|
|
583
|
-
if (!assetName) {
|
|
584
|
-
error(`Unsupported platform: ${platformKey}`);
|
|
585
|
-
return null;
|
|
586
|
-
}
|
|
587
|
-
const tag = version ?? await fetchLatestTag();
|
|
588
|
-
if (!tag) {
|
|
589
|
-
error("Could not determine latest release version.");
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
const versionedCacheDir = join2(getCacheDir(), tag);
|
|
593
|
-
const binaryName = getBinaryName();
|
|
594
|
-
const binaryPath = join2(versionedCacheDir, binaryName);
|
|
595
|
-
if (existsSync(binaryPath)) {
|
|
596
|
-
return binaryPath;
|
|
597
|
-
}
|
|
598
|
-
const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
|
|
599
|
-
const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
|
|
600
|
-
log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
|
|
601
|
-
try {
|
|
602
|
-
if (!existsSync(versionedCacheDir)) {
|
|
603
|
-
mkdirSync(versionedCacheDir, { recursive: true });
|
|
604
|
-
}
|
|
605
|
-
const [binaryResponse, checksumResponse] = await Promise.all([
|
|
606
|
-
fetch(downloadUrl, { redirect: "follow" }),
|
|
607
|
-
fetch(checksumUrl, { redirect: "follow" })
|
|
608
|
-
]);
|
|
609
|
-
if (!binaryResponse.ok) {
|
|
610
|
-
throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
|
|
611
|
-
}
|
|
612
|
-
const arrayBuffer = await binaryResponse.arrayBuffer();
|
|
613
|
-
if (!checksumResponse.ok) {
|
|
614
|
-
warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
|
|
615
|
-
return null;
|
|
616
|
-
}
|
|
617
|
-
const checksumText = await checksumResponse.text();
|
|
618
|
-
const expectedHash = parseChecksumForAsset(checksumText, assetName);
|
|
619
|
-
if (!expectedHash) {
|
|
620
|
-
warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
const { createHash } = await import("node:crypto");
|
|
624
|
-
const actualHash = createHash("sha256").update(Buffer.from(arrayBuffer)).digest("hex");
|
|
625
|
-
if (actualHash !== expectedHash) {
|
|
626
|
-
throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. The binary may have been tampered with.`);
|
|
627
|
-
}
|
|
628
|
-
log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
|
|
629
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
630
|
-
const { writeFileSync } = await import("node:fs");
|
|
631
|
-
writeFileSync(tmpPath, Buffer.from(arrayBuffer));
|
|
632
|
-
if (process.platform !== "win32") {
|
|
633
|
-
chmodSync(tmpPath, 493);
|
|
634
|
-
}
|
|
635
|
-
const { renameSync } = await import("node:fs");
|
|
636
|
-
renameSync(tmpPath, binaryPath);
|
|
637
|
-
log(`AFT binary ready at ${binaryPath}`);
|
|
638
|
-
return binaryPath;
|
|
639
|
-
} catch (err) {
|
|
640
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
641
|
-
error(`Failed to download AFT binary: ${msg}`);
|
|
642
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
643
|
-
if (existsSync(tmpPath)) {
|
|
644
|
-
try {
|
|
645
|
-
unlinkSync(tmpPath);
|
|
646
|
-
} catch {}
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
async function ensureBinary(version) {
|
|
652
|
-
if (version) {
|
|
653
|
-
const versionCached = getCachedBinaryPath(version);
|
|
654
|
-
if (versionCached) {
|
|
655
|
-
log(`Found cached binary for ${version}: ${versionCached}`);
|
|
656
|
-
return versionCached;
|
|
657
|
-
}
|
|
658
|
-
log(`No cached binary for ${version}, downloading...`);
|
|
659
|
-
return downloadBinary(version);
|
|
660
|
-
}
|
|
661
|
-
log("No cached binary found, downloading latest...");
|
|
662
|
-
return downloadBinary();
|
|
663
|
-
}
|
|
664
|
-
function parseChecksumForAsset(checksumText, assetName) {
|
|
665
|
-
for (const line of checksumText.split(`
|
|
666
|
-
`)) {
|
|
667
|
-
const trimmed = line.trim();
|
|
668
|
-
if (!trimmed)
|
|
669
|
-
continue;
|
|
670
|
-
const match = trimmed.match(/^([0-9a-f]{64})\s+(.+)$/);
|
|
671
|
-
if (match && match[2] === assetName) {
|
|
672
|
-
return match[1];
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
async function fetchLatestTag() {
|
|
678
|
-
try {
|
|
679
|
-
const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
|
680
|
-
headers: { Accept: "application/vnd.github.v3+json" }
|
|
681
|
-
});
|
|
682
|
-
if (!response.ok)
|
|
683
|
-
return null;
|
|
684
|
-
const data = await response.json();
|
|
685
|
-
return data.tag_name ?? null;
|
|
686
|
-
} catch {
|
|
687
|
-
return null;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// src/onnx-runtime.ts
|
|
691
|
-
import { execFileSync } from "node:child_process";
|
|
692
|
-
import { createHash } from "node:crypto";
|
|
693
|
-
import {
|
|
694
|
-
chmodSync as chmodSync2,
|
|
695
|
-
closeSync,
|
|
696
|
-
copyFileSync,
|
|
697
|
-
createWriteStream,
|
|
698
|
-
existsSync as existsSync2,
|
|
699
|
-
lstatSync,
|
|
700
|
-
mkdirSync as mkdirSync2,
|
|
701
|
-
openSync,
|
|
702
|
-
readdirSync,
|
|
703
|
-
readFileSync,
|
|
704
|
-
readlinkSync,
|
|
705
|
-
realpathSync,
|
|
706
|
-
rmSync,
|
|
707
|
-
statSync,
|
|
708
|
-
symlinkSync,
|
|
709
|
-
unlinkSync as unlinkSync2,
|
|
710
|
-
writeFileSync
|
|
711
|
-
} from "node:fs";
|
|
712
|
-
import { dirname, join as join3, relative, resolve } from "node:path";
|
|
713
|
-
import { Readable } from "node:stream";
|
|
714
|
-
import { pipeline } from "node:stream/promises";
|
|
715
|
-
var ORT_VERSION = "1.24.4";
|
|
716
|
-
var ORT_REPO = "microsoft/onnxruntime";
|
|
717
|
-
var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
|
|
718
|
-
var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
|
|
719
|
-
var ONNX_LOCK_FILE = ".aft-onnx-installing";
|
|
720
|
-
var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
|
|
721
|
-
var STALE_LOCK_MS = 30 * 60 * 1000;
|
|
722
|
-
var ORT_PLATFORM_MAP = {
|
|
723
|
-
darwin: {
|
|
724
|
-
arm64: {
|
|
725
|
-
assetName: `onnxruntime-osx-arm64-${ORT_VERSION}`,
|
|
726
|
-
libName: "libonnxruntime.dylib",
|
|
727
|
-
archiveType: "tgz"
|
|
728
|
-
}
|
|
729
|
-
},
|
|
730
|
-
linux: {
|
|
731
|
-
x64: {
|
|
732
|
-
assetName: `onnxruntime-linux-x64-${ORT_VERSION}`,
|
|
733
|
-
libName: "libonnxruntime.so",
|
|
734
|
-
archiveType: "tgz"
|
|
735
|
-
},
|
|
736
|
-
arm64: {
|
|
737
|
-
assetName: `onnxruntime-linux-aarch64-${ORT_VERSION}`,
|
|
738
|
-
libName: "libonnxruntime.so",
|
|
739
|
-
archiveType: "tgz"
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
win32: {
|
|
743
|
-
x64: {
|
|
744
|
-
assetName: `onnxruntime-win-x64-${ORT_VERSION}`,
|
|
745
|
-
libName: "onnxruntime.dll",
|
|
746
|
-
archiveType: "zip"
|
|
747
|
-
},
|
|
748
|
-
arm64: {
|
|
749
|
-
assetName: `onnxruntime-win-arm64-${ORT_VERSION}`,
|
|
750
|
-
libName: "onnxruntime.dll",
|
|
751
|
-
archiveType: "zip"
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
function getPlatformInfo() {
|
|
756
|
-
const platformMap = ORT_PLATFORM_MAP[process.platform];
|
|
757
|
-
if (!platformMap)
|
|
758
|
-
return null;
|
|
759
|
-
return platformMap[process.arch] || null;
|
|
760
|
-
}
|
|
761
|
-
function isOrtAutoDownloadSupported() {
|
|
762
|
-
return getPlatformInfo() !== null;
|
|
763
|
-
}
|
|
764
|
-
function getManualInstallHint() {
|
|
765
|
-
if (process.platform === "darwin" && process.arch === "x64") {
|
|
766
|
-
return "brew install onnxruntime";
|
|
767
|
-
}
|
|
768
|
-
if (process.platform === "linux") {
|
|
769
|
-
return "apt install libonnxruntime or download from https://github.com/microsoft/onnxruntime/releases";
|
|
770
|
-
}
|
|
771
|
-
return "Download from https://github.com/microsoft/onnxruntime/releases";
|
|
772
|
-
}
|
|
773
|
-
async function ensureOnnxRuntime(storageDir) {
|
|
774
|
-
const info = getPlatformInfo();
|
|
775
|
-
const ortDir = join3(storageDir, "onnxruntime", ORT_VERSION);
|
|
776
|
-
const libPath = join3(ortDir, info?.libName ?? "libonnxruntime.dylib");
|
|
777
|
-
if (existsSync2(libPath)) {
|
|
778
|
-
const meta = readOnnxInstalledMeta(ortDir);
|
|
779
|
-
if (meta?.sha256) {
|
|
780
|
-
try {
|
|
781
|
-
const currentHash = sha256File(libPath);
|
|
782
|
-
if (currentHash !== meta.sha256) {
|
|
783
|
-
error(`ONNX Runtime at ${ortDir}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${meta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-download from scratch.`);
|
|
784
|
-
} else {
|
|
785
|
-
log(`ONNX Runtime found at ${ortDir} (TOFU verified)`);
|
|
786
|
-
return ortDir;
|
|
787
|
-
}
|
|
788
|
-
} catch (err) {
|
|
789
|
-
warn(`Could not verify ONNX Runtime hash at ${ortDir}: ${err}`);
|
|
790
|
-
return ortDir;
|
|
791
|
-
}
|
|
792
|
-
} else {
|
|
793
|
-
log(`ONNX Runtime found at ${ortDir} (no recorded hash, accepting)`);
|
|
794
|
-
return ortDir;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
const systemPath = findSystemOnnxRuntime(info?.libName);
|
|
798
|
-
if (systemPath) {
|
|
799
|
-
log(`ONNX Runtime found at system path: ${systemPath}`);
|
|
800
|
-
return systemPath;
|
|
801
|
-
}
|
|
802
|
-
if (!info) {
|
|
803
|
-
warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
|
|
804
|
-
return null;
|
|
805
|
-
}
|
|
806
|
-
const onnxBaseDir = join3(storageDir, "onnxruntime");
|
|
807
|
-
mkdirSync2(onnxBaseDir, { recursive: true });
|
|
808
|
-
const lockPath = join3(onnxBaseDir, ONNX_LOCK_FILE);
|
|
809
|
-
if (!acquireLock(lockPath)) {
|
|
810
|
-
warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
|
|
811
|
-
return null;
|
|
812
|
-
}
|
|
813
|
-
try {
|
|
814
|
-
return await downloadOnnxRuntime(info, ortDir);
|
|
815
|
-
} finally {
|
|
816
|
-
releaseLock(lockPath);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
function findSystemOnnxRuntime(libName) {
|
|
820
|
-
if (!libName)
|
|
821
|
-
return null;
|
|
822
|
-
const searchPaths = [];
|
|
823
|
-
if (process.platform === "darwin") {
|
|
824
|
-
searchPaths.push("/opt/homebrew/lib", "/usr/local/lib");
|
|
825
|
-
} else if (process.platform === "linux") {
|
|
826
|
-
searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
|
|
827
|
-
}
|
|
828
|
-
for (const dir of searchPaths) {
|
|
829
|
-
if (existsSync2(join3(dir, libName))) {
|
|
830
|
-
return dir;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
835
|
-
async function downloadFileWithCap(url, destPath) {
|
|
836
|
-
const controller = new AbortController;
|
|
837
|
-
const timeout = setTimeout(() => controller.abort(), 300000);
|
|
838
|
-
try {
|
|
839
|
-
const res = await fetch(url, {
|
|
840
|
-
headers: { accept: "application/octet-stream" },
|
|
841
|
-
redirect: "follow",
|
|
842
|
-
signal: controller.signal
|
|
843
|
-
});
|
|
844
|
-
if (!res.ok || !res.body) {
|
|
845
|
-
throw new Error(`download failed (HTTP ${res.status})`);
|
|
846
|
-
}
|
|
847
|
-
const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
848
|
-
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
|
|
849
|
-
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
|
|
850
|
-
}
|
|
851
|
-
mkdirSync2(dirname(destPath), { recursive: true });
|
|
852
|
-
let bytesWritten = 0;
|
|
853
|
-
const guard = new TransformStream({
|
|
854
|
-
transform(chunk, transformController) {
|
|
855
|
-
bytesWritten += chunk.byteLength;
|
|
856
|
-
if (bytesWritten > MAX_DOWNLOAD_BYTES) {
|
|
857
|
-
transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
transformController.enqueue(chunk);
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
const guarded = res.body.pipeThrough(guard);
|
|
864
|
-
const nodeStream = Readable.fromWeb(guarded);
|
|
865
|
-
await pipeline(nodeStream, createWriteStream(destPath), { signal: controller.signal });
|
|
866
|
-
} catch (err) {
|
|
867
|
-
try {
|
|
868
|
-
unlinkSync2(destPath);
|
|
869
|
-
} catch {}
|
|
870
|
-
throw err;
|
|
871
|
-
} finally {
|
|
872
|
-
clearTimeout(timeout);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
function validateExtractedTree(stagingRoot) {
|
|
876
|
-
const realRoot = realpathSync(stagingRoot);
|
|
877
|
-
let totalBytes = 0;
|
|
878
|
-
const walk = (dir) => {
|
|
879
|
-
const entries = readdirSync(dir);
|
|
880
|
-
for (const entry of entries) {
|
|
881
|
-
const fullPath = join3(dir, entry);
|
|
882
|
-
const lst = lstatSync(fullPath);
|
|
883
|
-
if (lst.isSymbolicLink()) {
|
|
884
|
-
const linkTarget = readlinkSync(fullPath);
|
|
885
|
-
const resolvedTarget = resolve(dirname(fullPath), linkTarget);
|
|
886
|
-
const rel2 = relative(realRoot, resolvedTarget);
|
|
887
|
-
if (rel2.startsWith("..") || process.platform !== "win32" && rel2.startsWith("/")) {
|
|
888
|
-
throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
|
|
889
|
-
}
|
|
890
|
-
continue;
|
|
891
|
-
}
|
|
892
|
-
const rel = relative(realRoot, fullPath);
|
|
893
|
-
if (rel.startsWith("..") || process.platform !== "win32" && rel.startsWith("/")) {
|
|
894
|
-
throw new Error(`extracted entry ${fullPath} escapes staging root`);
|
|
895
|
-
}
|
|
896
|
-
if (lst.isDirectory()) {
|
|
897
|
-
walk(fullPath);
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
900
|
-
if (lst.isFile()) {
|
|
901
|
-
totalBytes += lst.size;
|
|
902
|
-
if (totalBytes > MAX_EXTRACT_BYTES) {
|
|
903
|
-
throw new Error(`extracted size ${totalBytes} exceeds max ${MAX_EXTRACT_BYTES} (decompression bomb defense)`);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
walk(realRoot);
|
|
909
|
-
}
|
|
910
|
-
async function downloadOnnxRuntime(info, targetDir) {
|
|
911
|
-
const url = `https://github.com/${ORT_REPO}/releases/download/v${ORT_VERSION}/${info.assetName}.${info.archiveType === "tgz" ? "tgz" : "zip"}`;
|
|
912
|
-
log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
|
|
913
|
-
const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
|
|
914
|
-
try {
|
|
915
|
-
mkdirSync2(tmpDir, { recursive: true });
|
|
916
|
-
const archivePath = join3(tmpDir, `onnxruntime.${info.archiveType}`);
|
|
917
|
-
await downloadFileWithCap(url, archivePath);
|
|
918
|
-
const archiveSha256 = sha256File(archivePath);
|
|
919
|
-
log(`ONNX Runtime archive sha256=${archiveSha256}`);
|
|
920
|
-
if (info.archiveType === "tgz") {
|
|
921
|
-
execFileSync("tar", ["xzf", archivePath, "-C", tmpDir], {
|
|
922
|
-
stdio: "pipe",
|
|
923
|
-
timeout: 120000
|
|
924
|
-
});
|
|
925
|
-
} else {
|
|
926
|
-
await extractZipArchive(archivePath, tmpDir);
|
|
927
|
-
}
|
|
928
|
-
try {
|
|
929
|
-
unlinkSync2(archivePath);
|
|
930
|
-
} catch {}
|
|
931
|
-
validateExtractedTree(tmpDir);
|
|
932
|
-
const extractedDir = join3(tmpDir, info.assetName, "lib");
|
|
933
|
-
if (!existsSync2(extractedDir)) {
|
|
934
|
-
throw new Error(`Expected directory not found: ${extractedDir}`);
|
|
935
|
-
}
|
|
936
|
-
mkdirSync2(targetDir, { recursive: true });
|
|
937
|
-
const libFiles = readdirSync(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
|
|
938
|
-
const realFiles = [];
|
|
939
|
-
const symlinks = [];
|
|
940
|
-
for (const libFile of libFiles) {
|
|
941
|
-
const src = join3(extractedDir, libFile);
|
|
942
|
-
try {
|
|
943
|
-
const stat = lstatSync(src);
|
|
944
|
-
log(`ORT extract: ${libFile} — isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
|
|
945
|
-
if (stat.isSymbolicLink()) {
|
|
946
|
-
symlinks.push({ name: libFile, target: readlinkSync(src) });
|
|
947
|
-
} else {
|
|
948
|
-
realFiles.push(libFile);
|
|
949
|
-
}
|
|
950
|
-
} catch (e) {
|
|
951
|
-
log(`ORT extract: ${libFile} — stat failed: ${e}`);
|
|
952
|
-
realFiles.push(libFile);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
for (const libFile of realFiles) {
|
|
956
|
-
const src = join3(extractedDir, libFile);
|
|
957
|
-
const dst = join3(targetDir, libFile);
|
|
958
|
-
try {
|
|
959
|
-
copyFileSync(src, dst);
|
|
960
|
-
if (process.platform !== "win32") {
|
|
961
|
-
chmodSync2(dst, 493);
|
|
962
|
-
}
|
|
963
|
-
} catch (copyErr) {
|
|
964
|
-
log(`ORT extract: failed to copy ${libFile}: ${copyErr}`);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
for (const link of symlinks) {
|
|
968
|
-
const dst = join3(targetDir, link.name);
|
|
969
|
-
try {
|
|
970
|
-
unlinkSync2(dst);
|
|
971
|
-
} catch {}
|
|
972
|
-
symlinkSync(link.target, dst);
|
|
973
|
-
}
|
|
974
|
-
const libPath = join3(targetDir, info.libName);
|
|
975
|
-
let libHash = null;
|
|
976
|
-
try {
|
|
977
|
-
libHash = sha256File(libPath);
|
|
978
|
-
} catch (err) {
|
|
979
|
-
warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
|
|
980
|
-
}
|
|
981
|
-
writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
|
|
982
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
983
|
-
log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
|
|
984
|
-
return targetDir;
|
|
985
|
-
} catch (err) {
|
|
986
|
-
error(`Failed to download ONNX Runtime: ${err}`);
|
|
987
|
-
try {
|
|
988
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
989
|
-
} catch {}
|
|
990
|
-
try {
|
|
991
|
-
rmSync(targetDir, { recursive: true, force: true });
|
|
992
|
-
} catch {}
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
async function extractZipArchive(archivePath, destinationDir) {
|
|
997
|
-
if (process.platform === "win32") {
|
|
998
|
-
execFileSync("tar.exe", ["-xf", archivePath, "-C", destinationDir], {
|
|
999
|
-
stdio: "pipe",
|
|
1000
|
-
timeout: 120000
|
|
1001
|
-
});
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
execFileSync("unzip", ["-q", archivePath, "-d", destinationDir], {
|
|
1005
|
-
stdio: "pipe",
|
|
1006
|
-
timeout: 120000
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
1010
|
-
try {
|
|
1011
|
-
const meta = {
|
|
1012
|
-
version,
|
|
1013
|
-
installedAt: new Date().toISOString(),
|
|
1014
|
-
...sha256 ? { sha256 } : {},
|
|
1015
|
-
archiveSha256
|
|
1016
|
-
};
|
|
1017
|
-
writeFileSync(join3(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
|
|
1018
|
-
} catch (err) {
|
|
1019
|
-
log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
function readOnnxInstalledMeta(installDir) {
|
|
1023
|
-
const path = join3(installDir, ONNX_INSTALLED_META_FILE);
|
|
1024
|
-
try {
|
|
1025
|
-
if (!statSync(path).isFile())
|
|
1026
|
-
return null;
|
|
1027
|
-
const raw = readFileSync(path, "utf8");
|
|
1028
|
-
const parsed = JSON.parse(raw);
|
|
1029
|
-
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
1030
|
-
return null;
|
|
1031
|
-
return {
|
|
1032
|
-
version: parsed.version,
|
|
1033
|
-
installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
|
|
1034
|
-
...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
|
|
1035
|
-
...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
|
|
1036
|
-
};
|
|
1037
|
-
} catch {
|
|
1038
|
-
return null;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function sha256File(path) {
|
|
1042
|
-
const hash = createHash("sha256");
|
|
1043
|
-
hash.update(readFileSync(path));
|
|
1044
|
-
return hash.digest("hex");
|
|
1045
|
-
}
|
|
1046
|
-
function acquireLock(lockPath) {
|
|
1047
|
-
const tryClaim = () => {
|
|
1048
|
-
try {
|
|
1049
|
-
const fd = openSync(lockPath, "wx");
|
|
1050
|
-
try {
|
|
1051
|
-
writeFileSync(fd, `${process.pid}
|
|
1052
|
-
${new Date().toISOString()}
|
|
1053
|
-
`);
|
|
1054
|
-
} finally {
|
|
1055
|
-
closeSync(fd);
|
|
1056
|
-
}
|
|
1057
|
-
return true;
|
|
1058
|
-
} catch (err) {
|
|
1059
|
-
const code = err.code;
|
|
1060
|
-
if (code === "EEXIST")
|
|
1061
|
-
return false;
|
|
1062
|
-
warn(`[onnx] unexpected error acquiring lock ${lockPath}: ${err}`);
|
|
1063
|
-
return false;
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
if (tryClaim())
|
|
1067
|
-
return true;
|
|
1068
|
-
let owningPid = null;
|
|
1069
|
-
let lockMtimeMs = 0;
|
|
1070
|
-
try {
|
|
1071
|
-
const raw = readFileSync(lockPath, "utf8");
|
|
1072
|
-
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
1073
|
-
const parsed = Number.parseInt(firstLine, 10);
|
|
1074
|
-
if (Number.isFinite(parsed) && parsed > 0)
|
|
1075
|
-
owningPid = parsed;
|
|
1076
|
-
lockMtimeMs = statSync(lockPath).mtimeMs;
|
|
1077
|
-
} catch {
|
|
1078
|
-
return tryClaim();
|
|
1079
|
-
}
|
|
1080
|
-
const age = Date.now() - lockMtimeMs;
|
|
1081
|
-
const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS;
|
|
1082
|
-
const skipLiveness = process.platform === "win32";
|
|
1083
|
-
const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive(owningPid);
|
|
1084
|
-
if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
|
|
1085
|
-
return false;
|
|
1086
|
-
}
|
|
1087
|
-
log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
1088
|
-
try {
|
|
1089
|
-
unlinkSync2(lockPath);
|
|
1090
|
-
} catch {}
|
|
1091
|
-
return tryClaim();
|
|
1092
|
-
}
|
|
1093
|
-
function releaseLock(lockPath) {
|
|
1094
|
-
try {
|
|
1095
|
-
let owningPid = null;
|
|
1096
|
-
try {
|
|
1097
|
-
const raw = readFileSync(lockPath, "utf8");
|
|
1098
|
-
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
1099
|
-
const parsed = Number.parseInt(firstLine, 10);
|
|
1100
|
-
if (Number.isFinite(parsed) && parsed > 0)
|
|
1101
|
-
owningPid = parsed;
|
|
1102
|
-
} catch (readErr) {
|
|
1103
|
-
const code = readErr.code;
|
|
1104
|
-
if (code === "ENOENT")
|
|
1105
|
-
return;
|
|
1106
|
-
warn(`[onnx] could not read lock ${lockPath} during release: ${readErr}`);
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
if (owningPid !== process.pid) {
|
|
1110
|
-
log(`[onnx] not releasing lock ${lockPath}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
try {
|
|
1114
|
-
unlinkSync2(lockPath);
|
|
1115
|
-
} catch (unlinkErr) {
|
|
1116
|
-
const code = unlinkErr.code;
|
|
1117
|
-
if (code !== "ENOENT") {
|
|
1118
|
-
warn(`[onnx] failed to release lock ${lockPath}: ${unlinkErr}`);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
} catch (err) {
|
|
1122
|
-
warn(`[onnx] unexpected error releasing lock ${lockPath}: ${err}`);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
function isProcessAlive(pid) {
|
|
1126
|
-
try {
|
|
1127
|
-
process.kill(pid, 0);
|
|
1128
|
-
return true;
|
|
1129
|
-
} catch (err) {
|
|
1130
|
-
const code = err.code;
|
|
1131
|
-
if (code === "ESRCH")
|
|
1132
|
-
return false;
|
|
1133
|
-
return true;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
function cleanupOnnxRuntime(storageDir) {
|
|
1137
|
-
try {
|
|
1138
|
-
const ortBase = join3(storageDir, "onnxruntime");
|
|
1139
|
-
if (existsSync2(ortBase)) {
|
|
1140
|
-
rmSync(ortBase, { recursive: true, force: true });
|
|
1141
|
-
}
|
|
1142
|
-
} catch {}
|
|
1143
|
-
}
|
|
1144
|
-
// src/pool.ts
|
|
1145
|
-
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1146
|
-
var DEFAULT_IDLE_TIMEOUT_MS = Infinity;
|
|
1147
|
-
var DEFAULT_MAX_POOL_SIZE = 8;
|
|
1148
|
-
var CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
1149
|
-
|
|
1150
|
-
class BridgePool {
|
|
1151
|
-
bridges = new Map;
|
|
1152
|
-
binaryPath;
|
|
1153
|
-
maxPoolSize;
|
|
1154
|
-
idleTimeoutMs;
|
|
1155
|
-
bridgeOptions;
|
|
1156
|
-
configOverrides;
|
|
1157
|
-
cleanupTimer = null;
|
|
1158
|
-
constructor(binaryPath, options = {}, configOverrides = {}) {
|
|
1159
|
-
this.binaryPath = binaryPath;
|
|
1160
|
-
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
1161
|
-
this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
1162
|
-
this.bridgeOptions = {
|
|
1163
|
-
timeoutMs: options.timeoutMs,
|
|
1164
|
-
maxRestarts: options.maxRestarts,
|
|
1165
|
-
minVersion: options.minVersion,
|
|
1166
|
-
onVersionMismatch: options.onVersionMismatch,
|
|
1167
|
-
onConfigureWarnings: options.onConfigureWarnings,
|
|
1168
|
-
onBashCompletion: options.onBashCompletion
|
|
1169
|
-
};
|
|
1170
|
-
this.configOverrides = configOverrides;
|
|
1171
|
-
if (Number.isFinite(this.idleTimeoutMs)) {
|
|
1172
|
-
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
1173
|
-
this.cleanupTimer.unref();
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
getActiveBridgeForRoot(projectRoot) {
|
|
1177
|
-
const key = normalizeKey(projectRoot);
|
|
1178
|
-
const entry = this.bridges.get(key);
|
|
1179
|
-
if (!entry?.bridge.isAlive())
|
|
1180
|
-
return null;
|
|
1181
|
-
entry.lastUsed = Date.now();
|
|
1182
|
-
return entry.bridge;
|
|
1183
|
-
}
|
|
1184
|
-
getBridge(projectRoot) {
|
|
1185
|
-
const key = normalizeKey(projectRoot);
|
|
1186
|
-
const existing = this.bridges.get(key);
|
|
1187
|
-
if (existing) {
|
|
1188
|
-
existing.lastUsed = Date.now();
|
|
1189
|
-
return existing.bridge;
|
|
1190
|
-
}
|
|
1191
|
-
if (this.bridges.size >= this.maxPoolSize) {
|
|
1192
|
-
this.evictLRU();
|
|
1193
|
-
}
|
|
1194
|
-
const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, this.configOverrides);
|
|
1195
|
-
this.bridges.set(key, { bridge, lastUsed: Date.now() });
|
|
1196
|
-
return bridge;
|
|
1197
|
-
}
|
|
1198
|
-
cleanup() {
|
|
1199
|
-
const now = Date.now();
|
|
1200
|
-
for (const [dir, entry] of this.bridges) {
|
|
1201
|
-
if (now - entry.lastUsed > this.idleTimeoutMs) {
|
|
1202
|
-
entry.bridge.shutdown().catch((err) => error("cleanup shutdown failed:", err));
|
|
1203
|
-
this.bridges.delete(dir);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
evictLRU() {
|
|
1208
|
-
let oldestDir = null;
|
|
1209
|
-
let oldestTime = Infinity;
|
|
1210
|
-
for (const [dir, entry] of this.bridges) {
|
|
1211
|
-
if (entry.lastUsed < oldestTime) {
|
|
1212
|
-
oldestTime = entry.lastUsed;
|
|
1213
|
-
oldestDir = dir;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
if (oldestDir) {
|
|
1217
|
-
const entry = this.bridges.get(oldestDir);
|
|
1218
|
-
entry?.bridge.shutdown().catch((err) => error("eviction shutdown failed:", err));
|
|
1219
|
-
this.bridges.delete(oldestDir);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
async shutdown() {
|
|
1223
|
-
if (this.cleanupTimer) {
|
|
1224
|
-
clearInterval(this.cleanupTimer);
|
|
1225
|
-
this.cleanupTimer = null;
|
|
1226
|
-
}
|
|
1227
|
-
const shutdowns = Array.from(this.bridges.values()).map((e) => e.bridge.shutdown());
|
|
1228
|
-
this.bridges.clear();
|
|
1229
|
-
await Promise.allSettled(shutdowns);
|
|
1230
|
-
}
|
|
1231
|
-
async replaceBinary(newPath) {
|
|
1232
|
-
this.binaryPath = newPath;
|
|
1233
|
-
const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
|
|
1234
|
-
this.bridges.clear();
|
|
1235
|
-
await Promise.allSettled(shutdowns);
|
|
1236
|
-
log(`Binary path updated to ${newPath}. All bridges cleared — next calls will use the new binary.`);
|
|
1237
|
-
}
|
|
1238
|
-
get size() {
|
|
1239
|
-
return this.bridges.size;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
function normalizeKey(projectRoot) {
|
|
1243
|
-
const stripped = projectRoot.replace(/[/\\]+$/, "");
|
|
1244
|
-
try {
|
|
1245
|
-
return realpathSync2(stripped);
|
|
1246
|
-
} catch {
|
|
1247
|
-
return stripped;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
// src/resolver.ts
|
|
1251
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
1252
|
-
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "node:fs";
|
|
1253
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
1254
|
-
import { homedir as homedir3 } from "node:os";
|
|
1255
|
-
import { join as join4 } from "node:path";
|
|
1256
|
-
function copyToVersionedCache(npmBinaryPath) {
|
|
1257
|
-
try {
|
|
1258
|
-
const result = spawnSync(npmBinaryPath, ["--version"], {
|
|
1259
|
-
encoding: "utf-8",
|
|
1260
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1261
|
-
timeout: 5000
|
|
1262
|
-
});
|
|
1263
|
-
const rawVersion = result.stdout?.trim();
|
|
1264
|
-
if (!rawVersion)
|
|
1265
|
-
return null;
|
|
1266
|
-
const version = rawVersion.replace(/^aft\s+/, "");
|
|
1267
|
-
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
1268
|
-
const cacheDir = getCacheDir();
|
|
1269
|
-
const versionedDir = join4(cacheDir, tag);
|
|
1270
|
-
const ext = process.platform === "win32" ? ".exe" : "";
|
|
1271
|
-
const cachedPath = join4(versionedDir, `aft${ext}`);
|
|
1272
|
-
if (existsSync3(cachedPath))
|
|
1273
|
-
return cachedPath;
|
|
1274
|
-
mkdirSync3(versionedDir, { recursive: true });
|
|
1275
|
-
const tmpPath = `${cachedPath}.tmp`;
|
|
1276
|
-
copyFileSync2(npmBinaryPath, tmpPath);
|
|
1277
|
-
if (process.platform !== "win32") {
|
|
1278
|
-
chmodSync3(tmpPath, 493);
|
|
1279
|
-
}
|
|
1280
|
-
renameSync(tmpPath, cachedPath);
|
|
1281
|
-
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
1282
|
-
return cachedPath;
|
|
1283
|
-
} catch (err) {
|
|
1284
|
-
warn(`Failed to copy binary to cache: ${err instanceof Error ? err.message : String(err)}`);
|
|
1285
|
-
return null;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
function platformKey(platform = process.platform, arch = process.arch) {
|
|
1289
|
-
const archMap = PLATFORM_ARCH_MAP[platform];
|
|
1290
|
-
if (!archMap) {
|
|
1291
|
-
throw new Error(`Unsupported platform: ${platform} (arch: ${arch}). ` + `Supported platforms: ${Object.keys(PLATFORM_ARCH_MAP).join(", ")}`);
|
|
1292
|
-
}
|
|
1293
|
-
const key = archMap[arch];
|
|
1294
|
-
if (!key) {
|
|
1295
|
-
throw new Error(`Unsupported architecture: ${arch} on platform ${platform}. ` + `Supported architectures for ${platform}: ${Object.keys(archMap).join(", ")}`);
|
|
1296
|
-
}
|
|
1297
|
-
return key;
|
|
1298
|
-
}
|
|
1299
|
-
function findBinarySync(expectedVersion) {
|
|
1300
|
-
const ext = process.platform === "win32" ? ".exe" : "";
|
|
1301
|
-
const pluginVersion = expectedVersion ?? (() => {
|
|
1302
|
-
try {
|
|
1303
|
-
const req = createRequire2(import.meta.url);
|
|
1304
|
-
return req("../package.json").version;
|
|
1305
|
-
} catch {
|
|
1306
|
-
return null;
|
|
1307
|
-
}
|
|
1308
|
-
})();
|
|
1309
|
-
if (pluginVersion) {
|
|
1310
|
-
const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
|
|
1311
|
-
const versionCached = getCachedBinaryPath(tag);
|
|
1312
|
-
if (versionCached)
|
|
1313
|
-
return versionCached;
|
|
1314
|
-
}
|
|
1315
|
-
try {
|
|
1316
|
-
const key = platformKey();
|
|
1317
|
-
const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
|
|
1318
|
-
const req = createRequire2(import.meta.url);
|
|
1319
|
-
const resolved = req.resolve(packageBin);
|
|
1320
|
-
if (existsSync3(resolved)) {
|
|
1321
|
-
const copied = copyToVersionedCache(resolved);
|
|
1322
|
-
return copied ?? resolved;
|
|
1323
|
-
}
|
|
1324
|
-
} catch {}
|
|
1325
|
-
try {
|
|
1326
|
-
const whichCmd = process.platform === "win32" ? "where aft" : "which aft";
|
|
1327
|
-
const result = execSync(whichCmd, {
|
|
1328
|
-
encoding: "utf-8",
|
|
1329
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1330
|
-
}).trim();
|
|
1331
|
-
if (result)
|
|
1332
|
-
return result;
|
|
1333
|
-
} catch {}
|
|
1334
|
-
const cargoPath = join4(homedir3(), ".cargo", "bin", `aft${ext}`);
|
|
1335
|
-
if (existsSync3(cargoPath))
|
|
1336
|
-
return cargoPath;
|
|
1337
|
-
return null;
|
|
1338
|
-
}
|
|
1339
|
-
async function findBinary(expectedVersion) {
|
|
1340
|
-
const syncResult = findBinarySync(expectedVersion);
|
|
1341
|
-
if (syncResult) {
|
|
1342
|
-
log(`Resolved binary: ${syncResult}`);
|
|
1343
|
-
return syncResult;
|
|
1344
|
-
}
|
|
1345
|
-
log("Binary not found locally, attempting auto-download...");
|
|
1346
|
-
const downloaded = await ensureBinary();
|
|
1347
|
-
if (downloaded)
|
|
1348
|
-
return downloaded;
|
|
1349
|
-
throw new Error([
|
|
1350
|
-
"Could not find the `aft` binary.",
|
|
1351
|
-
"",
|
|
1352
|
-
"Attempted sources:",
|
|
1353
|
-
" - Cache directory (~/.cache/aft/bin/)",
|
|
1354
|
-
" - npm platform package (@cortexkit/aft-<platform>)",
|
|
1355
|
-
" - PATH lookup (which aft)",
|
|
1356
|
-
" - ~/.cargo/bin/aft",
|
|
1357
|
-
" - Auto-download from GitHub releases (failed)",
|
|
1358
|
-
"",
|
|
1359
|
-
"Install it using one of these methods:",
|
|
1360
|
-
" npm install @cortexkit/aft-opencode # installs platform-specific binary via npm",
|
|
1361
|
-
" cargo install agent-file-tools # from crates.io",
|
|
1362
|
-
" cargo build --release # from source (binary at target/release/aft)",
|
|
1363
|
-
"",
|
|
1364
|
-
"Or add the aft directory to your PATH."
|
|
1365
|
-
].join(`
|
|
1366
|
-
`));
|
|
1367
|
-
}
|
|
1368
|
-
export {
|
|
1369
|
-
setActiveLogger,
|
|
1370
|
-
platformKey,
|
|
1371
|
-
isOrtAutoDownloadSupported,
|
|
1372
|
-
getManualInstallHint,
|
|
1373
|
-
getCachedBinaryPath,
|
|
1374
|
-
getCacheDir,
|
|
1375
|
-
getBinaryName,
|
|
1376
|
-
findBinarySync,
|
|
1377
|
-
findBinary,
|
|
1378
|
-
ensureOnnxRuntime,
|
|
1379
|
-
ensureBinary,
|
|
1380
|
-
downloadBinary,
|
|
1381
|
-
compareSemver,
|
|
1382
|
-
cleanupOnnxRuntime,
|
|
1383
|
-
PLATFORM_ASSET_MAP,
|
|
1384
|
-
PLATFORM_ARCH_MAP,
|
|
1385
|
-
BridgePool,
|
|
1386
|
-
BinaryBridge
|
|
1387
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @cortexkit/aft-bridge
|
|
3
|
+
*
|
|
4
|
+
* Shared transport, binary resolution, and ONNX runtime helpers for AFT
|
|
5
|
+
* agent-host plugins. Public surface intentionally narrow — host policies
|
|
6
|
+
* (config loading, permission UX, tool registration, notifications) stay in
|
|
7
|
+
* each host plugin.
|
|
8
|
+
*/
|
|
9
|
+
// --- logger contract ---
|
|
10
|
+
export { setActiveLogger } from "./active-logger.js";
|
|
11
|
+
// --- transport ---
|
|
12
|
+
export { BinaryBridge, compareSemver } from "./bridge.js";
|
|
13
|
+
// --- binary resolution ---
|
|
14
|
+
export { downloadBinary, ensureBinary, getBinaryName, getCacheDir, getCachedBinaryPath, } from "./downloader.js";
|
|
15
|
+
// --- ONNX runtime ---
|
|
16
|
+
export { cleanupOnnxRuntime, ensureOnnxRuntime, getManualInstallHint, isOrtAutoDownloadSupported, } from "./onnx-runtime.js";
|
|
17
|
+
// --- platform helpers ---
|
|
18
|
+
export { PLATFORM_ARCH_MAP, PLATFORM_ASSET_MAP } from "./platform.js";
|
|
19
|
+
export { BridgePool } from "./pool.js";
|
|
20
|
+
export { findBinary, findBinarySync, platformKey } from "./resolver.js";
|
|
21
|
+
// --- URL fetch (shared by aft_outline / aft_zoom URL targets) ---
|
|
22
|
+
export { _isPrivateIpv4, cleanupUrlCache, fetchUrlToTempFile } from "./url-fetch.js";
|
|
23
|
+
//# sourceMappingURL=index.js.map
|