open-agents-ai 0.184.7 → 0.184.9

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.
Files changed (2) hide show
  1. package/dist/index.js +231 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5571,6 +5571,123 @@ async function handleCmd(cmd) {
5571
5571
  writeResp(id, { ok: true, output: 'Message sent (id: ' + msgId + ')' });
5572
5572
  break;
5573
5573
  }
5574
+ // \u2500\u2500 Sponsor announcement via NATS \u2500\u2500
5575
+ case 'sponsor_announce': {
5576
+ // Publish sponsor metadata to NATS so other OA instances can discover it
5577
+ if (!_natsConn || !_natsCodec) {
5578
+ writeResp(id, { ok: false, output: 'NATS not connected \u2014 cannot announce sponsorship' });
5579
+ return;
5580
+ }
5581
+ var sponsorData = {
5582
+ type: 'sponsor.announce',
5583
+ peerId: globalThis._daemonPeerId || 'unknown',
5584
+ name: args.name || 'Anonymous Sponsor',
5585
+ models: args.models || [],
5586
+ tunnelUrl: args.tunnel_url || null,
5587
+ authKey: args.auth_key || '',
5588
+ limits: {
5589
+ maxRequestsPerMinute: parseInt(args.rpm || '60', 10),
5590
+ maxTokensPerDay: parseInt(args.tpd || '100000', 10),
5591
+ },
5592
+ banner: args.banner || null,
5593
+ message: args.message || '',
5594
+ linkUrl: args.link_url || '',
5595
+ linkText: args.link_text || '',
5596
+ status: 'active',
5597
+ timestamp: Date.now(),
5598
+ };
5599
+ _natsConn.publish('nexus.sponsors.announce', _natsCodec.encode(JSON.stringify(sponsorData)));
5600
+ dlog('sponsor_announce: published to nexus.sponsors.announce');
5601
+ // Also join the sponsors room and send as room message for persistence
5602
+ if (!rooms.has('sponsors')) {
5603
+ try {
5604
+ var spRoom = await nexus.joinRoom('sponsors');
5605
+ rooms.set('sponsors', spRoom);
5606
+ dlog('sponsor_announce: auto-joined sponsors room');
5607
+ } catch (roomErr) {
5608
+ dlog('sponsor_announce: room join failed: ' + (roomErr.message || roomErr));
5609
+ }
5610
+ }
5611
+ if (rooms.has('sponsors')) {
5612
+ try {
5613
+ await rooms.get('sponsors').send(JSON.stringify(sponsorData), { format: 'application/json' });
5614
+ } catch {}
5615
+ }
5616
+ writeResp(id, { ok: true, output: 'Sponsor announced: ' + sponsorData.name + ' (' + sponsorData.models.length + ' models)' });
5617
+ break;
5618
+ }
5619
+ case 'sponsor_discover': {
5620
+ // Subscribe to nexus.sponsors.announce for a short window and collect announcements
5621
+ if (!_natsConn || !_natsCodec) {
5622
+ writeResp(id, { ok: false, output: 'NATS not connected', sponsors: [] });
5623
+ return;
5624
+ }
5625
+ var foundSponsors = [];
5626
+ var discoverTimeout = parseInt(args.timeout_ms || '5000', 10);
5627
+
5628
+ // Also check sponsors room inbox for cached messages
5629
+ var sponsorInbox = join(inboxDir, 'sponsors');
5630
+ try {
5631
+ if (existsSync(sponsorInbox)) {
5632
+ var inboxFiles = readdirSync(sponsorInbox).filter(f => f.endsWith('.json')).sort().reverse().slice(0, 20);
5633
+ for (var fi = 0; fi < inboxFiles.length; fi++) {
5634
+ try {
5635
+ var msg = JSON.parse(readFileSync(join(sponsorInbox, inboxFiles[fi]), 'utf8'));
5636
+ var content = msg.content || '';
5637
+ try {
5638
+ var parsed = JSON.parse(content);
5639
+ if (parsed.type === 'sponsor.announce' && parsed.status === 'active') {
5640
+ // Only include if not too stale (< 10 minutes)
5641
+ if (Date.now() - (parsed.timestamp || 0) < 600000) {
5642
+ foundSponsors.push(parsed);
5643
+ }
5644
+ }
5645
+ } catch {}
5646
+ } catch {}
5647
+ }
5648
+ }
5649
+ } catch {}
5650
+
5651
+ // Join sponsors room to receive future announcements
5652
+ if (!rooms.has('sponsors')) {
5653
+ try {
5654
+ var spRoom2 = await nexus.joinRoom('sponsors');
5655
+ rooms.set('sponsors', spRoom2);
5656
+ } catch {}
5657
+ }
5658
+
5659
+ // Subscribe to NATS for live announcements (short window)
5660
+ try {
5661
+ var sub = _natsConn.subscribe('nexus.sponsors.announce');
5662
+ var subDone = false;
5663
+ setTimeout(() => { subDone = true; sub.unsubscribe(); }, discoverTimeout);
5664
+ for await (var natMsg of sub) {
5665
+ if (subDone) break;
5666
+ try {
5667
+ var sp = JSON.parse(_natsCodec.decode(natMsg.data));
5668
+ if (sp.type === 'sponsor.announce' && sp.status === 'active') {
5669
+ foundSponsors.push(sp);
5670
+ }
5671
+ } catch {}
5672
+ }
5673
+ } catch (subErr) {
5674
+ dlog('sponsor_discover: NATS sub error: ' + (subErr.message || subErr));
5675
+ }
5676
+
5677
+ // Deduplicate by peerId
5678
+ var seen = {};
5679
+ var unique = [];
5680
+ for (var si = 0; si < foundSponsors.length; si++) {
5681
+ var key = foundSponsors[si].peerId || foundSponsors[si].tunnelUrl || ('sp-' + si);
5682
+ if (!seen[key]) {
5683
+ seen[key] = true;
5684
+ unique.push(foundSponsors[si]);
5685
+ }
5686
+ }
5687
+
5688
+ writeResp(id, { ok: true, output: unique.length + ' sponsor(s) found', sponsors: unique });
5689
+ break;
5690
+ }
5574
5691
  case 'discover_peers': {
5575
5692
  const node = nexus.network?.node;
5576
5693
  const peers = node?.getPeers?.() || [];
@@ -8173,6 +8290,12 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
8173
8290
  case "expose":
8174
8291
  result = await this.sendDaemonCmd("expose", args, 6e4);
8175
8292
  break;
8293
+ case "sponsor_announce":
8294
+ result = await this.sendDaemonCmd("sponsor_announce", args, 1e4);
8295
+ break;
8296
+ case "sponsor_discover":
8297
+ result = await this.sendDaemonCmd("sponsor_discover", args, 1e4);
8298
+ break;
8176
8299
  case "pricing_menu":
8177
8300
  result = await this.sendDaemonCmd("pricing_menu", args);
8178
8301
  break;
@@ -47236,6 +47359,28 @@ async function handleSlashCommand(input, ctx) {
47236
47359
  renderError(`P2P start failed: ${err instanceof Error ? err.message : String(err)}`);
47237
47360
  }
47238
47361
  }
47362
+ try {
47363
+ const { NexusTool: NexusTool2 } = __require("@open-agents/execution");
47364
+ const nexus = new NexusTool2(projectDir);
47365
+ const tunnelGw = ctx.getExposeGateway?.();
47366
+ const enabledEps = config.endpoints.filter((e) => e.enabled);
47367
+ const allModels = enabledEps.flatMap((e) => e.models || []);
47368
+ await nexus.execute({
47369
+ action: "sponsor_announce",
47370
+ name: config.header.message || "OA Sponsor",
47371
+ models: allModels.join(","),
47372
+ tunnel_url: tunnelGw?.tunnelUrl || "",
47373
+ auth_key: tunnelGw?.authKey || "",
47374
+ rpm: String(config.rateLimits.maxRequestsPerMinute),
47375
+ tpd: String(config.rateLimits.maxTokensPerDay),
47376
+ banner: config.banner.preset,
47377
+ message: config.header.message,
47378
+ link_url: config.header.linkUrl,
47379
+ link_text: config.header.linkText
47380
+ });
47381
+ renderInfo("Announced on nexus P2P mesh \u2014 other users can discover you via /endpoint sponsor");
47382
+ } catch {
47383
+ }
47239
47384
  }
47240
47385
  });
47241
47386
  if (result) {
@@ -49006,39 +49151,75 @@ async function handleEndpoint(arg, ctx, local = false) {
49006
49151
  async function handleSponsoredEndpoint(ctx, local) {
49007
49152
  process.stdout.write(`
49008
49153
  ${c2.dim("Scanning for sponsored inference endpoints...")}
49009
-
49010
49154
  `);
49011
49155
  const sponsors = [];
49156
+ let nexusTool = null;
49157
+ try {
49158
+ const { NexusTool: NexusTool2 } = __require("@open-agents/execution");
49159
+ nexusTool = new NexusTool2(ctx.repoRoot ?? process.cwd());
49160
+ } catch {
49161
+ }
49162
+ if (nexusTool) {
49163
+ process.stdout.write(` ${c2.dim("Connecting to nexus P2P mesh...")}
49164
+ `);
49165
+ try {
49166
+ const statusResult = await nexusTool.execute({ action: "status" });
49167
+ const isConnected = typeof statusResult === "string" && statusResult.includes("connected");
49168
+ if (!isConnected) {
49169
+ process.stdout.write(` ${c2.dim("Starting nexus daemon...")}
49170
+ `);
49171
+ await nexusTool.execute({ action: "connect" });
49172
+ await new Promise((r) => setTimeout(r, 3e3));
49173
+ }
49174
+ process.stdout.write(` ${c2.dim("Querying nexus sponsors room...")}
49175
+ `);
49176
+ const discoverResult = await nexusTool.execute({
49177
+ action: "sponsor_discover",
49178
+ timeout_ms: "5000"
49179
+ });
49180
+ if (typeof discoverResult === "string") {
49181
+ try {
49182
+ const parsed = JSON.parse(discoverResult);
49183
+ const nexusSponsors = parsed.sponsors || [];
49184
+ for (const ns of nexusSponsors) {
49185
+ sponsors.push({
49186
+ name: ns.name || "Unknown Sponsor",
49187
+ url: ns.tunnelUrl || "",
49188
+ peerId: ns.peerId || void 0,
49189
+ authKey: ns.authKey || "",
49190
+ models: Array.isArray(ns.models) ? ns.models : (ns.models || "").split(",").filter(Boolean),
49191
+ limits: {
49192
+ rpm: ns.limits?.maxRequestsPerMinute || 60,
49193
+ tpd: ns.limits?.maxTokensPerDay || 1e5
49194
+ },
49195
+ source: "nexus",
49196
+ banner: { preset: ns.banner?.preset, message: ns.message }
49197
+ });
49198
+ }
49199
+ if (nexusSponsors.length > 0) {
49200
+ process.stdout.write(` ${c2.green("\u25CF")} Found ${nexusSponsors.length} sponsor(s) on nexus mesh
49201
+ `);
49202
+ }
49203
+ } catch {
49204
+ }
49205
+ }
49206
+ } catch (nexusErr) {
49207
+ process.stdout.write(` ${c2.dim("Nexus unavailable: " + (nexusErr instanceof Error ? nexusErr.message : String(nexusErr)).slice(0, 60))}
49208
+ `);
49209
+ }
49210
+ }
49012
49211
  const gateway = ctx.getExposeGateway?.();
49013
49212
  if (gateway && gateway.isActive) {
49014
49213
  const gwUrl = gateway.tunnelUrl || `http://127.0.0.1:${gateway._proxyPort || 11434}`;
49015
- sponsors.push({
49016
- name: "Local Gateway",
49017
- url: gwUrl,
49018
- authKey: gateway.authKey,
49019
- models: [],
49020
- limits: { rpm: 60, tpd: 1e5 },
49021
- source: "local"
49022
- });
49023
- }
49024
- const { loadSponsorConfig: loadSpCfg } = await Promise.resolve().then(() => (init_sponsor_wizard(), sponsor_wizard_exports));
49025
- const spCfg = loadSpCfg(ctx.repoRoot ?? process.cwd());
49026
- if (spCfg && spCfg.status === "active") {
49027
- const epUrls = new Set(sponsors.map((s) => s.url));
49028
- for (const ep of spCfg.endpoints.filter((e) => e.enabled)) {
49029
- if (!epUrls.has(ep.url)) {
49030
- sponsors.push({
49031
- name: ep.label,
49032
- url: ep.url,
49033
- authKey: "",
49034
- models: ep.models,
49035
- limits: {
49036
- rpm: spCfg.rateLimits.maxRequestsPerMinute,
49037
- tpd: spCfg.rateLimits.maxTokensPerDay
49038
- },
49039
- source: "local"
49040
- });
49041
- }
49214
+ if (!sponsors.some((s) => s.url === gwUrl)) {
49215
+ sponsors.push({
49216
+ name: "Local Gateway",
49217
+ url: gwUrl,
49218
+ authKey: gateway.authKey,
49219
+ models: [],
49220
+ limits: { rpm: 60, tpd: 1e5 },
49221
+ source: "local"
49222
+ });
49042
49223
  }
49043
49224
  }
49044
49225
  const sponsorDir2 = join58(ctx.repoRoot ?? process.cwd(), ".oa", "sponsor");
@@ -49047,30 +49228,19 @@ async function handleSponsoredEndpoint(ctx, local) {
49047
49228
  if (existsSync42(knownFile)) {
49048
49229
  const saved = JSON.parse(readFileSync31(knownFile, "utf8"));
49049
49230
  for (const s of saved) {
49050
- try {
49051
- const probe = await fetch(`${s.url}/api/tags`, {
49052
- headers: s.authKey ? { Authorization: `Bearer ${s.authKey}` } : {},
49053
- signal: AbortSignal.timeout(5e3)
49054
- });
49055
- if (probe.ok) {
49056
- const data = await probe.json();
49057
- s.models = (data.models || []).map((m) => m.name);
49058
- sponsors.push(s);
49059
- }
49060
- } catch {
49231
+ if (!sponsors.some((sp) => sp.url === s.url)) {
49232
+ sponsors.push({ ...s, source: "saved" });
49061
49233
  }
49062
49234
  }
49063
49235
  }
49064
49236
  } catch {
49065
49237
  }
49238
+ process.stdout.write("\n");
49066
49239
  if (sponsors.length === 0) {
49067
- renderInfo("No sponsored endpoints found.");
49068
- renderInfo("");
49069
- renderInfo("To connect to a sponsor, use:");
49070
- renderInfo(" /endpoint <tunnel-url> --auth <key>");
49240
+ renderInfo("No sponsored endpoints found on the nexus mesh.");
49071
49241
  renderInfo("");
49072
- renderInfo("Or add a known sponsor URL:");
49073
- renderInfo(" /endpoint sponsor add <url> --auth <key>");
49242
+ renderInfo("Make sure the nexus is connected (/nexus) and sponsors are online.");
49243
+ renderInfo("Or connect directly: /endpoint <tunnel-url> --auth <key>");
49074
49244
  return;
49075
49245
  }
49076
49246
  const items = [
@@ -49078,9 +49248,10 @@ async function handleSponsoredEndpoint(ctx, local) {
49078
49248
  ];
49079
49249
  for (const sp of sponsors) {
49080
49250
  const modelList = sp.models.length > 0 ? sp.models.slice(0, 3).join(", ") + (sp.models.length > 3 ? ` +${sp.models.length - 3}` : "") : "probing...";
49251
+ const transport = sp.url ? "tunnel" : sp.peerId ? "p2p" : "?";
49081
49252
  items.push({
49082
- key: sp.url,
49083
- label: ` ${sp.name} (${sp.source})`,
49253
+ key: sp.url || sp.peerId || sp.name,
49254
+ label: ` ${sp.name} (${sp.source}, ${transport})`,
49084
49255
  detail: ` Models: ${modelList} | ${sp.limits.rpm} req/min, ${sp.limits.tpd >= 1e3 ? (sp.limits.tpd / 1e3).toFixed(0) + "K" : sp.limits.tpd} tokens/day`
49085
49256
  });
49086
49257
  }
@@ -49108,15 +49279,24 @@ async function handleSponsoredEndpoint(ctx, local) {
49108
49279
  renderInfo("Use: /endpoint <tunnel-url> --auth <key>");
49109
49280
  return;
49110
49281
  }
49111
- const selected = sponsors.find((s) => s.url === result.key);
49282
+ const selectedKey = result.key;
49283
+ const selected = sponsors.find((s) => (s.url || s.peerId || s.name) === selectedKey);
49112
49284
  if (!selected)
49113
49285
  return;
49114
- const endpointArg = selected.authKey ? `${selected.url} --auth ${selected.authKey}` : selected.url;
49115
- await handleEndpoint(endpointArg, ctx, local);
49286
+ if (selected.url && selected.url.startsWith("http")) {
49287
+ const endpointArg = selected.authKey ? `${selected.url} --auth ${selected.authKey}` : selected.url;
49288
+ await handleEndpoint(endpointArg, ctx, local);
49289
+ } else if (selected.peerId) {
49290
+ await handlePeerEndpoint(selected.peerId, selected.authKey || void 0, ctx, local);
49291
+ } else {
49292
+ renderError("Sponsor has no reachable endpoint (no tunnel URL or peer ID).");
49293
+ return;
49294
+ }
49295
+ const saveKey = selected.url || selected.peerId || selected.name;
49116
49296
  try {
49117
49297
  mkdirSync18(sponsorDir2, { recursive: true });
49118
49298
  const existing = existsSync42(knownFile) ? JSON.parse(readFileSync31(knownFile, "utf8")) : [];
49119
- const updated = existing.filter((s) => s.url !== selected.url);
49299
+ const updated = existing.filter((s) => (s.url || s.peerId || s.name) !== saveKey);
49120
49300
  updated.push(selected);
49121
49301
  writeFileSync19(knownFile, JSON.stringify(updated, null, 2), "utf8");
49122
49302
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.7",
3
+ "version": "0.184.9",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) \u2014 interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",