2020117-agent 0.1.5 → 0.1.7
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 +28 -7
- package/dist/cashu.js +21 -4
- package/dist/session.js +79 -33
- package/dist/swarm.d.ts +2 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -470,13 +470,34 @@ async function startSwarmListener(label) {
|
|
|
470
470
|
const resBody = await res.text();
|
|
471
471
|
const resHeaders = {};
|
|
472
472
|
res.headers.forEach((v, k) => { resHeaders[k] = v; });
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
473
|
+
// Chunk large responses to avoid swarm transport truncation
|
|
474
|
+
const CHUNK_SIZE = 48_000; // ~48KB per chunk (safe margin under 64KB NOISE frame)
|
|
475
|
+
if (resBody.length > CHUNK_SIZE) {
|
|
476
|
+
const chunks = [];
|
|
477
|
+
for (let i = 0; i < resBody.length; i += CHUNK_SIZE) {
|
|
478
|
+
chunks.push(resBody.slice(i, i + CHUNK_SIZE));
|
|
479
|
+
}
|
|
480
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
481
|
+
node.send(socket, {
|
|
482
|
+
type: 'http_response',
|
|
483
|
+
id: msg.id,
|
|
484
|
+
status: i === 0 ? res.status : undefined,
|
|
485
|
+
headers: i === 0 ? resHeaders : undefined,
|
|
486
|
+
body: chunks[i],
|
|
487
|
+
chunk_index: i,
|
|
488
|
+
chunk_total: chunks.length,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
node.send(socket, {
|
|
494
|
+
type: 'http_response',
|
|
495
|
+
id: msg.id,
|
|
496
|
+
status: res.status,
|
|
497
|
+
headers: resHeaders,
|
|
498
|
+
body: resBody,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
480
501
|
}
|
|
481
502
|
catch (e) {
|
|
482
503
|
node.send(socket, {
|
package/dist/cashu.js
CHANGED
|
@@ -81,10 +81,27 @@ export async function splitTokens(tokenStr, perAmount) {
|
|
|
81
81
|
const total = remaining.reduce((sum, p) => sum + p.amount, 0);
|
|
82
82
|
if (total < perAmount)
|
|
83
83
|
break;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
let retries = 5;
|
|
85
|
+
while (retries > 0) {
|
|
86
|
+
try {
|
|
87
|
+
const { send, keep: kept } = await wallet.send(perAmount, remaining);
|
|
88
|
+
microTokens.push(getEncodedToken({ mint: mintUrl, proofs: send }));
|
|
89
|
+
remaining = kept;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
if (retries > 1 && /rate.limit/i.test(e.message || '')) {
|
|
94
|
+
const delay = (6 - retries) * 2000; // 2s, 4s, 6s, 8s
|
|
95
|
+
console.log(`[cashu] Rate limited, waiting ${delay / 1000}s... (${retries - 1} retries left)`);
|
|
96
|
+
await sleep(delay);
|
|
97
|
+
retries--;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (remaining.length === 0)
|
|
88
105
|
break;
|
|
89
106
|
}
|
|
90
107
|
console.log(`[cashu] Split into ${microTokens.length} micro-tokens of ${perAmount} sats each`);
|
package/dist/session.js
CHANGED
|
@@ -59,6 +59,8 @@ const state = {
|
|
|
59
59
|
sessionId: '',
|
|
60
60
|
skill: null,
|
|
61
61
|
satsPerMinute: 0,
|
|
62
|
+
satsPerToken: 1,
|
|
63
|
+
tickIntervalMs: 60_000,
|
|
62
64
|
microTokens: [],
|
|
63
65
|
tokenIndex: 0,
|
|
64
66
|
totalSpent: 0,
|
|
@@ -67,6 +69,7 @@ const state = {
|
|
|
67
69
|
httpServer: null,
|
|
68
70
|
shuttingDown: false,
|
|
69
71
|
pendingRequests: new Map(),
|
|
72
|
+
chunkBuffers: new Map(),
|
|
70
73
|
outputCounter: 0,
|
|
71
74
|
};
|
|
72
75
|
// --- Helpers ---
|
|
@@ -109,6 +112,34 @@ function sendAndWait(msg, timeoutMs) {
|
|
|
109
112
|
// --- 6. Message handler ---
|
|
110
113
|
function setupMessageHandler() {
|
|
111
114
|
state.node.on('message', (msg) => {
|
|
115
|
+
// Handle chunked HTTP responses — reassemble before resolving
|
|
116
|
+
if (msg.type === 'http_response' && msg.chunk_total && msg.chunk_total > 1) {
|
|
117
|
+
const id = msg.id;
|
|
118
|
+
let buf = state.chunkBuffers.get(id);
|
|
119
|
+
if (!buf) {
|
|
120
|
+
buf = { chunks: new Array(msg.chunk_total), total: msg.chunk_total, firstMsg: msg };
|
|
121
|
+
state.chunkBuffers.set(id, buf);
|
|
122
|
+
}
|
|
123
|
+
buf.chunks[msg.chunk_index ?? 0] = msg.body ?? '';
|
|
124
|
+
const received = buf.chunks.filter(c => c !== undefined).length;
|
|
125
|
+
if (received < buf.total)
|
|
126
|
+
return; // wait for more chunks
|
|
127
|
+
// All chunks received — reassemble and resolve
|
|
128
|
+
const assembled = {
|
|
129
|
+
...buf.firstMsg,
|
|
130
|
+
body: buf.chunks.join(''),
|
|
131
|
+
chunk_index: undefined,
|
|
132
|
+
chunk_total: undefined,
|
|
133
|
+
};
|
|
134
|
+
state.chunkBuffers.delete(id);
|
|
135
|
+
const pending = state.pendingRequests.get(id);
|
|
136
|
+
if (pending) {
|
|
137
|
+
clearTimeout(pending.timer);
|
|
138
|
+
state.pendingRequests.delete(id);
|
|
139
|
+
pending.resolve(assembled);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
112
143
|
// Check pending requests first — match by id
|
|
113
144
|
const pending = state.pendingRequests.get(msg.id);
|
|
114
145
|
if (pending) {
|
|
@@ -155,38 +186,42 @@ function setupMessageHandler() {
|
|
|
155
186
|
});
|
|
156
187
|
}
|
|
157
188
|
// --- 3. Tick timer ---
|
|
158
|
-
function
|
|
159
|
-
state.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
if (resp.balance !== undefined) {
|
|
183
|
-
log(`Tick OK — balance: ${resp.balance} sats`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (e) {
|
|
187
|
-
warn(`Tick failed: ${e.message}`);
|
|
189
|
+
async function sendTick() {
|
|
190
|
+
if (state.shuttingDown)
|
|
191
|
+
return false;
|
|
192
|
+
if (remainingTokens() <= 0) {
|
|
193
|
+
log('Budget exhausted — ending session');
|
|
194
|
+
await endSession();
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (remainingTokens() <= 2) {
|
|
198
|
+
warn(`Low balance! Only ${remainingTokens()} tick(s) remaining (${remainingSats()} sats)`);
|
|
199
|
+
}
|
|
200
|
+
const token = state.microTokens[state.tokenIndex++];
|
|
201
|
+
state.totalSpent += state.satsPerToken;
|
|
202
|
+
const tickId = randomBytes(4).toString('hex');
|
|
203
|
+
try {
|
|
204
|
+
const resp = await sendAndWait({
|
|
205
|
+
type: 'session_tick',
|
|
206
|
+
id: tickId,
|
|
207
|
+
session_id: state.sessionId,
|
|
208
|
+
token,
|
|
209
|
+
budget: BUDGET,
|
|
210
|
+
}, 15_000);
|
|
211
|
+
if (resp.balance !== undefined) {
|
|
212
|
+
log(`Tick OK — balance: ${resp.balance} sats`);
|
|
188
213
|
}
|
|
189
|
-
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
warn(`Tick failed: ${e.message}`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function startTickTimer() {
|
|
222
|
+
// Send first tick immediately (prepay) to prevent provider timeout
|
|
223
|
+
sendTick();
|
|
224
|
+
state.tickTimer = setInterval(() => sendTick(), state.tickIntervalMs);
|
|
190
225
|
}
|
|
191
226
|
// --- 4. HTTP proxy ---
|
|
192
227
|
function startHttpProxy() {
|
|
@@ -251,6 +286,11 @@ function startHttpProxy() {
|
|
|
251
286
|
}
|
|
252
287
|
// --- 5. CLI REPL ---
|
|
253
288
|
function startRepl() {
|
|
289
|
+
// Skip REPL when stdin is not a TTY (e.g. background process, piped input)
|
|
290
|
+
if (!process.stdin.isTTY) {
|
|
291
|
+
log('Non-interactive mode (no TTY) — session will run until budget exhausted or provider ends it');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
254
294
|
const rl = createInterface({
|
|
255
295
|
input: process.stdin,
|
|
256
296
|
output: process.stdout,
|
|
@@ -453,6 +493,7 @@ async function endSession() {
|
|
|
453
493
|
pending.reject(new Error('Session ending'));
|
|
454
494
|
state.pendingRequests.delete(id);
|
|
455
495
|
}
|
|
496
|
+
state.chunkBuffers.clear();
|
|
456
497
|
// Send session_end
|
|
457
498
|
if (state.node && state.socket && state.sessionId) {
|
|
458
499
|
const duration = elapsedSeconds();
|
|
@@ -519,10 +560,15 @@ async function main() {
|
|
|
519
560
|
log(`Pricing: ${satsPerMinute} sats/min`);
|
|
520
561
|
log(`Budget: ${BUDGET} sats (~${Math.floor(BUDGET / satsPerMinute)} min)`);
|
|
521
562
|
// 4. Mint and split tokens
|
|
563
|
+
// Cashu tokens must be whole sats. If rate < 1, use 1 sat tokens with longer tick intervals.
|
|
564
|
+
const satsPerToken = Math.max(1, Math.ceil(satsPerMinute));
|
|
565
|
+
const tickMinutes = satsPerToken / satsPerMinute; // e.g. 0.1 sats/min → 1 sat every 10 min
|
|
566
|
+
state.tickIntervalMs = Math.round(tickMinutes * 60_000);
|
|
567
|
+
state.satsPerToken = satsPerToken;
|
|
522
568
|
log(`Minting ${BUDGET} sats...`);
|
|
523
569
|
const { token: bigToken } = await mintTokens(BUDGET);
|
|
524
|
-
log(`Splitting into ${
|
|
525
|
-
state.microTokens = await splitTokens(bigToken,
|
|
570
|
+
log(`Splitting into ${satsPerToken}-sat micro-tokens (tick every ${tickMinutes} min)...`);
|
|
571
|
+
state.microTokens = await splitTokens(bigToken, satsPerToken);
|
|
526
572
|
log(`Ready: ${state.microTokens.length} micro-tokens`);
|
|
527
573
|
if (state.microTokens.length === 0) {
|
|
528
574
|
warn('Budget too small for even one tick payment');
|
package/dist/swarm.d.ts
CHANGED