@ariacompute/engine-ts 1.21.0 → 2.0.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.
package/README.md CHANGED
@@ -1,35 +1,10 @@
1
1
  # @ariacompute/engine-ts
2
2
 
3
- ```bash
4
- npm i @ariacompute/engine-ts
5
- ```
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
-
9
3
  ```ts
10
- import { Engine } from "@ariacompute/engine-ts";
11
- const eng = new Engine("/path/to/bundle");
12
- console.log(eng.complete([{ role: "user", content: "hi" }]));
13
- eng.close();
4
+ import { AriaEngine } from "@ariacompute/engine-ts";
5
+ const eng = new AriaEngine("/path/to/checkpoint", "encoder");
6
+ const resp = eng.systemone({ state: {}, questions: { q1: { type: "noul", instructions: "ok?", criteria: {} } } });
7
+ eng.destroy();
14
8
  ```
15
9
 
16
- ## Auto-download by model name
17
-
18
- `Engine.open(modelRef, { token, site })` accepts a local bundle path **or** an
19
- Aria model name (e.g. `gemma-4-e2b-it_q4`). A value containing `/` or already on
20
- disk is loaded directly; otherwise the SDK downloads it from the regional public
21
- hub (same as `aria-engine download`: `.com` → Hugging Face, `.cn` → ModelScope;
22
- `site` defaults to `https://ariacompute.com`) into `~/.ariacompute/models/{model}`
23
- and then loads it. Dashboard is not used. A Dashboard `sk-` / `bfvk-` token is
24
- ignored for hub auth. Token is optional for public models. A valid cached bundle
25
- is reused without re-downloading.
26
-
27
- Gated files: call `eng.setup({ hf_token })` (`.com`) or `eng.setup({ modelscope_api_token, site_url })` (`.cn`) **before** `open`. Same keys as `aria-engine setup`. Instance-only; does not write `engine.yml`. If omitted, the SDK reads `~/.ariacompute/engine.yml`.
28
-
29
- ```ts
30
- const eng = await Engine.open("gemma-4-e2b-it_q4");
31
-
32
- const gated = new Engine();
33
- gated.setup({ hf_token: "hf_..." });
34
- await gated.open("gemma-4-e2b-it_q4");
35
- ```
10
+ Requires `libaria-engine_ffi` via `aria-engine upgrade` or `ARIA_FFI_LIB`.
package/dist/index.d.ts CHANGED
@@ -1,112 +1,11 @@
1
- export interface GenerateOptions {
2
- max_tokens?: number;
3
- temperature?: number;
4
- top_p?: number;
5
- stream?: boolean;
6
- stop?: string[];
7
- response_format?: {
8
- type: "text" | "json_object" | "json_schema";
9
- json_schema?: unknown;
10
- };
11
- }
12
- export interface Turn {
13
- role: "system" | "user" | "assistant";
14
- content: string;
15
- }
16
- export interface Generation {
17
- success: boolean;
18
- response: string;
19
- error?: string;
20
- tokens_used?: number;
21
- finish_reason?: string;
22
- }
23
- export interface CompleteResult {
24
- success: boolean;
25
- response: string;
26
- error?: string;
27
- generation?: Generation;
28
- }
29
- export interface OpenOptions {
30
- /** Legacy generic hub token. Dashboard sk-/bfvk- keys are ignored. */
31
- token?: string;
32
- /** Hugging Face hub token (`.com`). Same field as `aria-engine setup` `hf_token`. */
33
- hfToken?: string;
34
- /** ModelScope hub token (`.cn`). Same field as `aria-engine setup` `modelscope_api_token`. */
35
- modelscopeApiToken?: string;
36
- /** Site used to pick the regional hub. Defaults to https://ariacompute.com (.com → HF, .cn → ModelScope). */
37
- site?: string;
38
- /** Explicit path to the FFI library. */
39
- ffiLib?: string;
40
- }
41
- export declare const INTL_SITE = "https://ariacompute.com";
42
- export declare const INTL_UPGRADE = "https://github.com/ariacompute";
43
- export declare const CN_SITE = "https://ariacompute.cn";
44
- export declare const CN_UPGRADE = "https://gitee.com/ariacompute";
45
- export interface SetupConfig {
46
- router: string;
47
- /** sk-aria_ or bfvk-; router associates by prefix */
48
- router_api_key: string;
49
- site_url: string;
50
- upgrade_url: string;
51
- compute: string;
52
- hf_token: string;
53
- modelscope_api_token: string;
54
- }
55
- export declare function defaultSetupConfig(): SetupConfig;
56
- export declare function fillSetupUrls(cfg: SetupConfig): SetupConfig;
57
- export declare function applySetup(existing: SetupConfig, updates: Partial<SetupConfig>): SetupConfig;
58
- /** Parse `slug`/`quant` from a model name such as `gemma-4-e2b-it_q4`. */
59
- export declare function parseBundleName(model: string): {
60
- slug: string;
61
- quant: string;
62
- };
63
- export declare function preferredPublicHub(site?: string): "huggingface" | "modelscope";
64
- export declare function hubBearer(token?: string): string | undefined;
65
- export declare function configYmlScalar(key: string): string | undefined;
66
- export declare function resolveHubToken(source: "huggingface" | "modelscope", opts?: Pick<OpenOptions, "token" | "hfToken" | "modelscopeApiToken">): string | undefined;
67
- export declare function hubFileUrls(source: "huggingface" | "modelscope", model: string, file: string, sdk?: string): string[];
68
- /** Download `model` from the regional public hub into
69
- * `~/.ariacompute/models/{model}` and return that directory.
70
- * Matches aria-engine download: .com → Hugging Face, .cn → ModelScope.
71
- * Dashboard is not used. Skips the download when a valid bundle is already cached. */
72
- export declare function downloadModel(model: string, tokenOrOpts?: string | OpenOptions, site?: string): Promise<string>;
73
- export declare function ffiLibName(platform?: string): string;
74
- export declare function cachedFfiPath(platform?: string): string | undefined;
75
- export declare function ffiAssetOs(platform?: string, arch?: string): string;
76
- export declare function selectLatestStable(releases: Array<Record<string, unknown>>): string;
77
- export declare function extractFfiArchive(archive: string, destDir: string, want?: string): string;
78
- /** Return a path to libaria-engine_ffi, downloading the latest stable Release if needed. */
79
- export declare function ensureFfiLib(site?: string): Promise<string>;
80
- export declare function isLocalRef(modelRef: string): boolean;
81
- export declare class Engine {
82
- private lib;
1
+ export type Track = "encoder" | "decoder";
2
+ /** Load koffi binding when native lib is present; otherwise throw with install hint. */
3
+ export declare function loadFfi(): any;
4
+ export declare class AriaEngine {
5
+ private ffi;
83
6
  private handle;
84
- private fnInit;
85
- private fnDestroy;
86
- private fnComplete;
87
- private fnEmbed;
88
- private fnTranscribe;
89
- private fnLastError;
90
- private cfg;
91
- private opts;
92
- /** Empty construct, or a local bundle directory. */
93
- constructor(bundle?: string, opts?: OpenOptions);
94
- /** Set Config / Run fields on this instance only. Does not write engine.yml. */
95
- setup(updates: Partial<SetupConfig>): this;
96
- setupStatus(): SetupConfig;
97
- /** Reset instance defaults. Does not delete ~/.ariacompute/engine.yml. */
98
- setupClear(): this;
99
- private bindAndInit;
100
- /** Download (if needed) and load a model using instance setup. */
101
- open(modelRef: string): Promise<this>;
102
- /** Auto-detect: a value containing a separator or already on disk is a local
103
- * path; otherwise it is a model name downloaded from the regional public hub. */
104
- static open(modelRef: string, opts?: OpenOptions): Promise<Engine>;
105
- complete(messages: Turn[], opts?: GenerateOptions): CompleteResult;
106
- embed(text: string): number[];
107
- transcribe(pcm: Uint8Array): string;
108
- close(): void;
7
+ constructor(checkpoint: string, track?: Track);
8
+ systemone(request: object): object;
9
+ destroy(): void;
109
10
  }
110
- export declare const _internal: {
111
- encoder: TextEncoder;
112
- };
11
+ export default AriaEngine;
package/dist/index.js CHANGED
@@ -33,654 +33,66 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports._internal = exports.Engine = exports.CN_UPGRADE = exports.CN_SITE = exports.INTL_UPGRADE = exports.INTL_SITE = void 0;
37
- exports.defaultSetupConfig = defaultSetupConfig;
38
- exports.fillSetupUrls = fillSetupUrls;
39
- exports.applySetup = applySetup;
40
- exports.parseBundleName = parseBundleName;
41
- exports.preferredPublicHub = preferredPublicHub;
42
- exports.hubBearer = hubBearer;
43
- exports.configYmlScalar = configYmlScalar;
44
- exports.resolveHubToken = resolveHubToken;
45
- exports.hubFileUrls = hubFileUrls;
46
- exports.downloadModel = downloadModel;
47
- exports.ffiLibName = ffiLibName;
48
- exports.cachedFfiPath = cachedFfiPath;
49
- exports.ffiAssetOs = ffiAssetOs;
50
- exports.selectLatestStable = selectLatestStable;
51
- exports.extractFfiArchive = extractFfiArchive;
52
- exports.ensureFfiLib = ensureFfiLib;
53
- exports.isLocalRef = isLocalRef;
54
- const node_child_process_1 = require("node:child_process");
55
- const fs = __importStar(require("node:fs"));
56
- const os = __importStar(require("node:os"));
57
- const path = __importStar(require("node:path"));
58
- const promises_1 = require("node:stream/promises");
59
- const node_stream_1 = require("node:stream");
60
- const zlib = __importStar(require("node:zlib"));
61
- const DEFAULT_SITE = "https://ariacompute.com";
62
- exports.INTL_SITE = "https://ariacompute.com";
63
- exports.INTL_UPGRADE = "https://github.com/ariacompute";
64
- exports.CN_SITE = "https://ariacompute.cn";
65
- exports.CN_UPGRADE = "https://gitee.com/ariacompute";
66
- function defaultSetupConfig() {
67
- return {
68
- router: "",
69
- router_api_key: "",
70
- site_url: "",
71
- upgrade_url: "",
72
- compute: "auto",
73
- hf_token: "",
74
- modelscope_api_token: "",
75
- };
76
- }
77
- function gatewayRegion(url) {
78
- const lower = (url || "").toLowerCase();
79
- if (lower.includes("ariacompute.cn") || lower.includes("gitee.com/ariacompute"))
80
- return "cn";
81
- if (lower.includes("ariacompute.com") || lower.includes("github.com/ariacompute"))
82
- return "intl";
83
- return undefined;
84
- }
85
- function pairUrls(region) {
86
- return region === "cn" ? [exports.CN_SITE, exports.CN_UPGRADE] : [exports.INTL_SITE, exports.INTL_UPGRADE];
87
- }
88
- function fillSetupUrls(cfg) {
89
- const out = { ...cfg };
90
- const region = gatewayRegion(out.site_url) || gatewayRegion(out.upgrade_url);
91
- if (!region)
92
- return out;
93
- const [site, upgrade] = pairUrls(region);
94
- if (!out.site_url)
95
- out.site_url = site;
96
- if (!out.upgrade_url)
97
- out.upgrade_url = upgrade;
98
- return out;
99
- }
100
- function validateRouterApiKey(key) {
101
- const t = (key || "").trim();
102
- if (!t)
103
- return;
104
- if (t.startsWith("sk-aria_") || t.startsWith("bfvk-"))
105
- return;
106
- throw new Error("router_api_key must start with sk-aria_ or bfvk-");
107
- }
108
- function applySetup(existing, updates) {
109
- const out = { ...existing };
110
- for (const [k, v] of Object.entries(updates)) {
111
- if (v === undefined)
112
- continue;
113
- out[k] = v;
114
- }
115
- if (!["auto", "cpu", "cuda"].includes(out.compute)) {
116
- throw new Error(`invalid compute: ${out.compute}`);
117
- }
118
- validateRouterApiKey(out.router_api_key);
119
- return fillSetupUrls(out);
120
- }
121
- const DEFAULT_SDK = "v1.0";
122
- const HUB_REQUIRED = ["config.json", "weight.bin"];
123
- const HUB_OPTIONAL = [
124
- "tokenizer.json",
125
- "tokenizer.model",
126
- "tokenizer_config.json",
127
- "special_tokens_map.json",
128
- "vocab.json",
129
- "merges.txt",
130
- ];
36
+ exports.AriaEngine = void 0;
37
+ exports.loadFfi = loadFfi;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
131
41
  function ariaHome() {
132
42
  return process.env.ARIA_COMPUTE_HOME || path.join(os.homedir(), ".ariacompute");
133
43
  }
134
- function cacheDir(model) {
135
- return path.join(ariaHome(), "models", model);
136
- }
137
- /** Parse `slug`/`quant` from a model name such as `gemma-4-e2b-it_q4`. */
138
- function parseBundleName(model) {
139
- if (!model || model.includes("/") || model.includes("\\")) {
140
- throw new Error(`invalid model name: ${model}`);
141
- }
142
- const idx = model.lastIndexOf("_q");
143
- if (idx !== -1) {
144
- const slug = model.slice(0, idx);
145
- let suffix = model.slice(idx + 2);
146
- if (suffix.endsWith("_channel") || suffix.endsWith("_group")) {
147
- suffix = suffix.slice(0, suffix.lastIndexOf("_"));
148
- }
149
- const quant = suffix === "4"
150
- ? "int4"
151
- : suffix === "8"
152
- ? "int8"
153
- : suffix === "326" || suffix === "3.26"
154
- ? "int326"
155
- : (() => {
156
- throw new Error(`unknown quant suffix _q${suffix}`);
157
- })();
158
- if (!slug)
159
- throw new Error(`invalid model name: ${model}`);
160
- return { slug, quant };
161
- }
162
- return { slug: model, quant: "int4" };
163
- }
164
- function isValidBundle(dir) {
165
- try {
166
- if (!fs.statSync(path.join(dir, "weight.bin")).isFile())
167
- return false;
168
- const meta = JSON.parse(fs.readFileSync(path.join(dir, "config.json"), "utf8"));
169
- return meta.format === "aria-quant-bundle";
170
- }
171
- catch {
172
- return false;
173
- }
174
- }
175
- function preferredPublicHub(site) {
176
- if (site && site.toLowerCase().includes("ariacompute.cn"))
177
- return "modelscope";
178
- return "huggingface";
179
- }
180
- function hubBearer(token) {
181
- const t = (token || "").trim();
182
- if (!t)
183
- return undefined;
184
- const low = t.toLowerCase();
185
- if (low.startsWith("sk-") || low.startsWith("bfvk-"))
186
- return undefined;
187
- return t;
188
- }
189
- function unquoteYaml(v) {
190
- const t = v.trim();
191
- if (t.length >= 2 && ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")))) {
192
- return t.slice(1, -1);
193
- }
194
- return t;
195
- }
196
- function configYmlScalar(key) {
197
- const home = ariaHome();
198
- for (const name of ["engine.yml", "config.yml"]) {
199
- try {
200
- const raw = fs.readFileSync(path.join(home, name), "utf8");
201
- for (const line of raw.split("\n")) {
202
- if (line.startsWith(" ") || line.startsWith("\t"))
203
- continue;
204
- const s = line.trim();
205
- if (!s || s.startsWith("#") || !s.includes(":"))
206
- continue;
207
- const idx = s.indexOf(":");
208
- if (s.slice(0, idx).trim() !== key)
209
- continue;
210
- const v = unquoteYaml(s.slice(idx + 1));
211
- if (v)
212
- return v;
213
- }
214
- }
215
- catch {
216
- continue;
217
- }
218
- }
219
- return undefined;
220
- }
221
- function resolveHubToken(source, opts = {}) {
222
- const named = source === "modelscope" ? opts.modelscopeApiToken : opts.hfToken;
223
- const field = source === "modelscope" ? "modelscope_api_token" : "hf_token";
224
- for (const cand of [named, opts.token, configYmlScalar(field)]) {
225
- const b = hubBearer(cand);
226
- if (b)
227
- return b;
228
- }
229
- return undefined;
230
- }
231
- function hubPathNames(model) {
232
- const names = [model];
233
- let lower = model.toLowerCase();
234
- let core = model;
235
- for (const suf of ["_channel", "_group"]) {
236
- if (lower.endsWith(suf)) {
237
- core = model.slice(0, -suf.length);
238
- lower = core.toLowerCase();
239
- break;
240
- }
241
- }
242
- const stems = [core];
243
- if (lower.endsWith("_q326"))
244
- stems.push(`${core.slice(0, -5)}q3.26`);
245
- else if (lower.endsWith("_q3.26"))
246
- stems.push(`${core.slice(0, -6)}q326`);
247
- for (const stem of stems) {
248
- for (const share of ["", "_channel", "_group"]) {
249
- const cand = `${stem}${share}`;
250
- if (!names.includes(cand))
251
- names.push(cand);
252
- }
253
- }
254
- return names;
255
- }
256
- function hubFileUrls(source, model, file, sdk = DEFAULT_SDK) {
257
- const urls = [];
258
- for (const name of hubPathNames(model)) {
259
- if (source === "modelscope") {
260
- for (const repo of [`AriaCompute/${name}`, "AriaCompute/model"]) {
261
- urls.push(`https://www.modelscope.cn/models/${repo}/resolve/master/${sdk}/${name}/${file}`);
262
- urls.push(`https://modelscope.cn/models/${repo}/resolve/master/${sdk}/${name}/${file}`);
263
- }
264
- }
265
- else {
266
- for (const repo of [`ariacompute/${name}`, "ariacompute/model"]) {
267
- urls.push(`https://huggingface.co/${repo}/resolve/main/${sdk}/${name}/${file}`);
268
- }
269
- }
270
- }
271
- return urls;
272
- }
273
- class HubHttpError extends Error {
274
- constructor(status, message) {
275
- super(message);
276
- this.status = status;
277
- }
278
- }
279
- async function fetchUrlToFile(url, dest, token) {
280
- const headers = {};
281
- if (token)
282
- headers.Authorization = `Bearer ${token}`;
283
- const resp = await fetch(url, { headers });
284
- if (resp.status === 401 || resp.status === 403) {
285
- throw new HubHttpError(resp.status, `HTTP ${resp.status}`);
286
- }
287
- if (!resp.ok)
288
- throw new HubHttpError(resp.status, `HTTP ${resp.status}`);
289
- if (!resp.body)
290
- throw new Error("empty response body");
291
- fs.mkdirSync(path.dirname(dest), { recursive: true });
292
- await (0, promises_1.pipeline)(node_stream_1.Readable.fromWeb(resp.body), fs.createWriteStream(dest));
293
- }
294
- async function fetchHubFile(source, model, file, dest, token, required) {
295
- let last;
296
- for (const url of hubFileUrls(source, model, file)) {
297
- try {
298
- await fetchUrlToFile(url, dest, token);
299
- return true;
300
- }
301
- catch (e) {
302
- last = e;
303
- if (e instanceof HubHttpError && (e.status === 401 || e.status === 403)) {
304
- const field = source === "modelscope" ? "modelscope_api_token" : "hf_token";
305
- throw new Error(`auth failed HTTP ${e.status}; set ${field} via aria-engine setup (do not pass a Dashboard sk-/bfvk- key as the hub token)`);
306
- }
307
- }
308
- }
309
- if (required)
310
- throw new Error(`${source}: missing ${file}${last ? `: ${last}` : ""}`);
311
- return false;
312
- }
313
- const encoder = new TextEncoder();
314
- /** Download `model` from the regional public hub into
315
- * `~/.ariacompute/models/{model}` and return that directory.
316
- * Matches aria-engine download: .com → Hugging Face, .cn → ModelScope.
317
- * Dashboard is not used. Skips the download when a valid bundle is already cached. */
318
- async function downloadModel(model, tokenOrOpts, site = DEFAULT_SITE) {
319
- const opts = tokenOrOpts && typeof tokenOrOpts === "object"
320
- ? tokenOrOpts
321
- : { token: tokenOrOpts, site };
322
- parseBundleName(model);
323
- const resolvedSite = opts.site ?? site ?? DEFAULT_SITE;
324
- const source = preferredPublicHub(resolvedSite);
325
- const hubToken = resolveHubToken(source, opts);
326
- const cache = cacheDir(model);
327
- if (fs.existsSync(cache) && isValidBundle(cache))
328
- return cache;
329
- const staging = path.join(ariaHome(), "models", `.${model}.partial`);
330
- try {
331
- if (fs.existsSync(staging))
332
- fs.rmSync(staging, { recursive: true, force: true });
333
- fs.mkdirSync(staging, { recursive: true });
334
- for (const file of HUB_REQUIRED) {
335
- await fetchHubFile(source, model, file, path.join(staging, file), hubToken, true);
336
- }
337
- for (const extra of HUB_OPTIONAL) {
338
- try {
339
- await fetchHubFile(source, model, extra, path.join(staging, extra), hubToken, false);
340
- }
341
- catch {
342
- /* optional tokenizer sidecar */
343
- }
344
- }
345
- if (!isValidBundle(staging)) {
346
- throw new Error(`${source} fetch completed but bundle invalid (need weight.bin + aria-quant-bundle config.json)`);
347
- }
348
- if (fs.existsSync(cache))
349
- fs.rmSync(cache, { recursive: true, force: true });
350
- fs.renameSync(staging, cache);
351
- return cache;
352
- }
353
- catch (e) {
354
- if (fs.existsSync(staging))
355
- fs.rmSync(staging, { recursive: true, force: true });
356
- throw e;
357
- }
358
- }
359
- const SDK_UA = "aria-engine-sdk/0.1.0";
360
- function ffiLibName(platform = process.platform) {
361
- if (platform === "win32" || platform.toLowerCase().startsWith("win"))
362
- return "aria-engine_ffi.dll";
363
- if (platform === "darwin")
364
- return "libaria-engine_ffi.dylib";
365
- return "libaria-engine_ffi.so";
366
- }
367
- function libDir() {
368
- return path.join(ariaHome(), "lib");
369
- }
370
- function cachedFfiPath(platform = process.platform) {
371
- const candidate = path.join(libDir(), ffiLibName(platform));
372
- return fs.existsSync(candidate) ? candidate : undefined;
373
- }
374
- function bundledFfiPath() {
375
- const candidate = path.join(__dirname, "lib", ffiLibName());
376
- return fs.existsSync(candidate) ? candidate : undefined;
377
- }
378
- function ffiAssetOs(platform = process.platform, arch = process.arch) {
379
- const p = platform.toLowerCase();
380
- const a = arch.toLowerCase();
381
- if (p === "linux" && (a === "x64" || a === "x86_64" || a === "amd64"))
382
- return "linux_x86_64";
383
- if (p === "linux" && (a === "arm64" || a === "aarch64"))
384
- return "linux_arm64";
385
- if (p === "darwin" || p === "macos")
386
- return "macos";
387
- if ((p === "win32" || p.startsWith("win")) && (a === "x64" || a === "x86_64" || a === "amd64")) {
388
- return "windows_x86_64";
389
- }
390
- throw new Error(`unsupported platform ${platform}/${arch} for libaria-engine_ffi`);
391
- }
392
- function stripV(tag) {
393
- const t = tag.trim();
394
- return t.startsWith("v") || t.startsWith("V") ? t.slice(1) : t;
395
- }
396
- function parseSemver(tag) {
397
- const core = stripV(tag).split("-", 1)[0].split("+", 1)[0];
398
- const parts = core.split(".");
399
- if (!parts.length || !/^\d+$/.test(parts[0]))
400
- return undefined;
401
- return [
402
- Number(parts[0]),
403
- parts[1] && /^\d+$/.test(parts[1]) ? Number(parts[1]) : 0,
404
- parts[2] && /^\d+$/.test(parts[2]) ? Number(parts[2]) : 0,
405
- ];
406
- }
407
- function selectLatestStable(releases) {
408
- let bestTag;
409
- let bestKey = [-1, -1, -1];
410
- for (const rel of releases) {
411
- if (rel.draft || rel.prerelease)
412
- continue;
413
- const tag = String(rel.tag_name || rel.tag || "");
414
- const parsed = parseSemver(tag);
415
- if (!parsed)
416
- continue;
417
- if (parsed[0] > bestKey[0] ||
418
- (parsed[0] === bestKey[0] && parsed[1] > bestKey[1]) ||
419
- (parsed[0] === bestKey[0] && parsed[1] === bestKey[1] && parsed[2] > bestKey[2])) {
420
- bestKey = parsed;
421
- bestTag = tag;
422
- }
423
- }
424
- if (!bestTag)
425
- throw new Error("no stable release found for libaria-engine_ffi");
426
- return stripV(bestTag);
427
- }
428
- function upgradeOrg(site) {
429
- const cfg = configYmlScalar("upgrade_url");
430
- if (cfg)
431
- return cfg.replace(/\/$/, "");
432
- const hint = (site || configYmlScalar("site_url") || DEFAULT_SITE).toLowerCase();
433
- if (hint.includes("ariacompute.cn") || hint.includes("gitee.com")) {
434
- return "https://gitee.com/ariacompute";
435
- }
436
- return "https://github.com/ariacompute";
437
- }
438
- function releasesApiUrl(org) {
439
- const owner = org.replace(/\/$/, "").split("/").pop() || "ariacompute";
440
- if (org.toLowerCase().includes("gitee.com")) {
441
- return `https://gitee.com/api/v5/repos/${owner}/engine/releases?per_page=30`;
442
- }
443
- return `https://api.github.com/repos/${owner}/engine/releases?per_page=30`;
444
- }
445
- function extractFfiArchive(archive, destDir, want = ffiLibName()) {
446
- const tar = zlib.gunzipSync(fs.readFileSync(archive));
447
- let offset = 0;
448
- while (offset + 512 <= tar.length) {
449
- const header = tar.subarray(offset, offset + 512);
450
- if (header.every((b) => b === 0))
451
- break;
452
- const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
453
- const sizeOctal = header.subarray(124, 136).toString("utf8").replace(/\0/g, "").trim();
454
- const size = Number.parseInt(sizeOctal, 8) || 0;
455
- const typeFlag = header[156];
456
- offset += 512;
457
- const isFile = typeFlag === 0 || typeFlag === 48; // '\0' or '0'
458
- if (isFile && path.basename(name) === want) {
459
- fs.mkdirSync(destDir, { recursive: true });
460
- const dest = path.join(destDir, want);
461
- fs.writeFileSync(dest, tar.subarray(offset, offset + size));
462
- try {
463
- fs.chmodSync(dest, 0o755);
464
- }
465
- catch {
466
- /* windows */
467
- }
468
- return dest;
469
- }
470
- offset += Math.ceil(size / 512) * 512;
471
- }
472
- throw new Error(`${want} not found in ${archive}`);
473
- }
474
- function httpGetBytesSync(url) {
475
- 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)})';
476
- const r = (0, node_child_process_1.spawnSync)(process.execPath, ["-e", script], {
477
- env: { ...process.env, ARIA_FFI_URL: url },
478
- encoding: "buffer",
479
- maxBuffer: 256 * 1024 * 1024,
480
- });
481
- if (r.status !== 0) {
482
- throw new Error(`download failed: ${(r.stderr && r.stderr.toString()) || "exit " + r.status}`);
483
- }
484
- return r.stdout;
485
- }
486
- async function httpGetBytes(url) {
487
- const resp = await fetch(url, { headers: { "User-Agent": SDK_UA }, redirect: "follow" });
488
- if (!resp.ok)
489
- throw new Error(`HTTP ${resp.status} ${url}`);
490
- return Buffer.from(await resp.arrayBuffer());
491
- }
492
- function installFfiFromReleases(raw, archiveBytes) {
493
- let releases;
494
- try {
495
- releases = JSON.parse(raw.toString("utf8"));
496
- }
497
- catch {
498
- throw new Error("invalid releases JSON");
499
- }
500
- if (!Array.isArray(releases))
501
- throw new Error("unexpected releases payload");
502
- const ver = selectLatestStable(releases);
503
- const assetName = `libaria-engine_ffi_${ver}_${ffiAssetOs()}.tar.gz`;
504
- let url;
505
- for (const rel of releases) {
506
- const tag = String(rel.tag_name || rel.tag || "");
507
- if (stripV(tag) !== ver)
508
- continue;
509
- const assets = rel.assets || [];
510
- for (const asset of assets) {
511
- if (asset.name === assetName) {
512
- url = asset.browser_download_url || asset.direct_asset_url;
513
- break;
514
- }
515
- }
516
- if (url)
517
- break;
518
- }
519
- if (!url)
520
- throw new Error(`release asset not found: ${assetName}`);
521
- const staging = path.join(ariaHome(), "tmp", `ffi-${ver}`);
522
- fs.rmSync(staging, { recursive: true, force: true });
523
- fs.mkdirSync(staging, { recursive: true });
524
- const archive = path.join(staging, assetName);
525
- try {
526
- fs.writeFileSync(archive, archiveBytes(url));
527
- return extractFfiArchive(archive, libDir(), ffiLibName());
528
- }
529
- finally {
530
- fs.rmSync(staging, { recursive: true, force: true });
531
- }
532
- }
533
- /** Return a path to libaria-engine_ffi, downloading the latest stable Release if needed. */
534
- async function ensureFfiLib(site) {
44
+ function ffiCandidates() {
535
45
  const env = process.env.ARIA_FFI_LIB;
536
- if (env && fs.existsSync(env))
537
- return env;
538
- const bundled = bundledFfiPath();
539
- if (bundled)
540
- return bundled;
541
- const cached = cachedFfiPath();
542
- if (cached)
543
- return cached;
544
- const org = upgradeOrg(site);
545
- const raw = await httpGetBytes(releasesApiUrl(org));
546
- return installFfiFromReleases(raw, (assetUrl) => httpGetBytesSync(assetUrl));
547
- }
548
- function ensureFfiLibSync(site) {
549
- const env = process.env.ARIA_FFI_LIB;
550
- if (env && fs.existsSync(env))
551
- return env;
552
- const bundled = bundledFfiPath();
553
- if (bundled)
554
- return bundled;
555
- const cached = cachedFfiPath();
556
- if (cached)
557
- return cached;
558
- const org = upgradeOrg(site);
559
- const raw = httpGetBytesSync(releasesApiUrl(org));
560
- return installFfiFromReleases(raw, httpGetBytesSync);
561
- }
562
- function loadLib(ffiLib, site) {
563
- const libPath = ffiLib ||
564
- process.env.ARIA_FFI_LIB ||
565
- bundledFfiPath() ||
566
- cachedFfiPath() ||
567
- ensureFfiLibSync(site);
568
- if (!libPath)
569
- throw new Error("Cannot locate libaria-engine_ffi");
570
- const koffiNS = require("koffi");
571
- return koffiNS.load(libPath);
572
- }
573
- function isLocalRef(modelRef) {
574
- return modelRef.includes("/") || modelRef.includes("\\") || fs.existsSync(modelRef);
46
+ if (env)
47
+ return [env];
48
+ const lib = path.join(ariaHome(), "lib");
49
+ if (process.platform === "win32")
50
+ return [path.join(lib, "aria-engine_ffi.dll")];
51
+ if (process.platform === "darwin")
52
+ return [path.join(lib, "libaria-engine_ffi.dylib")];
53
+ return [path.join(lib, "libaria-engine_ffi.so")];
54
+ }
55
+ /** Load koffi binding when native lib is present; otherwise throw with install hint. */
56
+ function loadFfi() {
57
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
58
+ const koffi = require("koffi");
59
+ const candidates = ffiCandidates();
60
+ const found = candidates.find((p) => fs.existsSync(p));
61
+ if (!found) {
62
+ throw new Error(`libaria-engine_ffi not found; run aria-engine upgrade or set ARIA_FFI_LIB (tried ${candidates.join(", ")})`);
63
+ }
64
+ const lib = koffi.load(found);
65
+ return {
66
+ path: found,
67
+ aria_last_error: lib.func("aria_last_error", "str", []),
68
+ aria_model_init: lib.func("aria_model_init", "void *", ["str", "str"]),
69
+ aria_model_destroy: lib.func("aria_model_destroy", "void", ["void *"]),
70
+ aria_systemone: lib.func("aria_systemone", "int", ["void *", "str", "char *", "size_t"]),
71
+ };
575
72
  }
576
- class Engine {
577
- /** Empty construct, or a local bundle directory. */
578
- constructor(bundle, opts = {}) {
579
- this.lib = null;
580
- this.handle = null;
581
- this.cfg = defaultSetupConfig();
582
- this.opts = {};
583
- this.opts = opts;
584
- if (opts.site || opts.hfToken || opts.modelscopeApiToken) {
585
- this.setup({
586
- site_url: opts.site,
587
- hf_token: opts.hfToken,
588
- modelscope_api_token: opts.modelscopeApiToken,
589
- });
590
- }
591
- if (bundle)
592
- this.bindAndInit(bundle, opts.ffiLib);
593
- }
594
- /** Set Config / Run fields on this instance only. Does not write engine.yml. */
595
- setup(updates) {
596
- this.cfg = applySetup(this.cfg, updates);
597
- return this;
598
- }
599
- setupStatus() {
600
- return { ...this.cfg };
601
- }
602
- /** Reset instance defaults. Does not delete ~/.ariacompute/engine.yml. */
603
- setupClear() {
604
- this.cfg = defaultSetupConfig();
605
- return this;
606
- }
607
- bindAndInit(bundle, ffiLib) {
608
- this.lib = loadLib(ffiLib || this.opts.ffiLib, this.cfg.site_url || this.opts.site);
609
- this.fnInit = this.lib.func("aria_model_init", "void*", ["str"]);
610
- this.fnDestroy = this.lib.func("aria_model_destroy", "void", ["void*"]);
611
- this.fnComplete = this.lib.func("aria_complete", "int", ["void*", "str", "str", "str", "void*", "size_t"]);
612
- this.fnEmbed = this.lib.func("aria_embed", "int", ["void*", "str", "void*", "size_t"]);
613
- this.fnTranscribe = this.lib.func("aria_transcribe", "int", ["void*", "void*", "size_t", "str", "void*", "size_t"]);
614
- this.fnLastError = this.lib.func("aria_last_error", "str", []);
615
- this.handle = this.fnInit(bundle);
73
+ class AriaEngine {
74
+ constructor(checkpoint, track = "encoder") {
75
+ this.ffi = loadFfi();
76
+ this.handle = this.ffi.aria_model_init(checkpoint, track);
616
77
  if (!this.handle) {
617
- const err = this.fnLastError();
618
- throw new Error(err || "init failed");
619
- }
620
- }
621
- /** Download (if needed) and load a model using instance setup. */
622
- async open(modelRef) {
623
- await ensureFfiLib(this.cfg.site_url || this.opts.site);
624
- const openOpts = {
625
- ...this.opts,
626
- site: this.cfg.site_url || this.opts.site,
627
- hfToken: this.cfg.hf_token || this.opts.hfToken,
628
- modelscopeApiToken: this.cfg.modelscope_api_token || this.opts.modelscopeApiToken,
629
- };
630
- const bundle = isLocalRef(modelRef) ? modelRef : await downloadModel(modelRef, openOpts);
631
- if (this.handle)
632
- this.close();
633
- this.bindAndInit(bundle, this.opts.ffiLib);
634
- return this;
635
- }
636
- /** Auto-detect: a value containing a separator or already on disk is a local
637
- * path; otherwise it is a model name downloaded from the regional public hub. */
638
- static async open(modelRef, opts = {}) {
639
- const eng = new Engine(undefined, opts);
640
- await eng.open(modelRef);
641
- return eng;
642
- }
643
- complete(messages, opts = {}) {
644
- const messagesJson = JSON.stringify(messages);
645
- const optionsJson = JSON.stringify(opts || { max_tokens: 16 });
646
- const toolsJson = JSON.stringify([]);
647
- const buf = Buffer.alloc(1 << 16);
648
- const rc = this.fnComplete(this.handle, messagesJson, optionsJson, toolsJson, buf, buf.length);
649
- if (rc !== 0) {
650
- const err = this.fnLastError();
651
- return { success: false, response: "", error: err || "complete failed" };
652
- }
653
- try {
654
- const parsed = JSON.parse(buf.toString("utf8").replace(/\0+$/, ""));
655
- return { success: true, response: parsed.response ?? "", generation: parsed };
656
- }
657
- catch {
658
- return { success: true, response: buf.toString("utf8").replace(/\0+$/, "") };
78
+ throw new Error(this.ffi.aria_last_error() || "aria_model_init failed");
659
79
  }
660
80
  }
661
- embed(text) {
81
+ systemone(request) {
662
82
  const buf = Buffer.alloc(1 << 20);
663
- const rc = this.fnEmbed(this.handle, JSON.stringify({ input: text }), buf, buf.length);
83
+ const rc = this.ffi.aria_systemone(this.handle, JSON.stringify(request), buf, buf.length);
664
84
  if (rc !== 0) {
665
- const err = this.fnLastError();
666
- throw new Error(err || "embed failed");
85
+ throw new Error(this.ffi.aria_last_error() || `aria_systemone rc=${rc}`);
667
86
  }
668
- return JSON.parse(buf.toString("utf8").replace(/\0+$/, ""));
87
+ const text = buf.toString("utf8").replace(/\0.*$/, "");
88
+ return JSON.parse(text);
669
89
  }
670
- transcribe(pcm) {
671
- const buf = Buffer.alloc(1 << 16);
672
- const rc = this.fnTranscribe(this.handle, Buffer.from(pcm), pcm.length, null, buf, buf.length);
673
- if (rc !== 0) {
674
- const err = this.fnLastError();
675
- throw new Error(err || "transcribe failed");
90
+ destroy() {
91
+ if (this.handle) {
92
+ this.ffi.aria_model_destroy(this.handle);
93
+ this.handle = null;
676
94
  }
677
- return buf.toString("utf8").replace(/\0+$/, "");
678
- }
679
- close() {
680
- if (this.handle && this.fnDestroy)
681
- this.fnDestroy(this.handle);
682
- this.handle = null;
683
95
  }
684
96
  }
685
- exports.Engine = Engine;
686
- exports._internal = { encoder };
97
+ exports.AriaEngine = AriaEngine;
98
+ exports.default = AriaEngine;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ariacompute/engine-ts",
3
- "version": "1.21.0",
4
- "description": "TypeScript/Node SDK for Aria Engine",
3
+ "version": "2.0.1",
4
+ "description": "TypeScript/Node SDK for AFM-D System One (koffi over libaria-engine_ffi)",
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 test/download.test.mjs"
9
+ "test": "node --test test/binding.test.mjs"
10
10
  },
11
11
  "license": "MIT",
12
12
  "dependencies": {