@ariacompute/engine-ts 1.13.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 +11 -5
- package/dist/index.d.ts +16 -7
- package/dist/index.js +186 -67
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -16,11 +16,17 @@ eng.close();
|
|
|
16
16
|
|
|
17
17
|
`Engine.open(modelRef, { token, site })` accepts a local bundle path **or** an
|
|
18
18
|
Aria model name (e.g. `gemma-4-e2b-it_q4`). A value containing `/` or already on
|
|
19
|
-
disk is loaded directly; otherwise the SDK downloads it from the
|
|
20
|
-
|
|
21
|
-
into `~/.ariacompute/models/{model}`
|
|
22
|
-
|
|
19
|
+
disk is loaded directly; otherwise the SDK downloads it from the regional public
|
|
20
|
+
hub (same as `aria-engine download`: `.com` → Hugging Face, `.cn` → ModelScope;
|
|
21
|
+
`site` defaults to `https://ariacompute.com`) into `~/.ariacompute/models/{model}`
|
|
22
|
+
and then loads it. Dashboard is not used. A Dashboard `sk-` / `bfvk-` token is
|
|
23
|
+
ignored for hub auth. Token is optional for public models. A valid cached bundle
|
|
24
|
+
is reused without re-downloading.
|
|
25
|
+
|
|
26
|
+
Gated files: pass `hfToken` (`.com`) or `modelscopeApiToken` (`.cn`) — same keys as
|
|
27
|
+
`aria-engine auth`. If omitted, the SDK reads `~/.ariacompute/config.yml`.
|
|
23
28
|
|
|
24
29
|
```ts
|
|
25
|
-
const eng = await Engine.open("gemma-4-e2b-it_q4"
|
|
30
|
+
const eng = await Engine.open("gemma-4-e2b-it_q4");
|
|
31
|
+
const gated = await Engine.open("gemma-4-e2b-it_q4", { hfToken: "hf_..." });
|
|
26
32
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -27,9 +27,13 @@ export interface CompleteResult {
|
|
|
27
27
|
generation?: Generation;
|
|
28
28
|
}
|
|
29
29
|
export interface OpenOptions {
|
|
30
|
-
/**
|
|
30
|
+
/** Legacy generic hub token. Dashboard sk-/bfvk- keys are ignored. */
|
|
31
31
|
token?: string;
|
|
32
|
-
/**
|
|
32
|
+
/** Hugging Face hub token (`.com`). Same field as `aria-engine auth` `hf_token`. */
|
|
33
|
+
hfToken?: string;
|
|
34
|
+
/** ModelScope hub token (`.cn`). Same field as `aria-engine auth` `modelscope_api_token`. */
|
|
35
|
+
modelscopeApiToken?: string;
|
|
36
|
+
/** Site used to pick the regional hub. Defaults to https://ariacompute.com (.com → HF, .cn → ModelScope). */
|
|
33
37
|
site?: string;
|
|
34
38
|
/** Explicit path to the FFI library. */
|
|
35
39
|
ffiLib?: string;
|
|
@@ -39,10 +43,16 @@ export declare function parseBundleName(model: string): {
|
|
|
39
43
|
slug: string;
|
|
40
44
|
quant: string;
|
|
41
45
|
};
|
|
42
|
-
|
|
46
|
+
export declare function preferredPublicHub(site?: string): "huggingface" | "modelscope";
|
|
47
|
+
export declare function hubBearer(token?: string): string | undefined;
|
|
48
|
+
export declare function configYmlScalar(key: string): string | undefined;
|
|
49
|
+
export declare function resolveHubToken(source: "huggingface" | "modelscope", opts?: Pick<OpenOptions, "token" | "hfToken" | "modelscopeApiToken">): string | undefined;
|
|
50
|
+
export declare function hubFileUrls(source: "huggingface" | "modelscope", model: string, file: string, sdk?: string): string[];
|
|
51
|
+
/** Download `model` from the regional public hub into
|
|
43
52
|
* `~/.ariacompute/models/{model}` and return that directory.
|
|
44
|
-
*
|
|
45
|
-
|
|
53
|
+
* Matches aria-engine download: .com → Hugging Face, .cn → ModelScope.
|
|
54
|
+
* Dashboard is not used. Skips the download when a valid bundle is already cached. */
|
|
55
|
+
export declare function downloadModel(model: string, tokenOrOpts?: string | OpenOptions, site?: string): Promise<string>;
|
|
46
56
|
export declare function isLocalRef(modelRef: string): boolean;
|
|
47
57
|
export declare class Engine {
|
|
48
58
|
private lib;
|
|
@@ -56,8 +66,7 @@ export declare class Engine {
|
|
|
56
66
|
/** Construct from a local bundle directory. */
|
|
57
67
|
constructor(bundle: string, opts?: OpenOptions);
|
|
58
68
|
/** Auto-detect: a value containing a separator or already on disk is a local
|
|
59
|
-
* path; otherwise it is a model name
|
|
60
|
-
* then loaded. */
|
|
69
|
+
* path; otherwise it is a model name downloaded from the regional public hub. */
|
|
61
70
|
static open(modelRef: string, opts?: OpenOptions): Promise<Engine>;
|
|
62
71
|
complete(messages: Turn[], opts?: GenerateOptions): CompleteResult;
|
|
63
72
|
embed(text: string): number[];
|
package/dist/index.js
CHANGED
|
@@ -35,13 +35,29 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports._internal = exports.Engine = void 0;
|
|
37
37
|
exports.parseBundleName = parseBundleName;
|
|
38
|
+
exports.preferredPublicHub = preferredPublicHub;
|
|
39
|
+
exports.hubBearer = hubBearer;
|
|
40
|
+
exports.configYmlScalar = configYmlScalar;
|
|
41
|
+
exports.resolveHubToken = resolveHubToken;
|
|
42
|
+
exports.hubFileUrls = hubFileUrls;
|
|
38
43
|
exports.downloadModel = downloadModel;
|
|
39
44
|
exports.isLocalRef = isLocalRef;
|
|
40
45
|
const fs = __importStar(require("node:fs"));
|
|
41
46
|
const os = __importStar(require("node:os"));
|
|
42
47
|
const path = __importStar(require("node:path"));
|
|
43
|
-
const
|
|
48
|
+
const promises_1 = require("node:stream/promises");
|
|
49
|
+
const node_stream_1 = require("node:stream");
|
|
44
50
|
const DEFAULT_SITE = "https://ariacompute.com";
|
|
51
|
+
const DEFAULT_SDK = "v1.0";
|
|
52
|
+
const HUB_REQUIRED = ["config.json", "weight.bin"];
|
|
53
|
+
const HUB_OPTIONAL = [
|
|
54
|
+
"tokenizer.json",
|
|
55
|
+
"tokenizer.model",
|
|
56
|
+
"tokenizer_config.json",
|
|
57
|
+
"special_tokens_map.json",
|
|
58
|
+
"vocab.json",
|
|
59
|
+
"merges.txt",
|
|
60
|
+
];
|
|
45
61
|
function ariaHome() {
|
|
46
62
|
return process.env.ARIA_COMPUTE_HOME || path.join(os.homedir(), ".ariacompute");
|
|
47
63
|
}
|
|
@@ -56,7 +72,10 @@ function parseBundleName(model) {
|
|
|
56
72
|
const idx = model.lastIndexOf("_q");
|
|
57
73
|
if (idx !== -1) {
|
|
58
74
|
const slug = model.slice(0, idx);
|
|
59
|
-
|
|
75
|
+
let suffix = model.slice(idx + 2);
|
|
76
|
+
if (suffix.endsWith("_channel") || suffix.endsWith("_group")) {
|
|
77
|
+
suffix = suffix.slice(0, suffix.lastIndexOf("_"));
|
|
78
|
+
}
|
|
60
79
|
const quant = suffix === "4"
|
|
61
80
|
? "int4"
|
|
62
81
|
: suffix === "8"
|
|
@@ -83,81 +102,185 @@ function isValidBundle(dir) {
|
|
|
83
102
|
return false;
|
|
84
103
|
}
|
|
85
104
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!
|
|
94
|
-
return;
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
function preferredPublicHub(site) {
|
|
106
|
+
if (site && site.toLowerCase().includes("ariacompute.cn"))
|
|
107
|
+
return "modelscope";
|
|
108
|
+
return "huggingface";
|
|
109
|
+
}
|
|
110
|
+
function hubBearer(token) {
|
|
111
|
+
const t = (token || "").trim();
|
|
112
|
+
if (!t)
|
|
113
|
+
return undefined;
|
|
114
|
+
const low = t.toLowerCase();
|
|
115
|
+
if (low.startsWith("sk-") || low.startsWith("bfvk-"))
|
|
116
|
+
return undefined;
|
|
117
|
+
return t;
|
|
118
|
+
}
|
|
119
|
+
function unquoteYaml(v) {
|
|
120
|
+
const t = v.trim();
|
|
121
|
+
if (t.length >= 2 && ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")))) {
|
|
122
|
+
return t.slice(1, -1);
|
|
123
|
+
}
|
|
124
|
+
return t;
|
|
125
|
+
}
|
|
126
|
+
function configYmlScalar(key) {
|
|
127
|
+
try {
|
|
128
|
+
const raw = fs.readFileSync(path.join(ariaHome(), "config.yml"), "utf8");
|
|
129
|
+
for (const line of raw.split("\n")) {
|
|
130
|
+
if (line.startsWith(" ") || line.startsWith("\t"))
|
|
131
|
+
continue;
|
|
132
|
+
const s = line.trim();
|
|
133
|
+
if (!s || s.startsWith("#") || !s.includes(":"))
|
|
134
|
+
continue;
|
|
135
|
+
const idx = s.indexOf(":");
|
|
136
|
+
if (s.slice(0, idx).trim() !== key)
|
|
137
|
+
continue;
|
|
138
|
+
const v = unquoteYaml(s.slice(idx + 1));
|
|
139
|
+
return v || undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
function resolveHubToken(source, opts = {}) {
|
|
148
|
+
const named = source === "modelscope" ? opts.modelscopeApiToken : opts.hfToken;
|
|
149
|
+
const field = source === "modelscope" ? "modelscope_api_token" : "hf_token";
|
|
150
|
+
for (const cand of [named, opts.token, configYmlScalar(field)]) {
|
|
151
|
+
const b = hubBearer(cand);
|
|
152
|
+
if (b)
|
|
153
|
+
return b;
|
|
97
154
|
}
|
|
98
|
-
|
|
155
|
+
return undefined;
|
|
99
156
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
157
|
+
function hubPathNames(model) {
|
|
158
|
+
const names = [model];
|
|
159
|
+
let lower = model.toLowerCase();
|
|
160
|
+
let core = model;
|
|
161
|
+
for (const suf of ["_channel", "_group"]) {
|
|
162
|
+
if (lower.endsWith(suf)) {
|
|
163
|
+
core = model.slice(0, -suf.length);
|
|
164
|
+
lower = core.toLowerCase();
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
103
167
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
168
|
+
const stems = [core];
|
|
169
|
+
if (lower.endsWith("_q326"))
|
|
170
|
+
stems.push(`${core.slice(0, -5)}q3.26`);
|
|
171
|
+
else if (lower.endsWith("_q3.26"))
|
|
172
|
+
stems.push(`${core.slice(0, -6)}q326`);
|
|
173
|
+
for (const stem of stems) {
|
|
174
|
+
for (const share of ["", "_channel", "_group"]) {
|
|
175
|
+
const cand = `${stem}${share}`;
|
|
176
|
+
if (!names.includes(cand))
|
|
177
|
+
names.push(cand);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return names;
|
|
181
|
+
}
|
|
182
|
+
function hubFileUrls(source, model, file, sdk = DEFAULT_SDK) {
|
|
183
|
+
const urls = [];
|
|
184
|
+
for (const name of hubPathNames(model)) {
|
|
185
|
+
if (source === "modelscope") {
|
|
186
|
+
for (const repo of [`AriaCompute/${name}`, "AriaCompute/model"]) {
|
|
187
|
+
urls.push(`https://www.modelscope.cn/models/${repo}/resolve/master/${sdk}/${name}/${file}`);
|
|
188
|
+
urls.push(`https://modelscope.cn/models/${repo}/resolve/master/${sdk}/${name}/${file}`);
|
|
189
|
+
}
|
|
110
190
|
}
|
|
111
191
|
else {
|
|
112
|
-
|
|
113
|
-
|
|
192
|
+
for (const repo of [`ariacompute/${name}`, "ariacompute/model"]) {
|
|
193
|
+
urls.push(`https://huggingface.co/${repo}/resolve/main/${sdk}/${name}/${file}`);
|
|
194
|
+
}
|
|
114
195
|
}
|
|
115
196
|
}
|
|
116
|
-
|
|
197
|
+
return urls;
|
|
117
198
|
}
|
|
118
|
-
|
|
119
|
-
|
|
199
|
+
class HubHttpError extends Error {
|
|
200
|
+
constructor(status, message) {
|
|
201
|
+
super(message);
|
|
202
|
+
this.status = status;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function fetchUrlToFile(url, dest, token) {
|
|
206
|
+
const headers = {};
|
|
207
|
+
if (token)
|
|
208
|
+
headers.Authorization = `Bearer ${token}`;
|
|
209
|
+
const resp = await fetch(url, { headers });
|
|
210
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
211
|
+
throw new HubHttpError(resp.status, `HTTP ${resp.status}`);
|
|
212
|
+
}
|
|
120
213
|
if (!resp.ok)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
214
|
+
throw new HubHttpError(resp.status, `HTTP ${resp.status}`);
|
|
215
|
+
if (!resp.body)
|
|
216
|
+
throw new Error("empty response body");
|
|
217
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
218
|
+
await (0, promises_1.pipeline)(node_stream_1.Readable.fromWeb(resp.body), fs.createWriteStream(dest));
|
|
219
|
+
}
|
|
220
|
+
async function fetchHubFile(source, model, file, dest, token, required) {
|
|
221
|
+
let last;
|
|
222
|
+
for (const url of hubFileUrls(source, model, file)) {
|
|
223
|
+
try {
|
|
224
|
+
await fetchUrlToFile(url, dest, token);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
last = e;
|
|
229
|
+
if (e instanceof HubHttpError && (e.status === 401 || e.status === 403)) {
|
|
230
|
+
const field = source === "modelscope" ? "modelscope_api_token" : "hf_token";
|
|
231
|
+
throw new Error(`auth failed HTTP ${e.status}; set ${field} via aria-engine auth (do not pass a Dashboard sk-/bfvk- key as the hub token)`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (required)
|
|
236
|
+
throw new Error(`${source}: missing ${file}${last ? `: ${last}` : ""}`);
|
|
237
|
+
return false;
|
|
126
238
|
}
|
|
127
239
|
const encoder = new TextEncoder();
|
|
128
|
-
/** Download `model` from the
|
|
240
|
+
/** Download `model` from the regional public hub into
|
|
129
241
|
* `~/.ariacompute/models/{model}` and return that directory.
|
|
130
|
-
*
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
242
|
+
* Matches aria-engine download: .com → Hugging Face, .cn → ModelScope.
|
|
243
|
+
* Dashboard is not used. Skips the download when a valid bundle is already cached. */
|
|
244
|
+
async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
|
|
245
|
+
const opts = tokenOrOpts && typeof tokenOrOpts === "object"
|
|
246
|
+
? tokenOrOpts
|
|
247
|
+
: { token: tokenOrOpts, site };
|
|
248
|
+
parseBundleName(model);
|
|
249
|
+
const resolvedSite = opts.site ?? site ?? DEFAULT_SITE;
|
|
250
|
+
const source = preferredPublicHub(resolvedSite);
|
|
251
|
+
const hubToken = resolveHubToken(source, opts);
|
|
135
252
|
const cache = cacheDir(model);
|
|
136
253
|
if (fs.existsSync(cache) && isValidBundle(cache))
|
|
137
254
|
return cache;
|
|
138
|
-
const metaUrl = `${site.replace(/\/$/, "")}/api/dashboard/models/${encodeURIComponent(slug)}/download?quant=${encodeURIComponent(quant)}&sdk=v1.0&format=json`;
|
|
139
|
-
const metaResp = await httpGet(metaUrl, token);
|
|
140
|
-
if (!metaResp.ok)
|
|
141
|
-
throw new Error(`dashboard request failed: ${metaResp.status}`);
|
|
142
|
-
const meta = metaResp.body;
|
|
143
|
-
if (!meta.url)
|
|
144
|
-
throw new Error("dashboard meta returned empty url");
|
|
145
|
-
const zipResp = await httpGet(meta.url, token);
|
|
146
|
-
if (!zipResp.ok)
|
|
147
|
-
throw new Error(`download stream failed: ${zipResp.status}`);
|
|
148
|
-
const data = zipResp.body;
|
|
149
255
|
const staging = path.join(ariaHome(), "models", `.${model}.partial`);
|
|
150
|
-
|
|
151
|
-
fs.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
256
|
+
try {
|
|
257
|
+
if (fs.existsSync(staging))
|
|
258
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
259
|
+
fs.mkdirSync(staging, { recursive: true });
|
|
260
|
+
for (const file of HUB_REQUIRED) {
|
|
261
|
+
await fetchHubFile(source, model, file, path.join(staging, file), hubToken, true);
|
|
262
|
+
}
|
|
263
|
+
for (const extra of HUB_OPTIONAL) {
|
|
264
|
+
try {
|
|
265
|
+
await fetchHubFile(source, model, extra, path.join(staging, extra), hubToken, false);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
/* optional tokenizer sidecar */
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!isValidBundle(staging)) {
|
|
272
|
+
throw new Error(`${source} fetch completed but bundle invalid (need weight.bin + aria-quant-bundle config.json)`);
|
|
273
|
+
}
|
|
274
|
+
if (fs.existsSync(cache))
|
|
275
|
+
fs.rmSync(cache, { recursive: true, force: true });
|
|
276
|
+
fs.renameSync(staging, cache);
|
|
277
|
+
return cache;
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
if (fs.existsSync(staging))
|
|
281
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
282
|
+
throw e;
|
|
283
|
+
}
|
|
161
284
|
}
|
|
162
285
|
function loadLib(ffiLib) {
|
|
163
286
|
const libPath = ffiLib || process.env.ARIA_FFI_LIB;
|
|
@@ -186,15 +309,11 @@ class Engine {
|
|
|
186
309
|
}
|
|
187
310
|
}
|
|
188
311
|
/** Auto-detect: a value containing a separator or already on disk is a local
|
|
189
|
-
* path; otherwise it is a model name
|
|
190
|
-
* then loaded. */
|
|
312
|
+
* path; otherwise it is a model name downloaded from the regional public hub. */
|
|
191
313
|
static async open(modelRef, opts = {}) {
|
|
192
314
|
if (isLocalRef(modelRef))
|
|
193
315
|
return new Engine(modelRef, opts);
|
|
194
|
-
|
|
195
|
-
throw new Error(`model name '${modelRef}' requires an api token to download`);
|
|
196
|
-
}
|
|
197
|
-
const bundle = await downloadModel(modelRef, opts.token, opts.site ?? DEFAULT_SITE);
|
|
316
|
+
const bundle = await downloadModel(modelRef, opts);
|
|
198
317
|
return new Engine(bundle, opts);
|
|
199
318
|
}
|
|
200
319
|
complete(messages, opts = {}) {
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ariacompute/engine-ts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "TypeScript/Node SDK for Aria Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc -p .",
|
|
9
|
-
"test": "node --test test/binding.test.mjs"
|
|
9
|
+
"test": "node --test test/binding.test.mjs test/download.test.mjs"
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"fflate": "^0.8.2",
|
|
14
13
|
"koffi": "^2.8.0"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|