@ariacompute/engine-rn 1.22.0 → 2.0.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,32 +1,8 @@
1
1
  # @ariacompute/engine-rn
2
2
 
3
3
  ```js
4
- import { AriaEngine } from '@ariacompute/engine-rn';
5
- const eng = new AriaEngine('/path/to/bundle');
6
- console.log(await eng.complete([{ role: 'user', content: 'hi' }]));
4
+ const { AriaEngine } = require('@ariacompute/engine-rn');
5
+ const eng = new AriaEngine(checkpoint, 'decoder');
7
6
  ```
8
7
 
9
- npm publish name: `@ariacompute/engine-rn`. Device-farm CI: `.github/workflows/bindings-mobile.yml`.
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
-
13
- ## Auto-download by model name
14
-
15
- `AriaEngine.open(modelRef, { token, site })` accepts a local bundle path **or**
16
- an Aria model name (e.g. `gemma-4-e2b-it_q4`). A value containing `/` or already
17
- on disk is loaded directly; otherwise the SDK downloads it from the regional
18
- public hub (same as `aria-engine download`: `.com` → Hugging Face, `.cn` →
19
- ModelScope; `site` defaults to `https://ariacompute.com`) into
20
- `~/.ariacompute/models/{model}` and then loads it. Dashboard is not used. A
21
- Dashboard `sk-` / `sk-bf-` token is ignored for hub auth. Token is optional for
22
- public models. Gated files: pass `hfToken` / `modelscopeApiToken` (same as
23
- `aria-engine setup`); if omitted, reads `~/.ariacompute/engine.yml`. Instance
24
- `setup` is in-memory only (does not write that file). A valid cached bundle is
25
- reused without re-downloading.
26
-
27
- ```js
28
- const eng = await AriaEngine.open("gemma-4-e2b-it_q4");
29
- const gated = new AriaEngine();
30
- gated.setup({ hf_token: "hf_..." });
31
- await gated.open("gemma-4-e2b-it_q4");
32
- ```
8
+ Installs `libaria-engine_ffi` into `~/.ariacompute/lib/` (same Releases asset as `aria-engine upgrade`).
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@ariacompute/engine-rn",
3
- "version": "1.22.0",
4
- "description": "React Native binding for Aria Engine",
3
+ "version": "2.0.0",
4
+ "description": "React Native binding for AFM-D System One",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
7
- "peerDependencies": {
8
- "react-native": ">=0.72.0"
9
- },
10
- "scripts": {
11
- "test": "node --test test/setup.test.cjs"
12
- }
7
+ "files": [
8
+ "src",
9
+ "README.md"
10
+ ]
13
11
  }
package/src/index.js CHANGED
@@ -1,487 +1,24 @@
1
+ 'use strict';
2
+
1
3
  /**
2
- * React Native surface — native module bridges to libaria-engine_ffi (Turbo Module / JSI).
3
- * JS API mirrors @ariacompute/engine-ts for docs samples.
4
- *
5
- * Auto-download: `AriaEngine.open(modelRef, { token, site })` resolves a model
6
- * name via the regional public hub (Hugging Face / ModelScope — same as
7
- * `aria-engine download`; Dashboard is not used), writes files to
8
- * `~/.ariacompute/models/{model}`, then loads via native init.
4
+ * React Native AFM-D engine binding.
5
+ * Native module should link libaria-engine_ffi and expose systemone(JSON).
6
+ * This JS layer mirrors the TypeScript API shape for host tests.
9
7
  */
10
- import { NativeModules } from 'react-native';
11
- import {
12
- defaultSetupConfig,
13
- applySetup,
14
- } from './setup.js';
15
-
16
- const AriaEngineModule = NativeModules.AriaEngine;
17
-
18
- const DEFAULT_SITE = 'https://ariacompute.com';
19
- const DEFAULT_SDK = 'v1.0';
20
- const HUB_REQUIRED = ['config.json', 'weight.bin'];
21
- const HUB_OPTIONAL = [
22
- 'tokenizer.json',
23
- 'tokenizer.model',
24
- 'tokenizer_config.json',
25
- 'special_tokens_map.json',
26
- 'vocab.json',
27
- 'merges.txt',
28
- ];
29
8
 
30
- function parseBundleName(model) {
31
- if (!model || model.includes('/') || model.includes('\\')) {
32
- throw new Error(`invalid model name: ${model}`);
9
+ class AriaEngine {
10
+ constructor(checkpoint, track = 'encoder') {
11
+ this.checkpoint = checkpoint;
12
+ this.track = track;
33
13
  }
34
- const idx = model.lastIndexOf('_q');
35
- if (idx !== -1) {
36
- const slug = model.slice(0, idx);
37
- let suffix = model.slice(idx + 2);
38
- if (suffix.endsWith('_channel') || suffix.endsWith('_group')) {
39
- suffix = suffix.slice(0, suffix.lastIndexOf('_'));
40
- }
41
- const quant =
42
- suffix === '4'
43
- ? 'int4'
44
- : suffix === '8'
45
- ? 'int8'
46
- : suffix === '326' || suffix === '3.26'
47
- ? 'int326'
48
- : (() => {
49
- throw new Error(`unknown quant suffix _q${suffix}`);
50
- })();
51
- if (!slug) throw new Error(`invalid model name: ${model}`);
52
- return { slug, quant };
53
- }
54
- return { slug: model, quant: 'int4' };
55
- }
56
-
57
- function ariaHome() {
58
- if (process.env.ARIA_COMPUTE_HOME) return process.env.ARIA_COMPUTE_HOME;
59
- return `${require('os').homedir()}/.ariacompute`;
60
- }
61
-
62
- function cacheDir(model) {
63
- return `${ariaHome()}/models/${model}`;
64
- }
65
14
 
66
- function isLocalRef(ref) {
67
- return ref.includes('/') || ref.includes('\\') || require('fs').existsSync(ref);
68
- }
69
-
70
- function preferredPublicHub(site) {
71
- if (site && String(site).toLowerCase().includes('ariacompute.cn')) return 'modelscope';
72
- return 'huggingface';
73
- }
74
-
75
- function hubBearer(token) {
76
- const t = (token || '').trim();
77
- if (!t) return undefined;
78
- const low = t.toLowerCase();
79
- if (low.startsWith('sk-')) return undefined;
80
- return t;
81
- }
82
-
83
- function unquoteYaml(v) {
84
- const t = (v || '').trim();
85
- if (t.length >= 2 && ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")))) {
86
- return t.slice(1, -1);
15
+ async systemone(request) {
16
+ throw new Error(
17
+ 'React Native native module not linked; embed libaria-engine_ffi and bridge aria_systemone'
18
+ );
87
19
  }
88
- return t;
89
- }
90
20
 
91
- function configYmlScalar(key) {
92
- const fs = require('fs');
93
- const path = require('path');
94
- const home = ariaHome();
95
- for (const name of ['engine.yml', 'config.yml']) {
96
- try {
97
- const raw = fs.readFileSync(path.join(home, name), 'utf8');
98
- for (const line of raw.split('\n')) {
99
- if (line.startsWith(' ') || line.startsWith('\t')) continue;
100
- const s = line.trim();
101
- if (!s || s.startsWith('#') || !s.includes(':')) continue;
102
- const idx = s.indexOf(':');
103
- if (s.slice(0, idx).trim() !== key) continue;
104
- const v = unquoteYaml(s.slice(idx + 1));
105
- if (v) return v;
106
- }
107
- } catch {
108
- continue;
109
- }
110
- }
111
- return undefined;
21
+ destroy() {}
112
22
  }
113
23
 
114
- function resolveHubToken(source, opts = {}) {
115
- const named = source === 'modelscope' ? opts.modelscopeApiToken : opts.hfToken;
116
- const field = source === 'modelscope' ? 'modelscope_api_token' : 'hf_token';
117
- for (const cand of [named, opts.token, configYmlScalar(field)]) {
118
- const b = hubBearer(cand);
119
- if (b) return b;
120
- }
121
- return undefined;
122
- }
123
-
124
- function hubPathNames(model) {
125
- const names = [model];
126
- let lower = model.toLowerCase();
127
- let core = model;
128
- for (const suf of ['_channel', '_group']) {
129
- if (lower.endsWith(suf)) {
130
- core = model.slice(0, -suf.length);
131
- lower = core.toLowerCase();
132
- break;
133
- }
134
- }
135
- const stems = [core];
136
- if (lower.endsWith('_q326')) stems.push(`${core.slice(0, -5)}q3.26`);
137
- else if (lower.endsWith('_q3.26')) stems.push(`${core.slice(0, -6)}q326`);
138
- for (const stem of stems) {
139
- for (const share of ['', '_channel', '_group']) {
140
- const cand = `${stem}${share}`;
141
- if (!names.includes(cand)) names.push(cand);
142
- }
143
- }
144
- return names;
145
- }
146
-
147
- function hubFileUrls(source, model, file) {
148
- const urls = [];
149
- for (const name of hubPathNames(model)) {
150
- if (source === 'modelscope') {
151
- for (const repo of [`AriaCompute/${name}`, 'AriaCompute/model']) {
152
- urls.push(`https://www.modelscope.cn/models/${repo}/resolve/master/${DEFAULT_SDK}/${name}/${file}`);
153
- urls.push(`https://modelscope.cn/models/${repo}/resolve/master/${DEFAULT_SDK}/${name}/${file}`);
154
- }
155
- } else {
156
- for (const repo of [`ariacompute/${name}`, 'ariacompute/model']) {
157
- urls.push(`https://huggingface.co/${repo}/resolve/main/${DEFAULT_SDK}/${name}/${file}`);
158
- }
159
- }
160
- }
161
- return urls;
162
- }
163
-
164
- function isValidBundleSync(dir) {
165
- const fs = require('fs');
166
- const path = require('path');
167
- try {
168
- if (!fs.statSync(path.join(dir, 'weight.bin')).isFile()) return false;
169
- const meta = JSON.parse(fs.readFileSync(path.join(dir, 'config.json'), 'utf8'));
170
- return meta.format === 'aria-quant-bundle';
171
- } catch {
172
- return false;
173
- }
174
- }
175
-
176
- async function isValidBundle(dir) {
177
- if (AriaEngineModule?.isValidBundle) {
178
- try {
179
- return await AriaEngineModule.isValidBundle(dir);
180
- } catch {
181
- /* fall through */
182
- }
183
- }
184
- return isValidBundleSync(dir);
185
- }
186
-
187
- async function fetchUrlToFile(url, dest, token) {
188
- const headers = {};
189
- if (token) headers.Authorization = `Bearer ${token}`;
190
- const resp = await fetch(url, { headers });
191
- if (resp.status === 401 || resp.status === 403) {
192
- const err = new Error(`HTTP ${resp.status}`);
193
- err.status = resp.status;
194
- throw err;
195
- }
196
- if (!resp.ok) {
197
- const err = new Error(`HTTP ${resp.status}`);
198
- err.status = resp.status;
199
- throw err;
200
- }
201
- const fs = require('fs');
202
- const path = require('path');
203
- fs.mkdirSync(path.dirname(dest), { recursive: true });
204
- const buf = Buffer.from(await resp.arrayBuffer());
205
- fs.writeFileSync(dest, buf);
206
- }
207
-
208
- async function fetchHubFile(source, model, file, dest, token, required) {
209
- let last;
210
- for (const url of hubFileUrls(source, model, file)) {
211
- try {
212
- await fetchUrlToFile(url, dest, token);
213
- return true;
214
- } catch (e) {
215
- last = e;
216
- if (e && (e.status === 401 || e.status === 403)) {
217
- const field = source === 'modelscope' ? 'modelscope_api_token' : 'hf_token';
218
- throw new Error(
219
- `auth failed HTTP ${e.status}; set ${field} via aria-engine setup (do not pass a Dashboard sk-/sk-bf- key as the hub token)`,
220
- );
221
- }
222
- }
223
- }
224
- if (required) throw new Error(`${source}: missing ${file}${last ? `: ${last}` : ''}`);
225
- return false;
226
- }
227
-
228
- async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
229
- const opts =
230
- tokenOrOpts && typeof tokenOrOpts === 'object'
231
- ? tokenOrOpts
232
- : { token: tokenOrOpts, site };
233
- parseBundleName(model);
234
- const fs = require('fs');
235
- const path = require('path');
236
- const resolvedSite = opts.site ?? site ?? DEFAULT_SITE;
237
- const source = preferredPublicHub(resolvedSite);
238
- const hubToken = resolveHubToken(source, opts);
239
- const cache = cacheDir(model);
240
- if (fs.existsSync(cache) && (await isValidBundle(cache))) {
241
- return cache;
242
- }
243
- const staging = path.join(ariaHome(), 'models', `.${model}.partial`);
244
- try {
245
- if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
246
- fs.mkdirSync(staging, { recursive: true });
247
- for (const file of HUB_REQUIRED) {
248
- await fetchHubFile(source, model, file, path.join(staging, file), hubToken, true);
249
- }
250
- for (const extra of HUB_OPTIONAL) {
251
- try {
252
- await fetchHubFile(source, model, extra, path.join(staging, extra), hubToken, false);
253
- } catch {
254
- /* optional tokenizer sidecar */
255
- }
256
- }
257
- if (!(await isValidBundle(staging))) {
258
- throw new Error(
259
- `${source} fetch completed but bundle invalid (need weight.bin + aria-quant-bundle config.json)`,
260
- );
261
- }
262
- if (fs.existsSync(cache)) fs.rmSync(cache, { recursive: true, force: true });
263
- fs.renameSync(staging, cache);
264
- return cache;
265
- } catch (e) {
266
- if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
267
- throw e;
268
- }
269
- }
270
-
271
- const SDK_UA = 'aria-engine-sdk/0.1.0';
272
- const zlib = require('zlib');
273
-
274
- function ffiLibName(platform = process.platform) {
275
- if (platform === 'win32' || String(platform).toLowerCase().startsWith('win')) return 'aria-engine_ffi.dll';
276
- if (platform === 'darwin') return 'libaria-engine_ffi.dylib';
277
- return 'libaria-engine_ffi.so';
278
- }
279
-
280
- function libDir() {
281
- const path = require('path');
282
- return path.join(ariaHome(), 'lib');
283
- }
284
-
285
- function cachedFfiPath(platform = process.platform) {
286
- const path = require('path');
287
- const fs = require('fs');
288
- const candidate = path.join(libDir(), ffiLibName(platform));
289
- return fs.existsSync(candidate) ? candidate : undefined;
290
- }
291
-
292
- function ffiAssetOs(platform = process.platform, arch = process.arch) {
293
- const p = String(platform).toLowerCase();
294
- const a = String(arch).toLowerCase();
295
- if (p === 'linux' && (a === 'x64' || a === 'x86_64' || a === 'amd64')) return 'linux_x86_64';
296
- if (p === 'linux' && (a === 'arm64' || a === 'aarch64')) return 'linux_arm64';
297
- if (p === 'darwin' || p === 'macos') return 'macos';
298
- if ((p === 'win32' || p.startsWith('win')) && (a === 'x64' || a === 'x86_64' || a === 'amd64')) {
299
- return 'windows_x86_64';
300
- }
301
- throw new Error(`unsupported platform ${platform}/${arch} for libaria-engine_ffi`);
302
- }
303
-
304
- function stripV(tag) {
305
- const t = String(tag).trim();
306
- return t.startsWith('v') || t.startsWith('V') ? t.slice(1) : t;
307
- }
308
-
309
- function selectLatestStable(releases) {
310
- let bestTag;
311
- let bestKey = [-1, -1, -1];
312
- for (const rel of releases) {
313
- if (rel.draft || rel.prerelease) continue;
314
- const tag = String(rel.tag_name || rel.tag || '');
315
- const core = stripV(tag).split('-')[0].split('+')[0];
316
- const parts = core.split('.');
317
- if (!parts.length || !/^\d+$/.test(parts[0])) continue;
318
- const parsed = [
319
- Number(parts[0]),
320
- parts[1] && /^\d+$/.test(parts[1]) ? Number(parts[1]) : 0,
321
- parts[2] && /^\d+$/.test(parts[2]) ? Number(parts[2]) : 0,
322
- ];
323
- if (
324
- parsed[0] > bestKey[0] ||
325
- (parsed[0] === bestKey[0] && parsed[1] > bestKey[1]) ||
326
- (parsed[0] === bestKey[0] && parsed[1] === bestKey[1] && parsed[2] > bestKey[2])
327
- ) {
328
- bestKey = parsed;
329
- bestTag = tag;
330
- }
331
- }
332
- if (!bestTag) throw new Error('no stable release found for libaria-engine_ffi');
333
- return stripV(bestTag);
334
- }
335
-
336
- function upgradeOrg(site) {
337
- const cfg = configYmlScalar('upgrade_url');
338
- if (cfg) return cfg.replace(/\/$/, '');
339
- const hint = String(site || configYmlScalar('site_url') || DEFAULT_SITE).toLowerCase();
340
- if (hint.includes('ariacompute.cn') || hint.includes('gitee.com')) {
341
- return 'https://gitee.com/ariacompute';
342
- }
343
- return 'https://github.com/ariacompute';
344
- }
345
-
346
- function releasesApiUrl(org) {
347
- const owner = org.replace(/\/$/, '').split('/').pop() || 'ariacompute';
348
- if (org.toLowerCase().includes('gitee.com')) {
349
- return `https://gitee.com/api/v5/repos/${owner}/engine/releases?per_page=30`;
350
- }
351
- return `https://api.github.com/repos/${owner}/engine/releases?per_page=30`;
352
- }
353
-
354
- function extractFfiArchive(archive, destDir, want = ffiLibName()) {
355
- const fs = require('fs');
356
- const path = require('path');
357
- const tar = zlib.gunzipSync(fs.readFileSync(archive));
358
- let offset = 0;
359
- while (offset + 512 <= tar.length) {
360
- const header = tar.subarray(offset, offset + 512);
361
- if (header.every((b) => b === 0)) break;
362
- const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
363
- const sizeOctal = header.subarray(124, 136).toString('utf8').replace(/\0/g, '').trim();
364
- const size = Number.parseInt(sizeOctal, 8) || 0;
365
- const typeFlag = header[156];
366
- offset += 512;
367
- const isFile = typeFlag === 0 || typeFlag === 48;
368
- if (isFile && path.basename(name) === want) {
369
- fs.mkdirSync(destDir, { recursive: true });
370
- const dest = path.join(destDir, want);
371
- fs.writeFileSync(dest, tar.subarray(offset, offset + size));
372
- try {
373
- fs.chmodSync(dest, 0o755);
374
- } catch {
375
- /* windows */
376
- }
377
- return dest;
378
- }
379
- offset += Math.ceil(size / 512) * 512;
380
- }
381
- throw new Error(`${want} not found in ${archive}`);
382
- }
383
-
384
- async function httpGetBytes(url) {
385
- const resp = await fetch(url, { headers: { 'User-Agent': SDK_UA }, redirect: 'follow' });
386
- if (!resp.ok) throw new Error(`HTTP ${resp.status} ${url}`);
387
- return Buffer.from(await resp.arrayBuffer());
388
- }
389
-
390
- async function ensureFfiLib(site) {
391
- const fs = require('fs');
392
- const path = require('path');
393
- const env = process.env.ARIA_FFI_LIB;
394
- if (env && fs.existsSync(env)) return env;
395
- const cached = cachedFfiPath();
396
- if (cached) return cached;
397
- const org = upgradeOrg(site);
398
- const raw = await httpGetBytes(releasesApiUrl(org));
399
- const releases = JSON.parse(raw.toString('utf8'));
400
- if (!Array.isArray(releases)) throw new Error('unexpected releases payload');
401
- const ver = selectLatestStable(releases);
402
- const assetName = `libaria-engine_ffi_${ver}_${ffiAssetOs()}.tar.gz`;
403
- let url;
404
- for (const rel of releases) {
405
- const tag = String(rel.tag_name || rel.tag || '');
406
- if (stripV(tag) !== ver) continue;
407
- for (const asset of rel.assets || []) {
408
- if (asset.name === assetName) {
409
- url = asset.browser_download_url || asset.direct_asset_url;
410
- break;
411
- }
412
- }
413
- if (url) break;
414
- }
415
- if (!url) throw new Error(`release asset not found: ${assetName}`);
416
- const staging = path.join(ariaHome(), 'tmp', `ffi-${ver}`);
417
- fs.rmSync(staging, { recursive: true, force: true });
418
- fs.mkdirSync(staging, { recursive: true });
419
- const archive = path.join(staging, assetName);
420
- try {
421
- fs.writeFileSync(archive, await httpGetBytes(url));
422
- return extractFfiArchive(archive, libDir(), ffiLibName());
423
- } finally {
424
- fs.rmSync(staging, { recursive: true, force: true });
425
- }
426
- }
427
-
428
- export class AriaEngine {
429
- constructor(bundlePath) {
430
- this.bundlePath = bundlePath;
431
- this.cfg = defaultSetupConfig();
432
- this.opts = {};
433
- // NativeModules.AriaEngine.init(bundlePath) when linked.
434
- }
435
-
436
- setup(updates) {
437
- this.cfg = applySetup(this.cfg, updates);
438
- return this;
439
- }
440
-
441
- setupStatus() {
442
- return { ...this.cfg };
443
- }
444
-
445
- setupClear() {
446
- this.cfg = defaultSetupConfig();
447
- return this;
448
- }
449
-
450
- async open(modelRef) {
451
- const opts = {
452
- ...this.opts,
453
- site: this.cfg.site_url || this.opts.site,
454
- hfToken: this.cfg.hf_token || this.opts.hfToken,
455
- modelscopeApiToken: this.cfg.modelscope_api_token || this.opts.modelscopeApiToken,
456
- };
457
- await ensureFfiLib(opts.site);
458
- this.bundlePath = isLocalRef(modelRef) ? modelRef : await downloadModel(modelRef, opts);
459
- return this;
460
- }
461
-
462
- static async open(modelRef, opts = {}) {
463
- const eng = new AriaEngine();
464
- eng.opts = opts;
465
- if (opts.site || opts.hfToken || opts.modelscopeApiToken) {
466
- eng.setup({
467
- site_url: opts.site,
468
- hf_token: opts.hfToken,
469
- modelscope_api_token: opts.modelscopeApiToken,
470
- });
471
- }
472
- await eng.open(modelRef);
473
- return eng;
474
- }
475
-
476
- async complete(messages, options = { max_tokens: 16 }, tools = []) {
477
- // return NativeModules.AriaEngine.complete(...)
478
- return {
479
- success: true,
480
- response: '',
481
- function_calls: [],
482
- note: 'Link native module to libaria-engine_ffi',
483
- };
484
- }
485
-
486
- async close() {}
487
- }
24
+ module.exports = { AriaEngine };
package/src/setup.js DELETED
@@ -1,69 +0,0 @@
1
- /** Instance-level Engine setup (in-memory; does not write engine.yml). */
2
-
3
- const INTL_SITE = 'https://ariacompute.com';
4
- const INTL_UPGRADE = 'https://github.com/ariacompute';
5
- const CN_SITE = 'https://ariacompute.cn';
6
- const CN_UPGRADE = 'https://gitee.com/ariacompute';
7
-
8
- function defaultSetupConfig() {
9
- return {
10
- router: '',
11
- router_api_key: '',
12
- site_url: '',
13
- upgrade_url: '',
14
- compute: 'auto',
15
- hf_token: '',
16
- modelscope_api_token: '',
17
- };
18
- }
19
-
20
- function gatewayRegion(url) {
21
- const lower = (url || '').toLowerCase();
22
- if (lower.includes('ariacompute.cn') || lower.includes('gitee.com/ariacompute')) return 'cn';
23
- if (lower.includes('ariacompute.com') || lower.includes('github.com/ariacompute')) return 'intl';
24
- return undefined;
25
- }
26
-
27
- function pairUrls(region) {
28
- return region === 'cn' ? [CN_SITE, CN_UPGRADE] : [INTL_SITE, INTL_UPGRADE];
29
- }
30
-
31
- function fillSetupUrls(cfg) {
32
- const out = { ...cfg };
33
- const region = gatewayRegion(out.site_url) || gatewayRegion(out.upgrade_url);
34
- if (!region) return out;
35
- const [site, upgrade] = pairUrls(region);
36
- if (!out.site_url) out.site_url = site;
37
- if (!out.upgrade_url) out.upgrade_url = upgrade;
38
- return out;
39
- }
40
-
41
- function validateRouterApiKey(key) {
42
- const t = (key || '').trim();
43
- if (!t) return;
44
- if (t.startsWith('sk-aria_') || t.startsWith('sk-bf-')) return;
45
- throw new Error('router_api_key must start with sk-aria_ or sk-bf-');
46
- }
47
-
48
- function applySetup(existing, updates) {
49
- const out = { ...existing };
50
- for (const [k, v] of Object.entries(updates || {})) {
51
- if (v === undefined) continue;
52
- out[k] = v;
53
- }
54
- if (!['auto', 'cpu', 'cuda'].includes(out.compute)) {
55
- throw new Error(`invalid compute: ${out.compute}`);
56
- }
57
- validateRouterApiKey(out.router_api_key);
58
- return fillSetupUrls(out);
59
- }
60
-
61
- module.exports = {
62
- INTL_SITE,
63
- INTL_UPGRADE,
64
- CN_SITE,
65
- CN_UPGRADE,
66
- defaultSetupConfig,
67
- fillSetupUrls,
68
- applySetup,
69
- };
@@ -1,77 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert/strict');
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
- const {
7
- applySetup,
8
- defaultSetupConfig,
9
- fillSetupUrls,
10
- CN_SITE,
11
- CN_UPGRADE,
12
- } = require('../src/setup.js');
13
-
14
- test('setup defaults', () => {
15
- const cfg = defaultSetupConfig();
16
- assert.equal(cfg.compute, 'auto');
17
- assert.equal(cfg.router, '');
18
- });
19
-
20
- test('setup invalid enum', () => {
21
- assert.throws(() => applySetup(defaultSetupConfig(), { compute: 'gpu' }));
22
- });
23
-
24
- test('setup fills urls from cn site', () => {
25
- const got = fillSetupUrls({ site_url: CN_SITE, upgrade_url: '' });
26
- assert.equal(got.upgrade_url, CN_UPGRADE);
27
- });
28
-
29
- test('setup instance all fields via applySetup', () => {
30
- const st = applySetup(defaultSetupConfig(), {
31
- router: 'http://127.0.0.1:8080',
32
- site_url: CN_SITE,
33
- upgrade_url: CN_UPGRADE,
34
- compute: 'cpu',
35
- hf_token: 'hf_abc',
36
- modelscope_api_token: 'ms_xyz',
37
- });
38
- assert.equal(st.router, 'http://127.0.0.1:8080');
39
- assert.equal(st.hf_token, 'hf_abc');
40
- });
41
-
42
- test('setup partial merge', () => {
43
- const once = applySetup(defaultSetupConfig(), { hf_token: 'hf_one', router: 'http://127.0.0.1:1' });
44
- const st = applySetup(once, { compute: 'cuda' });
45
- assert.equal(st.hf_token, 'hf_one');
46
- assert.equal(st.router, 'http://127.0.0.1:1');
47
- assert.equal(st.compute, 'cuda');
48
- });
49
-
50
- test('setup invalid enum leaves state', () => {
51
- const once = applySetup(defaultSetupConfig(), { compute: 'cpu' });
52
- assert.throws(() => applySetup(once, { compute: 'gpu' }));
53
- assert.equal(once.compute, 'cpu');
54
- });
55
-
56
- test('setup fills urls from site tld', () => {
57
- const st = applySetup(defaultSetupConfig(), { site_url: 'https://ariacompute.cn' });
58
- assert.equal(st.upgrade_url, CN_UPGRADE);
59
- });
60
-
61
- test('setup does not write engine.yml', () => {
62
- const prev = process.env.ARIA_COMPUTE_HOME;
63
- const home = fs.mkdtempSync(path.join(os.tmpdir(), 'aria-rn-setup-'));
64
- process.env.ARIA_COMPUTE_HOME = home;
65
- try {
66
- applySetup(defaultSetupConfig(), {
67
- router: 'http://127.0.0.1:8080',
68
- site_url: 'https://ariacompute.com',
69
- hf_token: 'hf_x',
70
- });
71
- assert.equal(fs.existsSync(path.join(home, 'engine.yml')), false);
72
- assert.equal(fs.existsSync(path.join(home, 'config.yml')), false);
73
- } finally {
74
- if (prev === undefined) delete process.env.ARIA_COMPUTE_HOME;
75
- else process.env.ARIA_COMPUTE_HOME = prev;
76
- }
77
- });