open-agents-ai 0.86.0 → 0.88.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 +67 -2
- package/dist/index.js +991 -48
- package/package.json +8 -3
- package/prompts/agentic/system-large.md +55 -4
package/dist/index.js
CHANGED
|
@@ -139,7 +139,7 @@ var init_config = __esm({
|
|
|
139
139
|
verbose: false,
|
|
140
140
|
dbPath: join(homedir(), ".open-agents", "memory.db")
|
|
141
141
|
});
|
|
142
|
-
VALID_BACKEND_TYPES = /* @__PURE__ */ new Set(["ollama", "vllm", "fake"]);
|
|
142
|
+
VALID_BACKEND_TYPES = /* @__PURE__ */ new Set(["ollama", "vllm", "fake", "nexus"]);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -11202,7 +11202,7 @@ ${truncated}`, durationMs: performance.now() - start };
|
|
|
11202
11202
|
});
|
|
11203
11203
|
|
|
11204
11204
|
// packages/execution/dist/tools/nexus.js
|
|
11205
|
-
import { readFile as readFile13, writeFile as writeFile12, mkdir as mkdir8, chmod, unlink, readdir as readdir3 } from "node:fs/promises";
|
|
11205
|
+
import { readFile as readFile13, writeFile as writeFile12, mkdir as mkdir8, chmod, unlink, readdir as readdir3, open as fsOpen } from "node:fs/promises";
|
|
11206
11206
|
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
|
|
11207
11207
|
import { resolve as resolve26, join as join28 } from "node:path";
|
|
11208
11208
|
import { randomBytes as randomBytes6, createCipheriv, createDecipheriv, scryptSync, createHash } from "node:crypto";
|
|
@@ -11244,6 +11244,8 @@ function dlog(msg) { try { appendFileSync(logFile, new Date().toISOString() + '
|
|
|
11244
11244
|
const pidFile = join(nexusDir, 'daemon.pid');
|
|
11245
11245
|
const invocationsDir = join(nexusDir, 'invocations');
|
|
11246
11246
|
const meteringFile = join(nexusDir, 'metering.jsonl');
|
|
11247
|
+
const x402KeyPath = join(nexusDir, 'x402-wallet.key');
|
|
11248
|
+
const hasX402Key = existsSync(x402KeyPath);
|
|
11247
11249
|
|
|
11248
11250
|
mkdirSync(inboxDir, { recursive: true });
|
|
11249
11251
|
mkdirSync(invocationsDir, { recursive: true });
|
|
@@ -11252,7 +11254,7 @@ mkdirSync(invocationsDir, { recursive: true });
|
|
|
11252
11254
|
writeFileSync(pidFile, String(process.pid));
|
|
11253
11255
|
|
|
11254
11256
|
const keyPath = join(nexusDir, 'identity.key');
|
|
11255
|
-
|
|
11257
|
+
var nexusOpts = {
|
|
11256
11258
|
keyStorePath: keyPath,
|
|
11257
11259
|
agentName,
|
|
11258
11260
|
agentType,
|
|
@@ -11264,7 +11266,18 @@ const nexus = new NexusClient({
|
|
|
11264
11266
|
enableCircuitRelay: true,
|
|
11265
11267
|
usePublicBootstrap: true,
|
|
11266
11268
|
trustPolicy: { denylist: [], allowlist: [] },
|
|
11267
|
-
}
|
|
11269
|
+
};
|
|
11270
|
+
if (hasX402Key) {
|
|
11271
|
+
nexusOpts.x402 = {
|
|
11272
|
+
enabled: true,
|
|
11273
|
+
walletKeyPath: x402KeyPath,
|
|
11274
|
+
maxPaymentPerRequest: '5000000',
|
|
11275
|
+
};
|
|
11276
|
+
if (process.env.ALCHEMY_API_KEY) {
|
|
11277
|
+
nexusOpts.x402.alchemyApiKey = process.env.ALCHEMY_API_KEY;
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11280
|
+
const nexus = new NexusClient(nexusOpts);
|
|
11268
11281
|
|
|
11269
11282
|
const rooms = new Map();
|
|
11270
11283
|
let connected = false;
|
|
@@ -11392,6 +11405,101 @@ async function handleCmd(cmd) {
|
|
|
11392
11405
|
}
|
|
11393
11406
|
break;
|
|
11394
11407
|
}
|
|
11408
|
+
case 'remote_infer': {
|
|
11409
|
+
var riModel = args.model;
|
|
11410
|
+
var riPrompt = args.prompt || args.input || '';
|
|
11411
|
+
var riMessages = args.messages || null;
|
|
11412
|
+
var riTools = args.tools || null;
|
|
11413
|
+
var riTargetPeer = args.target_peer || '';
|
|
11414
|
+
if (!riModel) { writeResp(id, { ok: false, output: 'model is required' }); return; }
|
|
11415
|
+
if (!riPrompt && !riMessages) { writeResp(id, { ok: false, output: 'prompt or messages is required' }); return; }
|
|
11416
|
+
|
|
11417
|
+
// Build invoke data \u2014 structured (messages+tools) or flat (prompt)
|
|
11418
|
+
var riData;
|
|
11419
|
+
if (riMessages) {
|
|
11420
|
+
var riParsedMsgs = typeof riMessages === 'string' ? JSON.parse(riMessages) : riMessages;
|
|
11421
|
+
riData = { messages: riParsedMsgs };
|
|
11422
|
+
if (riTools) {
|
|
11423
|
+
riData.tools = typeof riTools === 'string' ? JSON.parse(riTools) : riTools;
|
|
11424
|
+
}
|
|
11425
|
+
if (args.temperature !== undefined) riData.temperature = Number(args.temperature);
|
|
11426
|
+
if (args.max_tokens) riData.max_tokens = Number(args.max_tokens);
|
|
11427
|
+
} else {
|
|
11428
|
+
riData = { prompt: riPrompt };
|
|
11429
|
+
}
|
|
11430
|
+
|
|
11431
|
+
// Derive capability name from model
|
|
11432
|
+
var riCapName = 'inference:' + riModel.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11433
|
+
dlog('remote_infer: model=' + riModel + ' cap=' + riCapName + ' prompt_len=' + riPrompt.length);
|
|
11434
|
+
|
|
11435
|
+
// If target_peer specified, invoke directly. Otherwise auto-discover.
|
|
11436
|
+
if (riTargetPeer) {
|
|
11437
|
+
// Direct invoke on specified peer
|
|
11438
|
+
dlog('remote_infer: invoking ' + riCapName + ' on explicit peer ' + riTargetPeer.slice(0, 20));
|
|
11439
|
+
try {
|
|
11440
|
+
var RI_TIMEOUT = 120000;
|
|
11441
|
+
var riInvokeP = nexus.invokeCapability(riTargetPeer, riCapName, riData, { stream: false, maxDurationMs: 90000 });
|
|
11442
|
+
var riTimeoutP = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('remote_infer timed out after ' + (RI_TIMEOUT / 1000) + 's')); }, RI_TIMEOUT); });
|
|
11443
|
+
var riResult = await Promise.race([riInvokeP, riTimeoutP]);
|
|
11444
|
+
dlog('remote_infer: SUCCESS (explicit peer)');
|
|
11445
|
+
} catch (riErr) {
|
|
11446
|
+
dlog('remote_infer: ERROR (explicit peer) ' + (riErr.message || String(riErr)));
|
|
11447
|
+
writeResp(id, { ok: false, output: 'Remote inference failed: ' + (riErr.message || String(riErr)) });
|
|
11448
|
+
return;
|
|
11449
|
+
}
|
|
11450
|
+
} else {
|
|
11451
|
+
// Auto-discover: try speculative invoke on each 12D3KooW peer
|
|
11452
|
+
// Skip Qm-prefixed bootstrap/relay nodes \u2014 they never host inference
|
|
11453
|
+
var discStart = Date.now();
|
|
11454
|
+
var riResult = null;
|
|
11455
|
+
try {
|
|
11456
|
+
var riNode = nexus.network ? nexus.network.node : null;
|
|
11457
|
+
var riPeers = riNode && typeof riNode.getPeers === 'function' ? riNode.getPeers() : [];
|
|
11458
|
+
var riCandidates = [];
|
|
11459
|
+
for (var rfi = 0; rfi < riPeers.length; rfi++) {
|
|
11460
|
+
var rpStr = riPeers[rfi].toString ? riPeers[rfi].toString() : String(riPeers[rfi]);
|
|
11461
|
+
if (rpStr.startsWith('12D3KooW')) riCandidates.push(rpStr);
|
|
11462
|
+
}
|
|
11463
|
+
dlog('remote_infer: auto-discover across ' + riCandidates.length + ' peers (of ' + riPeers.length + ' total) for ' + riCapName);
|
|
11464
|
+
|
|
11465
|
+
for (var ri = 0; ri < riCandidates.length && !riResult; ri++) {
|
|
11466
|
+
var rPid = riCandidates[ri];
|
|
11467
|
+
dlog('remote_infer: trying peer ' + rPid.slice(0, 20) + '...');
|
|
11468
|
+
try {
|
|
11469
|
+
var PEER_INVOKE_TIMEOUT = 30000; // 30s per peer attempt
|
|
11470
|
+
var specInvoke = nexus.invokeCapability(rPid, riCapName, riData, { stream: false, maxDurationMs: 25000 });
|
|
11471
|
+
var specTimeout = new Promise(function(_, reject) {
|
|
11472
|
+
setTimeout(function() { reject(new Error('peer invoke timeout')); }, PEER_INVOKE_TIMEOUT);
|
|
11473
|
+
});
|
|
11474
|
+
riResult = await Promise.race([specInvoke, specTimeout]);
|
|
11475
|
+
riTargetPeer = rPid;
|
|
11476
|
+
dlog('remote_infer: SUCCESS via peer ' + rPid.slice(0, 20) + ' (' + (Date.now() - discStart) + 'ms)');
|
|
11477
|
+
} catch (specErr) {
|
|
11478
|
+
dlog('remote_infer: peer ' + rPid.slice(0, 20) + ' failed: ' + (specErr.message || specErr));
|
|
11479
|
+
}
|
|
11480
|
+
}
|
|
11481
|
+
} catch (discErr) {
|
|
11482
|
+
dlog('remote_infer: discovery error: ' + (discErr.message || discErr));
|
|
11483
|
+
}
|
|
11484
|
+
dlog('remote_infer: auto-discover took ' + (Date.now() - discStart) + 'ms');
|
|
11485
|
+
|
|
11486
|
+
if (!riResult) {
|
|
11487
|
+
writeResp(id, { ok: false, output: 'No peer found with capability: ' + riCapName + '. Ensure a provider is running expose with model ' + riModel });
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
}
|
|
11491
|
+
|
|
11492
|
+
// riResult and riTargetPeer are available from either path above
|
|
11493
|
+
var riOutput = {
|
|
11494
|
+
success: true,
|
|
11495
|
+
model: riModel,
|
|
11496
|
+
peer: riTargetPeer,
|
|
11497
|
+
capability: riCapName,
|
|
11498
|
+
result: riResult,
|
|
11499
|
+
};
|
|
11500
|
+
writeResp(id, { ok: true, output: JSON.stringify(riOutput, null, 2) });
|
|
11501
|
+
break;
|
|
11502
|
+
}
|
|
11395
11503
|
case 'store_content': {
|
|
11396
11504
|
const data = args.data;
|
|
11397
11505
|
if (!data) { writeResp(id, { ok: false, output: 'data is required' }); return; }
|
|
@@ -11643,6 +11751,18 @@ async function handleCmd(cmd) {
|
|
|
11643
11751
|
// Register as nexus capability
|
|
11644
11752
|
const capName = 'inference:' + model.name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11645
11753
|
if (typeof nexus.registerCapability === 'function') {
|
|
11754
|
+
var capOpts = {};
|
|
11755
|
+
if (margin > 0 && entry.pricing.input_per_1m_tokens > 0) {
|
|
11756
|
+
var tokensPerReq = 1000;
|
|
11757
|
+
var amountPerReq = Math.round(entry.pricing.input_per_1m_tokens * tokensPerReq);
|
|
11758
|
+
if (amountPerReq > 0) {
|
|
11759
|
+
capOpts.pricing = {
|
|
11760
|
+
amount: String(amountPerReq),
|
|
11761
|
+
currency: 'USDC',
|
|
11762
|
+
description: 'Inference: ' + model.name,
|
|
11763
|
+
};
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11646
11766
|
nexus.registerCapability(capName, async (request, stream) => {
|
|
11647
11767
|
const logEntry = {
|
|
11648
11768
|
ts: Date.now(),
|
|
@@ -11654,11 +11774,17 @@ async function handleCmd(cmd) {
|
|
|
11654
11774
|
const logFile = join(invocationsDir, Date.now() + '-' + capName + '.json');
|
|
11655
11775
|
try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
|
|
11656
11776
|
|
|
11657
|
-
// Collect input
|
|
11777
|
+
// Collect input via stream data events
|
|
11658
11778
|
let prompt = '';
|
|
11779
|
+
var dataChunks = [];
|
|
11780
|
+
var inputDone = false;
|
|
11659
11781
|
stream.onData((msg) => {
|
|
11660
11782
|
if (msg.type === 'invoke.chunk') {
|
|
11661
|
-
|
|
11783
|
+
var chunk = typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data);
|
|
11784
|
+
dataChunks.push(chunk);
|
|
11785
|
+
}
|
|
11786
|
+
if (msg.type === 'invoke.done' || msg.type === 'invoke.end' || msg.type === 'invoke.close') {
|
|
11787
|
+
inputDone = true;
|
|
11662
11788
|
}
|
|
11663
11789
|
});
|
|
11664
11790
|
|
|
@@ -11668,33 +11794,100 @@ async function handleCmd(cmd) {
|
|
|
11668
11794
|
requestId: request.requestId, accepted: true,
|
|
11669
11795
|
});
|
|
11670
11796
|
|
|
11671
|
-
//
|
|
11797
|
+
// Wait for input data to arrive (invoke protocol sends data after accept)
|
|
11798
|
+
var waitMs = 0;
|
|
11799
|
+
while (!inputDone && dataChunks.length === 0 && waitMs < 5000) {
|
|
11800
|
+
await new Promise(function(r) { setTimeout(r, 100); });
|
|
11801
|
+
waitMs += 100;
|
|
11802
|
+
}
|
|
11803
|
+
prompt = dataChunks.join('');
|
|
11804
|
+
dlog('expose: received ' + dataChunks.length + ' chunks, prompt_len=' + prompt.length + ' inputDone=' + inputDone);
|
|
11805
|
+
|
|
11806
|
+
// Forward to Ollama \u2014 supports both flat prompt and structured messages
|
|
11672
11807
|
try {
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11808
|
+
var parsedReq = null;
|
|
11809
|
+
try { parsedReq = JSON.parse(prompt); } catch (pe) { dlog('expose: JSON parse error: ' + (pe.message || pe)); }
|
|
11810
|
+
|
|
11811
|
+
var genResp, genData, output, inputTokens, outputTokens, responsePayload;
|
|
11812
|
+
|
|
11813
|
+
if (parsedReq && parsedReq.messages && Array.isArray(parsedReq.messages)) {
|
|
11814
|
+
// Structured request \u2014 use /v1/chat/completions (supports tools + multi-turn)
|
|
11815
|
+
var chatBody = {
|
|
11677
11816
|
model: model.name,
|
|
11678
|
-
|
|
11817
|
+
messages: parsedReq.messages,
|
|
11679
11818
|
stream: false,
|
|
11680
|
-
}
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11819
|
+
};
|
|
11820
|
+
if (parsedReq.tools && parsedReq.tools.length > 0) {
|
|
11821
|
+
chatBody.tools = parsedReq.tools;
|
|
11822
|
+
}
|
|
11823
|
+
if (parsedReq.temperature !== undefined) chatBody.temperature = parsedReq.temperature;
|
|
11824
|
+
if (parsedReq.max_tokens) chatBody.max_tokens = parsedReq.max_tokens;
|
|
11825
|
+
|
|
11826
|
+
dlog('expose: calling /v1/chat/completions with ' + chatBody.messages.length + ' messages, tools=' + (chatBody.tools ? chatBody.tools.length : 0));
|
|
11827
|
+
genResp = await fetch(ollamaUrl + '/v1/chat/completions', {
|
|
11828
|
+
method: 'POST',
|
|
11829
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11830
|
+
body: JSON.stringify(chatBody),
|
|
11831
|
+
});
|
|
11832
|
+
genData = await genResp.json();
|
|
11833
|
+
dlog('expose: ollama response keys=' + Object.keys(genData).join(',') + ' choices=' + (genData.choices ? genData.choices.length : 0));
|
|
11834
|
+
|
|
11835
|
+
var chatChoices = genData.choices || [];
|
|
11836
|
+
var firstMsg = (chatChoices[0] || {}).message || {};
|
|
11837
|
+
// Strip <think>...</think> tags from content (thinking models)
|
|
11838
|
+
var rawContent = firstMsg.content || '';
|
|
11839
|
+
output = rawContent.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();
|
|
11840
|
+
// Fallback: if content is empty but reasoning exists, use reasoning
|
|
11841
|
+
// (happens when max_tokens is too small for thinking models at temp=0)
|
|
11842
|
+
if (!output && firstMsg.reasoning) {
|
|
11843
|
+
output = firstMsg.reasoning;
|
|
11844
|
+
}
|
|
11845
|
+
// Update the choices to reflect final content
|
|
11846
|
+
if (chatChoices[0] && chatChoices[0].message) {
|
|
11847
|
+
chatChoices[0].message.content = output;
|
|
11848
|
+
}
|
|
11849
|
+
dlog('expose: rawContent_len=' + rawContent.length + ' output_len=' + output.length + ' reasoning=' + (firstMsg.reasoning ? firstMsg.reasoning.length : 0) + ' tool_calls=' + (firstMsg.tool_calls ? firstMsg.tool_calls.length : 0));
|
|
11850
|
+
inputTokens = (genData.usage || {}).prompt_tokens || 0;
|
|
11851
|
+
outputTokens = (genData.usage || {}).completion_tokens || 0;
|
|
11852
|
+
|
|
11853
|
+
responsePayload = {
|
|
11854
|
+
model: model.name,
|
|
11855
|
+
response: output,
|
|
11856
|
+
choices: chatChoices,
|
|
11857
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11858
|
+
pricing: entry.pricing,
|
|
11859
|
+
};
|
|
11860
|
+
} else {
|
|
11861
|
+
// Flat prompt \u2014 use /api/generate (original behavior)
|
|
11862
|
+
var flatPrompt = parsedReq && parsedReq.prompt ? parsedReq.prompt : (prompt || 'Hello');
|
|
11863
|
+
genResp = await fetch(ollamaUrl + '/api/generate', {
|
|
11864
|
+
method: 'POST',
|
|
11865
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11866
|
+
body: JSON.stringify({
|
|
11867
|
+
model: model.name,
|
|
11868
|
+
prompt: flatPrompt,
|
|
11869
|
+
stream: false,
|
|
11870
|
+
}),
|
|
11871
|
+
});
|
|
11872
|
+
genData = await genResp.json();
|
|
11873
|
+
output = genData.response || '';
|
|
11874
|
+
inputTokens = genData.prompt_eval_count || 0;
|
|
11875
|
+
outputTokens = genData.eval_count || 0;
|
|
11876
|
+
|
|
11877
|
+
responsePayload = {
|
|
11878
|
+
model: model.name,
|
|
11879
|
+
response: output,
|
|
11880
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11881
|
+
pricing: entry.pricing,
|
|
11882
|
+
};
|
|
11883
|
+
}
|
|
11686
11884
|
|
|
11687
11885
|
// Stream result back
|
|
11688
11886
|
await stream.write({
|
|
11689
11887
|
type: 'invoke.event', version: 1,
|
|
11690
11888
|
requestId: request.requestId, seq: 0,
|
|
11691
11889
|
event: 'result',
|
|
11692
|
-
data: JSON.stringify(
|
|
11693
|
-
model: model.name,
|
|
11694
|
-
response: output,
|
|
11695
|
-
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11696
|
-
pricing: entry.pricing,
|
|
11697
|
-
}),
|
|
11890
|
+
data: JSON.stringify(responsePayload),
|
|
11698
11891
|
});
|
|
11699
11892
|
|
|
11700
11893
|
await stream.write({
|
|
@@ -11702,7 +11895,7 @@ async function handleCmd(cmd) {
|
|
|
11702
11895
|
requestId: request.requestId,
|
|
11703
11896
|
usage: {
|
|
11704
11897
|
inputBytes: prompt.length,
|
|
11705
|
-
outputBytes: output.length,
|
|
11898
|
+
outputBytes: (output || '').length,
|
|
11706
11899
|
tokens: inputTokens + outputTokens,
|
|
11707
11900
|
},
|
|
11708
11901
|
});
|
|
@@ -11719,7 +11912,7 @@ async function handleCmd(cmd) {
|
|
|
11719
11912
|
});
|
|
11720
11913
|
}
|
|
11721
11914
|
stream.close();
|
|
11722
|
-
});
|
|
11915
|
+
}, capOpts);
|
|
11723
11916
|
}
|
|
11724
11917
|
}
|
|
11725
11918
|
|
|
@@ -11822,6 +12015,37 @@ process.on('unhandledRejection', (reason) => {
|
|
|
11822
12015
|
}
|
|
11823
12016
|
} catch {}
|
|
11824
12017
|
|
|
12018
|
+
// Write payment events to ledger.jsonl
|
|
12019
|
+
try {
|
|
12020
|
+
if (nexus.metering && typeof nexus.metering.addHook === 'function') {
|
|
12021
|
+
nexus.metering.addHook(function(record) {
|
|
12022
|
+
if (!record.payment) return;
|
|
12023
|
+
var ledgerFile = join(nexusDir, 'ledger.jsonl');
|
|
12024
|
+
var entry = {
|
|
12025
|
+
timestamp: new Date().toISOString(),
|
|
12026
|
+
type: record.direction === 'inbound' ? 'earned' : 'spent',
|
|
12027
|
+
amount: String(record.payment.amount || 0),
|
|
12028
|
+
amountUsd: (Number(record.payment.amount || 0) / 1000000).toFixed(6),
|
|
12029
|
+
peer: record.peerId || 'unknown',
|
|
12030
|
+
capability: record.service || record.capability || 'unknown',
|
|
12031
|
+
txHash: record.payment.txHash || '',
|
|
12032
|
+
note: 'auto:metering',
|
|
12033
|
+
};
|
|
12034
|
+
try { appendFileSync(ledgerFile, JSON.stringify(entry) + '\\n'); } catch {}
|
|
12035
|
+
});
|
|
12036
|
+
}
|
|
12037
|
+
} catch {}
|
|
12038
|
+
|
|
12039
|
+
// Init x402 wallet if key file exists
|
|
12040
|
+
if (hasX402Key && nexus.x402 && typeof nexus.x402.initWallet === 'function') {
|
|
12041
|
+
try {
|
|
12042
|
+
var addr = nexus.x402.initWallet(x402KeyPath);
|
|
12043
|
+
dlog('x402 wallet initialized: ' + addr);
|
|
12044
|
+
} catch (e) {
|
|
12045
|
+
dlog('x402 wallet init failed: ' + (e.message || e));
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
|
|
11825
12049
|
// v1.5.0: Client-level events for global message/DM/invoke routing
|
|
11826
12050
|
try {
|
|
11827
12051
|
if (typeof nexus.on === 'function') {
|
|
@@ -11876,7 +12100,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11876
12100
|
];
|
|
11877
12101
|
NexusTool = class {
|
|
11878
12102
|
name = "nexus";
|
|
11879
|
-
description = "Decentralized agent-to-agent communication via open-agents-nexus v1.5.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. EXPOSE: expose action queries Ollama models, fetches OpenRouter market rates, registers metered inference capabilities so peers can invoke your models. pricing_menu shows rates. v1.5.0: register_capability, block_peer/unblock_peer, metering_status, room_members. Also supports direct peer invoke, IPFS storage, DMs, discovery, x402, inference proofs.";
|
|
12103
|
+
description = "Decentralized agent-to-agent communication via open-agents-nexus v1.5.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. EXPOSE: expose action queries Ollama models, fetches OpenRouter market rates, registers metered inference capabilities so peers can invoke your models. pricing_menu shows rates. v1.5.0: register_capability, block_peer/unblock_peer, metering_status, room_members. Also supports direct peer invoke, IPFS storage, DMs, discovery, x402, inference proofs. WALLET: wallet_create generates EVM wallet (secp256k1, Base mainnet USDC). wallet_status shows address, USDC balance, and ledger summary. PAYMENTS: ledger_status tracks earnings/spending. budget_status/budget_set configure spending limits. spend: sign EIP-3009 USDC transfer (target_address + amount_usdc). x402 native payment via daemon. REMOTE INFERENCE: remote_infer routes a prompt to a remote peer's model on the mesh. Auto-discovers peers with the requested model capability, budget-checks, invokes, and returns the result. Use: nexus(action='remote_infer', model='qwen3.5:70b', prompt='...'). Optional: target_peer, temperature, max_tokens.";
|
|
11880
12104
|
parameters = {
|
|
11881
12105
|
type: "object",
|
|
11882
12106
|
properties: {
|
|
@@ -11908,7 +12132,12 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11908
12132
|
"metering_status",
|
|
11909
12133
|
"room_members",
|
|
11910
12134
|
"expose",
|
|
11911
|
-
"pricing_menu"
|
|
12135
|
+
"pricing_menu",
|
|
12136
|
+
"ledger_status",
|
|
12137
|
+
"budget_status",
|
|
12138
|
+
"budget_set",
|
|
12139
|
+
"spend",
|
|
12140
|
+
"remote_infer"
|
|
11912
12141
|
],
|
|
11913
12142
|
description: "The nexus action to perform"
|
|
11914
12143
|
},
|
|
@@ -11963,6 +12192,42 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11963
12192
|
margin: {
|
|
11964
12193
|
type: "string",
|
|
11965
12194
|
description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0.5"
|
|
12195
|
+
},
|
|
12196
|
+
daily_limit: {
|
|
12197
|
+
type: "string",
|
|
12198
|
+
description: "For budget_set: max daily USDC spend (e.g. '1.00')"
|
|
12199
|
+
},
|
|
12200
|
+
per_invoke_max: {
|
|
12201
|
+
type: "string",
|
|
12202
|
+
description: "For budget_set: max per-invoke USDC spend (e.g. '0.10')"
|
|
12203
|
+
},
|
|
12204
|
+
auto_approve_below: {
|
|
12205
|
+
type: "string",
|
|
12206
|
+
description: "For budget_set: auto-approve payments below this USD amount (e.g. '0.01')"
|
|
12207
|
+
},
|
|
12208
|
+
target_address: {
|
|
12209
|
+
type: "string",
|
|
12210
|
+
description: "For spend: target EVM address (0x + 40 hex chars)"
|
|
12211
|
+
},
|
|
12212
|
+
amount_usdc: {
|
|
12213
|
+
type: "string",
|
|
12214
|
+
description: "For spend: USDC amount to transfer (e.g. '0.10')"
|
|
12215
|
+
},
|
|
12216
|
+
model: {
|
|
12217
|
+
type: "string",
|
|
12218
|
+
description: "For remote_infer: model to request (e.g. 'qwen3.5:70b', 'nemotron-3-nano:30b')"
|
|
12219
|
+
},
|
|
12220
|
+
prompt: {
|
|
12221
|
+
type: "string",
|
|
12222
|
+
description: "For remote_infer: the prompt text to send to the remote model"
|
|
12223
|
+
},
|
|
12224
|
+
temperature: {
|
|
12225
|
+
type: "string",
|
|
12226
|
+
description: "For remote_infer: sampling temperature (e.g. '0.7'). Default: 0.7"
|
|
12227
|
+
},
|
|
12228
|
+
max_tokens: {
|
|
12229
|
+
type: "string",
|
|
12230
|
+
description: "For remote_infer: maximum tokens to generate (e.g. '4096'). Default: 4096"
|
|
11966
12231
|
}
|
|
11967
12232
|
},
|
|
11968
12233
|
required: ["action"],
|
|
@@ -12063,6 +12328,21 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12063
12328
|
case "pricing_menu":
|
|
12064
12329
|
result = await this.sendDaemonCmd("pricing_menu", args);
|
|
12065
12330
|
break;
|
|
12331
|
+
case "ledger_status":
|
|
12332
|
+
result = await this.doLedgerStatus();
|
|
12333
|
+
break;
|
|
12334
|
+
case "budget_status":
|
|
12335
|
+
result = await this.doBudgetStatus();
|
|
12336
|
+
break;
|
|
12337
|
+
case "budget_set":
|
|
12338
|
+
result = await this.doBudgetSet(args);
|
|
12339
|
+
break;
|
|
12340
|
+
case "spend":
|
|
12341
|
+
result = await this.doSpend(args);
|
|
12342
|
+
break;
|
|
12343
|
+
case "remote_infer":
|
|
12344
|
+
result = await this.doRemoteInfer(args);
|
|
12345
|
+
break;
|
|
12066
12346
|
default:
|
|
12067
12347
|
return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
|
|
12068
12348
|
}
|
|
@@ -12112,6 +12392,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12112
12392
|
}
|
|
12113
12393
|
return `Daemon did not respond within ${Math.round(timeoutMs / 1e3)}s. It may still be processing.`;
|
|
12114
12394
|
}
|
|
12395
|
+
/** Public interface for daemon commands — used by NexusAgenticBackend */
|
|
12396
|
+
async sendCommand(action, args, timeoutMs) {
|
|
12397
|
+
return this.sendDaemonCmd(action, args, timeoutMs);
|
|
12398
|
+
}
|
|
12399
|
+
/** Get the nexus data directory path */
|
|
12400
|
+
getNexusDir() {
|
|
12401
|
+
return this.nexusDir;
|
|
12402
|
+
}
|
|
12115
12403
|
// =========================================================================
|
|
12116
12404
|
// Actions
|
|
12117
12405
|
// =========================================================================
|
|
@@ -12396,6 +12684,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12396
12684
|
}
|
|
12397
12685
|
return lines.join("\n");
|
|
12398
12686
|
}
|
|
12687
|
+
// ---------------------------------------------------------------------------
|
|
12688
|
+
// Step 1: Wallet v2 — secp256k1 via viem for proper EVM addresses
|
|
12689
|
+
// ---------------------------------------------------------------------------
|
|
12399
12690
|
async doWalletStatus() {
|
|
12400
12691
|
await this.ensureDir();
|
|
12401
12692
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
@@ -12404,13 +12695,33 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12404
12695
|
}
|
|
12405
12696
|
try {
|
|
12406
12697
|
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
12407
|
-
|
|
12698
|
+
const lines = [
|
|
12408
12699
|
`Wallet Status:`,
|
|
12409
12700
|
` Address: ${w.address}`,
|
|
12701
|
+
` Version: ${w.version} (${w.version === 2 ? "secp256k1/EVM" : "legacy SHA256"})`,
|
|
12702
|
+
` Network: ${w.network || "none"} (chain ${w.chainId || "N/A"})`,
|
|
12410
12703
|
` Created: ${w.createdAt}`,
|
|
12411
12704
|
` Encryption: AES-256-GCM + scrypt`,
|
|
12412
12705
|
` NOTE: Private key is encrypted and NEVER accessible to the agent.`
|
|
12413
|
-
]
|
|
12706
|
+
];
|
|
12707
|
+
if (w.version === 2 && w.address) {
|
|
12708
|
+
const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
|
|
12709
|
+
if (balance !== null) {
|
|
12710
|
+
const usdcHuman = (Number(balance) / 1e6).toFixed(6);
|
|
12711
|
+
lines.push(` USDC Balance: $${usdcHuman}`);
|
|
12712
|
+
const budget = await this.loadBudget();
|
|
12713
|
+
if (budget && Number(balance) < budget.circuitBreakerUsdc) {
|
|
12714
|
+
lines.push(` \u26A0 CIRCUIT BREAKER: Balance below $${(budget.circuitBreakerUsdc / 1e6).toFixed(2)} \u2014 all payments BLOCKED`);
|
|
12715
|
+
} else if (budget && Number(balance) < budget.lowBalanceAlertUsdc) {
|
|
12716
|
+
lines.push(` \u26A0 LOW BALANCE: Below $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(2)} threshold`);
|
|
12717
|
+
}
|
|
12718
|
+
}
|
|
12719
|
+
}
|
|
12720
|
+
const ledgerSummary = await this.getLedgerSummary();
|
|
12721
|
+
if (ledgerSummary) {
|
|
12722
|
+
lines.push(` Earned: $${ledgerSummary.totalEarned} Spent: $${ledgerSummary.totalSpent} Net: $${ledgerSummary.net}`);
|
|
12723
|
+
}
|
|
12724
|
+
return lines.join("\n");
|
|
12414
12725
|
} catch {
|
|
12415
12726
|
return "Wallet file exists but could not be read.";
|
|
12416
12727
|
}
|
|
@@ -12419,51 +12730,550 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12419
12730
|
await this.ensureDir();
|
|
12420
12731
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
12421
12732
|
if (existsSync21(walletPath))
|
|
12422
|
-
return "Wallet already exists.";
|
|
12733
|
+
return "Wallet already exists. Delete wallet.enc to create a new one.";
|
|
12423
12734
|
const userAddress = args.wallet_address;
|
|
12424
12735
|
if (userAddress) {
|
|
12425
12736
|
if (!/^0x[0-9a-fA-F]{40}$/.test(userAddress)) {
|
|
12426
12737
|
return "Invalid address format. Expected 0x + 40 hex chars.";
|
|
12427
12738
|
}
|
|
12428
12739
|
const w2 = {
|
|
12429
|
-
version:
|
|
12740
|
+
version: 2,
|
|
12430
12741
|
address: userAddress,
|
|
12431
12742
|
encryptedKey: "",
|
|
12432
12743
|
iv: "",
|
|
12433
12744
|
authTag: "",
|
|
12434
12745
|
salt: "",
|
|
12435
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12746
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12747
|
+
network: "base",
|
|
12748
|
+
chainId: 8453
|
|
12436
12749
|
};
|
|
12437
|
-
await
|
|
12438
|
-
await
|
|
12439
|
-
|
|
12750
|
+
const walletFh = await fsOpen(walletPath, "w", 384);
|
|
12751
|
+
await walletFh.writeFile(JSON.stringify(w2, null, 2));
|
|
12752
|
+
await walletFh.close();
|
|
12753
|
+
await this.ensureDefaultBudget();
|
|
12754
|
+
return `Wallet configured: ${userAddress} (user-managed, Base mainnet, no x402 key \u2014 spend/sign requires self-hosted wallet)`;
|
|
12755
|
+
}
|
|
12756
|
+
let address;
|
|
12757
|
+
let privKeyHex;
|
|
12758
|
+
try {
|
|
12759
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
12760
|
+
const privKey = randomBytes6(32);
|
|
12761
|
+
privKeyHex = privKey.toString("hex");
|
|
12762
|
+
const account = privateKeyToAccount(`0x${privKeyHex}`);
|
|
12763
|
+
address = account.address;
|
|
12764
|
+
privKey.fill(0);
|
|
12765
|
+
} catch {
|
|
12766
|
+
const privKey = randomBytes6(32);
|
|
12767
|
+
privKeyHex = privKey.toString("hex");
|
|
12768
|
+
address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
|
|
12769
|
+
privKey.fill(0);
|
|
12440
12770
|
}
|
|
12441
|
-
const privKey = randomBytes6(32);
|
|
12442
|
-
const address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
|
|
12443
12771
|
const salt = randomBytes6(32);
|
|
12444
12772
|
const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
|
|
12445
12773
|
const iv = randomBytes6(16);
|
|
12446
12774
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
12447
|
-
let enc = cipher.update(
|
|
12775
|
+
let enc = cipher.update(privKeyHex, "utf8", "hex");
|
|
12448
12776
|
enc += cipher.final("hex");
|
|
12449
|
-
|
|
12777
|
+
const x402KeyPath = join28(this.nexusDir, "x402-wallet.key");
|
|
12778
|
+
const x402Fh = await fsOpen(x402KeyPath, "w", 384);
|
|
12779
|
+
await x402Fh.writeFile("0x" + privKeyHex);
|
|
12780
|
+
await x402Fh.close();
|
|
12781
|
+
privKeyHex = "0".repeat(64);
|
|
12450
12782
|
const w = {
|
|
12451
|
-
version:
|
|
12783
|
+
version: 2,
|
|
12452
12784
|
address,
|
|
12453
12785
|
encryptedKey: enc,
|
|
12454
12786
|
iv: iv.toString("hex"),
|
|
12455
12787
|
authTag: cipher.getAuthTag().toString("hex"),
|
|
12456
12788
|
salt: salt.toString("hex"),
|
|
12457
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12789
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12790
|
+
network: "base",
|
|
12791
|
+
chainId: 8453
|
|
12458
12792
|
};
|
|
12459
|
-
await
|
|
12460
|
-
await
|
|
12793
|
+
const walletFh2 = await fsOpen(walletPath, "w", 384);
|
|
12794
|
+
await walletFh2.writeFile(JSON.stringify(w, null, 2));
|
|
12795
|
+
await walletFh2.close();
|
|
12796
|
+
await this.ensureDefaultBudget();
|
|
12461
12797
|
return [
|
|
12462
|
-
`Wallet created
|
|
12798
|
+
`Wallet created (v2, secp256k1/EVM):`,
|
|
12799
|
+
` Address: ${address}`,
|
|
12800
|
+
` Network: Base mainnet (chain 8453)`,
|
|
12801
|
+
` Token: USDC`,
|
|
12463
12802
|
` Encrypted with AES-256-GCM (key NEVER visible to agent)`,
|
|
12464
|
-
` File: ${walletPath} (0600)
|
|
12803
|
+
` File: ${walletPath} (0600)`,
|
|
12804
|
+
``,
|
|
12805
|
+
`Fund this address with USDC on Base to enable paid inference.`,
|
|
12806
|
+
`Free inference (margin=0) works without funding.`
|
|
12465
12807
|
].join("\n");
|
|
12466
12808
|
}
|
|
12809
|
+
// ---------------------------------------------------------------------------
|
|
12810
|
+
// Step 2: USDC Balance Query via Base RPC
|
|
12811
|
+
// ---------------------------------------------------------------------------
|
|
12812
|
+
balanceCache = null;
|
|
12813
|
+
async queryUsdcBalance(address, chainId) {
|
|
12814
|
+
if (this.balanceCache && Date.now() - this.balanceCache.fetchedAt < 6e4) {
|
|
12815
|
+
return this.balanceCache.value;
|
|
12816
|
+
}
|
|
12817
|
+
const rpcUrl = chainId === 84532 ? "https://sepolia.base.org" : "https://mainnet.base.org";
|
|
12818
|
+
const usdcContract = chainId === 84532 ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
12819
|
+
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
12820
|
+
const data = `0x70a08231${paddedAddress}`;
|
|
12821
|
+
try {
|
|
12822
|
+
const resp = await fetch(rpcUrl, {
|
|
12823
|
+
method: "POST",
|
|
12824
|
+
headers: { "Content-Type": "application/json" },
|
|
12825
|
+
body: JSON.stringify({
|
|
12826
|
+
jsonrpc: "2.0",
|
|
12827
|
+
id: 1,
|
|
12828
|
+
method: "eth_call",
|
|
12829
|
+
params: [{ to: usdcContract, data }, "latest"]
|
|
12830
|
+
}),
|
|
12831
|
+
signal: AbortSignal.timeout(5e3)
|
|
12832
|
+
});
|
|
12833
|
+
const json = await resp.json();
|
|
12834
|
+
if (json.result) {
|
|
12835
|
+
const balance = BigInt(json.result).toString();
|
|
12836
|
+
this.balanceCache = { value: balance, fetchedAt: Date.now() };
|
|
12837
|
+
return balance;
|
|
12838
|
+
}
|
|
12839
|
+
} catch {
|
|
12840
|
+
}
|
|
12841
|
+
return null;
|
|
12842
|
+
}
|
|
12843
|
+
// ---------------------------------------------------------------------------
|
|
12844
|
+
// Step 3: Ledger — Track earnings and expenditures
|
|
12845
|
+
// ---------------------------------------------------------------------------
|
|
12846
|
+
async doLedgerStatus() {
|
|
12847
|
+
await this.ensureDir();
|
|
12848
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12849
|
+
if (!existsSync21(ledgerPath)) {
|
|
12850
|
+
return "No ledger entries. Earnings and spending will be tracked here after x402 transactions.";
|
|
12851
|
+
}
|
|
12852
|
+
try {
|
|
12853
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
12854
|
+
const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
12855
|
+
if (entries.length === 0) {
|
|
12856
|
+
return "Ledger is empty. No transactions recorded.";
|
|
12857
|
+
}
|
|
12858
|
+
let totalEarned = 0n, totalSpent = 0n, pending = 0n;
|
|
12859
|
+
for (const e of entries) {
|
|
12860
|
+
const amt = BigInt(e.amount);
|
|
12861
|
+
if (e.type === "earned")
|
|
12862
|
+
totalEarned += amt;
|
|
12863
|
+
else if (e.type === "spent")
|
|
12864
|
+
totalSpent += amt;
|
|
12865
|
+
else if (e.type === "pending")
|
|
12866
|
+
pending += amt;
|
|
12867
|
+
}
|
|
12868
|
+
const lines = [
|
|
12869
|
+
`Ledger Status (${entries.length} entries):`,
|
|
12870
|
+
` Total Earned: $${(Number(totalEarned) / 1e6).toFixed(6)} USDC`,
|
|
12871
|
+
` Total Spent: $${(Number(totalSpent) / 1e6).toFixed(6)} USDC`,
|
|
12872
|
+
` Net: $${(Number(totalEarned - totalSpent) / 1e6).toFixed(6)} USDC`
|
|
12873
|
+
];
|
|
12874
|
+
if (pending > 0n) {
|
|
12875
|
+
lines.push(` Pending: $${(Number(pending) / 1e6).toFixed(6)} USDC`);
|
|
12876
|
+
}
|
|
12877
|
+
const recent = entries.slice(-5);
|
|
12878
|
+
lines.push(``, `Recent transactions:`);
|
|
12879
|
+
for (const e of recent) {
|
|
12880
|
+
const sign = e.type === "earned" ? "+" : e.type === "spent" ? "-" : "~";
|
|
12881
|
+
const peerShort = (e.peer || "?").slice(0, 16) + "...";
|
|
12882
|
+
lines.push(` ${e.timestamp.slice(0, 19)} ${sign}$${e.amountUsd} ${e.capability} (${peerShort})`);
|
|
12883
|
+
}
|
|
12884
|
+
return lines.join("\n");
|
|
12885
|
+
} catch {
|
|
12886
|
+
return "Error reading ledger file.";
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
async getLedgerSummary() {
|
|
12890
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12891
|
+
if (!existsSync21(ledgerPath))
|
|
12892
|
+
return null;
|
|
12893
|
+
try {
|
|
12894
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
12895
|
+
const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
12896
|
+
let totalEarned = 0n, totalSpent = 0n;
|
|
12897
|
+
for (const e of entries) {
|
|
12898
|
+
const amt = BigInt(e.amount);
|
|
12899
|
+
if (e.type === "earned")
|
|
12900
|
+
totalEarned += amt;
|
|
12901
|
+
else if (e.type === "spent")
|
|
12902
|
+
totalSpent += amt;
|
|
12903
|
+
}
|
|
12904
|
+
return {
|
|
12905
|
+
totalEarned: (Number(totalEarned) / 1e6).toFixed(6),
|
|
12906
|
+
totalSpent: (Number(totalSpent) / 1e6).toFixed(6),
|
|
12907
|
+
net: (Number(totalEarned - totalSpent) / 1e6).toFixed(6)
|
|
12908
|
+
};
|
|
12909
|
+
} catch {
|
|
12910
|
+
return null;
|
|
12911
|
+
}
|
|
12912
|
+
}
|
|
12913
|
+
async appendLedger(entry) {
|
|
12914
|
+
await this.ensureDir();
|
|
12915
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12916
|
+
const line = JSON.stringify(entry) + "\n";
|
|
12917
|
+
await writeFile12(ledgerPath, existsSync21(ledgerPath) ? await readFile13(ledgerPath, "utf8") + line : line);
|
|
12918
|
+
}
|
|
12919
|
+
// ---------------------------------------------------------------------------
|
|
12920
|
+
// Step 4: Budget Policy — Spending limits and auto-approve thresholds
|
|
12921
|
+
// ---------------------------------------------------------------------------
|
|
12922
|
+
async loadBudget() {
|
|
12923
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
12924
|
+
if (!existsSync21(budgetPath))
|
|
12925
|
+
return null;
|
|
12926
|
+
try {
|
|
12927
|
+
return JSON.parse(await readFile13(budgetPath, "utf8"));
|
|
12928
|
+
} catch {
|
|
12929
|
+
return null;
|
|
12930
|
+
}
|
|
12931
|
+
}
|
|
12932
|
+
async ensureDefaultBudget() {
|
|
12933
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
12934
|
+
if (existsSync21(budgetPath))
|
|
12935
|
+
return;
|
|
12936
|
+
const defaultBudget = {
|
|
12937
|
+
version: 1,
|
|
12938
|
+
autoApproveBelow: 1e3,
|
|
12939
|
+
// $0.001 — auto-approve micropayments
|
|
12940
|
+
dailyLimitUsdc: 1e6,
|
|
12941
|
+
// $1.00/day default
|
|
12942
|
+
perInvokeMaxUsdc: 1e5,
|
|
12943
|
+
// $0.10 per invoke max
|
|
12944
|
+
requireConfirmationAbove: 5e5,
|
|
12945
|
+
// $0.50 — ask human above this
|
|
12946
|
+
lowBalanceAlertUsdc: 5e5,
|
|
12947
|
+
// $0.50 — warn when low
|
|
12948
|
+
circuitBreakerUsdc: 1e5,
|
|
12949
|
+
// $0.10 — refuse payments below this balance
|
|
12950
|
+
allowedCapabilities: ["inference:*", "transfer:direct", "remote_infer"],
|
|
12951
|
+
deniedPeers: []
|
|
12952
|
+
};
|
|
12953
|
+
await this.ensureDir();
|
|
12954
|
+
await writeFile12(budgetPath, JSON.stringify(defaultBudget, null, 2));
|
|
12955
|
+
}
|
|
12956
|
+
async doBudgetStatus() {
|
|
12957
|
+
await this.ensureDir();
|
|
12958
|
+
const budget = await this.loadBudget();
|
|
12959
|
+
if (!budget) {
|
|
12960
|
+
return "No budget policy configured. Use wallet_create to initialize defaults, or budget_set to configure.";
|
|
12961
|
+
}
|
|
12962
|
+
const todaySpent = await this.getTodaySpending();
|
|
12963
|
+
const lines = [
|
|
12964
|
+
`Budget Policy:`,
|
|
12965
|
+
` Auto-approve below: $${(budget.autoApproveBelow / 1e6).toFixed(6)}`,
|
|
12966
|
+
` Per-invoke max: $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}`,
|
|
12967
|
+
` Daily limit: $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
|
|
12968
|
+
` Today's spending: $${(todaySpent / 1e6).toFixed(6)} / $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
|
|
12969
|
+
` Confirm above: $${(budget.requireConfirmationAbove / 1e6).toFixed(6)}`,
|
|
12970
|
+
` Low balance alert: $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(6)}`,
|
|
12971
|
+
` Circuit breaker: $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}`,
|
|
12972
|
+
` Allowed caps: ${budget.allowedCapabilities.join(", ")}`
|
|
12973
|
+
];
|
|
12974
|
+
if (budget.deniedPeers.length > 0) {
|
|
12975
|
+
lines.push(` Denied peers: ${budget.deniedPeers.length} blocked`);
|
|
12976
|
+
}
|
|
12977
|
+
return lines.join("\n");
|
|
12978
|
+
}
|
|
12979
|
+
async doBudgetSet(args) {
|
|
12980
|
+
await this.ensureDir();
|
|
12981
|
+
let budget = await this.loadBudget();
|
|
12982
|
+
if (!budget) {
|
|
12983
|
+
await this.ensureDefaultBudget();
|
|
12984
|
+
budget = await this.loadBudget();
|
|
12985
|
+
}
|
|
12986
|
+
const parseUsdc = (s) => Math.round(parseFloat(s) * 1e6);
|
|
12987
|
+
const changes = [];
|
|
12988
|
+
if (args.daily_limit) {
|
|
12989
|
+
budget.dailyLimitUsdc = parseUsdc(args.daily_limit);
|
|
12990
|
+
changes.push(`daily_limit=$${args.daily_limit}`);
|
|
12991
|
+
}
|
|
12992
|
+
if (args.per_invoke_max) {
|
|
12993
|
+
budget.perInvokeMaxUsdc = parseUsdc(args.per_invoke_max);
|
|
12994
|
+
changes.push(`per_invoke_max=$${args.per_invoke_max}`);
|
|
12995
|
+
}
|
|
12996
|
+
if (args.auto_approve_below) {
|
|
12997
|
+
budget.autoApproveBelow = parseUsdc(args.auto_approve_below);
|
|
12998
|
+
changes.push(`auto_approve_below=$${args.auto_approve_below}`);
|
|
12999
|
+
}
|
|
13000
|
+
if (changes.length === 0) {
|
|
13001
|
+
return "No budget parameters provided. Use daily_limit, per_invoke_max, or auto_approve_below.";
|
|
13002
|
+
}
|
|
13003
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
13004
|
+
await writeFile12(budgetPath, JSON.stringify(budget, null, 2));
|
|
13005
|
+
return `Budget updated: ${changes.join(", ")}`;
|
|
13006
|
+
}
|
|
13007
|
+
async getTodaySpending() {
|
|
13008
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
13009
|
+
if (!existsSync21(ledgerPath))
|
|
13010
|
+
return 0;
|
|
13011
|
+
try {
|
|
13012
|
+
const todayPrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
13013
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
13014
|
+
let total = 0;
|
|
13015
|
+
for (const line of raw.trim().split("\n")) {
|
|
13016
|
+
if (!line.trim())
|
|
13017
|
+
continue;
|
|
13018
|
+
const entry = JSON.parse(line);
|
|
13019
|
+
if (entry.type === "spent" && entry.timestamp.startsWith(todayPrefix)) {
|
|
13020
|
+
total += Number(BigInt(entry.amount));
|
|
13021
|
+
}
|
|
13022
|
+
}
|
|
13023
|
+
return total;
|
|
13024
|
+
} catch {
|
|
13025
|
+
return 0;
|
|
13026
|
+
}
|
|
13027
|
+
}
|
|
13028
|
+
async checkBudget(amountUsdc, capability, peerId) {
|
|
13029
|
+
const budget = await this.loadBudget();
|
|
13030
|
+
if (!budget)
|
|
13031
|
+
return { allowed: true };
|
|
13032
|
+
if (budget.deniedPeers.includes(peerId)) {
|
|
13033
|
+
return { allowed: false, reason: `Peer ${peerId.slice(0, 16)}... is denied by budget policy` };
|
|
13034
|
+
}
|
|
13035
|
+
const capAllowed = budget.allowedCapabilities.some((pattern) => {
|
|
13036
|
+
if (pattern === "*")
|
|
13037
|
+
return true;
|
|
13038
|
+
if (pattern.endsWith(":*"))
|
|
13039
|
+
return capability.startsWith(pattern.slice(0, -1));
|
|
13040
|
+
return capability === pattern;
|
|
13041
|
+
});
|
|
13042
|
+
if (!capAllowed) {
|
|
13043
|
+
return { allowed: false, reason: `Capability "${capability}" not in allowed list` };
|
|
13044
|
+
}
|
|
13045
|
+
if (amountUsdc > budget.perInvokeMaxUsdc) {
|
|
13046
|
+
return { allowed: false, reason: `Amount $${(amountUsdc / 1e6).toFixed(6)} exceeds per-invoke max $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}` };
|
|
13047
|
+
}
|
|
13048
|
+
const todaySpent = await this.getTodaySpending();
|
|
13049
|
+
if (todaySpent + amountUsdc > budget.dailyLimitUsdc) {
|
|
13050
|
+
return { allowed: false, reason: `Would exceed daily limit ($${((todaySpent + amountUsdc) / 1e6).toFixed(6)} > $${(budget.dailyLimitUsdc / 1e6).toFixed(6)})` };
|
|
13051
|
+
}
|
|
13052
|
+
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
13053
|
+
if (existsSync21(walletPath)) {
|
|
13054
|
+
try {
|
|
13055
|
+
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
13056
|
+
if (w.version === 2 && w.address) {
|
|
13057
|
+
const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
|
|
13058
|
+
if (balance !== null && Number(balance) < budget.circuitBreakerUsdc) {
|
|
13059
|
+
return { allowed: false, reason: `Balance $${(Number(balance) / 1e6).toFixed(6)} below circuit breaker threshold $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}` };
|
|
13060
|
+
}
|
|
13061
|
+
}
|
|
13062
|
+
} catch {
|
|
13063
|
+
}
|
|
13064
|
+
}
|
|
13065
|
+
return { allowed: true };
|
|
13066
|
+
}
|
|
13067
|
+
// ---------------------------------------------------------------------------
|
|
13068
|
+
// Step 5: Spend — Agent-initiated USDC transfer (EIP-3009)
|
|
13069
|
+
// ---------------------------------------------------------------------------
|
|
13070
|
+
async doSpend(args) {
|
|
13071
|
+
const targetAddress = args.target_address;
|
|
13072
|
+
const amountUsd = args.amount_usdc;
|
|
13073
|
+
if (!targetAddress || !/^0x[0-9a-fA-F]{40}$/.test(targetAddress))
|
|
13074
|
+
throw new Error("Valid target_address (0x + 40 hex) is required");
|
|
13075
|
+
if (!amountUsd || isNaN(parseFloat(amountUsd)))
|
|
13076
|
+
throw new Error("amount_usdc is required (e.g. '0.10')");
|
|
13077
|
+
const amountSmallest = Math.round(parseFloat(amountUsd) * 1e6);
|
|
13078
|
+
if (amountSmallest <= 0)
|
|
13079
|
+
throw new Error("amount must be positive");
|
|
13080
|
+
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct", targetAddress);
|
|
13081
|
+
if (!budgetResult.allowed)
|
|
13082
|
+
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
13083
|
+
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
13084
|
+
if (!existsSync21(walletPath))
|
|
13085
|
+
throw new Error("No wallet. Use wallet_create first.");
|
|
13086
|
+
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
13087
|
+
if (!w.encryptedKey)
|
|
13088
|
+
throw new Error("User-managed wallet cannot spend (no private key)");
|
|
13089
|
+
const salt = Buffer.from(w.salt, "hex");
|
|
13090
|
+
const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
|
|
13091
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(w.iv, "hex"));
|
|
13092
|
+
decipher.setAuthTag(Buffer.from(w.authTag, "hex"));
|
|
13093
|
+
let privKeyHex = decipher.update(w.encryptedKey, "hex", "utf8");
|
|
13094
|
+
privKeyHex += decipher.final("utf8");
|
|
13095
|
+
try {
|
|
13096
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
13097
|
+
const account = privateKeyToAccount(`0x${privKeyHex}`);
|
|
13098
|
+
const nonce = "0x" + randomBytes6(32).toString("hex");
|
|
13099
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
13100
|
+
const validAfter = now - 60;
|
|
13101
|
+
const validBefore = now + 3600;
|
|
13102
|
+
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
13103
|
+
const domain = {
|
|
13104
|
+
name: "USD Coin",
|
|
13105
|
+
version: "2",
|
|
13106
|
+
chainId: 8453,
|
|
13107
|
+
verifyingContract: USDC_ADDRESS
|
|
13108
|
+
};
|
|
13109
|
+
const types = {
|
|
13110
|
+
TransferWithAuthorization: [
|
|
13111
|
+
{ name: "from", type: "address" },
|
|
13112
|
+
{ name: "to", type: "address" },
|
|
13113
|
+
{ name: "value", type: "uint256" },
|
|
13114
|
+
{ name: "validAfter", type: "uint256" },
|
|
13115
|
+
{ name: "validBefore", type: "uint256" },
|
|
13116
|
+
{ name: "nonce", type: "bytes32" }
|
|
13117
|
+
]
|
|
13118
|
+
};
|
|
13119
|
+
const message = {
|
|
13120
|
+
from: account.address,
|
|
13121
|
+
to: targetAddress,
|
|
13122
|
+
value: BigInt(amountSmallest),
|
|
13123
|
+
validAfter: BigInt(validAfter),
|
|
13124
|
+
validBefore: BigInt(validBefore),
|
|
13125
|
+
nonce
|
|
13126
|
+
};
|
|
13127
|
+
const { signTypedData } = await import("viem/accounts");
|
|
13128
|
+
const signature = await signTypedData({
|
|
13129
|
+
privateKey: `0x${privKeyHex}`,
|
|
13130
|
+
domain,
|
|
13131
|
+
types,
|
|
13132
|
+
primaryType: "TransferWithAuthorization",
|
|
13133
|
+
message
|
|
13134
|
+
});
|
|
13135
|
+
await this.appendLedger({
|
|
13136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13137
|
+
type: "spent",
|
|
13138
|
+
amount: String(amountSmallest),
|
|
13139
|
+
amountUsd: (amountSmallest / 1e6).toFixed(6),
|
|
13140
|
+
peer: targetAddress,
|
|
13141
|
+
capability: "transfer:direct",
|
|
13142
|
+
note: "signed, awaiting submission"
|
|
13143
|
+
});
|
|
13144
|
+
const proofFile = join28(this.nexusDir, "pending-transfer.json");
|
|
13145
|
+
const proof = {
|
|
13146
|
+
from: account.address,
|
|
13147
|
+
to: targetAddress,
|
|
13148
|
+
value: String(amountSmallest),
|
|
13149
|
+
validAfter: String(validAfter),
|
|
13150
|
+
validBefore: String(validBefore),
|
|
13151
|
+
nonce,
|
|
13152
|
+
signature,
|
|
13153
|
+
chainId: 8453,
|
|
13154
|
+
usdcContract: USDC_ADDRESS,
|
|
13155
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13156
|
+
};
|
|
13157
|
+
await writeFile12(proofFile, JSON.stringify(proof, null, 2));
|
|
13158
|
+
return [
|
|
13159
|
+
`Transfer signed (EIP-3009 TransferWithAuthorization):`,
|
|
13160
|
+
` From: ${account.address}`,
|
|
13161
|
+
` To: ${targetAddress}`,
|
|
13162
|
+
` Amount: $${(amountSmallest / 1e6).toFixed(6)} USDC`,
|
|
13163
|
+
` Valid: ${new Date(validAfter * 1e3).toISOString()} to ${new Date(validBefore * 1e3).toISOString()}`,
|
|
13164
|
+
` Signature: ${signature.slice(0, 20)}...`,
|
|
13165
|
+
` Proof saved: ${proofFile}`,
|
|
13166
|
+
``,
|
|
13167
|
+
`To submit on-chain: anyone can call USDC.transferWithAuthorization() with this proof.`,
|
|
13168
|
+
`The recipient or a facilitator can submit it \u2014 no gas needed from the payer.`
|
|
13169
|
+
].join("\n");
|
|
13170
|
+
} finally {
|
|
13171
|
+
privKeyHex = "0".repeat(64);
|
|
13172
|
+
}
|
|
13173
|
+
}
|
|
13174
|
+
// ---------------------------------------------------------------------------
|
|
13175
|
+
// Step 6: Remote Inference — Route inference to remote peers on the mesh
|
|
13176
|
+
// ---------------------------------------------------------------------------
|
|
13177
|
+
async doRemoteInfer(args) {
|
|
13178
|
+
const model = args.model;
|
|
13179
|
+
const prompt = args.prompt || args.input;
|
|
13180
|
+
if (!model)
|
|
13181
|
+
throw new Error("model is required (e.g., 'qwen3.5:70b', 'nemotron-3-nano:30b')");
|
|
13182
|
+
if (!prompt)
|
|
13183
|
+
throw new Error("prompt is required");
|
|
13184
|
+
const estimatedTokens = Math.ceil(prompt.length / 4) + 1e3;
|
|
13185
|
+
let estimatedCostSmallest = 0;
|
|
13186
|
+
const pricingPath = join28(this.nexusDir, "pricing.json");
|
|
13187
|
+
if (existsSync21(pricingPath)) {
|
|
13188
|
+
try {
|
|
13189
|
+
const pricing = JSON.parse(await readFile13(pricingPath, "utf8"));
|
|
13190
|
+
const modelPricing = (pricing.models || []).find((m) => m.model === model || m.model.startsWith(model.split(":")[0]));
|
|
13191
|
+
if (modelPricing?.pricing?.input_per_1m_tokens > 0) {
|
|
13192
|
+
estimatedCostSmallest = Math.ceil(estimatedTokens / 1e6 * modelPricing.pricing.input_per_1m_tokens * 1e6);
|
|
13193
|
+
}
|
|
13194
|
+
} catch {
|
|
13195
|
+
}
|
|
13196
|
+
}
|
|
13197
|
+
if (estimatedCostSmallest > 0) {
|
|
13198
|
+
const budgetResult = await this.checkBudget(estimatedCostSmallest, "remote_infer", args.target_peer || "");
|
|
13199
|
+
if (!budgetResult.allowed) {
|
|
13200
|
+
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
13203
|
+
const budget = await this.loadBudget();
|
|
13204
|
+
if (budget && !budget.allowedCapabilities.some((c3) => c3 === "remote_infer" || c3 === "inference:*" || c3 === "*")) {
|
|
13205
|
+
return "BLOCKED: remote_infer not in budget allowedCapabilities. Use budget_set to allow it.";
|
|
13206
|
+
}
|
|
13207
|
+
const daemonArgs = {
|
|
13208
|
+
model,
|
|
13209
|
+
target_peer: args.target_peer,
|
|
13210
|
+
temperature: args.temperature,
|
|
13211
|
+
max_tokens: args.max_tokens
|
|
13212
|
+
};
|
|
13213
|
+
if (args.messages) {
|
|
13214
|
+
daemonArgs.messages = args.messages;
|
|
13215
|
+
if (args.tools)
|
|
13216
|
+
daemonArgs.tools = args.tools;
|
|
13217
|
+
} else {
|
|
13218
|
+
daemonArgs.prompt = prompt;
|
|
13219
|
+
}
|
|
13220
|
+
const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 12e4);
|
|
13221
|
+
let peerUsed = args.target_peer || "auto-selected";
|
|
13222
|
+
try {
|
|
13223
|
+
const parsed = JSON.parse(result);
|
|
13224
|
+
if (parsed.peer)
|
|
13225
|
+
peerUsed = parsed.peer;
|
|
13226
|
+
if (estimatedCostSmallest > 0) {
|
|
13227
|
+
await this.appendLedger({
|
|
13228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13229
|
+
type: "spent",
|
|
13230
|
+
amount: String(estimatedCostSmallest),
|
|
13231
|
+
amountUsd: (estimatedCostSmallest / 1e6).toFixed(6),
|
|
13232
|
+
peer: peerUsed,
|
|
13233
|
+
capability: "remote_infer:" + model,
|
|
13234
|
+
note: "remote inference"
|
|
13235
|
+
});
|
|
13236
|
+
}
|
|
13237
|
+
const inferResult = parsed.result;
|
|
13238
|
+
let responseText = "";
|
|
13239
|
+
if (typeof inferResult === "string") {
|
|
13240
|
+
responseText = inferResult;
|
|
13241
|
+
} else if (inferResult) {
|
|
13242
|
+
const events = Array.isArray(inferResult) ? inferResult : [inferResult];
|
|
13243
|
+
for (const evt of events) {
|
|
13244
|
+
if (evt.event === "result" && evt.data) {
|
|
13245
|
+
try {
|
|
13246
|
+
const data = typeof evt.data === "string" ? JSON.parse(evt.data) : evt.data;
|
|
13247
|
+
responseText = data.response || data.content || JSON.stringify(data);
|
|
13248
|
+
} catch {
|
|
13249
|
+
responseText = String(evt.data);
|
|
13250
|
+
}
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
if (!responseText) {
|
|
13254
|
+
responseText = JSON.stringify(inferResult, null, 2);
|
|
13255
|
+
}
|
|
13256
|
+
}
|
|
13257
|
+
const lines = [
|
|
13258
|
+
`Remote inference result (${model} via ${peerUsed.slice(0, 20)}...):`,
|
|
13259
|
+
``,
|
|
13260
|
+
responseText,
|
|
13261
|
+
``,
|
|
13262
|
+
`--- Metadata ---`,
|
|
13263
|
+
`Model: ${parsed.model || model}`,
|
|
13264
|
+
`Peer: ${peerUsed}`,
|
|
13265
|
+
`Capability: ${parsed.capability || "inference:" + model}`
|
|
13266
|
+
];
|
|
13267
|
+
if (estimatedCostSmallest > 0) {
|
|
13268
|
+
lines.push(`Estimated cost: $${(estimatedCostSmallest / 1e6).toFixed(6)} USDC`);
|
|
13269
|
+
} else {
|
|
13270
|
+
lines.push(`Cost: FREE (provider margin = 0)`);
|
|
13271
|
+
}
|
|
13272
|
+
return lines.join("\n");
|
|
13273
|
+
} catch {
|
|
13274
|
+
return result;
|
|
13275
|
+
}
|
|
13276
|
+
}
|
|
12467
13277
|
async doInferenceProof() {
|
|
12468
13278
|
try {
|
|
12469
13279
|
const psRaw = execSync21("ollama ps 2>/dev/null", { timeout: 1e4, encoding: "utf8" });
|
|
@@ -17416,6 +18226,121 @@ ${transcript}`
|
|
|
17416
18226
|
}
|
|
17417
18227
|
});
|
|
17418
18228
|
|
|
18229
|
+
// packages/orchestrator/dist/nexusBackend.js
|
|
18230
|
+
var NexusAgenticBackend;
|
|
18231
|
+
var init_nexusBackend = __esm({
|
|
18232
|
+
"packages/orchestrator/dist/nexusBackend.js"() {
|
|
18233
|
+
"use strict";
|
|
18234
|
+
NexusAgenticBackend = class {
|
|
18235
|
+
sendFn;
|
|
18236
|
+
model;
|
|
18237
|
+
targetPeer;
|
|
18238
|
+
constructor(sendFn, model, targetPeer) {
|
|
18239
|
+
this.sendFn = sendFn;
|
|
18240
|
+
this.model = model;
|
|
18241
|
+
this.targetPeer = targetPeer || "";
|
|
18242
|
+
}
|
|
18243
|
+
async chatCompletion(request) {
|
|
18244
|
+
const daemonArgs = {
|
|
18245
|
+
model: this.model,
|
|
18246
|
+
messages: JSON.stringify(request.messages),
|
|
18247
|
+
tools: JSON.stringify(request.tools),
|
|
18248
|
+
temperature: String(request.temperature),
|
|
18249
|
+
max_tokens: String(request.maxTokens)
|
|
18250
|
+
};
|
|
18251
|
+
if (this.targetPeer) {
|
|
18252
|
+
daemonArgs.target_peer = this.targetPeer;
|
|
18253
|
+
}
|
|
18254
|
+
const rawResult = await this.sendFn("remote_infer", daemonArgs, request.timeoutMs || 12e4);
|
|
18255
|
+
let parsed;
|
|
18256
|
+
try {
|
|
18257
|
+
parsed = JSON.parse(rawResult);
|
|
18258
|
+
} catch {
|
|
18259
|
+
throw new Error(`Invalid response from nexus daemon: ${rawResult.slice(0, 200)}`);
|
|
18260
|
+
}
|
|
18261
|
+
if (parsed.success === false) {
|
|
18262
|
+
throw new Error(`Remote inference failed: ${parsed.output || rawResult}`);
|
|
18263
|
+
}
|
|
18264
|
+
const invokeResult = parsed.result;
|
|
18265
|
+
let responseData = null;
|
|
18266
|
+
if (typeof invokeResult === "string") {
|
|
18267
|
+
try {
|
|
18268
|
+
responseData = JSON.parse(invokeResult);
|
|
18269
|
+
} catch {
|
|
18270
|
+
responseData = { response: invokeResult };
|
|
18271
|
+
}
|
|
18272
|
+
} else if (invokeResult) {
|
|
18273
|
+
const events = Array.isArray(invokeResult) ? invokeResult : [invokeResult];
|
|
18274
|
+
for (const evt of events) {
|
|
18275
|
+
if (evt.event === "result" && evt.data) {
|
|
18276
|
+
const evtData = evt.data;
|
|
18277
|
+
try {
|
|
18278
|
+
responseData = typeof evtData === "string" ? JSON.parse(evtData) : evtData;
|
|
18279
|
+
} catch {
|
|
18280
|
+
responseData = { response: String(evtData) };
|
|
18281
|
+
}
|
|
18282
|
+
}
|
|
18283
|
+
}
|
|
18284
|
+
}
|
|
18285
|
+
if (!responseData) {
|
|
18286
|
+
throw new Error("No response data from remote inference");
|
|
18287
|
+
}
|
|
18288
|
+
const choices = responseData.choices;
|
|
18289
|
+
if (choices && Array.isArray(choices)) {
|
|
18290
|
+
return {
|
|
18291
|
+
choices: choices.map((c3) => {
|
|
18292
|
+
const msg = c3.message || {};
|
|
18293
|
+
const toolCalls = msg.tool_calls || [];
|
|
18294
|
+
return {
|
|
18295
|
+
message: {
|
|
18296
|
+
content: typeof msg.content === "string" ? msg.content : msg.content ?? null,
|
|
18297
|
+
toolCalls: toolCalls.length > 0 ? toolCalls.map((tc) => {
|
|
18298
|
+
const fn = tc.function || {};
|
|
18299
|
+
let args;
|
|
18300
|
+
try {
|
|
18301
|
+
args = typeof fn.arguments === "string" ? JSON.parse(fn.arguments) : fn.arguments ?? {};
|
|
18302
|
+
} catch {
|
|
18303
|
+
args = { _raw: fn.arguments };
|
|
18304
|
+
}
|
|
18305
|
+
return {
|
|
18306
|
+
id: tc.id || crypto.randomUUID(),
|
|
18307
|
+
name: fn.name,
|
|
18308
|
+
arguments: args
|
|
18309
|
+
};
|
|
18310
|
+
}) : void 0
|
|
18311
|
+
}
|
|
18312
|
+
};
|
|
18313
|
+
}),
|
|
18314
|
+
usage: this.extractUsage(responseData)
|
|
18315
|
+
};
|
|
18316
|
+
}
|
|
18317
|
+
const content = responseData.response || JSON.stringify(responseData);
|
|
18318
|
+
return {
|
|
18319
|
+
choices: [{
|
|
18320
|
+
message: {
|
|
18321
|
+
content,
|
|
18322
|
+
toolCalls: void 0
|
|
18323
|
+
}
|
|
18324
|
+
}],
|
|
18325
|
+
usage: this.extractUsage(responseData)
|
|
18326
|
+
};
|
|
18327
|
+
}
|
|
18328
|
+
extractUsage(data) {
|
|
18329
|
+
const usage = data.usage;
|
|
18330
|
+
if (!usage)
|
|
18331
|
+
return void 0;
|
|
18332
|
+
const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
|
|
18333
|
+
const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
|
|
18334
|
+
return {
|
|
18335
|
+
totalTokens: inputTokens + outputTokens,
|
|
18336
|
+
promptTokens: inputTokens,
|
|
18337
|
+
completionTokens: outputTokens
|
|
18338
|
+
};
|
|
18339
|
+
}
|
|
18340
|
+
};
|
|
18341
|
+
}
|
|
18342
|
+
});
|
|
18343
|
+
|
|
17419
18344
|
// packages/orchestrator/dist/costTracker.js
|
|
17420
18345
|
var DEFAULT_PRICING, CostTracker;
|
|
17421
18346
|
var init_costTracker = __esm({
|
|
@@ -17895,6 +18820,7 @@ var init_dist5 = __esm({
|
|
|
17895
18820
|
init_mergeRunner();
|
|
17896
18821
|
init_retryController();
|
|
17897
18822
|
init_agenticRunner();
|
|
18823
|
+
init_nexusBackend();
|
|
17898
18824
|
init_costTracker();
|
|
17899
18825
|
init_workEvaluator();
|
|
17900
18826
|
init_sessionMetrics();
|
|
@@ -38561,7 +39487,13 @@ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
|
|
|
38561
39487
|
if (!task) {
|
|
38562
39488
|
return { success: false, output: "", error: "task is required" };
|
|
38563
39489
|
}
|
|
38564
|
-
|
|
39490
|
+
let backend;
|
|
39491
|
+
if (config.backendType === "nexus") {
|
|
39492
|
+
const nexusTool = new NexusTool(repoRoot);
|
|
39493
|
+
backend = new NexusAgenticBackend(nexusTool.sendCommand.bind(nexusTool), config.model);
|
|
39494
|
+
} else {
|
|
39495
|
+
backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
|
|
39496
|
+
}
|
|
38565
39497
|
const subCtxWindow = ctxWindowSize ?? 0;
|
|
38566
39498
|
const subTier = getModelTier(config.model);
|
|
38567
39499
|
const subCompaction = subTier === "small" ? 12e3 : subTier === "medium" ? 24e3 : 4e4;
|
|
@@ -38712,7 +39644,14 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
38712
39644
|
if (taskType && modelTier !== "small") {
|
|
38713
39645
|
dynamicContext += "\n\n" + buildTaskContext(taskType);
|
|
38714
39646
|
}
|
|
38715
|
-
|
|
39647
|
+
let backend;
|
|
39648
|
+
if (config.backendType === "nexus") {
|
|
39649
|
+
const nexusTool = new NexusTool(repoRoot);
|
|
39650
|
+
const sendFn = nexusTool.sendCommand.bind(nexusTool);
|
|
39651
|
+
backend = new NexusAgenticBackend(sendFn, config.model);
|
|
39652
|
+
} else {
|
|
39653
|
+
backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
|
|
39654
|
+
}
|
|
38716
39655
|
const compactionThreshold = modelTier === "small" ? 12e3 : modelTier === "medium" ? 24e3 : 4e4;
|
|
38717
39656
|
const runner = new AgenticRunner(backend, {
|
|
38718
39657
|
maxTurns: 60,
|
|
@@ -41924,6 +42863,10 @@ async function evalCommand(opts, config) {
|
|
|
41924
42863
|
const evalRepoRoot = opts.repoPath ?? createTempEvalRepo();
|
|
41925
42864
|
let rawBackend;
|
|
41926
42865
|
if (useLive) {
|
|
42866
|
+
if (config.backendType === "nexus") {
|
|
42867
|
+
printError("Nexus backend is not supported for eval. Use --backend ollama or --backend vllm.");
|
|
42868
|
+
process.exit(1);
|
|
42869
|
+
}
|
|
41927
42870
|
rawBackend = createBackend({
|
|
41928
42871
|
type: config.backendType,
|
|
41929
42872
|
model: config.model,
|