neuralos 3.9.5 → 3.10.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/LICENSE +202 -0
- package/README.md +103 -337
- package/cli.js +103 -0
- package/engine.js +124 -0
- package/index.js +230 -0
- package/package.json +38 -43
- package/test.js +20 -0
- package/tools.js +89 -0
- package/validate.js +263 -0
- package/bin/gybackend.cjs +0 -411663
- package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
- package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
- package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
- package/plugins/agentspan-bridge/index.mjs +0 -389
- package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
- package/plugins/agentspan-bridge/plugin.json +0 -27
- package/plugins/fraudops/index.d.mts +0 -9
- package/plugins/fraudops/index.mjs +0 -185
- package/plugins/fraudops/plugin.json +0 -10
- package/plugins/iam-connector/index.d.mts +0 -10
- package/plugins/iam-connector/index.mjs +0 -229
- package/plugins/iam-connector/plugin.json +0 -10
- package/plugins/mitmproxy-bridge/index.d.mts +0 -20
- package/plugins/mitmproxy-bridge/index.mjs +0 -256
- package/plugins/mitmproxy-bridge/plugin.json +0 -17
- package/plugins/monid-bridge/index.mjs +0 -228
- package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
- package/plugins/monid-bridge/plugin.json +0 -15
- package/plugins/netdata-rterm/index.mjs +0 -229
- package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
- package/plugins/netdata-rterm/plugin.json +0 -22
- package/plugins/netexec-bridge/index.d.mts +0 -28
- package/plugins/netexec-bridge/index.mjs +0 -325
- package/plugins/netexec-bridge/plugin.json +0 -20
- package/plugins/neuralos/index.mjs +0 -137
- package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
- package/plugins/neuralos/neuralosEngine.mjs +0 -369
- package/plugins/neuralos/plugin.json +0 -15
- package/plugins/numbat-bridge/index.mjs +0 -229
- package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
- package/plugins/numbat-bridge/plugin.json +0 -21
- package/plugins/patch-manager/index.d.mts +0 -10
- package/plugins/patch-manager/index.mjs +0 -288
- package/plugins/patch-manager/plugin.json +0 -10
- package/plugins/promptfoo-redteam/index.d.mts +0 -15
- package/plugins/promptfoo-redteam/index.mjs +0 -277
- package/plugins/promptfoo-redteam/plugin.json +0 -16
- package/plugins/reactorpro-bridge/index.mjs +0 -499
- package/plugins/reactorpro-bridge/plugin.json +0 -26
- package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
- package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
- package/plugins/request-router/index.d.mts +0 -9
- package/plugins/request-router/index.mjs +0 -239
- package/plugins/request-router/plugin.json +0 -10
- package/plugins/sample-k8s-slo/index.ts +0 -58
- package/plugins/sample-k8s-slo/plugin.json +0 -11
- package/plugins/sop-assistant/index.d.mts +0 -9
- package/plugins/sop-assistant/index.mjs +0 -325
- package/plugins/sop-assistant/plugin.json +0 -10
- package/plugins/synapse-bridge/index.mjs +0 -484
- package/plugins/synapse-bridge/plugin.json +0 -29
- package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
- package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
- package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
- package/plugins/web-intel/index.mjs +0 -381
- package/plugins/web-intel/plugin.json +0 -28
- package/plugins/web-intel/sidecar.mjs +0 -134
- package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
- package/plugins/web-intel/wigoloClient.mjs +0 -145
package/engine.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* neuralOS engine loader — locates the platform engine package
|
|
5
|
+
* (libneedle3 shared library + needle3.cact weights), binds the C ABI
|
|
6
|
+
* through koffi, and loads the base weights once per process.
|
|
7
|
+
*
|
|
8
|
+
* The C ABI mirrors needle/__init__.py in the Python distribution:
|
|
9
|
+
* int needle_init(const char *system, const char *tools, const char *tool_index)
|
|
10
|
+
* int needle_complete(const char *text, int max_new_tokens, char *buffer, int buffer_len)
|
|
11
|
+
* int needle_embed(const char *text, float *out, int dim)
|
|
12
|
+
* void needle_reset(void)
|
|
13
|
+
* int needle_load(const char *data, unsigned long long len)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const koffi = require('koffi');
|
|
19
|
+
|
|
20
|
+
const PLATFORM_PACKAGES = {
|
|
21
|
+
'darwin-arm64': { pkg: 'neuralos-darwin-arm64', lib: 'libneedle3.dylib' },
|
|
22
|
+
'darwin-x64': { pkg: 'neuralos-darwin-x64', lib: 'libneedle3.dylib' },
|
|
23
|
+
'linux-x64-gnu': { pkg: 'neuralos-linux-x64-gnu', lib: 'libneedle3.so' },
|
|
24
|
+
'linux-arm64-gnu': { pkg: 'neuralos-linux-arm64-gnu', lib: 'libneedle3.so' },
|
|
25
|
+
'win32-x64': { pkg: 'neuralos-win32-x64', lib: 'libneedle3.dll' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function platformKey() {
|
|
29
|
+
const p = process.platform;
|
|
30
|
+
const a = process.arch;
|
|
31
|
+
if (p === 'darwin') return a === 'arm64' ? 'darwin-arm64' : (a === 'x64' ? 'darwin-x64' : null);
|
|
32
|
+
if (p === 'win32') return a === 'x64' ? 'win32-x64' : null;
|
|
33
|
+
if (p === 'linux') {
|
|
34
|
+
if (a !== 'x64' && a !== 'arm64') return null;
|
|
35
|
+
// musl (Alpine) engines are not bundled; detect via process.report.
|
|
36
|
+
try {
|
|
37
|
+
const report = process.report && process.report.getReport && process.report.getReport();
|
|
38
|
+
if (report && report.header && !report.header.glibcVersionRuntime) return null;
|
|
39
|
+
} catch (_) { /* assume glibc */ }
|
|
40
|
+
return a === 'x64' ? 'linux-x64-gnu' : 'linux-arm64-gnu';
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function platformPackageDir() {
|
|
46
|
+
const key = platformKey();
|
|
47
|
+
if (!key) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`neuralOS: no bundled engine for ${process.platform}/${process.arch}. ` +
|
|
50
|
+
'Supported: darwin arm64/x64, linux x64/arm64 (glibc), win32 x64.');
|
|
51
|
+
}
|
|
52
|
+
const spec = PLATFORM_PACKAGES[key];
|
|
53
|
+
let dir;
|
|
54
|
+
try {
|
|
55
|
+
dir = path.dirname(require.resolve(spec.pkg + '/package.json'));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`neuralOS: engine package ${spec.pkg} is not installed. Reinstall neuralos ` +
|
|
59
|
+
`(optional dependencies must be allowed: npm i neuralos --include=optional).`);
|
|
60
|
+
}
|
|
61
|
+
return { dir, lib: spec.lib, key };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let _engine = null;
|
|
65
|
+
|
|
66
|
+
function engine() {
|
|
67
|
+
if (_engine) return _engine;
|
|
68
|
+
const { dir, lib: libName } = platformPackageDir();
|
|
69
|
+
const libPath = path.join(dir, 'bin', libName);
|
|
70
|
+
const weightsPath = path.join(dir, 'bin', 'needle3.cact');
|
|
71
|
+
if (!fs.existsSync(libPath)) throw new Error(`neuralOS: engine library missing: ${libPath}`);
|
|
72
|
+
if (!fs.existsSync(weightsPath)) throw new Error(`neuralOS: weights missing: ${weightsPath}`);
|
|
73
|
+
|
|
74
|
+
const lib = koffi.load(libPath);
|
|
75
|
+
const needleInit = lib.func('int needle_init(const char *system, const char *tools, const char *tool_index)');
|
|
76
|
+
const needleComplete = lib.func('int needle_complete(const char *text, int max_new_tokens, _Out_ char *buffer, int buffer_len)');
|
|
77
|
+
const needleEmbed = lib.func('int needle_embed(const char *text, float *out, int dim)');
|
|
78
|
+
const needleReset = lib.func('void needle_reset()');
|
|
79
|
+
const needleLoad = lib.func('int needle_load(const char *data, unsigned long long len)');
|
|
80
|
+
|
|
81
|
+
const weights = fs.readFileSync(weightsPath);
|
|
82
|
+
const dataPtr = koffi.as(weights, 'const char *');
|
|
83
|
+
if (needleLoad(dataPtr, weights.length) < 0) {
|
|
84
|
+
throw new Error(`neuralOS: needle_load failed for ${weightsPath}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_engine = {
|
|
88
|
+
libPath,
|
|
89
|
+
weightsPath,
|
|
90
|
+
init: (systemText, toolsJson, toolIndexPath) =>
|
|
91
|
+
needleInit(systemText, toolsJson, toolIndexPath || null),
|
|
92
|
+
complete: (text, maxNewTokens, buf, len) =>
|
|
93
|
+
needleComplete(text, maxNewTokens, buf, len),
|
|
94
|
+
embed: (text, out, dim) => needleEmbed(text, out, dim),
|
|
95
|
+
reset: () => needleReset(),
|
|
96
|
+
allocBuffer: (n) => koffi.alloc('char', n),
|
|
97
|
+
allocFloats: (n) => koffi.alloc('float', n),
|
|
98
|
+
// koffi.decode(ptr, 'string') would deref the pointer as char**; the
|
|
99
|
+
// correct read of a raw char buffer is 'char' + length, NUL-trimmed.
|
|
100
|
+
decodeString: (ptr, n) => {
|
|
101
|
+
const bytes = Buffer.from(koffi.decode(ptr, 'char', n));
|
|
102
|
+
const end = bytes.indexOf(0);
|
|
103
|
+
return bytes.subarray(0, end === -1 ? bytes.length : end).toString('utf8');
|
|
104
|
+
},
|
|
105
|
+
decodeFloats: (ptr, n) => koffi.decode(ptr, 'float', n),
|
|
106
|
+
};
|
|
107
|
+
return _engine;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The currently bound agent (needle_init is engine-global, like Python's). */
|
|
111
|
+
let _active = null;
|
|
112
|
+
|
|
113
|
+
function bindAgent(agent) {
|
|
114
|
+
const e = engine();
|
|
115
|
+
if (_active === agent) return e;
|
|
116
|
+
if (e.init(agent.systemText, agent.toolsJson, null) < 0) {
|
|
117
|
+
_active = null;
|
|
118
|
+
throw new Error('neuralOS: needle_init failed');
|
|
119
|
+
}
|
|
120
|
+
_active = agent;
|
|
121
|
+
return e;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { engine, bindAgent, platformKey, platformPackageDir, PLATFORM_PACKAGES };
|
package/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* neuralOS — the automation foundation model for tiny devices, for Node.js.
|
|
5
|
+
*
|
|
6
|
+
* Offline tool-calling, structured extraction and embeddings on the bundled
|
|
7
|
+
* libneedle3 engine + needle3.cact weights (installed via the platform
|
|
8
|
+
* optional dependency for your OS/arch).
|
|
9
|
+
*
|
|
10
|
+
* const { Needle, tool } = require('neuralos');
|
|
11
|
+
*
|
|
12
|
+
* const getWeather = tool({
|
|
13
|
+
* name: 'get_weather',
|
|
14
|
+
* description: 'Get the current weather for a city.',
|
|
15
|
+
* parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
|
|
16
|
+
* }, (args) => ({ city: args.city, temp_c: 27, sky: 'clear' }));
|
|
17
|
+
*
|
|
18
|
+
* const agent = new Needle({ tools: [getWeather] });
|
|
19
|
+
* console.log(agent.run("What's the weather in Lagos?"));
|
|
20
|
+
*
|
|
21
|
+
* Structured extraction (Python parity):
|
|
22
|
+
*
|
|
23
|
+
* const { extract, Field } = require('neuralos');
|
|
24
|
+
* const invoice = extract('Invoice INV-7, 12 May 2024, total 49.62', {
|
|
25
|
+
* type: 'object',
|
|
26
|
+
* properties: {
|
|
27
|
+
* invoice_no: Field({ type: 'string' }),
|
|
28
|
+
* issue_date: Field({ type: 'string', format: 'date' }),
|
|
29
|
+
* total: Field({ type: 'number', ge: 0 }),
|
|
30
|
+
* },
|
|
31
|
+
* required: ['invoice_no', 'total'],
|
|
32
|
+
* });
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const engineLoader = require('./engine');
|
|
36
|
+
const { normalizeTools, tool, Field } = require('./tools');
|
|
37
|
+
const { ExtractionValidationError, sourceYears, groundedNumberPaths,
|
|
38
|
+
ungroundedPaths, annotateUngrounded, validateExtraction } = require('./validate');
|
|
39
|
+
|
|
40
|
+
const UNRESET_TURNS = 4;
|
|
41
|
+
|
|
42
|
+
function withDateFact(system, autoDate) {
|
|
43
|
+
const text = system || '';
|
|
44
|
+
if (!autoDate) return text;
|
|
45
|
+
// Mirror Python: skip when the caller already supplied a date fact/ISO stamp.
|
|
46
|
+
if (/\bdate\s*:/.test(text) || /\d{4}-\d{2}-\d{2}/.test(text)) return text;
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
49
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
50
|
+
const fact =
|
|
51
|
+
`date: ${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
|
52
|
+
`${days[now.getDay()]} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
53
|
+
if (!text.trim()) return fact;
|
|
54
|
+
return fact + '; ' + text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class Needle {
|
|
58
|
+
constructor({
|
|
59
|
+
tools = [],
|
|
60
|
+
system = '',
|
|
61
|
+
autoDate = true,
|
|
62
|
+
stateless = false,
|
|
63
|
+
bufferSize = 65536,
|
|
64
|
+
} = {}) {
|
|
65
|
+
this._functions = new Map();
|
|
66
|
+
this._schemas = normalizeTools(tools, this._functions);
|
|
67
|
+
this.systemText = withDateFact(system, autoDate);
|
|
68
|
+
this.toolsJson = JSON.stringify(this._schemas);
|
|
69
|
+
this._stateless = !!stateless;
|
|
70
|
+
this._seenYears = new Set();
|
|
71
|
+
this._turns = 0;
|
|
72
|
+
this._bufferSize = bufferSize;
|
|
73
|
+
this._bind();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_bind() {
|
|
77
|
+
engineLoader.bindAgent(this);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_countQuery() {
|
|
81
|
+
this._turns++;
|
|
82
|
+
if (this._turns === UNRESET_TURNS + 1) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`neuralOS: ${UNRESET_TURNS} queries on this agent without reset(); every turn stays in the ` +
|
|
85
|
+
'conversation, so unrelated queries lose accuracy. Call reset() between independent queries.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
complete(text = '', maxNewTokens = 512) {
|
|
90
|
+
if (this._stateless) this.reset();
|
|
91
|
+
this._countQuery();
|
|
92
|
+
return this._complete(text, maxNewTokens);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_complete(text, maxNewTokens, parse = true, annotate = true) {
|
|
96
|
+
this._bind();
|
|
97
|
+
const e = engineLoader.engine();
|
|
98
|
+
const buffer = e.allocBuffer(this._bufferSize);
|
|
99
|
+
const rc = e.complete(text || '', Math.trunc(maxNewTokens), buffer, this._bufferSize);
|
|
100
|
+
if (rc < 0) {
|
|
101
|
+
const detail = e.decodeString(buffer, this._bufferSize);
|
|
102
|
+
throw new Error(detail || `neuralOS: needle_complete failed (code ${rc})`);
|
|
103
|
+
}
|
|
104
|
+
const raw = e.decodeString(buffer, this._bufferSize);
|
|
105
|
+
if (!parse) return raw;
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = JSON.parse(raw);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
throw new Error(`neuralOS: engine returned an unparseable envelope (${err.message})`);
|
|
111
|
+
}
|
|
112
|
+
for (const year of sourceYears(text || '')) this._seenYears.add(year);
|
|
113
|
+
if (annotate) {
|
|
114
|
+
annotateUngrounded(response, this._schemas, this._seenYears, this.systemText, text);
|
|
115
|
+
}
|
|
116
|
+
return response;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Execute tool calls until the model responds. Mirrors Python's run(). */
|
|
120
|
+
run(query = '', { maxSteps = 8, maxNewTokens = 512, strict = true } = {}) {
|
|
121
|
+
if (this._stateless) this.reset();
|
|
122
|
+
this._countQuery();
|
|
123
|
+
let response = this._complete(query, maxNewTokens);
|
|
124
|
+
const executed = [];
|
|
125
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
126
|
+
const calls = response.function_calls || [];
|
|
127
|
+
if (response.type !== 'call' || !calls.length) break;
|
|
128
|
+
const results = [];
|
|
129
|
+
const ungrounded = ungroundedPaths(response);
|
|
130
|
+
for (const call of calls) {
|
|
131
|
+
const name = String(call.name);
|
|
132
|
+
let fabricated = [...(ungrounded.get(name) || [])].sort();
|
|
133
|
+
if (strict && fabricated.length) {
|
|
134
|
+
const grounded = groundedNumberPaths(call.arguments || {}, query, this.systemText);
|
|
135
|
+
fabricated = fabricated.filter((path) => !grounded.has(path));
|
|
136
|
+
}
|
|
137
|
+
if (strict && fabricated.length) {
|
|
138
|
+
results.push({ error: 'ungrounded ' + fabricated.join(', ') });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const fn = this._functions.get(name);
|
|
142
|
+
if (!fn) {
|
|
143
|
+
results.push({ error: 'unknown tool: ' + name });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
results.push(fn(call.arguments || {}));
|
|
148
|
+
} catch (err) {
|
|
149
|
+
results.push({ error: String((err && err.message) || err) });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
executed.push(...results);
|
|
153
|
+
response = this._complete(JSON.stringify(results), maxNewTokens, false, false);
|
|
154
|
+
try { response = JSON.parse(response); } catch (_) { /* handled below */ }
|
|
155
|
+
if (typeof response !== 'object' || response === null) {
|
|
156
|
+
throw new Error('neuralOS: engine returned an unparseable envelope');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
response.results = executed;
|
|
160
|
+
return response;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
embed(text = '') {
|
|
164
|
+
this._bind();
|
|
165
|
+
const e = engineLoader.engine();
|
|
166
|
+
const dim = e.embed(text || '', null, 0);
|
|
167
|
+
if (dim <= 0) throw new Error(`neuralOS: needle_embed failed (code ${dim})`);
|
|
168
|
+
const out = e.allocFloats(dim);
|
|
169
|
+
const rc = e.embed(text || '', out, dim);
|
|
170
|
+
if (rc !== dim) throw new Error(`neuralOS: needle_embed failed (code ${rc})`);
|
|
171
|
+
return e.decodeFloats(out, dim);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** One-shot structured extraction against this agent's system prompt. */
|
|
175
|
+
extract(text, schema, { maxNewTokens = 512, strict = true } = {}) {
|
|
176
|
+
return extract(text, schema, {
|
|
177
|
+
system: this.systemText, maxNewTokens, strict, autoDate: false,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
reset() {
|
|
182
|
+
this._bind();
|
|
183
|
+
engineLoader.engine().reset();
|
|
184
|
+
this._turns = 0;
|
|
185
|
+
this._seenYears = new Set();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
close() {
|
|
189
|
+
this._closed = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* One-shot structured extraction — Python-parity semantics:
|
|
195
|
+
* the record is the only tool, the first call is the answer, and with
|
|
196
|
+
* strict=true (default) ungrounded values raise ExtractionValidationError
|
|
197
|
+
* instead of being returned silently.
|
|
198
|
+
*/
|
|
199
|
+
function extract(text, schema, { system = '', maxNewTokens = 512, strict = true,
|
|
200
|
+
autoDate = true } = {}) {
|
|
201
|
+
const agent = new Needle({ tools: [schema], system, autoDate });
|
|
202
|
+
let response;
|
|
203
|
+
try {
|
|
204
|
+
response = agent._complete(text, maxNewTokens);
|
|
205
|
+
} finally {
|
|
206
|
+
agent.close();
|
|
207
|
+
}
|
|
208
|
+
const calls = (response.function_calls && response.function_calls.length)
|
|
209
|
+
? response.function_calls
|
|
210
|
+
: (response.suppressed_calls || []);
|
|
211
|
+
if (!calls.length) return null;
|
|
212
|
+
const arguments_ = calls[0].arguments || {};
|
|
213
|
+
if (strict) validateExtraction(text, schema, arguments_, response, agent.systemText);
|
|
214
|
+
return arguments_;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
Needle,
|
|
219
|
+
tool,
|
|
220
|
+
Field,
|
|
221
|
+
extract,
|
|
222
|
+
ExtractionValidationError,
|
|
223
|
+
validate: require('./validate'),
|
|
224
|
+
version: require('./package.json').version,
|
|
225
|
+
platformKey: engineLoader.platformKey,
|
|
226
|
+
engineInfo: () => {
|
|
227
|
+
const e = engineLoader.engine();
|
|
228
|
+
return { lib: e.libPath, weights: e.weightsPath };
|
|
229
|
+
},
|
|
230
|
+
};
|
package/package.json
CHANGED
|
@@ -1,55 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"name": "neuralos",
|
|
3
|
+
"version": "3.10.0",
|
|
4
|
+
"description": "neuralOS - automation foundation model for tiny devices: offline tool-calling, structured extraction and embeddings for Node.js, weights bundled",
|
|
5
|
+
"main": "index.js",
|
|
3
6
|
"bin": {
|
|
4
|
-
"
|
|
7
|
+
"neuralos": "cli.js"
|
|
5
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"engine.js",
|
|
12
|
+
"validate.js",
|
|
13
|
+
"tools.js",
|
|
14
|
+
"cli.js",
|
|
15
|
+
"test.js",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
6
19
|
"engines": {
|
|
7
20
|
"node": ">=18"
|
|
8
21
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"better-sqlite3": "^12.11.1"
|
|
14
|
-
},
|
|
15
|
-
"optionalDependencies": {
|
|
16
|
-
"node-pty": "1.2.0-beta.3",
|
|
17
|
-
"serialport": "^12.0.0",
|
|
18
|
-
"ssh2": "^1.17.0"
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/DrOlu/neuralOS.git"
|
|
19
26
|
},
|
|
27
|
+
"homepage": "https://github.com/DrOlu/neuralOS#readme",
|
|
20
28
|
"keywords": [
|
|
21
|
-
"forward-deployed-engineer",
|
|
22
|
-
"fde",
|
|
23
|
-
"ai-terminal",
|
|
24
|
-
"ai-agent",
|
|
25
|
-
"ssh",
|
|
26
|
-
"winrm",
|
|
27
|
-
"serial-console",
|
|
28
|
-
"cisco",
|
|
29
|
-
"network-automation",
|
|
30
|
-
"sre",
|
|
31
|
-
"observability",
|
|
32
|
-
"incident-response",
|
|
33
|
-
"change-management",
|
|
34
|
-
"infrastructure-automation",
|
|
35
|
-
"runbooks",
|
|
36
29
|
"neuralos",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
30
|
+
"needle",
|
|
31
|
+
"llm",
|
|
32
|
+
"tool-calling",
|
|
33
|
+
"structured-extraction",
|
|
34
|
+
"embeddings",
|
|
35
|
+
"on-device",
|
|
36
|
+
"offline",
|
|
37
|
+
"tinyml",
|
|
38
|
+
"ai"
|
|
45
39
|
],
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/DrOlu/RTerm.git"
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"koffi": "^2.9.0"
|
|
51
42
|
},
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
"optionalDependencies": {
|
|
44
|
+
"neuralos-darwin-arm64": "3.10.0",
|
|
45
|
+
"neuralos-darwin-x64": "3.10.0",
|
|
46
|
+
"neuralos-linux-x64-gnu": "3.10.0",
|
|
47
|
+
"neuralos-linux-arm64-gnu": "3.10.0",
|
|
48
|
+
"neuralos-win32-x64": "3.10.0"
|
|
49
|
+
}
|
|
55
50
|
}
|
package/test.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Needle, tool } = require('./index');
|
|
4
|
+
|
|
5
|
+
const getWeather = tool({
|
|
6
|
+
name: 'get_weather',
|
|
7
|
+
description: 'Get the current weather for a city.',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: { city: { type: 'string' } },
|
|
11
|
+
required: ['city'],
|
|
12
|
+
},
|
|
13
|
+
}, (args) => ({ city: args.city, temp_c: 27, sky: 'clear' }));
|
|
14
|
+
|
|
15
|
+
const agent = new Needle({ tools: [getWeather] });
|
|
16
|
+
const result = agent.run('What is the weather in Lagos?');
|
|
17
|
+
console.log(JSON.stringify(result.results));
|
|
18
|
+
|
|
19
|
+
const vector = agent.embed('hello world');
|
|
20
|
+
console.log('embed dim:', vector.length);
|
package/tools.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool schema normalization. A tool is one of:
|
|
5
|
+
* - a schema object: { name, description, parameters, implementation? }
|
|
6
|
+
* (parameters is a JSON schema object; implementation is optional)
|
|
7
|
+
* - a function with a `.needleSchema` property (schema + implementation in one)
|
|
8
|
+
*
|
|
9
|
+
* Tool functions receive ONE argument: the arguments object the model produced.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function normalizeTools(tools, functions) {
|
|
13
|
+
const schemas = [];
|
|
14
|
+
for (const entry of tools || []) {
|
|
15
|
+
if (typeof entry === 'function' && entry.needleSchema) {
|
|
16
|
+
const schema = entry.needleSchema;
|
|
17
|
+
if (!schema || typeof schema.name !== 'string') {
|
|
18
|
+
throw new TypeError('neuralOS: function.needleSchema must be {name, description, parameters}');
|
|
19
|
+
}
|
|
20
|
+
schemas.push(schema);
|
|
21
|
+
functions.set(schema.name, entry);
|
|
22
|
+
} else if (entry && typeof entry === 'object' && typeof entry.name === 'string') {
|
|
23
|
+
schemas.push({
|
|
24
|
+
name: entry.name,
|
|
25
|
+
description: entry.description || '',
|
|
26
|
+
parameters: entry.parameters || { type: 'object', properties: {} },
|
|
27
|
+
});
|
|
28
|
+
if (typeof entry.implementation === 'function') {
|
|
29
|
+
functions.set(entry.name, entry.implementation);
|
|
30
|
+
}
|
|
31
|
+
} else if (entry && typeof entry === 'object') {
|
|
32
|
+
// An ANONYMOUS schema: the record itself is the only tool (Python parity —
|
|
33
|
+
// needle._resolve passes plain dicts through unwrapped, which is what
|
|
34
|
+
// extract() relies on: the engine answers with arguments only).
|
|
35
|
+
schemas.push(entry);
|
|
36
|
+
} else {
|
|
37
|
+
throw new TypeError(
|
|
38
|
+
'neuralOS: each tool must be a schema object {name, description, parameters, implementation?} ' +
|
|
39
|
+
'or a function with a .needleSchema property');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return schemas;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A tiny `tool` helper, mirroring the Python decorator ergonomics. */
|
|
46
|
+
function tool(schema, fn) {
|
|
47
|
+
const wrapped = (args) => fn(args);
|
|
48
|
+
wrapped.needleSchema = schema;
|
|
49
|
+
return wrapped;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Field — declarative constraints for a schema property, mirroring the Python
|
|
54
|
+
* `needle.Field`. Build the property, then attach it to your JSON schema:
|
|
55
|
+
*
|
|
56
|
+
* parameters: {
|
|
57
|
+
* type: 'object',
|
|
58
|
+
* properties: {
|
|
59
|
+
* total: Field({ type: 'number', description: 'invoice total', ge: 0 }),
|
|
60
|
+
* status: Field({ type: 'string', enum: ['paid', 'unpaid', 'overdue'] }),
|
|
61
|
+
* },
|
|
62
|
+
* required: ['total'],
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* Python parity: ge/le → minimum/maximum, gt/lt → exclusiveMinimum/Maximum,
|
|
66
|
+
* min_length/max_length → minLength/maxLength, min_items/max_items →
|
|
67
|
+
* minItems/maxItems, plus enum/const/pattern/format/multipleOf.
|
|
68
|
+
*/
|
|
69
|
+
function Field({ default: defaultValue, description, enum: enum_, const: const_,
|
|
70
|
+
ge, le, gt, lt, multiple_of: multipleOf, min_length: minLength,
|
|
71
|
+
max_length: maxLength, pattern, format, min_items: minItems,
|
|
72
|
+
max_items: maxItems, unique_items: uniqueItems, ...rest } = {}) {
|
|
73
|
+
const schema = { ...rest };
|
|
74
|
+
const pairs = [['description', description], ['enum', enum_],
|
|
75
|
+
['minimum', ge], ['maximum', le],
|
|
76
|
+
['exclusiveMinimum', gt], ['exclusiveMaximum', lt],
|
|
77
|
+
['multipleOf', multipleOf], ['minLength', minLength],
|
|
78
|
+
['maxLength', maxLength], ['pattern', pattern], ['format', format],
|
|
79
|
+
['minItems', minItems], ['maxItems', maxItems],
|
|
80
|
+
['uniqueItems', uniqueItems]];
|
|
81
|
+
for (const [key, value] of pairs) {
|
|
82
|
+
if (value !== undefined && value !== null) schema[key] = value;
|
|
83
|
+
}
|
|
84
|
+
if (const_ !== undefined) schema.const = const_;
|
|
85
|
+
if (defaultValue !== undefined) schema.default = defaultValue;
|
|
86
|
+
return schema;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { normalizeTools, tool, Field };
|