@ariacompute/engine-ts 1.5.0 → 1.6.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 +13 -0
- package/dist/index.d.ts +56 -6
- package/dist/index.js +227 -61
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -11,3 +11,16 @@ const eng = new Engine("/path/to/bundle");
|
|
|
11
11
|
console.log(eng.complete([{ role: "user", content: "hi" }]));
|
|
12
12
|
eng.close();
|
|
13
13
|
```
|
|
14
|
+
|
|
15
|
+
## Auto-download by model name
|
|
16
|
+
|
|
17
|
+
`Engine.open(modelRef, { token, site })` accepts a local bundle path **or** an
|
|
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 Dashboard
|
|
20
|
+
private source (requires `token`; `site` defaults to `https://ariacompute.com`)
|
|
21
|
+
into `~/.ariacompute/models/{model}` and then loads it. A valid cached bundle is
|
|
22
|
+
reused without re-downloading.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const eng = await Engine.open("gemma-4-e2b-it_q4", { token: "DASHBOARD_TOKEN" });
|
|
26
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,63 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
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";
|
|
3
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
|
+
/** Dashboard bearer token. Required when modelRef is a model name. */
|
|
31
|
+
token?: string;
|
|
32
|
+
/** Dashboard base URL. Defaults to https://ariacompute.com. */
|
|
33
|
+
site?: string;
|
|
34
|
+
/** Explicit path to the FFI library. */
|
|
35
|
+
ffiLib?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Parse `slug`/`quant` from a model name such as `gemma-4-e2b-it_q4`. */
|
|
38
|
+
export declare function parseBundleName(model: string): {
|
|
39
|
+
slug: string;
|
|
40
|
+
quant: string;
|
|
4
41
|
};
|
|
42
|
+
/** Download `model` from the Dashboard private source into
|
|
43
|
+
* `~/.ariacompute/models/{model}` and return that directory.
|
|
44
|
+
* Skips the download when a valid bundle is already cached. */
|
|
45
|
+
export declare function downloadModel(model: string, token: string, site?: string): Promise<string>;
|
|
46
|
+
export declare function isLocalRef(modelRef: string): boolean;
|
|
5
47
|
export declare class Engine {
|
|
6
48
|
private lib;
|
|
7
49
|
private handle;
|
|
8
|
-
|
|
50
|
+
/** Construct from a local bundle directory. */
|
|
51
|
+
constructor(bundle: string, opts?: OpenOptions);
|
|
52
|
+
/** Auto-detect: a value containing a separator or already on disk is a local
|
|
53
|
+
* path; otherwise it is a model name that is downloaded (requires `token`)
|
|
54
|
+
* then loaded. */
|
|
55
|
+
static open(modelRef: string, opts?: OpenOptions): Promise<Engine>;
|
|
56
|
+
complete(messages: Turn[], opts?: GenerateOptions): CompleteResult;
|
|
57
|
+
embed(text: string): number[];
|
|
58
|
+
transcribe(pcm: Uint8Array): string;
|
|
9
59
|
close(): void;
|
|
10
|
-
complete(messages: ChatMessage[], options?: Record<string, unknown>, tools?: unknown[]): any;
|
|
11
|
-
embed(input: string): any;
|
|
12
|
-
transcribe(pcm: Buffer): any;
|
|
13
60
|
}
|
|
61
|
+
export declare const _internal: {
|
|
62
|
+
encoder: TextEncoder;
|
|
63
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,75 +1,241 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"void *",
|
|
27
|
-
"uint8 *",
|
|
28
|
-
"size_t",
|
|
29
|
-
"str",
|
|
30
|
-
"uint8 *",
|
|
31
|
-
"size_t",
|
|
32
|
-
]),
|
|
33
|
-
lastError: lib.func("aria_last_error", "str", []),
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
34
26
|
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._internal = exports.Engine = void 0;
|
|
37
|
+
exports.parseBundleName = parseBundleName;
|
|
38
|
+
exports.downloadModel = downloadModel;
|
|
39
|
+
exports.isLocalRef = isLocalRef;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const fflate = __importStar(require("fflate"));
|
|
44
|
+
const DEFAULT_SITE = "https://ariacompute.com";
|
|
45
|
+
function ariaHome() {
|
|
46
|
+
return process.env.ARIA_COMPUTE_HOME || path.join(os.homedir(), ".ariacompute");
|
|
47
|
+
}
|
|
48
|
+
function cacheDir(model) {
|
|
49
|
+
return path.join(ariaHome(), "models", model);
|
|
50
|
+
}
|
|
51
|
+
/** Parse `slug`/`quant` from a model name such as `gemma-4-e2b-it_q4`. */
|
|
52
|
+
function parseBundleName(model) {
|
|
53
|
+
if (!model || model.includes("/") || model.includes("\\")) {
|
|
54
|
+
throw new Error(`invalid model name: ${model}`);
|
|
55
|
+
}
|
|
56
|
+
const idx = model.lastIndexOf("_q");
|
|
57
|
+
if (idx !== -1) {
|
|
58
|
+
const slug = model.slice(0, idx);
|
|
59
|
+
const suffix = model.slice(idx + 2);
|
|
60
|
+
const quant = suffix === "4"
|
|
61
|
+
? "int4"
|
|
62
|
+
: suffix === "8"
|
|
63
|
+
? "int8"
|
|
64
|
+
: suffix === "326" || suffix === "3.26"
|
|
65
|
+
? "int326"
|
|
66
|
+
: (() => {
|
|
67
|
+
throw new Error(`unknown quant suffix _q${suffix}`);
|
|
68
|
+
})();
|
|
69
|
+
if (!slug)
|
|
70
|
+
throw new Error(`invalid model name: ${model}`);
|
|
71
|
+
return { slug, quant };
|
|
72
|
+
}
|
|
73
|
+
return { slug: model, quant: "int4" };
|
|
35
74
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
function isValidBundle(dir) {
|
|
76
|
+
try {
|
|
77
|
+
if (!fs.statSync(path.join(dir, "weight.bin")).isFile())
|
|
78
|
+
return false;
|
|
79
|
+
const meta = JSON.parse(fs.readFileSync(path.join(dir, "config.json"), "utf8"));
|
|
80
|
+
return meta.format === "aria-quant-bundle";
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function flattenSingleSubdir(dir) {
|
|
87
|
+
const entries = fs.readdirSync(dir).filter((e) => !e.startsWith("."));
|
|
88
|
+
if (entries.length !== 1)
|
|
89
|
+
return;
|
|
90
|
+
const only = path.join(dir, entries[0]);
|
|
91
|
+
if (!fs.statSync(only).isDirectory())
|
|
92
|
+
return;
|
|
93
|
+
if (!fs.existsSync(path.join(only, "config.json")))
|
|
94
|
+
return;
|
|
95
|
+
for (const name of fs.readdirSync(only)) {
|
|
96
|
+
fs.renameSync(path.join(only, name), path.join(dir, name));
|
|
97
|
+
}
|
|
98
|
+
fs.rmdirSync(only);
|
|
99
|
+
}
|
|
100
|
+
async function extractZip(data, dest) {
|
|
101
|
+
if (data[0] !== 0x50 || data[1] !== 0x4b) {
|
|
102
|
+
throw new Error("downloaded archive is not a valid zip");
|
|
103
|
+
}
|
|
104
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
105
|
+
const entries = fflate.unzipSync(data);
|
|
106
|
+
for (const [name, content] of Object.entries(entries)) {
|
|
107
|
+
const out = path.join(dest, name);
|
|
108
|
+
if (name.endsWith("/")) {
|
|
109
|
+
fs.mkdirSync(out, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
113
|
+
fs.writeFileSync(out, content);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
flattenSingleSubdir(dest);
|
|
117
|
+
}
|
|
118
|
+
async function httpGet(url, token) {
|
|
119
|
+
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
120
|
+
if (!resp.ok)
|
|
121
|
+
return { ok: false, status: resp.status, body: null };
|
|
122
|
+
const ct = resp.headers.get("content-type") || "";
|
|
123
|
+
if (ct.includes("json"))
|
|
124
|
+
return { ok: true, status: resp.status, body: await resp.json() };
|
|
125
|
+
return { ok: true, status: resp.status, body: Buffer.from(await resp.arrayBuffer()) };
|
|
126
|
+
}
|
|
127
|
+
const encoder = new TextEncoder();
|
|
128
|
+
/** Download `model` from the Dashboard private source into
|
|
129
|
+
* `~/.ariacompute/models/{model}` and return that directory.
|
|
130
|
+
* Skips the download when a valid bundle is already cached. */
|
|
131
|
+
async function downloadModel(model, token, site = DEFAULT_SITE) {
|
|
132
|
+
if (!token)
|
|
133
|
+
throw new Error("dashboard token is required to download a model");
|
|
134
|
+
const { slug, quant } = parseBundleName(model);
|
|
135
|
+
const cache = cacheDir(model);
|
|
136
|
+
if (fs.existsSync(cache) && isValidBundle(cache))
|
|
137
|
+
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
|
+
const staging = path.join(ariaHome(), "models", `.${model}.partial`);
|
|
150
|
+
if (fs.existsSync(staging))
|
|
151
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
152
|
+
await extractZip(data, staging);
|
|
153
|
+
if (!isValidBundle(staging)) {
|
|
154
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
155
|
+
throw new Error("downloaded archive did not contain a valid aria-quant-bundle");
|
|
156
|
+
}
|
|
157
|
+
if (fs.existsSync(cache))
|
|
158
|
+
fs.rmSync(cache, { recursive: true, force: true });
|
|
159
|
+
fs.renameSync(staging, cache);
|
|
160
|
+
return cache;
|
|
161
|
+
}
|
|
162
|
+
function loadLib(ffiLib) {
|
|
163
|
+
const libPath = ffiLib || process.env.ARIA_FFI_LIB;
|
|
164
|
+
if (!libPath)
|
|
165
|
+
throw new Error("aria FFI lib not found; set ARIA_FFI_LIB or pass ffiLib");
|
|
166
|
+
const koffiNS = require("koffi");
|
|
167
|
+
return koffiNS.load(libPath);
|
|
168
|
+
}
|
|
169
|
+
function isLocalRef(modelRef) {
|
|
170
|
+
return modelRef.includes("/") || modelRef.includes("\\") || fs.existsSync(modelRef);
|
|
39
171
|
}
|
|
40
172
|
class Engine {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
|
|
173
|
+
/** Construct from a local bundle directory. */
|
|
174
|
+
constructor(bundle, opts = {}) {
|
|
175
|
+
this.lib = loadLib(opts.ffiLib);
|
|
176
|
+
this.lib.func("aria_model_init", "void*", ["str"]);
|
|
177
|
+
this.lib.func("aria_model_destroy", "void", ["void*"]);
|
|
178
|
+
this.lib.func("aria_complete", "int", ["void*", "str", "str", "str", "str", "size_t"]);
|
|
179
|
+
this.lib.func("aria_embed", "int", ["void*", "str", "str", "size_t"]);
|
|
180
|
+
this.lib.func("aria_transcribe", "int", ["void*", "void*", "size_t", "str", "str", "size_t"]);
|
|
181
|
+
this.lib.func("aria_last_error", "str", []);
|
|
182
|
+
this.handle = this.lib.aria_model_init(bundle);
|
|
183
|
+
if (!this.handle) {
|
|
184
|
+
const err = this.lib.aria_last_error();
|
|
185
|
+
throw new Error(err || "init failed");
|
|
186
|
+
}
|
|
46
187
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
188
|
+
/** Auto-detect: a value containing a separator or already on disk is a local
|
|
189
|
+
* path; otherwise it is a model name that is downloaded (requires `token`)
|
|
190
|
+
* then loaded. */
|
|
191
|
+
static async open(modelRef, opts = {}) {
|
|
192
|
+
if (isLocalRef(modelRef))
|
|
193
|
+
return new Engine(modelRef, opts);
|
|
194
|
+
if (!opts.token) {
|
|
195
|
+
throw new Error(`model name '${modelRef}' requires a dashboard token to download`);
|
|
51
196
|
}
|
|
197
|
+
const bundle = await downloadModel(modelRef, opts.token, opts.site ?? DEFAULT_SITE);
|
|
198
|
+
return new Engine(bundle, opts);
|
|
52
199
|
}
|
|
53
|
-
complete(messages,
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
200
|
+
complete(messages, opts = {}) {
|
|
201
|
+
const payload = JSON.stringify({ messages, opts });
|
|
202
|
+
const buf = Buffer.alloc(1 << 16);
|
|
203
|
+
const rc = this.lib.aria_complete(this.handle, payload, "", "", buf, buf.length);
|
|
204
|
+
if (rc !== 0) {
|
|
205
|
+
const err = this.lib.aria_last_error();
|
|
206
|
+
return { success: false, response: "", error: err || "complete failed" };
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(buf.toString("utf8").replace(/\0+$/, ""));
|
|
210
|
+
return { success: true, response: parsed.response ?? "", generation: parsed };
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return { success: true, response: buf.toString("utf8").replace(/\0+$/, "") };
|
|
214
|
+
}
|
|
59
215
|
}
|
|
60
|
-
embed(
|
|
61
|
-
const
|
|
62
|
-
const rc = this.lib.
|
|
63
|
-
if (rc !== 0)
|
|
64
|
-
|
|
65
|
-
|
|
216
|
+
embed(text) {
|
|
217
|
+
const buf = Buffer.alloc(1 << 20);
|
|
218
|
+
const rc = this.lib.aria_embed(this.handle, text, buf, buf.length);
|
|
219
|
+
if (rc !== 0) {
|
|
220
|
+
const err = this.lib.aria_last_error();
|
|
221
|
+
throw new Error(err || "embed failed");
|
|
222
|
+
}
|
|
223
|
+
return JSON.parse(buf.toString("utf8").replace(/\0+$/, ""));
|
|
66
224
|
}
|
|
67
225
|
transcribe(pcm) {
|
|
68
|
-
const
|
|
69
|
-
const rc = this.lib.
|
|
70
|
-
if (rc !== 0)
|
|
71
|
-
|
|
72
|
-
|
|
226
|
+
const buf = Buffer.alloc(1 << 16);
|
|
227
|
+
const rc = this.lib.aria_transcribe(this.handle, pcm, pcm.length, "", buf, buf.length);
|
|
228
|
+
if (rc !== 0) {
|
|
229
|
+
const err = this.lib.aria_last_error();
|
|
230
|
+
throw new Error(err || "transcribe failed");
|
|
231
|
+
}
|
|
232
|
+
return buf.toString("utf8").replace(/\0+$/, "");
|
|
233
|
+
}
|
|
234
|
+
close() {
|
|
235
|
+
if (this.handle)
|
|
236
|
+
this.lib.aria_model_destroy(this.handle);
|
|
237
|
+
this.handle = null;
|
|
73
238
|
}
|
|
74
239
|
}
|
|
75
240
|
exports.Engine = Engine;
|
|
241
|
+
exports._internal = { encoder };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ariacompute/engine-ts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "TypeScript/Node SDK for Aria Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"fflate": "^0.8.2",
|
|
13
14
|
"koffi": "^2.8.0"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|