2020117-agent 0.6.4 → 0.6.6
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 +108 -18
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -80,6 +80,7 @@ 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
86
|
import { generateInvoice } from './lnurl.js';
|
|
@@ -257,10 +258,11 @@ async function setupNostr(label) {
|
|
|
257
258
|
// 7. Subscribe to NIP-XX prompts (Kind 25802)
|
|
258
259
|
subscribeNipXX(label);
|
|
259
260
|
// 8. Subscribe to DVM requests (Kind 5xxx)
|
|
260
|
-
// P2P-only:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
// P2P-only: skip relay subscription entirely — only serve P2P customers
|
|
262
|
+
if (!P2P_ONLY) {
|
|
263
|
+
subscribeDvmRequests(label);
|
|
264
|
+
subscribeDvmResults(label);
|
|
265
|
+
}
|
|
264
266
|
// 9. Start heartbeat (Kind 30333 to relay)
|
|
265
267
|
const pricing = {};
|
|
266
268
|
const priceSats = SATS_PER_CHUNK * CHUNKS_PER_PAYMENT;
|
|
@@ -551,12 +553,6 @@ async function handleDvmRequest(label, event) {
|
|
|
551
553
|
// Skip own events
|
|
552
554
|
if (event.pubkey === state.sovereignKeys.pubkey)
|
|
553
555
|
return;
|
|
554
|
-
// P2P-only mode: only handle direct requests (p tag pointing to us)
|
|
555
|
-
if (P2P_ONLY) {
|
|
556
|
-
const isDirected = event.tags.some(t => t[0] === 'p' && t[1] === state.sovereignKeys.pubkey);
|
|
557
|
-
if (!isDirected)
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
556
|
// Dedup: skip already-seen events
|
|
561
557
|
if (!markSeen(event.id))
|
|
562
558
|
return;
|
|
@@ -742,6 +738,80 @@ async function delegateNostr(label, kind, input, bidSats, provider) {
|
|
|
742
738
|
});
|
|
743
739
|
});
|
|
744
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* Inject think:false into Ollama chat/generate requests.
|
|
743
|
+
* Suppresses reasoning tokens on models like qwen3 that enable thinking by default.
|
|
744
|
+
*/
|
|
745
|
+
function injectThinkFalse(rawBytes) {
|
|
746
|
+
const raw = rawBytes.toString();
|
|
747
|
+
const sep = raw.indexOf('\r\n\r\n');
|
|
748
|
+
if (sep === -1)
|
|
749
|
+
return rawBytes;
|
|
750
|
+
const header = raw.slice(0, sep + 4);
|
|
751
|
+
const body = raw.slice(sep + 4);
|
|
752
|
+
if (!header.includes('POST') || (!header.includes('/api/generate') && !header.includes('/api/chat')))
|
|
753
|
+
return rawBytes;
|
|
754
|
+
try {
|
|
755
|
+
const json = JSON.parse(body);
|
|
756
|
+
if (json.think !== undefined)
|
|
757
|
+
return rawBytes;
|
|
758
|
+
json.think = false;
|
|
759
|
+
const newBody = JSON.stringify(json);
|
|
760
|
+
const newHeader = header.replace(/Content-Length:\s*\d+/i, `Content-Length: ${Buffer.byteLength(newBody)}`);
|
|
761
|
+
return Buffer.from(newHeader + newBody);
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
return rawBytes;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* After session payment, switch this socket to a raw TCP pipe to the HTTP backend.
|
|
769
|
+
* The customer then has direct HTTP API access (Ollama, SD-WebUI, etc.) with true streaming.
|
|
770
|
+
* This removes SwarmNode's JSON framing from the socket and replaces it with a bidirectional pipe.
|
|
771
|
+
*/
|
|
772
|
+
function startTcpProxy(session, node, processorUrl, label) {
|
|
773
|
+
let addr;
|
|
774
|
+
try {
|
|
775
|
+
const u = new URL(processorUrl);
|
|
776
|
+
addr = { host: u.hostname, port: Number(u.port) || (u.protocol === 'https:' ? 443 : 80) };
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
console.error(`[${label}] Session ${session.sessionId}: invalid backend URL ${processorUrl}`);
|
|
780
|
+
endSession(node, session, label);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
session.proxyStarted = true;
|
|
784
|
+
const socket = session.socket;
|
|
785
|
+
const backend = createConnection(addr.port, addr.host);
|
|
786
|
+
backend.setKeepAlive(true);
|
|
787
|
+
// Remove SwarmNode's newline-delimited JSON listener so raw HTTP bytes flow through
|
|
788
|
+
socket.removeAllListeners('data');
|
|
789
|
+
socket.on('data', (chunk) => backend.write(injectThinkFalse(chunk)));
|
|
790
|
+
backend.pipe(socket);
|
|
791
|
+
let cleaned = false;
|
|
792
|
+
const cleanup = () => {
|
|
793
|
+
if (cleaned)
|
|
794
|
+
return;
|
|
795
|
+
cleaned = true;
|
|
796
|
+
try {
|
|
797
|
+
socket.destroy();
|
|
798
|
+
}
|
|
799
|
+
catch { }
|
|
800
|
+
try {
|
|
801
|
+
backend.destroy();
|
|
802
|
+
}
|
|
803
|
+
catch { }
|
|
804
|
+
endSession(node, session, label);
|
|
805
|
+
};
|
|
806
|
+
socket.on('close', cleanup);
|
|
807
|
+
socket.on('error', () => cleanup());
|
|
808
|
+
backend.on('close', cleanup);
|
|
809
|
+
backend.on('error', (e) => {
|
|
810
|
+
console.log(`[${label}] Session ${session.sessionId}: backend error: ${e.message}`);
|
|
811
|
+
cleanup();
|
|
812
|
+
});
|
|
813
|
+
console.log(`[${label}] Session ${session.sessionId}: TCP proxy → ${addr.host}:${addr.port}`);
|
|
814
|
+
}
|
|
745
815
|
const activeSessions = new Map();
|
|
746
816
|
// Dedup: track recently seen DVM request event IDs (prevent double-processing from relay + inbox)
|
|
747
817
|
const seenEventIds = new Set();
|
|
@@ -800,7 +870,9 @@ async function startSwarmListener(label) {
|
|
|
800
870
|
}
|
|
801
871
|
// --- Session protocol ---
|
|
802
872
|
if (msg.type === 'session_start') {
|
|
803
|
-
|
|
873
|
+
const processorUrl = process.env.PROCESSOR || '';
|
|
874
|
+
const proxyMode = processorUrl.startsWith('http://') || processorUrl.startsWith('https://');
|
|
875
|
+
if (!proxyMode && !LIGHTNING_ADDRESS) {
|
|
804
876
|
node.send(socket, { type: 'error', id: msg.id, message: 'Provider Lightning Address not configured' });
|
|
805
877
|
return;
|
|
806
878
|
}
|
|
@@ -812,7 +884,8 @@ async function startSwarmListener(label) {
|
|
|
812
884
|
const BILLING_INTERVAL_MIN = 1;
|
|
813
885
|
const billingAmount = satsPerMinute * BILLING_INTERVAL_MIN;
|
|
814
886
|
const sessionId = randomBytes(8).toString('hex');
|
|
815
|
-
|
|
887
|
+
const modeLabel = proxyMode ? `proxy→${processorUrl}` : `${satsPerMinute} sats/min`;
|
|
888
|
+
console.log(`[${label}] Session ${sessionId} from ${tag}: ${modeLabel}`);
|
|
816
889
|
const session = {
|
|
817
890
|
socket,
|
|
818
891
|
peerId,
|
|
@@ -825,6 +898,8 @@ async function startSwarmListener(label) {
|
|
|
825
898
|
billingTimer: null,
|
|
826
899
|
timeoutTimer: null,
|
|
827
900
|
customerPubkey: msg.pubkey || undefined,
|
|
901
|
+
proxyMode,
|
|
902
|
+
proxyStarted: false,
|
|
828
903
|
};
|
|
829
904
|
activeSessions.set(sessionId, session);
|
|
830
905
|
node.send(socket, {
|
|
@@ -835,12 +910,23 @@ async function startSwarmListener(label) {
|
|
|
835
910
|
payment_method: paymentMethod,
|
|
836
911
|
pubkey: state.sovereignKeys?.pubkey,
|
|
837
912
|
});
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
913
|
+
if (proxyMode) {
|
|
914
|
+
if (billingAmount <= 0 || !LIGHTNING_ADDRESS) {
|
|
915
|
+
// Free proxy — open TCP pipe immediately
|
|
916
|
+
startTcpProxy(session, node, processorUrl, label);
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
// Paid proxy — one-time session fee, TCP pipe starts on payment (session_tick_ack)
|
|
920
|
+
await sendBillingTick(node, session, billingAmount, label);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
// Per-minute billing for structured request/result sessions
|
|
925
|
+
await sendBillingTick(node, session, billingAmount, label);
|
|
926
|
+
session.billingTimer = setInterval(() => {
|
|
927
|
+
sendBillingTick(node, session, billingAmount, label);
|
|
928
|
+
}, BILLING_INTERVAL_MIN * 60_000);
|
|
929
|
+
}
|
|
844
930
|
return;
|
|
845
931
|
}
|
|
846
932
|
// Customer sent payment (Lightning preimage)
|
|
@@ -854,6 +940,10 @@ async function startSwarmListener(label) {
|
|
|
854
940
|
session.lastPaidAt = Date.now();
|
|
855
941
|
console.log(`[${label}] Session ${session.sessionId}: invoice payment received (+${amount}, total: ${session.totalEarned} sats)`);
|
|
856
942
|
}
|
|
943
|
+
// Proxy mode: switch to raw TCP pipe after first payment confirmed
|
|
944
|
+
if (session.proxyMode && !session.proxyStarted) {
|
|
945
|
+
startTcpProxy(session, node, process.env.PROCESSOR || '', label);
|
|
946
|
+
}
|
|
857
947
|
return;
|
|
858
948
|
}
|
|
859
949
|
if (msg.type === 'session_end') {
|