@ariacompute/engine-rn 1.12.0 → 1.14.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 +10 -6
- package/package.json +1 -1
- package/src/index.js +211 -24
package/README.md
CHANGED
|
@@ -12,12 +12,16 @@ npm publish name: `@ariacompute/engine-rn`. Device-farm CI: `.github/workflows/b
|
|
|
12
12
|
|
|
13
13
|
`AriaEngine.open(modelRef, { token, site })` accepts a local bundle path **or**
|
|
14
14
|
an Aria model name (e.g. `gemma-4-e2b-it_q4`). A value containing `/` or already
|
|
15
|
-
on disk is loaded directly; otherwise the SDK downloads it from the
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
on disk is loaded directly; otherwise the SDK downloads it from the regional
|
|
16
|
+
public hub (same as `aria-engine download`: `.com` → Hugging Face, `.cn` →
|
|
17
|
+
ModelScope; `site` defaults to `https://ariacompute.com`) into
|
|
18
|
+
`~/.ariacompute/models/{model}` and then loads it. Dashboard is not used. A
|
|
19
|
+
Dashboard `sk-` / `bfvk-` token is ignored for hub auth. Token is optional for
|
|
20
|
+
public models. Gated files: pass `hfToken` / `modelscopeApiToken` (same as
|
|
21
|
+
`aria-engine auth`); if omitted, reads `~/.ariacompute/config.yml`. A valid
|
|
22
|
+
cached bundle is reused without re-downloading.
|
|
20
23
|
|
|
21
24
|
```js
|
|
22
|
-
const eng = await AriaEngine.open("gemma-4-e2b-it_q4"
|
|
25
|
+
const eng = await AriaEngine.open("gemma-4-e2b-it_q4");
|
|
26
|
+
const gated = await AriaEngine.open("gemma-4-e2b-it_q4", { hfToken: "hf_..." });
|
|
23
27
|
```
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -3,15 +3,25 @@
|
|
|
3
3
|
* JS API mirrors @ariacompute/engine-ts for docs samples.
|
|
4
4
|
*
|
|
5
5
|
* Auto-download: `AriaEngine.open(modelRef, { token, site })` resolves a model
|
|
6
|
-
* name via the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
9
9
|
*/
|
|
10
|
-
import { NativeModules
|
|
10
|
+
import { NativeModules } from 'react-native';
|
|
11
11
|
|
|
12
12
|
const AriaEngineModule = NativeModules.AriaEngine;
|
|
13
13
|
|
|
14
14
|
const DEFAULT_SITE = 'https://ariacompute.com';
|
|
15
|
+
const DEFAULT_SDK = 'v1.0';
|
|
16
|
+
const HUB_REQUIRED = ['config.json', 'weight.bin'];
|
|
17
|
+
const HUB_OPTIONAL = [
|
|
18
|
+
'tokenizer.json',
|
|
19
|
+
'tokenizer.model',
|
|
20
|
+
'tokenizer_config.json',
|
|
21
|
+
'special_tokens_map.json',
|
|
22
|
+
'vocab.json',
|
|
23
|
+
'merges.txt',
|
|
24
|
+
];
|
|
15
25
|
|
|
16
26
|
function parseBundleName(model) {
|
|
17
27
|
if (!model || model.includes('/') || model.includes('\\')) {
|
|
@@ -20,7 +30,10 @@ function parseBundleName(model) {
|
|
|
20
30
|
const idx = model.lastIndexOf('_q');
|
|
21
31
|
if (idx !== -1) {
|
|
22
32
|
const slug = model.slice(0, idx);
|
|
23
|
-
|
|
33
|
+
let suffix = model.slice(idx + 2);
|
|
34
|
+
if (suffix.endsWith('_channel') || suffix.endsWith('_group')) {
|
|
35
|
+
suffix = suffix.slice(0, suffix.lastIndexOf('_'));
|
|
36
|
+
}
|
|
24
37
|
const quant =
|
|
25
38
|
suffix === '4'
|
|
26
39
|
? 'int4'
|
|
@@ -50,25 +63,202 @@ function isLocalRef(ref) {
|
|
|
50
63
|
return ref.includes('/') || ref.includes('\\') || require('fs').existsSync(ref);
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
66
|
+
function preferredPublicHub(site) {
|
|
67
|
+
if (site && String(site).toLowerCase().includes('ariacompute.cn')) return 'modelscope';
|
|
68
|
+
return 'huggingface';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hubBearer(token) {
|
|
72
|
+
const t = (token || '').trim();
|
|
73
|
+
if (!t) return undefined;
|
|
74
|
+
const low = t.toLowerCase();
|
|
75
|
+
if (low.startsWith('sk-') || low.startsWith('bfvk-')) return undefined;
|
|
76
|
+
return t;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function unquoteYaml(v) {
|
|
80
|
+
const t = (v || '').trim();
|
|
81
|
+
if (t.length >= 2 && ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")))) {
|
|
82
|
+
return t.slice(1, -1);
|
|
83
|
+
}
|
|
84
|
+
return t;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function configYmlScalar(key) {
|
|
88
|
+
const fs = require('fs');
|
|
89
|
+
const path = require('path');
|
|
90
|
+
try {
|
|
91
|
+
const raw = fs.readFileSync(path.join(ariaHome(), 'config.yml'), 'utf8');
|
|
92
|
+
for (const line of raw.split('\n')) {
|
|
93
|
+
if (line.startsWith(' ') || line.startsWith('\t')) continue;
|
|
94
|
+
const s = line.trim();
|
|
95
|
+
if (!s || s.startsWith('#') || !s.includes(':')) continue;
|
|
96
|
+
const idx = s.indexOf(':');
|
|
97
|
+
if (s.slice(0, idx).trim() !== key) continue;
|
|
98
|
+
const v = unquoteYaml(s.slice(idx + 1));
|
|
99
|
+
return v || undefined;
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveHubToken(source, opts = {}) {
|
|
108
|
+
const named = source === 'modelscope' ? opts.modelscopeApiToken : opts.hfToken;
|
|
109
|
+
const field = source === 'modelscope' ? 'modelscope_api_token' : 'hf_token';
|
|
110
|
+
for (const cand of [named, opts.token, configYmlScalar(field)]) {
|
|
111
|
+
const b = hubBearer(cand);
|
|
112
|
+
if (b) return b;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hubPathNames(model) {
|
|
118
|
+
const names = [model];
|
|
119
|
+
let lower = model.toLowerCase();
|
|
120
|
+
let core = model;
|
|
121
|
+
for (const suf of ['_channel', '_group']) {
|
|
122
|
+
if (lower.endsWith(suf)) {
|
|
123
|
+
core = model.slice(0, -suf.length);
|
|
124
|
+
lower = core.toLowerCase();
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const stems = [core];
|
|
129
|
+
if (lower.endsWith('_q326')) stems.push(`${core.slice(0, -5)}q3.26`);
|
|
130
|
+
else if (lower.endsWith('_q3.26')) stems.push(`${core.slice(0, -6)}q326`);
|
|
131
|
+
for (const stem of stems) {
|
|
132
|
+
for (const share of ['', '_channel', '_group']) {
|
|
133
|
+
const cand = `${stem}${share}`;
|
|
134
|
+
if (!names.includes(cand)) names.push(cand);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return names;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hubFileUrls(source, model, file) {
|
|
141
|
+
const urls = [];
|
|
142
|
+
for (const name of hubPathNames(model)) {
|
|
143
|
+
if (source === 'modelscope') {
|
|
144
|
+
for (const repo of [`AriaCompute/${name}`, 'AriaCompute/model']) {
|
|
145
|
+
urls.push(`https://www.modelscope.cn/models/${repo}/resolve/master/${DEFAULT_SDK}/${name}/${file}`);
|
|
146
|
+
urls.push(`https://modelscope.cn/models/${repo}/resolve/master/${DEFAULT_SDK}/${name}/${file}`);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
for (const repo of [`ariacompute/${name}`, 'ariacompute/model']) {
|
|
150
|
+
urls.push(`https://huggingface.co/${repo}/resolve/main/${DEFAULT_SDK}/${name}/${file}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return urls;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isValidBundleSync(dir) {
|
|
158
|
+
const fs = require('fs');
|
|
159
|
+
const path = require('path');
|
|
160
|
+
try {
|
|
161
|
+
if (!fs.statSync(path.join(dir, 'weight.bin')).isFile()) return false;
|
|
162
|
+
const meta = JSON.parse(fs.readFileSync(path.join(dir, 'config.json'), 'utf8'));
|
|
163
|
+
return meta.format === 'aria-quant-bundle';
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function isValidBundle(dir) {
|
|
170
|
+
if (AriaEngineModule?.isValidBundle) {
|
|
171
|
+
try {
|
|
172
|
+
return await AriaEngineModule.isValidBundle(dir);
|
|
173
|
+
} catch {
|
|
174
|
+
/* fall through */
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return isValidBundleSync(dir);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function fetchUrlToFile(url, dest, token) {
|
|
181
|
+
const headers = {};
|
|
182
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
183
|
+
const resp = await fetch(url, { headers });
|
|
184
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
185
|
+
const err = new Error(`HTTP ${resp.status}`);
|
|
186
|
+
err.status = resp.status;
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
if (!resp.ok) {
|
|
190
|
+
const err = new Error(`HTTP ${resp.status}`);
|
|
191
|
+
err.status = resp.status;
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
const fs = require('fs');
|
|
195
|
+
const path = require('path');
|
|
196
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
197
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
198
|
+
fs.writeFileSync(dest, buf);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function fetchHubFile(source, model, file, dest, token, required) {
|
|
202
|
+
let last;
|
|
203
|
+
for (const url of hubFileUrls(source, model, file)) {
|
|
204
|
+
try {
|
|
205
|
+
await fetchUrlToFile(url, dest, token);
|
|
206
|
+
return true;
|
|
207
|
+
} catch (e) {
|
|
208
|
+
last = e;
|
|
209
|
+
if (e && (e.status === 401 || e.status === 403)) {
|
|
210
|
+
const field = source === 'modelscope' ? 'modelscope_api_token' : 'hf_token';
|
|
211
|
+
throw new Error(
|
|
212
|
+
`auth failed HTTP ${e.status}; set ${field} via aria-engine auth (do not pass a Dashboard sk-/bfvk- key as the hub token)`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (required) throw new Error(`${source}: missing ${file}${last ? `: ${last}` : ''}`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
|
|
222
|
+
const opts =
|
|
223
|
+
tokenOrOpts && typeof tokenOrOpts === 'object'
|
|
224
|
+
? tokenOrOpts
|
|
225
|
+
: { token: tokenOrOpts, site };
|
|
226
|
+
parseBundleName(model);
|
|
227
|
+
const fs = require('fs');
|
|
228
|
+
const path = require('path');
|
|
229
|
+
const resolvedSite = opts.site ?? site ?? DEFAULT_SITE;
|
|
230
|
+
const source = preferredPublicHub(resolvedSite);
|
|
231
|
+
const hubToken = resolveHubToken(source, opts);
|
|
56
232
|
const cache = cacheDir(model);
|
|
57
|
-
if (
|
|
233
|
+
if (fs.existsSync(cache) && (await isValidBundle(cache))) {
|
|
58
234
|
return cache;
|
|
59
235
|
}
|
|
60
|
-
|
|
61
|
-
|
|
236
|
+
const staging = path.join(ariaHome(), 'models', `.${model}.partial`);
|
|
237
|
+
try {
|
|
238
|
+
if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
|
|
239
|
+
fs.mkdirSync(staging, { recursive: true });
|
|
240
|
+
for (const file of HUB_REQUIRED) {
|
|
241
|
+
await fetchHubFile(source, model, file, path.join(staging, file), hubToken, true);
|
|
242
|
+
}
|
|
243
|
+
for (const extra of HUB_OPTIONAL) {
|
|
244
|
+
try {
|
|
245
|
+
await fetchHubFile(source, model, extra, path.join(staging, extra), hubToken, false);
|
|
246
|
+
} catch {
|
|
247
|
+
/* optional tokenizer sidecar */
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!(await isValidBundle(staging))) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`${source} fetch completed but bundle invalid (need weight.bin + aria-quant-bundle config.json)`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (fs.existsSync(cache)) fs.rmSync(cache, { recursive: true, force: true });
|
|
256
|
+
fs.renameSync(staging, cache);
|
|
257
|
+
return cache;
|
|
258
|
+
} catch (e) {
|
|
259
|
+
if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
|
|
260
|
+
throw e;
|
|
62
261
|
}
|
|
63
|
-
const metaUrl = `${site.replace(/\/$/, '')}/api/dashboard/models/${encodeURIComponent(
|
|
64
|
-
slug,
|
|
65
|
-
)}/download?quant=${encodeURIComponent(quant)}&sdk=v1.0&format=json`;
|
|
66
|
-
const metaResp = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
67
|
-
if (!metaResp.ok) throw new Error(`dashboard request failed: ${metaResp.status}`);
|
|
68
|
-
const meta = await metaResp.json();
|
|
69
|
-
if (!meta.url) throw new Error('dashboard meta returned empty url');
|
|
70
|
-
// Delegates streaming + zip extraction (and bundle validation) to native.
|
|
71
|
-
return AriaEngineModule.downloadModel(model, meta.url, token, cache);
|
|
72
262
|
}
|
|
73
263
|
|
|
74
264
|
export class AriaEngine {
|
|
@@ -79,10 +269,7 @@ export class AriaEngine {
|
|
|
79
269
|
|
|
80
270
|
static async open(modelRef, opts = {}) {
|
|
81
271
|
if (isLocalRef(modelRef)) return new AriaEngine(modelRef);
|
|
82
|
-
|
|
83
|
-
throw new Error(`model name '${modelRef}' requires an api token to download`);
|
|
84
|
-
}
|
|
85
|
-
const bundle = await downloadModel(modelRef, opts.token, opts.site ?? DEFAULT_SITE);
|
|
272
|
+
const bundle = await downloadModel(modelRef, opts);
|
|
86
273
|
return new AriaEngine(bundle);
|
|
87
274
|
}
|
|
88
275
|
|