2020117-agent 0.6.3 → 0.6.5

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/agent.js CHANGED
@@ -80,9 +80,10 @@ for (const arg of process.argv.slice(2)) {
80
80
  }
81
81
  }
82
82
  import { randomBytes } from 'crypto';
83
+ import { createConnection } from 'net';
83
84
  import { SwarmNode, topicFromKind } from './swarm.js';
84
85
  import { createProcessor } from './processor.js';
85
- import { generateInvoice } from './clink.js';
86
+ import { generateInvoice } from './lnurl.js';
86
87
  import { generateKeypair, loadSovereignKeys, saveSovereignKeys, loadAgentName, signEvent, signEventWithPow, nip44Encrypt, nip44Decrypt, pubkeyFromPrivkey, RelayPool, } from './nostr.js';
87
88
  import { parseNwcUri, nwcGetBalance, nwcPayLightningAddress } from './nwc.js';
88
89
  import { readFileSync } from 'fs';
@@ -742,6 +743,80 @@ async function delegateNostr(label, kind, input, bidSats, provider) {
742
743
  });
743
744
  });
744
745
  }
746
+ /**
747
+ * Inject think:false into Ollama chat/generate requests.
748
+ * Suppresses reasoning tokens on models like qwen3 that enable thinking by default.
749
+ */
750
+ function injectThinkFalse(rawBytes) {
751
+ const raw = rawBytes.toString();
752
+ const sep = raw.indexOf('\r\n\r\n');
753
+ if (sep === -1)
754
+ return rawBytes;
755
+ const header = raw.slice(0, sep + 4);
756
+ const body = raw.slice(sep + 4);
757
+ if (!header.includes('POST') || (!header.includes('/api/generate') && !header.includes('/api/chat')))
758
+ return rawBytes;
759
+ try {
760
+ const json = JSON.parse(body);
761
+ if (json.think !== undefined)
762
+ return rawBytes;
763
+ json.think = false;
764
+ const newBody = JSON.stringify(json);
765
+ const newHeader = header.replace(/Content-Length:\s*\d+/i, `Content-Length: ${Buffer.byteLength(newBody)}`);
766
+ return Buffer.from(newHeader + newBody);
767
+ }
768
+ catch {
769
+ return rawBytes;
770
+ }
771
+ }
772
+ /**
773
+ * After session payment, switch this socket to a raw TCP pipe to the HTTP backend.
774
+ * The customer then has direct HTTP API access (Ollama, SD-WebUI, etc.) with true streaming.
775
+ * This removes SwarmNode's JSON framing from the socket and replaces it with a bidirectional pipe.
776
+ */
777
+ function startTcpProxy(session, node, processorUrl, label) {
778
+ let addr;
779
+ try {
780
+ const u = new URL(processorUrl);
781
+ addr = { host: u.hostname, port: Number(u.port) || (u.protocol === 'https:' ? 443 : 80) };
782
+ }
783
+ catch {
784
+ console.error(`[${label}] Session ${session.sessionId}: invalid backend URL ${processorUrl}`);
785
+ endSession(node, session, label);
786
+ return;
787
+ }
788
+ session.proxyStarted = true;
789
+ const socket = session.socket;
790
+ const backend = createConnection(addr.port, addr.host);
791
+ backend.setKeepAlive(true);
792
+ // Remove SwarmNode's newline-delimited JSON listener so raw HTTP bytes flow through
793
+ socket.removeAllListeners('data');
794
+ socket.on('data', (chunk) => backend.write(injectThinkFalse(chunk)));
795
+ backend.pipe(socket);
796
+ let cleaned = false;
797
+ const cleanup = () => {
798
+ if (cleaned)
799
+ return;
800
+ cleaned = true;
801
+ try {
802
+ socket.destroy();
803
+ }
804
+ catch { }
805
+ try {
806
+ backend.destroy();
807
+ }
808
+ catch { }
809
+ endSession(node, session, label);
810
+ };
811
+ socket.on('close', cleanup);
812
+ socket.on('error', () => cleanup());
813
+ backend.on('close', cleanup);
814
+ backend.on('error', (e) => {
815
+ console.log(`[${label}] Session ${session.sessionId}: backend error: ${e.message}`);
816
+ cleanup();
817
+ });
818
+ console.log(`[${label}] Session ${session.sessionId}: TCP proxy → ${addr.host}:${addr.port}`);
819
+ }
745
820
  const activeSessions = new Map();
746
821
  // Dedup: track recently seen DVM request event IDs (prevent double-processing from relay + inbox)
747
822
  const seenEventIds = new Set();
@@ -800,7 +875,9 @@ async function startSwarmListener(label) {
800
875
  }
801
876
  // --- Session protocol ---
802
877
  if (msg.type === 'session_start') {
803
- if (!LIGHTNING_ADDRESS) {
878
+ const processorUrl = process.env.PROCESSOR || '';
879
+ const proxyMode = processorUrl.startsWith('http://') || processorUrl.startsWith('https://');
880
+ if (!proxyMode && !LIGHTNING_ADDRESS) {
804
881
  node.send(socket, { type: 'error', id: msg.id, message: 'Provider Lightning Address not configured' });
805
882
  return;
806
883
  }
@@ -812,7 +889,8 @@ async function startSwarmListener(label) {
812
889
  const BILLING_INTERVAL_MIN = 1;
813
890
  const billingAmount = satsPerMinute * BILLING_INTERVAL_MIN;
814
891
  const sessionId = randomBytes(8).toString('hex');
815
- console.log(`[${label}] Session ${sessionId} from ${tag}: ${satsPerMinute} sats/min, payment=${paymentMethod}, billing every ${BILLING_INTERVAL_MIN}min (${billingAmount} sats)`);
892
+ const modeLabel = proxyMode ? `proxy→${processorUrl}` : `${satsPerMinute} sats/min`;
893
+ console.log(`[${label}] Session ${sessionId} from ${tag}: ${modeLabel}`);
816
894
  const session = {
817
895
  socket,
818
896
  peerId,
@@ -825,6 +903,8 @@ async function startSwarmListener(label) {
825
903
  billingTimer: null,
826
904
  timeoutTimer: null,
827
905
  customerPubkey: msg.pubkey || undefined,
906
+ proxyMode,
907
+ proxyStarted: false,
828
908
  };
829
909
  activeSessions.set(sessionId, session);
830
910
  node.send(socket, {
@@ -835,12 +915,23 @@ async function startSwarmListener(label) {
835
915
  payment_method: paymentMethod,
836
916
  pubkey: state.sovereignKeys?.pubkey,
837
917
  });
838
- // Send first billing tick
839
- await sendBillingTick(node, session, billingAmount, label);
840
- // Recurring billing every 10 minutes
841
- session.billingTimer = setInterval(() => {
842
- sendBillingTick(node, session, billingAmount, label);
843
- }, BILLING_INTERVAL_MIN * 60_000);
918
+ if (proxyMode) {
919
+ if (billingAmount <= 0 || !LIGHTNING_ADDRESS) {
920
+ // Free proxy open TCP pipe immediately
921
+ startTcpProxy(session, node, processorUrl, label);
922
+ }
923
+ else {
924
+ // Paid proxy — one-time session fee, TCP pipe starts on payment (session_tick_ack)
925
+ await sendBillingTick(node, session, billingAmount, label);
926
+ }
927
+ }
928
+ else {
929
+ // Per-minute billing for structured request/result sessions
930
+ await sendBillingTick(node, session, billingAmount, label);
931
+ session.billingTimer = setInterval(() => {
932
+ sendBillingTick(node, session, billingAmount, label);
933
+ }, BILLING_INTERVAL_MIN * 60_000);
934
+ }
844
935
  return;
845
936
  }
846
937
  // Customer sent payment (Lightning preimage)
@@ -854,6 +945,10 @@ async function startSwarmListener(label) {
854
945
  session.lastPaidAt = Date.now();
855
946
  console.log(`[${label}] Session ${session.sessionId}: invoice payment received (+${amount}, total: ${session.totalEarned} sats)`);
856
947
  }
948
+ // Proxy mode: switch to raw TCP pipe after first payment confirmed
949
+ if (session.proxyMode && !session.proxyStarted) {
950
+ startTcpProxy(session, node, process.env.PROCESSOR || '', label);
951
+ }
857
952
  return;
858
953
  }
859
954
  if (msg.type === 'session_end') {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Lightning payment utilities — invoice generation via LNURL-pay
3
+ *
4
+ * Provider generates invoices from their own Lightning Address.
5
+ * Customer pays invoices via NWC wallet.
6
+ */
7
+ /**
8
+ * Resolve a Lightning Address to a bolt11 invoice via LNURL-pay protocol.
9
+ * The provider calls this on their OWN Lightning Address to generate
10
+ * an invoice that pays themselves.
11
+ *
12
+ * Flow: address → .well-known/lnurlp → callback?amount= → bolt11
13
+ */
14
+ export declare function generateInvoice(lightningAddress: string, amountSats: number): Promise<string>;
package/dist/lnurl.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Lightning payment utilities — invoice generation via LNURL-pay
3
+ *
4
+ * Provider generates invoices from their own Lightning Address.
5
+ * Customer pays invoices via NWC wallet.
6
+ */
7
+ /**
8
+ * Resolve a Lightning Address to a bolt11 invoice via LNURL-pay protocol.
9
+ * The provider calls this on their OWN Lightning Address to generate
10
+ * an invoice that pays themselves.
11
+ *
12
+ * Flow: address → .well-known/lnurlp → callback?amount= → bolt11
13
+ */
14
+ export async function generateInvoice(lightningAddress, amountSats) {
15
+ const [user, domain] = lightningAddress.split('@');
16
+ if (!user || !domain)
17
+ throw new Error(`Invalid Lightning Address: ${lightningAddress}`);
18
+ // Step 1: Fetch LNURL-pay metadata
19
+ const metaUrl = `https://${domain}/.well-known/lnurlp/${user}`;
20
+ const metaResp = await fetch(metaUrl);
21
+ if (!metaResp.ok)
22
+ throw new Error(`LNURL fetch failed: ${metaResp.status} from ${metaUrl}`);
23
+ const meta = await metaResp.json();
24
+ if (meta.tag !== 'payRequest')
25
+ throw new Error(`Not a LNURL-pay endpoint (tag: ${meta.tag})`);
26
+ const amountMsats = amountSats * 1000;
27
+ if (amountMsats < meta.minSendable)
28
+ throw new Error(`Amount ${amountSats} sats below min ${meta.minSendable / 1000} sats`);
29
+ if (amountMsats > meta.maxSendable)
30
+ throw new Error(`Amount ${amountSats} sats above max ${meta.maxSendable / 1000} sats`);
31
+ // Step 2: Request invoice from callback
32
+ const sep = meta.callback.includes('?') ? '&' : '?';
33
+ const invoiceUrl = `${meta.callback}${sep}amount=${amountMsats}`;
34
+ const invoiceResp = await fetch(invoiceUrl);
35
+ if (!invoiceResp.ok)
36
+ throw new Error(`Invoice request failed: ${invoiceResp.status}`);
37
+ const invoiceData = await invoiceResp.json();
38
+ if (!invoiceData.pr)
39
+ throw new Error(`No invoice returned: ${invoiceData.reason || 'unknown error'}`);
40
+ return invoiceData.pr;
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "2020117-agent",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "2020117 agent runtime — Nostr-native relay subscription + Hyperswarm P2P + Lightning payments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "exports": {
19
19
  "./processor": "./dist/processor.js",
20
20
  "./swarm": "./dist/swarm.js",
21
- "./lightning": "./dist/clink.js",
21
+ "./lightning": "./dist/lnurl.js",
22
22
  "./nostr": "./dist/nostr.js",
23
23
  "./nwc": "./dist/nwc.js"
24
24
  },