open-agents-ai 0.84.0 → 0.85.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/dist/index.js +223 -5
- package/package.json +1 -1
- package/prompts/agentic/system-large.md +16 -3
package/dist/index.js
CHANGED
|
@@ -11551,6 +11551,207 @@ async function handleCmd(cmd) {
|
|
|
11551
11551
|
break;
|
|
11552
11552
|
}
|
|
11553
11553
|
|
|
11554
|
+
// \u2500\u2500 Metered Inference Exposure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11555
|
+
case 'expose': {
|
|
11556
|
+
// Query Ollama for available models
|
|
11557
|
+
const ollamaUrl = args.ollama_url || process.env.OLLAMA_URL || 'http://localhost:11434';
|
|
11558
|
+
let models = [];
|
|
11559
|
+
try {
|
|
11560
|
+
const tagsResp = await fetch(ollamaUrl + '/api/tags');
|
|
11561
|
+
if (tagsResp.ok) {
|
|
11562
|
+
const tagsData = await tagsResp.json();
|
|
11563
|
+
models = (tagsData.models || []).map(m => ({
|
|
11564
|
+
name: m.name,
|
|
11565
|
+
size: m.size || 0,
|
|
11566
|
+
parameterSize: m.details?.parameter_size || '',
|
|
11567
|
+
family: m.details?.family || '',
|
|
11568
|
+
quantization: m.details?.quantization_level || '',
|
|
11569
|
+
}));
|
|
11570
|
+
}
|
|
11571
|
+
} catch (e) {
|
|
11572
|
+
writeResp(id, { ok: false, output: 'Cannot reach Ollama at ' + ollamaUrl + ': ' + e.message });
|
|
11573
|
+
return;
|
|
11574
|
+
}
|
|
11575
|
+
|
|
11576
|
+
if (models.length === 0) {
|
|
11577
|
+
writeResp(id, { ok: false, output: 'No models found on Ollama. Pull a model first.' });
|
|
11578
|
+
return;
|
|
11579
|
+
}
|
|
11580
|
+
|
|
11581
|
+
// Fetch market rates from OpenRouter (free, no auth)
|
|
11582
|
+
let marketRates = {};
|
|
11583
|
+
try {
|
|
11584
|
+
const orResp = await fetch('https://openrouter.ai/api/v1/models');
|
|
11585
|
+
if (orResp.ok) {
|
|
11586
|
+
const orData = await orResp.json();
|
|
11587
|
+
for (const m of (orData.data || [])) {
|
|
11588
|
+
if (m.pricing) {
|
|
11589
|
+
marketRates[m.id] = {
|
|
11590
|
+
input: parseFloat(m.pricing.prompt || '0') * 1_000_000,
|
|
11591
|
+
output: parseFloat(m.pricing.completion || '0') * 1_000_000,
|
|
11592
|
+
};
|
|
11593
|
+
}
|
|
11594
|
+
}
|
|
11595
|
+
}
|
|
11596
|
+
} catch { /* offline \u2014 use zero rates */ }
|
|
11597
|
+
|
|
11598
|
+
// Build pricing menu \u2014 match local models to market rates
|
|
11599
|
+
const margin = parseFloat(args.margin || '0.5'); // default 50% of market rate
|
|
11600
|
+
const pricingMenu = [];
|
|
11601
|
+
|
|
11602
|
+
for (const model of models) {
|
|
11603
|
+
// Try to match to OpenRouter model ID
|
|
11604
|
+
const baseName = model.name.replace(/:latest$/, '').toLowerCase();
|
|
11605
|
+
let matched = null;
|
|
11606
|
+
for (const [orId, rates] of Object.entries(marketRates)) {
|
|
11607
|
+
const orBase = orId.toLowerCase();
|
|
11608
|
+
if (orBase.includes(baseName) || baseName.includes(orBase.split('/').pop())) {
|
|
11609
|
+
matched = { orId, ...rates };
|
|
11610
|
+
break;
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
|
|
11614
|
+
const entry = {
|
|
11615
|
+
model: model.name,
|
|
11616
|
+
parameterSize: model.parameterSize,
|
|
11617
|
+
family: model.family,
|
|
11618
|
+
quantization: model.quantization,
|
|
11619
|
+
pricing: {
|
|
11620
|
+
input_per_1m_tokens: matched ? +(matched.input * margin).toFixed(4) : 0,
|
|
11621
|
+
output_per_1m_tokens: matched ? +(matched.output * margin).toFixed(4) : 0,
|
|
11622
|
+
currency: 'USD',
|
|
11623
|
+
source: matched ? 'openrouter:' + matched.orId : 'self-hosted:free',
|
|
11624
|
+
margin: margin,
|
|
11625
|
+
},
|
|
11626
|
+
};
|
|
11627
|
+
pricingMenu.push(entry);
|
|
11628
|
+
|
|
11629
|
+
// Register as nexus capability
|
|
11630
|
+
const capName = 'inference:' + model.name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11631
|
+
if (typeof nexus.registerCapability === 'function') {
|
|
11632
|
+
nexus.registerCapability(capName, async (request, stream) => {
|
|
11633
|
+
const logEntry = {
|
|
11634
|
+
ts: Date.now(),
|
|
11635
|
+
from: request.from || 'unknown',
|
|
11636
|
+
capability: capName,
|
|
11637
|
+
model: model.name,
|
|
11638
|
+
requestId: request.requestId,
|
|
11639
|
+
};
|
|
11640
|
+
const logFile = join(invocationsDir, Date.now() + '-' + capName + '.json');
|
|
11641
|
+
try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
|
|
11642
|
+
|
|
11643
|
+
// Collect input
|
|
11644
|
+
let prompt = '';
|
|
11645
|
+
stream.onData((msg) => {
|
|
11646
|
+
if (msg.type === 'invoke.chunk') {
|
|
11647
|
+
prompt += (typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data));
|
|
11648
|
+
}
|
|
11649
|
+
});
|
|
11650
|
+
|
|
11651
|
+
// Accept
|
|
11652
|
+
await stream.write({
|
|
11653
|
+
type: 'invoke.accept', version: 1,
|
|
11654
|
+
requestId: request.requestId, accepted: true,
|
|
11655
|
+
});
|
|
11656
|
+
|
|
11657
|
+
// Forward to Ollama
|
|
11658
|
+
try {
|
|
11659
|
+
const genResp = await fetch(ollamaUrl + '/api/generate', {
|
|
11660
|
+
method: 'POST',
|
|
11661
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11662
|
+
body: JSON.stringify({
|
|
11663
|
+
model: model.name,
|
|
11664
|
+
prompt: prompt || 'Hello',
|
|
11665
|
+
stream: false,
|
|
11666
|
+
}),
|
|
11667
|
+
});
|
|
11668
|
+
const genData = await genResp.json();
|
|
11669
|
+
const output = genData.response || '';
|
|
11670
|
+
const inputTokens = genData.prompt_eval_count || 0;
|
|
11671
|
+
const outputTokens = genData.eval_count || 0;
|
|
11672
|
+
|
|
11673
|
+
// Stream result back
|
|
11674
|
+
await stream.write({
|
|
11675
|
+
type: 'invoke.event', version: 1,
|
|
11676
|
+
requestId: request.requestId, seq: 0,
|
|
11677
|
+
event: 'result',
|
|
11678
|
+
data: JSON.stringify({
|
|
11679
|
+
model: model.name,
|
|
11680
|
+
response: output,
|
|
11681
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11682
|
+
pricing: entry.pricing,
|
|
11683
|
+
}),
|
|
11684
|
+
});
|
|
11685
|
+
|
|
11686
|
+
await stream.write({
|
|
11687
|
+
type: 'invoke.done', version: 1,
|
|
11688
|
+
requestId: request.requestId,
|
|
11689
|
+
usage: {
|
|
11690
|
+
inputBytes: prompt.length,
|
|
11691
|
+
outputBytes: output.length,
|
|
11692
|
+
tokens: inputTokens + outputTokens,
|
|
11693
|
+
},
|
|
11694
|
+
});
|
|
11695
|
+
} catch (e) {
|
|
11696
|
+
await stream.write({
|
|
11697
|
+
type: 'invoke.event', version: 1,
|
|
11698
|
+
requestId: request.requestId, seq: 0,
|
|
11699
|
+
event: 'error', data: 'Inference failed: ' + e.message,
|
|
11700
|
+
});
|
|
11701
|
+
await stream.write({
|
|
11702
|
+
type: 'invoke.done', version: 1,
|
|
11703
|
+
requestId: request.requestId,
|
|
11704
|
+
usage: { inputBytes: 0, outputBytes: 0 },
|
|
11705
|
+
});
|
|
11706
|
+
}
|
|
11707
|
+
stream.close();
|
|
11708
|
+
});
|
|
11709
|
+
}
|
|
11710
|
+
}
|
|
11711
|
+
|
|
11712
|
+
// Write pricing menu to file
|
|
11713
|
+
const pricingFile = join(nexusDir, 'pricing.json');
|
|
11714
|
+
writeFileSync(pricingFile, JSON.stringify({ updated: new Date().toISOString(), models: pricingMenu }, null, 2));
|
|
11715
|
+
writeStatus({ exposedModels: pricingMenu.length });
|
|
11716
|
+
|
|
11717
|
+
const lines = ['Exposed ' + pricingMenu.length + ' model(s) as nexus capabilities:'];
|
|
11718
|
+
for (const p of pricingMenu) {
|
|
11719
|
+
const cost = p.pricing.input_per_1m_tokens === 0
|
|
11720
|
+
? 'FREE (self-hosted)'
|
|
11721
|
+
: '$' + p.pricing.input_per_1m_tokens + '/$' + p.pricing.output_per_1m_tokens + ' per 1M tokens';
|
|
11722
|
+
lines.push(' inference:' + p.model + ' \u2014 ' + cost);
|
|
11723
|
+
}
|
|
11724
|
+
lines.push('');
|
|
11725
|
+
lines.push('Pricing menu saved to ' + pricingFile);
|
|
11726
|
+
lines.push('Market rates: ' + Object.keys(marketRates).length + ' models from OpenRouter');
|
|
11727
|
+
writeResp(id, { ok: true, output: lines.join('\\n') });
|
|
11728
|
+
break;
|
|
11729
|
+
}
|
|
11730
|
+
|
|
11731
|
+
case 'pricing_menu': {
|
|
11732
|
+
const pricingFile = join(nexusDir, 'pricing.json');
|
|
11733
|
+
if (!existsSync(pricingFile)) {
|
|
11734
|
+
writeResp(id, { ok: false, output: 'No pricing menu. Run expose first.' });
|
|
11735
|
+
return;
|
|
11736
|
+
}
|
|
11737
|
+
try {
|
|
11738
|
+
const menu = JSON.parse(readFileSync(pricingFile, 'utf8'));
|
|
11739
|
+
const lines = ['Inference Pricing Menu (updated: ' + menu.updated + ')'];
|
|
11740
|
+
lines.push('');
|
|
11741
|
+
for (const m of (menu.models || [])) {
|
|
11742
|
+
const cost = m.pricing.input_per_1m_tokens === 0
|
|
11743
|
+
? 'FREE'
|
|
11744
|
+
: '$' + m.pricing.input_per_1m_tokens + ' in / $' + m.pricing.output_per_1m_tokens + ' out per 1M tokens';
|
|
11745
|
+
lines.push(' ' + m.model + ' (' + m.parameterSize + ', ' + m.quantization + ')');
|
|
11746
|
+
lines.push(' ' + cost + ' [' + m.pricing.source + ']');
|
|
11747
|
+
}
|
|
11748
|
+
writeResp(id, { ok: true, output: lines.join('\\n') });
|
|
11749
|
+
} catch (e) {
|
|
11750
|
+
writeResp(id, { ok: false, output: 'Failed to read pricing menu: ' + e.message });
|
|
11751
|
+
}
|
|
11752
|
+
break;
|
|
11753
|
+
}
|
|
11754
|
+
|
|
11554
11755
|
case 'ping': {
|
|
11555
11756
|
writeResp(id, { ok: true, output: 'pong' });
|
|
11556
11757
|
break;
|
|
@@ -11659,7 +11860,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11659
11860
|
];
|
|
11660
11861
|
NexusTool = class {
|
|
11661
11862
|
name = "nexus";
|
|
11662
|
-
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. v1.5.0: register_capability
|
|
11863
|
+
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.";
|
|
11663
11864
|
parameters = {
|
|
11664
11865
|
type: "object",
|
|
11665
11866
|
properties: {
|
|
@@ -11689,7 +11890,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11689
11890
|
"block_peer",
|
|
11690
11891
|
"unblock_peer",
|
|
11691
11892
|
"metering_status",
|
|
11692
|
-
"room_members"
|
|
11893
|
+
"room_members",
|
|
11894
|
+
"expose",
|
|
11895
|
+
"pricing_menu"
|
|
11693
11896
|
],
|
|
11694
11897
|
description: "The nexus action to perform"
|
|
11695
11898
|
},
|
|
@@ -11736,6 +11939,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11736
11939
|
cid: {
|
|
11737
11940
|
type: "string",
|
|
11738
11941
|
description: "Content ID for retrieve_content"
|
|
11942
|
+
},
|
|
11943
|
+
ollama_url: {
|
|
11944
|
+
type: "string",
|
|
11945
|
+
description: "Ollama API URL for expose (default: http://localhost:11434)"
|
|
11946
|
+
},
|
|
11947
|
+
margin: {
|
|
11948
|
+
type: "string",
|
|
11949
|
+
description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0.5"
|
|
11739
11950
|
}
|
|
11740
11951
|
},
|
|
11741
11952
|
required: ["action"],
|
|
@@ -11830,6 +12041,12 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11830
12041
|
case "room_members":
|
|
11831
12042
|
result = await this.sendDaemonCmd("room_members", args);
|
|
11832
12043
|
break;
|
|
12044
|
+
case "expose":
|
|
12045
|
+
result = await this.sendDaemonCmd("expose", args, 3e4);
|
|
12046
|
+
break;
|
|
12047
|
+
case "pricing_menu":
|
|
12048
|
+
result = await this.sendDaemonCmd("pricing_menu", args);
|
|
12049
|
+
break;
|
|
11833
12050
|
default:
|
|
11834
12051
|
return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
|
|
11835
12052
|
}
|
|
@@ -11853,7 +12070,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11853
12070
|
return null;
|
|
11854
12071
|
}
|
|
11855
12072
|
}
|
|
11856
|
-
async sendDaemonCmd(action, args) {
|
|
12073
|
+
async sendDaemonCmd(action, args, timeoutMs = 15e3) {
|
|
11857
12074
|
const pid = this.getDaemonPid();
|
|
11858
12075
|
if (!pid)
|
|
11859
12076
|
throw new Error("Nexus daemon not running. Use action 'connect' first.");
|
|
@@ -11864,7 +12081,8 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11864
12081
|
await unlink(respFile).catch(() => {
|
|
11865
12082
|
});
|
|
11866
12083
|
await writeFile12(cmdFile, JSON.stringify({ id: cmdId, action, args }, null, 2));
|
|
11867
|
-
|
|
12084
|
+
const polls = Math.ceil(timeoutMs / 500);
|
|
12085
|
+
for (let i = 0; i < polls; i++) {
|
|
11868
12086
|
await new Promise((r) => setTimeout(r, 500));
|
|
11869
12087
|
if (!existsSync21(respFile))
|
|
11870
12088
|
continue;
|
|
@@ -11876,7 +12094,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11876
12094
|
} catch {
|
|
11877
12095
|
}
|
|
11878
12096
|
}
|
|
11879
|
-
return
|
|
12097
|
+
return `Daemon did not respond within ${Math.round(timeoutMs / 1e3)}s. It may still be processing.`;
|
|
11880
12098
|
}
|
|
11881
12099
|
// =========================================================================
|
|
11882
12100
|
// Actions
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ If a tool fails, try a different approach. If you're unsure, explore with your t
|
|
|
26
26
|
- web_fetch: Fetch a web page and extract text content (for docs, MDN, w3schools.com, etc.)
|
|
27
27
|
- memory_read: Read from persistent memory (learned patterns, solutions)
|
|
28
28
|
- memory_write: Store a fact, pattern, or solution in persistent memory for future tasks
|
|
29
|
-
- nexus: P2P agent networking (libp2p + NATS + IPFS). Connect to other agents, join rooms, send messages, discover peers, invoke capabilities, store content, manage wallet.
|
|
29
|
+
- nexus: P2P agent networking (libp2p + NATS + IPFS). Connect to other agents, join rooms, send messages, discover peers, invoke capabilities, store content, manage wallet. Key actions: connect, join_room, send_message, discover_peers, invoke_capability, expose (metered inference), pricing_menu, register_capability, block_peer, metering_status, room_members. Full list: connect, disconnect, status, join_room, leave_room, send_message, read_messages, discover_peers, list_rooms, send_dm, find_agent, invoke_capability, store_content, retrieve_content, wallet_status, wallet_create, inference_proof, register_capability, unregister_capability, list_capabilities, block_peer, unblock_peer, metering_status, room_members, expose, pricing_menu.
|
|
30
30
|
- task_complete: Signal task completion with a summary
|
|
31
31
|
|
|
32
32
|
## Parallel Execution & Sub-Agents
|
|
@@ -251,12 +251,25 @@ Use invoke_capability for real work (inference, tool calls) — NOT room message
|
|
|
251
251
|
### v1.5.0: Room Members
|
|
252
252
|
nexus(action='room_members', room_id='general') — live member list with capabilities
|
|
253
253
|
|
|
254
|
+
### Metered Inference Exposure
|
|
255
|
+
nexus(action='expose') — expose ALL local Ollama models as nexus capabilities
|
|
256
|
+
nexus(action='expose', margin='0.5') — set pricing at 50% of market rate (default)
|
|
257
|
+
nexus(action='expose', margin='0') — expose for free (self-hosted, no cost)
|
|
258
|
+
nexus(action='expose', margin='1.0') — match market rate
|
|
259
|
+
nexus(action='pricing_menu') — show current pricing menu for exposed models
|
|
260
|
+
|
|
261
|
+
expose queries local Ollama for models, fetches live market rates from OpenRouter
|
|
262
|
+
(https://openrouter.ai/api/v1/models — free, no auth), registers each model as a
|
|
263
|
+
nexus capability (inference:{model_name}), and writes pricing to .oa/nexus/pricing.json.
|
|
264
|
+
Peers can invoke your models via invoke_capability and see metered usage.
|
|
265
|
+
|
|
254
266
|
SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
|
|
255
267
|
All outbound messages are scanned for key material leaks.
|
|
256
268
|
|
|
257
269
|
When the user asks about expanding capabilities or connecting with other agents, suggest
|
|
258
|
-
enabling nexus networking. Use
|
|
259
|
-
to
|
|
270
|
+
enabling nexus networking. Use expose to share your models with the network, pricing_menu
|
|
271
|
+
to check rates, register_capability to serve custom invocations, and room_members to
|
|
272
|
+
discover who's online.
|
|
260
273
|
|
|
261
274
|
## Temporal Agency — Scheduling, Reminders & Long-Horizon Tasks
|
|
262
275
|
|