@ariacompute/engine-ts 1.14.0 → 1.15.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/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # @ariacompute/engine-ts
2
2
 
3
3
  ```bash
4
- export ARIA_FFI_LIB=/path/to/libaria_ffi.so
5
4
  npm i @ariacompute/engine-ts
6
5
  ```
7
6
 
7
+ `libaria_ffi` is resolved as `ARIA_FFI_LIB` → package-bundled `dist/lib/` → `~/.ariacompute/lib/` (same as `aria-engine upgrade`). If none exist, `Engine.open` downloads the latest stable `libaria_ffi_{ver}_{os}.tar.gz` from regional Releases. `ARIA_FFI_LIB` remains an optional override.
8
+
8
9
  ```ts
9
10
  import { Engine } from "@ariacompute/engine-ts";
10
11
  const eng = new Engine("/path/to/bundle");
package/dist/index.d.ts CHANGED
@@ -53,6 +53,13 @@ export declare function hubFileUrls(source: "huggingface" | "modelscope", model:
53
53
  * Matches aria-engine download: .com → Hugging Face, .cn → ModelScope.
54
54
  * Dashboard is not used. Skips the download when a valid bundle is already cached. */
55
55
  export declare function downloadModel(model: string, tokenOrOpts?: string | OpenOptions, site?: string): Promise<string>;
56
+ export declare function ffiLibName(platform?: string): string;
57
+ export declare function cachedFfiPath(platform?: string): string | undefined;
58
+ export declare function ffiAssetOs(platform?: string, arch?: string): string;
59
+ export declare function selectLatestStable(releases: Array<Record<string, unknown>>): string;
60
+ export declare function extractFfiArchive(archive: string, destDir: string, want?: string): string;
61
+ /** Return a path to libaria_ffi, downloading the latest stable Release if needed. */
62
+ export declare function ensureFfiLib(site?: string): Promise<string>;
56
63
  export declare function isLocalRef(modelRef: string): boolean;
57
64
  export declare class Engine {
58
65
  private lib;
package/dist/index.js CHANGED
@@ -41,12 +41,20 @@ exports.configYmlScalar = configYmlScalar;
41
41
  exports.resolveHubToken = resolveHubToken;
42
42
  exports.hubFileUrls = hubFileUrls;
43
43
  exports.downloadModel = downloadModel;
44
+ exports.ffiLibName = ffiLibName;
45
+ exports.cachedFfiPath = cachedFfiPath;
46
+ exports.ffiAssetOs = ffiAssetOs;
47
+ exports.selectLatestStable = selectLatestStable;
48
+ exports.extractFfiArchive = extractFfiArchive;
49
+ exports.ensureFfiLib = ensureFfiLib;
44
50
  exports.isLocalRef = isLocalRef;
51
+ const node_child_process_1 = require("node:child_process");
45
52
  const fs = __importStar(require("node:fs"));
46
53
  const os = __importStar(require("node:os"));
47
54
  const path = __importStar(require("node:path"));
48
55
  const promises_1 = require("node:stream/promises");
49
56
  const node_stream_1 = require("node:stream");
57
+ const zlib = __importStar(require("node:zlib"));
50
58
  const DEFAULT_SITE = "https://ariacompute.com";
51
59
  const DEFAULT_SDK = "v1.0";
52
60
  const HUB_REQUIRED = ["config.json", "weight.bin"];
@@ -282,10 +290,217 @@ async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
282
290
  throw e;
283
291
  }
284
292
  }
285
- function loadLib(ffiLib) {
286
- const libPath = ffiLib || process.env.ARIA_FFI_LIB;
293
+ const SDK_UA = "aria-engine-sdk/0.1.0";
294
+ function ffiLibName(platform = process.platform) {
295
+ if (platform === "win32" || platform.toLowerCase().startsWith("win"))
296
+ return "aria_ffi.dll";
297
+ if (platform === "darwin")
298
+ return "libaria_ffi.dylib";
299
+ return "libaria_ffi.so";
300
+ }
301
+ function libDir() {
302
+ return path.join(ariaHome(), "lib");
303
+ }
304
+ function cachedFfiPath(platform = process.platform) {
305
+ const candidate = path.join(libDir(), ffiLibName(platform));
306
+ return fs.existsSync(candidate) ? candidate : undefined;
307
+ }
308
+ function bundledFfiPath() {
309
+ const candidate = path.join(__dirname, "lib", ffiLibName());
310
+ return fs.existsSync(candidate) ? candidate : undefined;
311
+ }
312
+ function ffiAssetOs(platform = process.platform, arch = process.arch) {
313
+ const p = platform.toLowerCase();
314
+ const a = arch.toLowerCase();
315
+ if (p === "linux" && (a === "x64" || a === "x86_64" || a === "amd64"))
316
+ return "linux_x86_64";
317
+ if (p === "linux" && (a === "arm64" || a === "aarch64"))
318
+ return "linux_arm64";
319
+ if (p === "darwin" || p === "macos")
320
+ return "macos";
321
+ if ((p === "win32" || p.startsWith("win")) && (a === "x64" || a === "x86_64" || a === "amd64")) {
322
+ return "windows_x86_64";
323
+ }
324
+ throw new Error(`unsupported platform ${platform}/${arch} for libaria_ffi`);
325
+ }
326
+ function stripV(tag) {
327
+ const t = tag.trim();
328
+ return t.startsWith("v") || t.startsWith("V") ? t.slice(1) : t;
329
+ }
330
+ function parseSemver(tag) {
331
+ const core = stripV(tag).split("-", 1)[0].split("+", 1)[0];
332
+ const parts = core.split(".");
333
+ if (!parts.length || !/^\d+$/.test(parts[0]))
334
+ return undefined;
335
+ return [
336
+ Number(parts[0]),
337
+ parts[1] && /^\d+$/.test(parts[1]) ? Number(parts[1]) : 0,
338
+ parts[2] && /^\d+$/.test(parts[2]) ? Number(parts[2]) : 0,
339
+ ];
340
+ }
341
+ function selectLatestStable(releases) {
342
+ let bestTag;
343
+ let bestKey = [-1, -1, -1];
344
+ for (const rel of releases) {
345
+ if (rel.draft || rel.prerelease)
346
+ continue;
347
+ const tag = String(rel.tag_name || rel.tag || "");
348
+ const parsed = parseSemver(tag);
349
+ if (!parsed)
350
+ continue;
351
+ if (parsed[0] > bestKey[0] ||
352
+ (parsed[0] === bestKey[0] && parsed[1] > bestKey[1]) ||
353
+ (parsed[0] === bestKey[0] && parsed[1] === bestKey[1] && parsed[2] > bestKey[2])) {
354
+ bestKey = parsed;
355
+ bestTag = tag;
356
+ }
357
+ }
358
+ if (!bestTag)
359
+ throw new Error("no stable release found for libaria_ffi");
360
+ return stripV(bestTag);
361
+ }
362
+ function upgradeOrg(site) {
363
+ const cfg = configYmlScalar("upgrade_url");
364
+ if (cfg)
365
+ return cfg.replace(/\/$/, "");
366
+ const hint = (site || configYmlScalar("site_url") || DEFAULT_SITE).toLowerCase();
367
+ if (hint.includes("ariacompute.cn") || hint.includes("gitee.com")) {
368
+ return "https://gitee.com/ariacompute";
369
+ }
370
+ return "https://github.com/ariacompute";
371
+ }
372
+ function releasesApiUrl(org) {
373
+ const owner = org.replace(/\/$/, "").split("/").pop() || "ariacompute";
374
+ if (org.toLowerCase().includes("gitee.com")) {
375
+ return `https://gitee.com/api/v5/repos/${owner}/engine/releases?per_page=30`;
376
+ }
377
+ return `https://api.github.com/repos/${owner}/engine/releases?per_page=30`;
378
+ }
379
+ function extractFfiArchive(archive, destDir, want = ffiLibName()) {
380
+ const tar = zlib.gunzipSync(fs.readFileSync(archive));
381
+ let offset = 0;
382
+ while (offset + 512 <= tar.length) {
383
+ const header = tar.subarray(offset, offset + 512);
384
+ if (header.every((b) => b === 0))
385
+ break;
386
+ const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
387
+ const sizeOctal = header.subarray(124, 136).toString("utf8").replace(/\0/g, "").trim();
388
+ const size = Number.parseInt(sizeOctal, 8) || 0;
389
+ const typeFlag = header[156];
390
+ offset += 512;
391
+ const isFile = typeFlag === 0 || typeFlag === 48; // '\0' or '0'
392
+ if (isFile && path.basename(name) === want) {
393
+ fs.mkdirSync(destDir, { recursive: true });
394
+ const dest = path.join(destDir, want);
395
+ fs.writeFileSync(dest, tar.subarray(offset, offset + size));
396
+ try {
397
+ fs.chmodSync(dest, 0o755);
398
+ }
399
+ catch {
400
+ /* windows */
401
+ }
402
+ return dest;
403
+ }
404
+ offset += Math.ceil(size / 512) * 512;
405
+ }
406
+ throw new Error(`${want} not found in ${archive}`);
407
+ }
408
+ function httpGetBytesSync(url) {
409
+ const script = 'fetch(process.env.ARIA_FFI_URL,{headers:{"User-Agent":"aria-engine-sdk/0.1.0"},redirect:"follow"}).then(async r=>{if(!r.ok){process.stderr.write("HTTP "+r.status);process.exit(1)}process.stdout.write(Buffer.from(await r.arrayBuffer()))}).catch(e=>{process.stderr.write(String(e&&e.message||e));process.exit(1)})';
410
+ const r = (0, node_child_process_1.spawnSync)(process.execPath, ["-e", script], {
411
+ env: { ...process.env, ARIA_FFI_URL: url },
412
+ encoding: "buffer",
413
+ maxBuffer: 256 * 1024 * 1024,
414
+ });
415
+ if (r.status !== 0) {
416
+ throw new Error(`download failed: ${(r.stderr && r.stderr.toString()) || "exit " + r.status}`);
417
+ }
418
+ return r.stdout;
419
+ }
420
+ async function httpGetBytes(url) {
421
+ const resp = await fetch(url, { headers: { "User-Agent": SDK_UA }, redirect: "follow" });
422
+ if (!resp.ok)
423
+ throw new Error(`HTTP ${resp.status} ${url}`);
424
+ return Buffer.from(await resp.arrayBuffer());
425
+ }
426
+ function installFfiFromReleases(raw, archiveBytes) {
427
+ let releases;
428
+ try {
429
+ releases = JSON.parse(raw.toString("utf8"));
430
+ }
431
+ catch {
432
+ throw new Error("invalid releases JSON");
433
+ }
434
+ if (!Array.isArray(releases))
435
+ throw new Error("unexpected releases payload");
436
+ const ver = selectLatestStable(releases);
437
+ const assetName = `libaria_ffi_${ver}_${ffiAssetOs()}.tar.gz`;
438
+ let url;
439
+ for (const rel of releases) {
440
+ const tag = String(rel.tag_name || rel.tag || "");
441
+ if (stripV(tag) !== ver)
442
+ continue;
443
+ const assets = rel.assets || [];
444
+ for (const asset of assets) {
445
+ if (asset.name === assetName) {
446
+ url = asset.browser_download_url || asset.direct_asset_url;
447
+ break;
448
+ }
449
+ }
450
+ if (url)
451
+ break;
452
+ }
453
+ if (!url)
454
+ throw new Error(`release asset not found: ${assetName}`);
455
+ const staging = path.join(ariaHome(), "tmp", `ffi-${ver}`);
456
+ fs.rmSync(staging, { recursive: true, force: true });
457
+ fs.mkdirSync(staging, { recursive: true });
458
+ const archive = path.join(staging, assetName);
459
+ try {
460
+ fs.writeFileSync(archive, archiveBytes(url));
461
+ return extractFfiArchive(archive, libDir(), ffiLibName());
462
+ }
463
+ finally {
464
+ fs.rmSync(staging, { recursive: true, force: true });
465
+ }
466
+ }
467
+ /** Return a path to libaria_ffi, downloading the latest stable Release if needed. */
468
+ async function ensureFfiLib(site) {
469
+ const env = process.env.ARIA_FFI_LIB;
470
+ if (env && fs.existsSync(env))
471
+ return env;
472
+ const bundled = bundledFfiPath();
473
+ if (bundled)
474
+ return bundled;
475
+ const cached = cachedFfiPath();
476
+ if (cached)
477
+ return cached;
478
+ const org = upgradeOrg(site);
479
+ const raw = await httpGetBytes(releasesApiUrl(org));
480
+ return installFfiFromReleases(raw, (assetUrl) => httpGetBytesSync(assetUrl));
481
+ }
482
+ function ensureFfiLibSync(site) {
483
+ const env = process.env.ARIA_FFI_LIB;
484
+ if (env && fs.existsSync(env))
485
+ return env;
486
+ const bundled = bundledFfiPath();
487
+ if (bundled)
488
+ return bundled;
489
+ const cached = cachedFfiPath();
490
+ if (cached)
491
+ return cached;
492
+ const org = upgradeOrg(site);
493
+ const raw = httpGetBytesSync(releasesApiUrl(org));
494
+ return installFfiFromReleases(raw, httpGetBytesSync);
495
+ }
496
+ function loadLib(ffiLib, site) {
497
+ const libPath = ffiLib ||
498
+ process.env.ARIA_FFI_LIB ||
499
+ bundledFfiPath() ||
500
+ cachedFfiPath() ||
501
+ ensureFfiLibSync(site);
287
502
  if (!libPath)
288
- throw new Error("aria FFI lib not found; set ARIA_FFI_LIB or pass ffiLib");
503
+ throw new Error("Cannot locate libaria_ffi");
289
504
  const koffiNS = require("koffi");
290
505
  return koffiNS.load(libPath);
291
506
  }
@@ -295,7 +510,7 @@ function isLocalRef(modelRef) {
295
510
  class Engine {
296
511
  /** Construct from a local bundle directory. */
297
512
  constructor(bundle, opts = {}) {
298
- this.lib = loadLib(opts.ffiLib);
513
+ this.lib = loadLib(opts.ffiLib, opts.site);
299
514
  this.fnInit = this.lib.func("aria_model_init", "void*", ["str"]);
300
515
  this.fnDestroy = this.lib.func("aria_model_destroy", "void", ["void*"]);
301
516
  this.fnComplete = this.lib.func("aria_complete", "int", ["void*", "str", "str", "str", "void*", "size_t"]);
@@ -311,6 +526,7 @@ class Engine {
311
526
  /** Auto-detect: a value containing a separator or already on disk is a local
312
527
  * path; otherwise it is a model name downloaded from the regional public hub. */
313
528
  static async open(modelRef, opts = {}) {
529
+ await ensureFfiLib(opts.site);
314
530
  if (isLocalRef(modelRef))
315
531
  return new Engine(modelRef, opts);
316
532
  const bundle = await downloadModel(modelRef, opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ariacompute/engine-ts",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "TypeScript/Node SDK for Aria Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",