@ariacompute/router-rn 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 +8 -1
- package/src/index.js +215 -2
- package/test/setup.test.cjs +36 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @ariacompute/router-rn — in-memory setup helpers + Router over native module or host koffi.
|
|
3
|
+
*/
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
2
7
|
|
|
3
8
|
function defaultSetup() {
|
|
4
9
|
return { base_url: '', token: '' };
|
|
@@ -12,4 +17,212 @@ function applySetup(existing, updates) {
|
|
|
12
17
|
return out;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
function ariaHome() {
|
|
21
|
+
return process.env.ARIA_COMPUTE_HOME || path.join(os.homedir(), '.ariacompute');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ffiLibNames() {
|
|
25
|
+
if (process.platform === 'win32') return ['aria-router_ffi.dll', 'aria_router_ffi.dll'];
|
|
26
|
+
if (process.platform === 'darwin') {
|
|
27
|
+
return ['libaria-router_ffi.dylib', 'libaria_router_ffi.dylib'];
|
|
28
|
+
}
|
|
29
|
+
return ['libaria-router_ffi.so', 'libaria_router_ffi.so'];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function firstExisting(dir) {
|
|
33
|
+
for (const name of ffiLibNames()) {
|
|
34
|
+
const p = path.join(dir, name);
|
|
35
|
+
if (fs.existsSync(p)) return p;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveLibPath(explicit) {
|
|
41
|
+
if (explicit && fs.existsSync(explicit)) return explicit;
|
|
42
|
+
const env = process.env.ARIA_ROUTER_FFI_LIB;
|
|
43
|
+
if (env && fs.existsSync(env)) return env;
|
|
44
|
+
const bundled = firstExisting(path.join(__dirname, '..', 'lib'));
|
|
45
|
+
if (bundled) return bundled;
|
|
46
|
+
const cached = firstExisting(path.join(ariaHome(), 'lib'));
|
|
47
|
+
if (cached) return cached;
|
|
48
|
+
throw new Error('libaria-router_ffi not found; set ARIA_ROUTER_FFI_LIB');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function expandHome(p) {
|
|
52
|
+
if (!p) return p;
|
|
53
|
+
if (p === '~') return os.homedir();
|
|
54
|
+
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseJsonBuf(buf) {
|
|
59
|
+
const text = buf.toString('utf8').replace(/\0+$/, '');
|
|
60
|
+
if (!text) return {};
|
|
61
|
+
return JSON.parse(text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tryNativeModule() {
|
|
65
|
+
try {
|
|
66
|
+
const rn = require('react-native');
|
|
67
|
+
return rn?.NativeModules?.AriaRouter || null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createKoffiBackend() {
|
|
74
|
+
let koffi;
|
|
75
|
+
try {
|
|
76
|
+
koffi = require('koffi');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error('koffi required for host FFI: ' + (e && e.message));
|
|
79
|
+
}
|
|
80
|
+
const lib = koffi.load(resolveLibPath());
|
|
81
|
+
return {
|
|
82
|
+
init: lib.func('aria_router_init', 'void*', ['str']),
|
|
83
|
+
connect: lib.func('aria_router_connect', 'void*', ['str']),
|
|
84
|
+
destroy: lib.func('aria_router_destroy', 'void', ['void*']),
|
|
85
|
+
complete: lib.func('aria_router_complete', 'int', ['void*', 'str', 'str', 'void*', 'size_t']),
|
|
86
|
+
models: lib.func('aria_router_models', 'int', ['void*', 'void*', 'size_t']),
|
|
87
|
+
lastRoute: lib.func('aria_router_last_route', 'int', ['void*', 'void*', 'size_t']),
|
|
88
|
+
lastError: lib.func('aria_router_last_error', 'str', []),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class Router {
|
|
93
|
+
constructor() {
|
|
94
|
+
this._auth = defaultSetup();
|
|
95
|
+
this._native = tryNativeModule();
|
|
96
|
+
this._fn = null;
|
|
97
|
+
this._handle = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setup(u = {}) {
|
|
101
|
+
this._auth = applySetup(this._auth, u);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setupStatus() {
|
|
106
|
+
return { ...this._auth };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setupClear() {
|
|
110
|
+
this._auth = defaultSetup();
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_ensureHost() {
|
|
115
|
+
if (this._native) return 'native';
|
|
116
|
+
if (typeof process !== 'undefined' && process.env && process.env.ARIA_ROUTER_FFI_LIB) {
|
|
117
|
+
if (!this._fn) this._fn = createKoffiBackend();
|
|
118
|
+
return 'koffi';
|
|
119
|
+
}
|
|
120
|
+
throw new Error('native AriaRouter module not linked');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_err(fallback) {
|
|
124
|
+
if (this._fn) return this._fn.lastError() || fallback;
|
|
125
|
+
return fallback;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
init(configPath) {
|
|
129
|
+
const mode = this._ensureHost();
|
|
130
|
+
if (mode === 'native') {
|
|
131
|
+
this._native.init(configPath || null);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
if (this._handle) this.close();
|
|
135
|
+
let pathArg = null;
|
|
136
|
+
if (configPath != null && String(configPath).trim() !== '') {
|
|
137
|
+
pathArg = expandHome(String(configPath).trim());
|
|
138
|
+
}
|
|
139
|
+
this._handle = this._fn.init(pathArg);
|
|
140
|
+
if (!this._handle) throw new Error(this._err('init failed'));
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
connect(baseUrl) {
|
|
145
|
+
const mode = this._ensureHost();
|
|
146
|
+
if (mode === 'native') {
|
|
147
|
+
this._native.connect(baseUrl);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
if (this._handle) this.close();
|
|
151
|
+
this._handle = this._fn.connect(baseUrl);
|
|
152
|
+
if (!this._handle) throw new Error(this._err('connect failed'));
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
close() {
|
|
157
|
+
if (this._native) {
|
|
158
|
+
try {
|
|
159
|
+
this._native.destroy?.() || this._native.close?.();
|
|
160
|
+
} catch {
|
|
161
|
+
/* ignore */
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (this._handle && this._fn) this._fn.destroy(this._handle);
|
|
166
|
+
this._handle = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
destroy() {
|
|
170
|
+
this.close();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
complete(messages, options = {}) {
|
|
174
|
+
const mode = this._ensureHost();
|
|
175
|
+
if (mode === 'native') {
|
|
176
|
+
const raw = this._native.complete(JSON.stringify(messages), JSON.stringify(options || {}));
|
|
177
|
+
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
178
|
+
}
|
|
179
|
+
if (!this._handle) throw new Error('router not initialized');
|
|
180
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
181
|
+
const rc = this._fn.complete(
|
|
182
|
+
this._handle,
|
|
183
|
+
JSON.stringify(messages),
|
|
184
|
+
JSON.stringify(options || {}),
|
|
185
|
+
buf,
|
|
186
|
+
buf.length,
|
|
187
|
+
);
|
|
188
|
+
if (rc !== 0) throw new Error(this._err('complete failed'));
|
|
189
|
+
return parseJsonBuf(buf);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
models() {
|
|
193
|
+
const mode = this._ensureHost();
|
|
194
|
+
if (mode === 'native') {
|
|
195
|
+
const raw = this._native.models();
|
|
196
|
+
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
197
|
+
}
|
|
198
|
+
if (!this._handle) throw new Error('router not initialized');
|
|
199
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
200
|
+
const rc = this._fn.models(this._handle, buf, buf.length);
|
|
201
|
+
if (rc !== 0) throw new Error(this._err('models failed'));
|
|
202
|
+
return parseJsonBuf(buf);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lastRoute() {
|
|
206
|
+
const mode = this._ensureHost();
|
|
207
|
+
if (mode === 'native') {
|
|
208
|
+
try {
|
|
209
|
+
const raw = this._native.lastRoute();
|
|
210
|
+
if (!raw) return {};
|
|
211
|
+
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
212
|
+
} catch {
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!this._handle) return {};
|
|
217
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
218
|
+
const rc = this._fn.lastRoute(this._handle, buf, buf.length);
|
|
219
|
+
if (rc !== 0) return {};
|
|
220
|
+
try {
|
|
221
|
+
return parseJsonBuf(buf);
|
|
222
|
+
} catch {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { defaultSetup, applySetup, Router };
|
package/test/setup.test.cjs
CHANGED
|
@@ -3,7 +3,11 @@ const assert = require('node:assert/strict');
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
-
const { applySetup, defaultSetup } = require('../src/index.js');
|
|
6
|
+
const { applySetup, defaultSetup, Router } = require('../src/index.js');
|
|
7
|
+
|
|
8
|
+
const ffi = process.env.ARIA_ROUTER_FFI_LIB;
|
|
9
|
+
const cfg = process.env.ARIA_ROUTER_CONFIG;
|
|
10
|
+
const hasFfi = Boolean(ffi && cfg);
|
|
7
11
|
|
|
8
12
|
test('setup memory only', () => {
|
|
9
13
|
const st = applySetup(defaultSetup(), { base_url: 'http://127.0.0.1:8899', token: 't' });
|
|
@@ -24,3 +28,34 @@ test('setup does not write router.yml', () => {
|
|
|
24
28
|
else process.env.ARIA_COMPUTE_HOME = prev;
|
|
25
29
|
}
|
|
26
30
|
});
|
|
31
|
+
|
|
32
|
+
test('Router setup memory', () => {
|
|
33
|
+
const r = new Router();
|
|
34
|
+
r.setup({ base_url: 'http://127.0.0.1:8899', token: 't' });
|
|
35
|
+
assert.equal(r.setupStatus().token, 't');
|
|
36
|
+
r.setupClear();
|
|
37
|
+
assert.equal(r.setupStatus().token, '');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('init_ok', { skip: !hasFfi }, () => {
|
|
41
|
+
const r = new Router().init(cfg);
|
|
42
|
+
try {
|
|
43
|
+
const m = r.models();
|
|
44
|
+
assert.ok(JSON.stringify(m).includes('semantic-auto'));
|
|
45
|
+
} finally {
|
|
46
|
+
r.close();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('complete_ok', { skip: !hasFfi }, () => {
|
|
51
|
+
const r = new Router().init(cfg);
|
|
52
|
+
try {
|
|
53
|
+
const out = r.complete(
|
|
54
|
+
[{ role: 'user', content: 'hi' }],
|
|
55
|
+
{ model: 'ariacompute/semantic-auto' },
|
|
56
|
+
);
|
|
57
|
+
assert.ok(JSON.stringify(out).includes('hello-from-router'));
|
|
58
|
+
} finally {
|
|
59
|
+
r.close();
|
|
60
|
+
}
|
|
61
|
+
});
|