@ariacompute/router-ts 1.2.0 → 1.3.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/package.json +4 -1
- package/src/index.js +149 -0
- package/test/binding.test.mjs +62 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function ariaHome() {
|
|
11
|
+
return process.env.ARIA_COMPUTE_HOME || path.join(os.homedir(), ".ariacompute");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ffiLibNames() {
|
|
15
|
+
if (process.platform === "win32") {
|
|
16
|
+
return ["aria-router_ffi.dll", "aria_router_ffi.dll"];
|
|
17
|
+
}
|
|
18
|
+
if (process.platform === "darwin") {
|
|
19
|
+
return ["libaria-router_ffi.dylib", "libaria_router_ffi.dylib"];
|
|
20
|
+
}
|
|
21
|
+
return ["libaria-router_ffi.so", "libaria_router_ffi.so"];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function firstExisting(dir) {
|
|
25
|
+
for (const name of ffiLibNames()) {
|
|
26
|
+
const p = path.join(dir, name);
|
|
27
|
+
if (fs.existsSync(p)) return p;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveLibPath(explicit) {
|
|
33
|
+
if (explicit && fs.existsSync(explicit)) return explicit;
|
|
34
|
+
const env = process.env.ARIA_ROUTER_FFI_LIB;
|
|
35
|
+
if (env && fs.existsSync(env)) return env;
|
|
36
|
+
const bundled = firstExisting(path.join(__dirname, "..", "lib"));
|
|
37
|
+
if (bundled) return bundled;
|
|
38
|
+
const cached = firstExisting(path.join(ariaHome(), "lib"));
|
|
39
|
+
if (cached) return cached;
|
|
40
|
+
throw new Error("libaria-router_ffi not found; set ARIA_ROUTER_FFI_LIB");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function expandHome(p) {
|
|
44
|
+
if (!p) return p;
|
|
45
|
+
if (p === "~") return os.homedir();
|
|
46
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
47
|
+
return p;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseJsonBuf(buf) {
|
|
51
|
+
const text = buf.toString("utf8").replace(/\0+$/, "");
|
|
52
|
+
if (!text) return {};
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
export class Router {
|
|
2
57
|
constructor() {
|
|
3
58
|
this._auth = { base_url: "", token: "" };
|
|
59
|
+
this._lib = null;
|
|
60
|
+
this._handle = null;
|
|
61
|
+
this._fn = {};
|
|
4
62
|
}
|
|
5
63
|
|
|
6
64
|
setup(u = {}) {
|
|
@@ -17,4 +75,95 @@ export class Router {
|
|
|
17
75
|
this._auth = { base_url: "", token: "" };
|
|
18
76
|
return this;
|
|
19
77
|
}
|
|
78
|
+
|
|
79
|
+
_ensure(ffiLib) {
|
|
80
|
+
if (this._lib) return;
|
|
81
|
+
const koffi = require("koffi");
|
|
82
|
+
this._lib = koffi.load(resolveLibPath(ffiLib));
|
|
83
|
+
this._fn.init = this._lib.func("aria_router_init", "void*", ["str"]);
|
|
84
|
+
this._fn.connect = this._lib.func("aria_router_connect", "void*", ["str"]);
|
|
85
|
+
this._fn.destroy = this._lib.func("aria_router_destroy", "void", ["void*"]);
|
|
86
|
+
this._fn.complete = this._lib.func("aria_router_complete", "int", [
|
|
87
|
+
"void*",
|
|
88
|
+
"str",
|
|
89
|
+
"str",
|
|
90
|
+
"void*",
|
|
91
|
+
"size_t",
|
|
92
|
+
]);
|
|
93
|
+
this._fn.models = this._lib.func("aria_router_models", "int", ["void*", "void*", "size_t"]);
|
|
94
|
+
this._fn.lastRoute = this._lib.func("aria_router_last_route", "int", [
|
|
95
|
+
"void*",
|
|
96
|
+
"void*",
|
|
97
|
+
"size_t",
|
|
98
|
+
]);
|
|
99
|
+
this._fn.lastError = this._lib.func("aria_router_last_error", "str", []);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_err(fallback) {
|
|
103
|
+
const err = this._fn.lastError?.();
|
|
104
|
+
return err || fallback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
init(configPath, ffiLib) {
|
|
108
|
+
this._ensure(ffiLib);
|
|
109
|
+
if (this._handle) this.close();
|
|
110
|
+
let pathArg = null;
|
|
111
|
+
if (configPath != null && String(configPath).trim() !== "") {
|
|
112
|
+
pathArg = expandHome(String(configPath).trim());
|
|
113
|
+
}
|
|
114
|
+
this._handle = this._fn.init(pathArg);
|
|
115
|
+
if (!this._handle) throw new Error(this._err("init failed"));
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
connect(baseUrl, ffiLib) {
|
|
120
|
+
this._ensure(ffiLib);
|
|
121
|
+
if (this._handle) this.close();
|
|
122
|
+
this._handle = this._fn.connect(baseUrl);
|
|
123
|
+
if (!this._handle) throw new Error(this._err("connect failed"));
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
close() {
|
|
128
|
+
if (this._handle && this._fn.destroy) this._fn.destroy(this._handle);
|
|
129
|
+
this._handle = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
destroy() {
|
|
133
|
+
this.close();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
complete(messages, options = {}) {
|
|
137
|
+
if (!this._handle) throw new Error("router not initialized");
|
|
138
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
139
|
+
const rc = this._fn.complete(
|
|
140
|
+
this._handle,
|
|
141
|
+
JSON.stringify(messages),
|
|
142
|
+
JSON.stringify(options || {}),
|
|
143
|
+
buf,
|
|
144
|
+
buf.length,
|
|
145
|
+
);
|
|
146
|
+
if (rc !== 0) throw new Error(this._err("complete failed"));
|
|
147
|
+
return parseJsonBuf(buf);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
models() {
|
|
151
|
+
if (!this._handle) throw new Error("router not initialized");
|
|
152
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
153
|
+
const rc = this._fn.models(this._handle, buf, buf.length);
|
|
154
|
+
if (rc !== 0) throw new Error(this._err("models failed"));
|
|
155
|
+
return parseJsonBuf(buf);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
lastRoute() {
|
|
159
|
+
if (!this._handle) return {};
|
|
160
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
161
|
+
const rc = this._fn.lastRoute(this._handle, buf, buf.length);
|
|
162
|
+
if (rc !== 0) return {};
|
|
163
|
+
try {
|
|
164
|
+
return parseJsonBuf(buf);
|
|
165
|
+
} catch {
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
20
169
|
}
|
package/test/binding.test.mjs
CHANGED
|
@@ -2,6 +2,10 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { Router } from "../src/index.js";
|
|
4
4
|
|
|
5
|
+
const ffi = process.env.ARIA_ROUTER_FFI_LIB;
|
|
6
|
+
const cfg = process.env.ARIA_ROUTER_CONFIG;
|
|
7
|
+
const hasFfi = Boolean(ffi && cfg);
|
|
8
|
+
|
|
5
9
|
test("setup memory only", () => {
|
|
6
10
|
const r = new Router();
|
|
7
11
|
r.setup({ base_url: "http://127.0.0.1:8899", token: "t" });
|
|
@@ -9,3 +13,61 @@ test("setup memory only", () => {
|
|
|
9
13
|
r.setupClear();
|
|
10
14
|
assert.equal(r.setupStatus().token, "");
|
|
11
15
|
});
|
|
16
|
+
|
|
17
|
+
test("init_ok", { skip: !hasFfi }, () => {
|
|
18
|
+
const r = new Router().init(cfg);
|
|
19
|
+
try {
|
|
20
|
+
const m = r.models();
|
|
21
|
+
assert.ok(JSON.stringify(m).includes("semantic-auto"));
|
|
22
|
+
} finally {
|
|
23
|
+
r.close();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("models_ok", { skip: !hasFfi }, () => {
|
|
28
|
+
const r = new Router().init(cfg);
|
|
29
|
+
try {
|
|
30
|
+
const m = r.models();
|
|
31
|
+
const data = m.data || [];
|
|
32
|
+
assert.ok(data.some((x) => String(x.id || x).includes("semantic-auto")));
|
|
33
|
+
} finally {
|
|
34
|
+
r.close();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("complete_ok", { skip: !hasFfi }, () => {
|
|
39
|
+
const r = new Router().init(cfg);
|
|
40
|
+
try {
|
|
41
|
+
const out = r.complete(
|
|
42
|
+
[{ role: "user", content: "hi" }],
|
|
43
|
+
{ model: "ariacompute/semantic-auto" },
|
|
44
|
+
);
|
|
45
|
+
assert.ok(JSON.stringify(out).includes("hello-from-router"));
|
|
46
|
+
} finally {
|
|
47
|
+
r.close();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("init_missing_path", { skip: !hasFfi }, () => {
|
|
52
|
+
assert.throws(() => new Router().init("/no/such.yaml"), /./);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("connect_without_server", { skip: !hasFfi }, () => {
|
|
56
|
+
const r = new Router().connect("http://127.0.0.1:9");
|
|
57
|
+
try {
|
|
58
|
+
assert.ok(r);
|
|
59
|
+
} finally {
|
|
60
|
+
r.close();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("last_route_after_complete", { skip: !hasFfi }, () => {
|
|
65
|
+
const r = new Router().init(cfg);
|
|
66
|
+
try {
|
|
67
|
+
r.complete([{ role: "user", content: "hi" }], { model: "ariacompute/semantic-auto" });
|
|
68
|
+
const lr = r.lastRoute();
|
|
69
|
+
assert.equal(lr.layer, "semantic");
|
|
70
|
+
} finally {
|
|
71
|
+
r.close();
|
|
72
|
+
}
|
|
73
|
+
});
|