open-agents-ai 0.87.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 +9 -0
- package/dist/index.js +470 -35
- package/package.json +1 -1
- package/prompts/agentic/system-large.md +18 -0
package/README.md
CHANGED
|
@@ -999,6 +999,7 @@ The steering sub-agent uses the same model and backend as the main agent with `m
|
|
|
999
999
|
| `nexus:expose` | Expose local Ollama models as metered inference capabilities with OpenRouter-based pricing |
|
|
1000
1000
|
| `nexus:wallet_create` | Generate secp256k1/EVM wallet (Base mainnet USDC) with AES-256-GCM encryption + x402-wallet.key |
|
|
1001
1001
|
| `nexus:spend` | Sign EIP-3009 USDC TransferWithAuthorization — budget-checked, gasless for payer |
|
|
1002
|
+
| `nexus:remote_infer` | Route inference to a remote peer's model — auto-discovers peers, budget-checks, invokes, returns result |
|
|
1002
1003
|
| `nexus:ledger_status` | Transaction history (earned/spent/pending USDC) |
|
|
1003
1004
|
| `nexus:budget_set` | Configure spending limits — daily cap, per-invoke max, auto-approve threshold |
|
|
1004
1005
|
|
|
@@ -1077,6 +1078,13 @@ nexus(action='spend', target_address='0x...', amount_usdc='0.10')
|
|
|
1077
1078
|
```
|
|
1078
1079
|
Signs an EIP-3009 `TransferWithAuthorization`. Budget-checked before signing. The recipient (or any facilitator) submits on-chain — no gas needed from the payer. Proof saved to `.oa/nexus/pending-transfer.json`.
|
|
1079
1080
|
|
|
1081
|
+
### Remote Inference — Tap Into the Mesh
|
|
1082
|
+
```
|
|
1083
|
+
nexus(action='remote_infer', model='qwen3.5:70b', prompt='Complex analysis task...')
|
|
1084
|
+
nexus(action='remote_infer', model='llama3.3:70b', prompt='...', target_peer='12D3KooW...')
|
|
1085
|
+
```
|
|
1086
|
+
Route a prompt to a remote peer's model on the P2P mesh. Auto-discovers peers that have the requested model exposed, budget-checks the estimated cost, invokes the inference capability, and returns the response. Use `target_peer` to route to a specific provider, or omit for automatic peer selection. Your 8B laptop can seamlessly tap into a 122B model running on the mesh.
|
|
1087
|
+
|
|
1080
1088
|
### Ledger & Budget
|
|
1081
1089
|
```
|
|
1082
1090
|
nexus(action='ledger_status') # Earned/spent/pending history
|
|
@@ -1093,6 +1101,7 @@ nexus(action='budget_set', auto_approve_below='0.01') # Auto-approve micropayme
|
|
|
1093
1101
|
4. Consumer's daemon auto-signs `payment_proof` → provider validates → invoke proceeds
|
|
1094
1102
|
5. Metering hook writes payment events to `ledger.jsonl`
|
|
1095
1103
|
6. **spend** → direct agent-to-agent USDC transfers (EIP-3009, gasless)
|
|
1104
|
+
7. **remote_infer** → auto-discover + invoke in one action (budget-checked, with ledger entry)
|
|
1096
1105
|
|
|
1097
1106
|
### Security Model
|
|
1098
1107
|
- Private keys: AES-256-GCM encrypted in `wallet.enc` (scrypt-derived key)
|
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";
|
|
@@ -11405,6 +11405,101 @@ async function handleCmd(cmd) {
|
|
|
11405
11405
|
}
|
|
11406
11406
|
break;
|
|
11407
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
|
+
}
|
|
11408
11503
|
case 'store_content': {
|
|
11409
11504
|
const data = args.data;
|
|
11410
11505
|
if (!data) { writeResp(id, { ok: false, output: 'data is required' }); return; }
|
|
@@ -11679,11 +11774,17 @@ async function handleCmd(cmd) {
|
|
|
11679
11774
|
const logFile = join(invocationsDir, Date.now() + '-' + capName + '.json');
|
|
11680
11775
|
try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
|
|
11681
11776
|
|
|
11682
|
-
// Collect input
|
|
11777
|
+
// Collect input via stream data events
|
|
11683
11778
|
let prompt = '';
|
|
11779
|
+
var dataChunks = [];
|
|
11780
|
+
var inputDone = false;
|
|
11684
11781
|
stream.onData((msg) => {
|
|
11685
11782
|
if (msg.type === 'invoke.chunk') {
|
|
11686
|
-
|
|
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;
|
|
11687
11788
|
}
|
|
11688
11789
|
});
|
|
11689
11790
|
|
|
@@ -11693,33 +11794,100 @@ async function handleCmd(cmd) {
|
|
|
11693
11794
|
requestId: request.requestId, accepted: true,
|
|
11694
11795
|
});
|
|
11695
11796
|
|
|
11696
|
-
//
|
|
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
|
|
11697
11807
|
try {
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
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 = {
|
|
11702
11816
|
model: model.name,
|
|
11703
|
-
|
|
11817
|
+
messages: parsedReq.messages,
|
|
11704
11818
|
stream: false,
|
|
11705
|
-
}
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
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
|
+
}
|
|
11711
11884
|
|
|
11712
11885
|
// Stream result back
|
|
11713
11886
|
await stream.write({
|
|
11714
11887
|
type: 'invoke.event', version: 1,
|
|
11715
11888
|
requestId: request.requestId, seq: 0,
|
|
11716
11889
|
event: 'result',
|
|
11717
|
-
data: JSON.stringify(
|
|
11718
|
-
model: model.name,
|
|
11719
|
-
response: output,
|
|
11720
|
-
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11721
|
-
pricing: entry.pricing,
|
|
11722
|
-
}),
|
|
11890
|
+
data: JSON.stringify(responsePayload),
|
|
11723
11891
|
});
|
|
11724
11892
|
|
|
11725
11893
|
await stream.write({
|
|
@@ -11727,7 +11895,7 @@ async function handleCmd(cmd) {
|
|
|
11727
11895
|
requestId: request.requestId,
|
|
11728
11896
|
usage: {
|
|
11729
11897
|
inputBytes: prompt.length,
|
|
11730
|
-
outputBytes: output.length,
|
|
11898
|
+
outputBytes: (output || '').length,
|
|
11731
11899
|
tokens: inputTokens + outputTokens,
|
|
11732
11900
|
},
|
|
11733
11901
|
});
|
|
@@ -11932,7 +12100,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11932
12100
|
];
|
|
11933
12101
|
NexusTool = class {
|
|
11934
12102
|
name = "nexus";
|
|
11935
|
-
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.";
|
|
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.";
|
|
11936
12104
|
parameters = {
|
|
11937
12105
|
type: "object",
|
|
11938
12106
|
properties: {
|
|
@@ -11968,7 +12136,8 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11968
12136
|
"ledger_status",
|
|
11969
12137
|
"budget_status",
|
|
11970
12138
|
"budget_set",
|
|
11971
|
-
"spend"
|
|
12139
|
+
"spend",
|
|
12140
|
+
"remote_infer"
|
|
11972
12141
|
],
|
|
11973
12142
|
description: "The nexus action to perform"
|
|
11974
12143
|
},
|
|
@@ -12043,6 +12212,22 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12043
12212
|
amount_usdc: {
|
|
12044
12213
|
type: "string",
|
|
12045
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"
|
|
12046
12231
|
}
|
|
12047
12232
|
},
|
|
12048
12233
|
required: ["action"],
|
|
@@ -12155,6 +12340,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12155
12340
|
case "spend":
|
|
12156
12341
|
result = await this.doSpend(args);
|
|
12157
12342
|
break;
|
|
12343
|
+
case "remote_infer":
|
|
12344
|
+
result = await this.doRemoteInfer(args);
|
|
12345
|
+
break;
|
|
12158
12346
|
default:
|
|
12159
12347
|
return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
|
|
12160
12348
|
}
|
|
@@ -12204,6 +12392,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12204
12392
|
}
|
|
12205
12393
|
return `Daemon did not respond within ${Math.round(timeoutMs / 1e3)}s. It may still be processing.`;
|
|
12206
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
|
+
}
|
|
12207
12403
|
// =========================================================================
|
|
12208
12404
|
// Actions
|
|
12209
12405
|
// =========================================================================
|
|
@@ -12551,8 +12747,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12551
12747
|
network: "base",
|
|
12552
12748
|
chainId: 8453
|
|
12553
12749
|
};
|
|
12554
|
-
await
|
|
12555
|
-
await
|
|
12750
|
+
const walletFh = await fsOpen(walletPath, "w", 384);
|
|
12751
|
+
await walletFh.writeFile(JSON.stringify(w2, null, 2));
|
|
12752
|
+
await walletFh.close();
|
|
12556
12753
|
await this.ensureDefaultBudget();
|
|
12557
12754
|
return `Wallet configured: ${userAddress} (user-managed, Base mainnet, no x402 key \u2014 spend/sign requires self-hosted wallet)`;
|
|
12558
12755
|
}
|
|
@@ -12578,8 +12775,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12578
12775
|
let enc = cipher.update(privKeyHex, "utf8", "hex");
|
|
12579
12776
|
enc += cipher.final("hex");
|
|
12580
12777
|
const x402KeyPath = join28(this.nexusDir, "x402-wallet.key");
|
|
12581
|
-
await
|
|
12582
|
-
await
|
|
12778
|
+
const x402Fh = await fsOpen(x402KeyPath, "w", 384);
|
|
12779
|
+
await x402Fh.writeFile("0x" + privKeyHex);
|
|
12780
|
+
await x402Fh.close();
|
|
12583
12781
|
privKeyHex = "0".repeat(64);
|
|
12584
12782
|
const w = {
|
|
12585
12783
|
version: 2,
|
|
@@ -12592,8 +12790,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12592
12790
|
network: "base",
|
|
12593
12791
|
chainId: 8453
|
|
12594
12792
|
};
|
|
12595
|
-
await
|
|
12596
|
-
await
|
|
12793
|
+
const walletFh2 = await fsOpen(walletPath, "w", 384);
|
|
12794
|
+
await walletFh2.writeFile(JSON.stringify(w, null, 2));
|
|
12795
|
+
await walletFh2.close();
|
|
12597
12796
|
await this.ensureDefaultBudget();
|
|
12598
12797
|
return [
|
|
12599
12798
|
`Wallet created (v2, secp256k1/EVM):`,
|
|
@@ -12748,7 +12947,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12748
12947
|
// $0.50 — warn when low
|
|
12749
12948
|
circuitBreakerUsdc: 1e5,
|
|
12750
12949
|
// $0.10 — refuse payments below this balance
|
|
12751
|
-
allowedCapabilities: ["inference:*", "transfer:direct"],
|
|
12950
|
+
allowedCapabilities: ["inference:*", "transfer:direct", "remote_infer"],
|
|
12752
12951
|
deniedPeers: []
|
|
12753
12952
|
};
|
|
12754
12953
|
await this.ensureDir();
|
|
@@ -12878,7 +13077,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12878
13077
|
const amountSmallest = Math.round(parseFloat(amountUsd) * 1e6);
|
|
12879
13078
|
if (amountSmallest <= 0)
|
|
12880
13079
|
throw new Error("amount must be positive");
|
|
12881
|
-
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct",
|
|
13080
|
+
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct", targetAddress);
|
|
12882
13081
|
if (!budgetResult.allowed)
|
|
12883
13082
|
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
12884
13083
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
@@ -12972,6 +13171,109 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12972
13171
|
privKeyHex = "0".repeat(64);
|
|
12973
13172
|
}
|
|
12974
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
|
+
}
|
|
12975
13277
|
async doInferenceProof() {
|
|
12976
13278
|
try {
|
|
12977
13279
|
const psRaw = execSync21("ollama ps 2>/dev/null", { timeout: 1e4, encoding: "utf8" });
|
|
@@ -17924,6 +18226,121 @@ ${transcript}`
|
|
|
17924
18226
|
}
|
|
17925
18227
|
});
|
|
17926
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
|
+
|
|
17927
18344
|
// packages/orchestrator/dist/costTracker.js
|
|
17928
18345
|
var DEFAULT_PRICING, CostTracker;
|
|
17929
18346
|
var init_costTracker = __esm({
|
|
@@ -18403,6 +18820,7 @@ var init_dist5 = __esm({
|
|
|
18403
18820
|
init_mergeRunner();
|
|
18404
18821
|
init_retryController();
|
|
18405
18822
|
init_agenticRunner();
|
|
18823
|
+
init_nexusBackend();
|
|
18406
18824
|
init_costTracker();
|
|
18407
18825
|
init_workEvaluator();
|
|
18408
18826
|
init_sessionMetrics();
|
|
@@ -39069,7 +39487,13 @@ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
|
|
|
39069
39487
|
if (!task) {
|
|
39070
39488
|
return { success: false, output: "", error: "task is required" };
|
|
39071
39489
|
}
|
|
39072
|
-
|
|
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
|
+
}
|
|
39073
39497
|
const subCtxWindow = ctxWindowSize ?? 0;
|
|
39074
39498
|
const subTier = getModelTier(config.model);
|
|
39075
39499
|
const subCompaction = subTier === "small" ? 12e3 : subTier === "medium" ? 24e3 : 4e4;
|
|
@@ -39220,7 +39644,14 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
39220
39644
|
if (taskType && modelTier !== "small") {
|
|
39221
39645
|
dynamicContext += "\n\n" + buildTaskContext(taskType);
|
|
39222
39646
|
}
|
|
39223
|
-
|
|
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
|
+
}
|
|
39224
39655
|
const compactionThreshold = modelTier === "small" ? 12e3 : modelTier === "medium" ? 24e3 : 4e4;
|
|
39225
39656
|
const runner = new AgenticRunner(backend, {
|
|
39226
39657
|
maxTurns: 60,
|
|
@@ -42432,6 +42863,10 @@ async function evalCommand(opts, config) {
|
|
|
42432
42863
|
const evalRepoRoot = opts.repoPath ?? createTempEvalRepo();
|
|
42433
42864
|
let rawBackend;
|
|
42434
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
|
+
}
|
|
42435
42870
|
rawBackend = createBackend({
|
|
42436
42871
|
type: config.backendType,
|
|
42437
42872
|
model: config.model,
|
package/package.json
CHANGED
|
@@ -288,12 +288,30 @@ Signs an EIP-3009 TransferWithAuthorization for USDC on Base. Budget-checked bef
|
|
|
288
288
|
The signed proof is saved to `.oa/nexus/pending-transfer.json` — anyone can submit it on-chain
|
|
289
289
|
via `USDC.transferWithAuthorization()`. No gas needed from the payer.
|
|
290
290
|
|
|
291
|
+
### Remote Inference — `nexus(action='remote_infer', model='...', prompt='...')`
|
|
292
|
+
|
|
293
|
+
Route a prompt to a remote peer's model on the P2P mesh. The action auto-discovers peers
|
|
294
|
+
that have the requested model exposed, budget-checks the estimated cost, invokes the
|
|
295
|
+
inference capability, and returns the response text.
|
|
296
|
+
|
|
297
|
+
**Parameters**:
|
|
298
|
+
- `model` (required) — model name the provider is running (e.g., `qwen3.5:70b`, `nemotron-3-nano:30b`)
|
|
299
|
+
- `prompt` (required) — the text prompt to send
|
|
300
|
+
- `target_peer` (optional) — specific peer ID; if omitted, auto-selects the first peer with the model
|
|
301
|
+
- `temperature` (optional) — sampling temperature (default: 0.7)
|
|
302
|
+
- `max_tokens` (optional) — max tokens to generate (default: 4096)
|
|
303
|
+
|
|
304
|
+
**When to use**: When a task needs a larger/different model than what's available locally,
|
|
305
|
+
or when you want to offload inference to a remote GPU. The provider must be connected to
|
|
306
|
+
the mesh and have run `expose` to advertise their models.
|
|
307
|
+
|
|
291
308
|
### x402 Flow Summary
|
|
292
309
|
1. wallet_create → generates wallet + x402-wallet.key (plaintext, 0600, for daemon)
|
|
293
310
|
2. expose with margin > 0 → registers capabilities with USDC pricing
|
|
294
311
|
3. Peers invoke_capability → daemon auto-handles payment_required/payment_proof
|
|
295
312
|
4. Metering hook writes payment events to ledger.jsonl
|
|
296
313
|
5. spend → sign direct USDC transfers (EIP-3009)
|
|
314
|
+
6. remote_infer → auto-discover peer + invoke inference + budget check + ledger entry
|
|
297
315
|
|
|
298
316
|
SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
|
|
299
317
|
x402-wallet.key is 0600-permissioned for daemon use only. All outbound messages are scanned
|