@ariacompute/engine-rn 1.14.1 → 1.15.1

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/index.js +158 -0
package/README.md CHANGED
@@ -8,6 +8,8 @@ console.log(await eng.complete([{ role: 'user', content: 'hi' }]));
8
8
 
9
9
  npm publish name: `@ariacompute/engine-rn`. Device-farm CI: `.github/workflows/bindings-mobile.yml`.
10
10
 
11
+ `AriaEngine.open` installs `libaria_ffi` into `~/.ariacompute/lib/` when it is not already on `ARIA_FFI_LIB` or in that cache (same Releases asset as `aria-engine upgrade`).
12
+
11
13
  ## Auto-download by model name
12
14
 
13
15
  `AriaEngine.open(modelRef, { token, site })` accepts a local bundle path **or**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ariacompute/engine-rn",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "description": "React Native binding for Aria Engine",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
package/src/index.js CHANGED
@@ -261,6 +261,163 @@ async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
261
261
  }
262
262
  }
263
263
 
264
+ const SDK_UA = 'aria-engine-sdk/0.1.0';
265
+ const zlib = require('zlib');
266
+
267
+ function ffiLibName(platform = process.platform) {
268
+ if (platform === 'win32' || String(platform).toLowerCase().startsWith('win')) return 'aria_ffi.dll';
269
+ if (platform === 'darwin') return 'libaria_ffi.dylib';
270
+ return 'libaria_ffi.so';
271
+ }
272
+
273
+ function libDir() {
274
+ const path = require('path');
275
+ return path.join(ariaHome(), 'lib');
276
+ }
277
+
278
+ function cachedFfiPath(platform = process.platform) {
279
+ const path = require('path');
280
+ const fs = require('fs');
281
+ const candidate = path.join(libDir(), ffiLibName(platform));
282
+ return fs.existsSync(candidate) ? candidate : undefined;
283
+ }
284
+
285
+ function ffiAssetOs(platform = process.platform, arch = process.arch) {
286
+ const p = String(platform).toLowerCase();
287
+ const a = String(arch).toLowerCase();
288
+ if (p === 'linux' && (a === 'x64' || a === 'x86_64' || a === 'amd64')) return 'linux_x86_64';
289
+ if (p === 'linux' && (a === 'arm64' || a === 'aarch64')) return 'linux_arm64';
290
+ if (p === 'darwin' || p === 'macos') return 'macos';
291
+ if ((p === 'win32' || p.startsWith('win')) && (a === 'x64' || a === 'x86_64' || a === 'amd64')) {
292
+ return 'windows_x86_64';
293
+ }
294
+ throw new Error(`unsupported platform ${platform}/${arch} for libaria_ffi`);
295
+ }
296
+
297
+ function stripV(tag) {
298
+ const t = String(tag).trim();
299
+ return t.startsWith('v') || t.startsWith('V') ? t.slice(1) : t;
300
+ }
301
+
302
+ function selectLatestStable(releases) {
303
+ let bestTag;
304
+ let bestKey = [-1, -1, -1];
305
+ for (const rel of releases) {
306
+ if (rel.draft || rel.prerelease) continue;
307
+ const tag = String(rel.tag_name || rel.tag || '');
308
+ const core = stripV(tag).split('-')[0].split('+')[0];
309
+ const parts = core.split('.');
310
+ if (!parts.length || !/^\d+$/.test(parts[0])) continue;
311
+ const parsed = [
312
+ Number(parts[0]),
313
+ parts[1] && /^\d+$/.test(parts[1]) ? Number(parts[1]) : 0,
314
+ parts[2] && /^\d+$/.test(parts[2]) ? Number(parts[2]) : 0,
315
+ ];
316
+ if (
317
+ parsed[0] > bestKey[0] ||
318
+ (parsed[0] === bestKey[0] && parsed[1] > bestKey[1]) ||
319
+ (parsed[0] === bestKey[0] && parsed[1] === bestKey[1] && parsed[2] > bestKey[2])
320
+ ) {
321
+ bestKey = parsed;
322
+ bestTag = tag;
323
+ }
324
+ }
325
+ if (!bestTag) throw new Error('no stable release found for libaria_ffi');
326
+ return stripV(bestTag);
327
+ }
328
+
329
+ function upgradeOrg(site) {
330
+ const cfg = configYmlScalar('upgrade_url');
331
+ if (cfg) return cfg.replace(/\/$/, '');
332
+ const hint = String(site || configYmlScalar('site_url') || DEFAULT_SITE).toLowerCase();
333
+ if (hint.includes('ariacompute.cn') || hint.includes('gitee.com')) {
334
+ return 'https://gitee.com/ariacompute';
335
+ }
336
+ return 'https://github.com/ariacompute';
337
+ }
338
+
339
+ function releasesApiUrl(org) {
340
+ const owner = org.replace(/\/$/, '').split('/').pop() || 'ariacompute';
341
+ if (org.toLowerCase().includes('gitee.com')) {
342
+ return `https://gitee.com/api/v5/repos/${owner}/engine/releases?per_page=30`;
343
+ }
344
+ return `https://api.github.com/repos/${owner}/engine/releases?per_page=30`;
345
+ }
346
+
347
+ function extractFfiArchive(archive, destDir, want = ffiLibName()) {
348
+ const fs = require('fs');
349
+ const path = require('path');
350
+ const tar = zlib.gunzipSync(fs.readFileSync(archive));
351
+ let offset = 0;
352
+ while (offset + 512 <= tar.length) {
353
+ const header = tar.subarray(offset, offset + 512);
354
+ if (header.every((b) => b === 0)) break;
355
+ const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
356
+ const sizeOctal = header.subarray(124, 136).toString('utf8').replace(/\0/g, '').trim();
357
+ const size = Number.parseInt(sizeOctal, 8) || 0;
358
+ const typeFlag = header[156];
359
+ offset += 512;
360
+ const isFile = typeFlag === 0 || typeFlag === 48;
361
+ if (isFile && path.basename(name) === want) {
362
+ fs.mkdirSync(destDir, { recursive: true });
363
+ const dest = path.join(destDir, want);
364
+ fs.writeFileSync(dest, tar.subarray(offset, offset + size));
365
+ try {
366
+ fs.chmodSync(dest, 0o755);
367
+ } catch {
368
+ /* windows */
369
+ }
370
+ return dest;
371
+ }
372
+ offset += Math.ceil(size / 512) * 512;
373
+ }
374
+ throw new Error(`${want} not found in ${archive}`);
375
+ }
376
+
377
+ async function httpGetBytes(url) {
378
+ const resp = await fetch(url, { headers: { 'User-Agent': SDK_UA }, redirect: 'follow' });
379
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} ${url}`);
380
+ return Buffer.from(await resp.arrayBuffer());
381
+ }
382
+
383
+ async function ensureFfiLib(site) {
384
+ const fs = require('fs');
385
+ const path = require('path');
386
+ const env = process.env.ARIA_FFI_LIB;
387
+ if (env && fs.existsSync(env)) return env;
388
+ const cached = cachedFfiPath();
389
+ if (cached) return cached;
390
+ const org = upgradeOrg(site);
391
+ const raw = await httpGetBytes(releasesApiUrl(org));
392
+ const releases = JSON.parse(raw.toString('utf8'));
393
+ if (!Array.isArray(releases)) throw new Error('unexpected releases payload');
394
+ const ver = selectLatestStable(releases);
395
+ const assetName = `libaria_ffi_${ver}_${ffiAssetOs()}.tar.gz`;
396
+ let url;
397
+ for (const rel of releases) {
398
+ const tag = String(rel.tag_name || rel.tag || '');
399
+ if (stripV(tag) !== ver) continue;
400
+ for (const asset of rel.assets || []) {
401
+ if (asset.name === assetName) {
402
+ url = asset.browser_download_url || asset.direct_asset_url;
403
+ break;
404
+ }
405
+ }
406
+ if (url) break;
407
+ }
408
+ if (!url) throw new Error(`release asset not found: ${assetName}`);
409
+ const staging = path.join(ariaHome(), 'tmp', `ffi-${ver}`);
410
+ fs.rmSync(staging, { recursive: true, force: true });
411
+ fs.mkdirSync(staging, { recursive: true });
412
+ const archive = path.join(staging, assetName);
413
+ try {
414
+ fs.writeFileSync(archive, await httpGetBytes(url));
415
+ return extractFfiArchive(archive, libDir(), ffiLibName());
416
+ } finally {
417
+ fs.rmSync(staging, { recursive: true, force: true });
418
+ }
419
+ }
420
+
264
421
  export class AriaEngine {
265
422
  constructor(bundlePath) {
266
423
  this.bundlePath = bundlePath;
@@ -268,6 +425,7 @@ export class AriaEngine {
268
425
  }
269
426
 
270
427
  static async open(modelRef, opts = {}) {
428
+ await ensureFfiLib(opts.site);
271
429
  if (isLocalRef(modelRef)) return new AriaEngine(modelRef);
272
430
  const bundle = await downloadModel(modelRef, opts);
273
431
  return new AriaEngine(bundle);