@kehto/paja 0.16.4 → 0.16.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/browser-host.js +462 -28
- package/dist/browser-host.js.map +1 -1
- package/package.json +3 -3
package/dist/browser-host.js
CHANGED
|
@@ -15255,15 +15255,35 @@ var KIND_GIFT_WRAP_EPHEMERAL = 21059;
|
|
|
15255
15255
|
var KIND_GIFT_WRAP_REGULAR = 1059;
|
|
15256
15256
|
var KIND_ANNOUNCE_SERVER = 11316;
|
|
15257
15257
|
var KIND_ANNOUNCE_TOOLS = 11317;
|
|
15258
|
+
var SUPPORT_ENCRYPTION = "support_encryption";
|
|
15259
|
+
var SUPPORT_ENCRYPTION_EPHEMERAL = "support_encryption_ephemeral";
|
|
15260
|
+
var SUPPORT_OVERSIZED_TRANSFER = "support_oversized_transfer";
|
|
15261
|
+
var SUPPORT_OPEN_STREAM = "support_open_stream";
|
|
15258
15262
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
15259
15263
|
var DEFAULT_DISCOVER_TIMEOUT_MS = 6e3;
|
|
15260
15264
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
15261
15265
|
var SEEN_WRAP_LIMIT = 512;
|
|
15266
|
+
var DIGEST_PREFIX = "sha256:";
|
|
15267
|
+
var MAX_TRANSFER_CHUNKS = 1e4;
|
|
15268
|
+
var MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
15269
|
+
var MAX_ACTIVE_TRANSFERS = 8;
|
|
15270
|
+
var MAX_TRANSFER_LIFETIME_MS = 12e4;
|
|
15271
|
+
var COMPLETION_MODE_RENDER = "render";
|
|
15272
|
+
var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
15273
|
+
var DEFAULT_STREAM_PROBE_TIMEOUT_MS = 1e4;
|
|
15274
|
+
var MAX_STREAM_BUFFERED_CHUNKS = 256;
|
|
15275
|
+
var MAX_STREAM_BUFFERED_CODE_UNITS = 8 * 1024 * 1024;
|
|
15276
|
+
var MAX_PING_NONCE_BYTES = 64;
|
|
15262
15277
|
var correlationCounter = 0;
|
|
15263
15278
|
function nextCorrelationId() {
|
|
15264
15279
|
correlationCounter += 1;
|
|
15265
15280
|
return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15266
15281
|
}
|
|
15282
|
+
var streamNonceCounter = 0;
|
|
15283
|
+
function nextStreamNonce() {
|
|
15284
|
+
streamNonceCounter += 1;
|
|
15285
|
+
return `cvm-ping-${streamNonceCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15286
|
+
}
|
|
15267
15287
|
function randomizedPastTimestamp() {
|
|
15268
15288
|
const jitter = Math.floor(Math.random() * 172800);
|
|
15269
15289
|
return Math.floor(Date.now() / 1e3) - jitter;
|
|
@@ -15271,6 +15291,19 @@ function randomizedPastTimestamp() {
|
|
|
15271
15291
|
function tagValue(tags, name) {
|
|
15272
15292
|
return tags.find((tag) => tag[0] === name)?.[1];
|
|
15273
15293
|
}
|
|
15294
|
+
function progressTokenKey(token) {
|
|
15295
|
+
if (typeof token === "string") return token.length > 0 ? `s:${token}` : null;
|
|
15296
|
+
if (typeof token === "number" && Number.isFinite(token)) return `n:${token}`;
|
|
15297
|
+
return null;
|
|
15298
|
+
}
|
|
15299
|
+
function isBoundedInteger(value, min, max) {
|
|
15300
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
15301
|
+
}
|
|
15302
|
+
async function sha256Hex(text) {
|
|
15303
|
+
const bytes = new TextEncoder().encode(text);
|
|
15304
|
+
const buf = await crypto.subtle.digest("SHA-256", bytes);
|
|
15305
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15306
|
+
}
|
|
15274
15307
|
function simplePoolAdapter(sp) {
|
|
15275
15308
|
return {
|
|
15276
15309
|
subscribe(relays, filter, params) {
|
|
@@ -15295,11 +15328,14 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15295
15328
|
const clientSecretKey = options.clientSecretKey ?? generateSecretKey();
|
|
15296
15329
|
const clientPubkey = getPublicKey(clientSecretKey);
|
|
15297
15330
|
const clientInfo = options.clientInfo ?? { name: "kehto-cvm", version: "1.0.0" };
|
|
15331
|
+
const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
15332
|
+
const streamProbeTimeoutMs = options.streamProbeTimeoutMs ?? DEFAULT_STREAM_PROBE_TIMEOUT_MS;
|
|
15298
15333
|
const sessions = /* @__PURE__ */ new Map();
|
|
15299
15334
|
const pending = /* @__PURE__ */ new Map();
|
|
15300
15335
|
const eventHandlers = /* @__PURE__ */ new Set();
|
|
15301
15336
|
const relayRefcount = /* @__PURE__ */ new Map();
|
|
15302
15337
|
const seenWraps = /* @__PURE__ */ new Set();
|
|
15338
|
+
const issuedTokens = /* @__PURE__ */ new Map();
|
|
15303
15339
|
let inbound = null;
|
|
15304
15340
|
let subscribedRelays = "";
|
|
15305
15341
|
function resolveRelays(server) {
|
|
@@ -15361,23 +15397,379 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15361
15397
|
} catch {
|
|
15362
15398
|
return;
|
|
15363
15399
|
}
|
|
15400
|
+
if (mcp.method === "notifications/progress") {
|
|
15401
|
+
const params = mcp.params ?? {};
|
|
15402
|
+
const cvm = params.cvm;
|
|
15403
|
+
if (cvm && typeof cvm.type === "string") {
|
|
15404
|
+
if (cvm.type === "oversized-transfer") {
|
|
15405
|
+
void handleOversizedFrame(serverPubkey, params, cvm);
|
|
15406
|
+
return;
|
|
15407
|
+
}
|
|
15408
|
+
if (cvm.type === "open-stream") {
|
|
15409
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15410
|
+
if (!entry) return;
|
|
15411
|
+
handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp);
|
|
15412
|
+
return;
|
|
15413
|
+
}
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
routeMessage(serverPubkey, mcp);
|
|
15417
|
+
}
|
|
15418
|
+
async function publishStreamFrame(entry, stream, frame) {
|
|
15419
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15420
|
+
if (!session) return;
|
|
15421
|
+
stream.localProgress += 1;
|
|
15422
|
+
await publishMcp(
|
|
15423
|
+
{ pubkey: entry.serverPubkey },
|
|
15424
|
+
session.relays,
|
|
15425
|
+
{
|
|
15426
|
+
jsonrpc: "2.0",
|
|
15427
|
+
method: "notifications/progress",
|
|
15428
|
+
params: {
|
|
15429
|
+
progressToken: stream.token,
|
|
15430
|
+
progress: stream.localProgress,
|
|
15431
|
+
cvm: { type: "open-stream", ...frame }
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
);
|
|
15435
|
+
}
|
|
15436
|
+
function admitFrame(serverPubkey, token) {
|
|
15437
|
+
const tokenKey = progressTokenKey(token);
|
|
15438
|
+
if (!tokenKey) return null;
|
|
15439
|
+
const correlationId = issuedTokens.get(`${serverPubkey}:${tokenKey}`);
|
|
15440
|
+
if (!correlationId) return null;
|
|
15441
|
+
const entry = pending.get(correlationId);
|
|
15442
|
+
if (!entry || entry.serverPubkey !== serverPubkey) return null;
|
|
15443
|
+
return entry;
|
|
15444
|
+
}
|
|
15445
|
+
function releaseTransfer(entry) {
|
|
15446
|
+
if (!entry.oversized) return;
|
|
15447
|
+
clearTimeout(entry.oversized.expiryTimer);
|
|
15448
|
+
entry.oversized = void 0;
|
|
15449
|
+
}
|
|
15450
|
+
function releaseStream(entry) {
|
|
15451
|
+
if (!entry.stream) return;
|
|
15452
|
+
clearTimeout(entry.stream.idleTimer);
|
|
15453
|
+
if (entry.stream.probeTimer) clearTimeout(entry.stream.probeTimer);
|
|
15454
|
+
entry.stream = void 0;
|
|
15455
|
+
}
|
|
15456
|
+
function endStream(entry) {
|
|
15457
|
+
releaseStream(entry);
|
|
15458
|
+
entry.streamEnded = true;
|
|
15459
|
+
}
|
|
15460
|
+
function failStream(entry, reason) {
|
|
15461
|
+
const stream = entry.stream;
|
|
15462
|
+
if (!stream || entry.streamEnded) return;
|
|
15463
|
+
endStream(entry);
|
|
15464
|
+
void publishStreamFrame(entry, stream, { frameType: "abort", reason }).catch(() => {
|
|
15465
|
+
});
|
|
15466
|
+
}
|
|
15467
|
+
function resetStreamIdleTimer(entry, stream) {
|
|
15468
|
+
clearTimeout(stream.idleTimer);
|
|
15469
|
+
stream.idleTimer = setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs);
|
|
15470
|
+
}
|
|
15471
|
+
function onStreamIdle(entry) {
|
|
15472
|
+
const stream = entry.stream;
|
|
15473
|
+
if (!stream || entry.streamEnded) return;
|
|
15474
|
+
if (stream.pendingNonce !== null) return;
|
|
15475
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15476
|
+
if (!session) {
|
|
15477
|
+
failStream(entry, "session closed");
|
|
15478
|
+
return;
|
|
15479
|
+
}
|
|
15480
|
+
stream.pendingNonce = nextStreamNonce();
|
|
15481
|
+
stream.probeTimer = setTimeout(() => failStream(entry, "probe timeout"), streamProbeTimeoutMs);
|
|
15482
|
+
void publishStreamFrame(entry, stream, { frameType: "ping", nonce: stream.pendingNonce }).catch(() => failStream(entry, "ping publication failed"));
|
|
15483
|
+
}
|
|
15484
|
+
function releaseFrameState(entry) {
|
|
15485
|
+
releaseTransfer(entry);
|
|
15486
|
+
releaseStream(entry);
|
|
15487
|
+
if (entry.tokenKey && issuedTokens.get(entry.tokenKey) === entry.correlationId) {
|
|
15488
|
+
issuedTokens.delete(entry.tokenKey);
|
|
15489
|
+
}
|
|
15490
|
+
entry.tokenKey = void 0;
|
|
15491
|
+
}
|
|
15492
|
+
function settlePending(correlationId) {
|
|
15493
|
+
const entry = pending.get(correlationId);
|
|
15494
|
+
if (!entry) return null;
|
|
15495
|
+
clearTimeout(entry.timer);
|
|
15496
|
+
pending.delete(correlationId);
|
|
15497
|
+
releaseFrameState(entry);
|
|
15498
|
+
return entry;
|
|
15499
|
+
}
|
|
15500
|
+
function countActiveTransfers() {
|
|
15501
|
+
let active = 0;
|
|
15502
|
+
for (const entry of pending.values()) {
|
|
15503
|
+
if (entry.oversized) active += 1;
|
|
15504
|
+
}
|
|
15505
|
+
return active;
|
|
15506
|
+
}
|
|
15507
|
+
async function handleOversizedFrame(serverPubkey, params, cvm) {
|
|
15508
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15509
|
+
if (!entry) return;
|
|
15510
|
+
const progress = Number(params.progress ?? NaN);
|
|
15511
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15512
|
+
if (cvm.frameType === "start") {
|
|
15513
|
+
const digest = typeof cvm.digest === "string" ? cvm.digest : "";
|
|
15514
|
+
const { totalBytes, totalChunks } = cvm;
|
|
15515
|
+
if (cvm.completionMode !== COMPLETION_MODE_RENDER) return;
|
|
15516
|
+
if (digest === "") return;
|
|
15517
|
+
if (!isBoundedInteger(totalChunks, 1, MAX_TRANSFER_CHUNKS)) return;
|
|
15518
|
+
if (!isBoundedInteger(totalBytes, 1, MAX_TRANSFER_BYTES)) return;
|
|
15519
|
+
if (progress > Number.MAX_SAFE_INTEGER - totalChunks - 1) return;
|
|
15520
|
+
if (!entry.oversized && countActiveTransfers() >= MAX_ACTIVE_TRANSFERS) return;
|
|
15521
|
+
releaseTransfer(entry);
|
|
15522
|
+
entry.oversized = {
|
|
15523
|
+
digest,
|
|
15524
|
+
totalBytes,
|
|
15525
|
+
totalChunks,
|
|
15526
|
+
startProgress: progress,
|
|
15527
|
+
acceptProgress: null,
|
|
15528
|
+
chunks: /* @__PURE__ */ new Map(),
|
|
15529
|
+
codeUnitsReceived: 0,
|
|
15530
|
+
// Hard expiry: progress frames extend the request deadline but never
|
|
15531
|
+
// how long buffers may be held.
|
|
15532
|
+
expiryTimer: setTimeout(() => releaseTransfer(entry), MAX_TRANSFER_LIFETIME_MS)
|
|
15533
|
+
};
|
|
15534
|
+
refreshPendingTimeout(entry);
|
|
15535
|
+
return;
|
|
15536
|
+
}
|
|
15537
|
+
const transfer = entry.oversized;
|
|
15538
|
+
if (!transfer) return;
|
|
15539
|
+
if (cvm.frameType === "accept") {
|
|
15540
|
+
if (progress !== transfer.startProgress + 1) {
|
|
15541
|
+
releaseTransfer(entry);
|
|
15542
|
+
return;
|
|
15543
|
+
}
|
|
15544
|
+
transfer.acceptProgress = progress;
|
|
15545
|
+
refreshPendingTimeout(entry);
|
|
15546
|
+
return;
|
|
15547
|
+
}
|
|
15548
|
+
if (cvm.frameType === "chunk") {
|
|
15549
|
+
if (typeof cvm.data !== "string" || progress <= transfer.startProgress) return;
|
|
15550
|
+
if (transfer.chunks.has(progress)) return;
|
|
15551
|
+
if (transfer.chunks.size >= transfer.totalChunks) {
|
|
15552
|
+
releaseTransfer(entry);
|
|
15553
|
+
return;
|
|
15554
|
+
}
|
|
15555
|
+
transfer.codeUnitsReceived += cvm.data.length;
|
|
15556
|
+
if (transfer.codeUnitsReceived > MAX_TRANSFER_BYTES) {
|
|
15557
|
+
releaseTransfer(entry);
|
|
15558
|
+
return;
|
|
15559
|
+
}
|
|
15560
|
+
transfer.chunks.set(progress, cvm.data);
|
|
15561
|
+
refreshPendingTimeout(entry);
|
|
15562
|
+
return;
|
|
15563
|
+
}
|
|
15564
|
+
if (cvm.frameType === "abort") {
|
|
15565
|
+
releaseTransfer(entry);
|
|
15566
|
+
return;
|
|
15567
|
+
}
|
|
15568
|
+
if (cvm.frameType === "end") {
|
|
15569
|
+
const message = await assembleTransfer(transfer, progress);
|
|
15570
|
+
releaseTransfer(entry);
|
|
15571
|
+
if (message) routeMessage(serverPubkey, message);
|
|
15572
|
+
return;
|
|
15573
|
+
}
|
|
15574
|
+
}
|
|
15575
|
+
async function assembleTransfer(transfer, endProgress) {
|
|
15576
|
+
const { startProgress, acceptProgress, totalChunks } = transfer;
|
|
15577
|
+
const hasCompleteRange = (first) => {
|
|
15578
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15579
|
+
if (!transfer.chunks.has(first + i4)) return false;
|
|
15580
|
+
}
|
|
15581
|
+
return true;
|
|
15582
|
+
};
|
|
15583
|
+
const directStart = startProgress + 1;
|
|
15584
|
+
const acceptGatedStart = startProgress + 2;
|
|
15585
|
+
const firstProgress = hasCompleteRange(directStart) && endProgress === directStart + totalChunks ? directStart : acceptProgress === startProgress + 1 && hasCompleteRange(acceptGatedStart) && endProgress === acceptGatedStart + totalChunks ? acceptGatedStart : null;
|
|
15586
|
+
if (firstProgress === null) return null;
|
|
15587
|
+
let payload = "";
|
|
15588
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15589
|
+
payload += transfer.chunks.get(firstProgress + i4);
|
|
15590
|
+
}
|
|
15591
|
+
if (new TextEncoder().encode(payload).byteLength !== transfer.totalBytes) return null;
|
|
15592
|
+
const expected = transfer.digest.startsWith(DIGEST_PREFIX) ? transfer.digest.slice(DIGEST_PREFIX.length) : transfer.digest;
|
|
15593
|
+
if (await sha256Hex(payload) !== expected) return null;
|
|
15594
|
+
try {
|
|
15595
|
+
return JSON.parse(payload);
|
|
15596
|
+
} catch {
|
|
15597
|
+
return null;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
function handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp) {
|
|
15601
|
+
const progress = Number(params.progress ?? NaN);
|
|
15602
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15603
|
+
if (entry.streamEnded) return;
|
|
15604
|
+
const stream = entry.stream;
|
|
15605
|
+
if (!stream) {
|
|
15606
|
+
if (cvm.frameType !== "start") return;
|
|
15607
|
+
entry.stream = {
|
|
15608
|
+
token: params.progressToken,
|
|
15609
|
+
lastProgress: progress,
|
|
15610
|
+
localProgress: 0,
|
|
15611
|
+
nextChunkIndex: 0,
|
|
15612
|
+
outOfOrder: /* @__PURE__ */ new Map(),
|
|
15613
|
+
outOfOrderCodeUnits: 0,
|
|
15614
|
+
pendingNonce: null,
|
|
15615
|
+
idleTimer: setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs),
|
|
15616
|
+
probeTimer: null
|
|
15617
|
+
};
|
|
15618
|
+
refreshPendingTimeout(entry);
|
|
15619
|
+
routeEvent(serverPubkey, mcp);
|
|
15620
|
+
return;
|
|
15621
|
+
}
|
|
15622
|
+
const admitOrderedActivity = () => {
|
|
15623
|
+
if (progress <= stream.lastProgress) {
|
|
15624
|
+
failStream(entry, "non-monotonic progress");
|
|
15625
|
+
return false;
|
|
15626
|
+
}
|
|
15627
|
+
stream.lastProgress = progress;
|
|
15628
|
+
resetStreamIdleTimer(entry, stream);
|
|
15629
|
+
refreshPendingTimeout(entry);
|
|
15630
|
+
return true;
|
|
15631
|
+
};
|
|
15632
|
+
switch (cvm.frameType) {
|
|
15633
|
+
case "start":
|
|
15634
|
+
failStream(entry, "duplicate start");
|
|
15635
|
+
return;
|
|
15636
|
+
case "accept":
|
|
15637
|
+
if (!admitOrderedActivity()) return;
|
|
15638
|
+
routeEvent(serverPubkey, mcp);
|
|
15639
|
+
return;
|
|
15640
|
+
case "chunk": {
|
|
15641
|
+
if (typeof cvm.data !== "string" || !isBoundedInteger(cvm.chunkIndex, 0, Number.MAX_SAFE_INTEGER)) {
|
|
15642
|
+
failStream(entry, "malformed chunk");
|
|
15643
|
+
return;
|
|
15644
|
+
}
|
|
15645
|
+
const index = cvm.chunkIndex;
|
|
15646
|
+
if (index < stream.nextChunkIndex) return;
|
|
15647
|
+
if (index > stream.nextChunkIndex) {
|
|
15648
|
+
const existing = stream.outOfOrder.get(index);
|
|
15649
|
+
if (existing) {
|
|
15650
|
+
if (existing.progress !== progress || JSON.stringify(existing.message) !== JSON.stringify(mcp)) {
|
|
15651
|
+
failStream(entry, "conflicting duplicate chunk");
|
|
15652
|
+
}
|
|
15653
|
+
return;
|
|
15654
|
+
}
|
|
15655
|
+
if (progress <= stream.lastProgress || stream.outOfOrder.size >= MAX_STREAM_BUFFERED_CHUNKS || stream.outOfOrderCodeUnits + cvm.data.length > MAX_STREAM_BUFFERED_CODE_UNITS) {
|
|
15656
|
+
failStream(entry, progress <= stream.lastProgress ? "non-monotonic progress" : "gap buffer exhausted");
|
|
15657
|
+
return;
|
|
15658
|
+
}
|
|
15659
|
+
stream.outOfOrder.set(index, { message: mcp, progress, codeUnits: cvm.data.length });
|
|
15660
|
+
stream.outOfOrderCodeUnits += cvm.data.length;
|
|
15661
|
+
resetStreamIdleTimer(entry, stream);
|
|
15662
|
+
refreshPendingTimeout(entry);
|
|
15663
|
+
return;
|
|
15664
|
+
}
|
|
15665
|
+
const ready = [
|
|
15666
|
+
{ message: mcp, progress, codeUnits: 0 }
|
|
15667
|
+
];
|
|
15668
|
+
let nextIndex = index + 1;
|
|
15669
|
+
while (stream.outOfOrder.has(nextIndex)) {
|
|
15670
|
+
ready.push(stream.outOfOrder.get(nextIndex));
|
|
15671
|
+
nextIndex += 1;
|
|
15672
|
+
}
|
|
15673
|
+
let previousProgress = stream.lastProgress;
|
|
15674
|
+
for (const item of ready) {
|
|
15675
|
+
if (item.progress <= previousProgress) {
|
|
15676
|
+
failStream(entry, "non-monotonic chunk order");
|
|
15677
|
+
return;
|
|
15678
|
+
}
|
|
15679
|
+
previousProgress = item.progress;
|
|
15680
|
+
}
|
|
15681
|
+
for (let bufferedIndex = index + 1; bufferedIndex < nextIndex; bufferedIndex++) {
|
|
15682
|
+
const buffered = stream.outOfOrder.get(bufferedIndex);
|
|
15683
|
+
stream.outOfOrder.delete(bufferedIndex);
|
|
15684
|
+
stream.outOfOrderCodeUnits -= buffered.codeUnits;
|
|
15685
|
+
}
|
|
15686
|
+
for (const item of ready) {
|
|
15687
|
+
routeEvent(serverPubkey, item.message);
|
|
15688
|
+
stream.nextChunkIndex += 1;
|
|
15689
|
+
}
|
|
15690
|
+
stream.lastProgress = previousProgress;
|
|
15691
|
+
resetStreamIdleTimer(entry, stream);
|
|
15692
|
+
refreshPendingTimeout(entry);
|
|
15693
|
+
return;
|
|
15694
|
+
}
|
|
15695
|
+
case "ping": {
|
|
15696
|
+
if (typeof cvm.nonce !== "string") return;
|
|
15697
|
+
if (new TextEncoder().encode(cvm.nonce).byteLength > MAX_PING_NONCE_BYTES) return;
|
|
15698
|
+
if (!admitOrderedActivity()) return;
|
|
15699
|
+
void publishStreamFrame(entry, stream, { frameType: "pong", nonce: cvm.nonce }).catch(() => {
|
|
15700
|
+
failStream(entry, "pong publication failed");
|
|
15701
|
+
});
|
|
15702
|
+
return;
|
|
15703
|
+
}
|
|
15704
|
+
case "pong": {
|
|
15705
|
+
if (stream.pendingNonce === null || cvm.nonce !== stream.pendingNonce) return;
|
|
15706
|
+
if (!admitOrderedActivity()) return;
|
|
15707
|
+
stream.pendingNonce = null;
|
|
15708
|
+
if (stream.probeTimer) clearTimeout(stream.probeTimer);
|
|
15709
|
+
stream.probeTimer = null;
|
|
15710
|
+
return;
|
|
15711
|
+
}
|
|
15712
|
+
case "close": {
|
|
15713
|
+
if (cvm.lastChunkIndex !== void 0) {
|
|
15714
|
+
const bound = cvm.lastChunkIndex;
|
|
15715
|
+
const complete = isBoundedInteger(bound, 0, Number.MAX_SAFE_INTEGER) && stream.nextChunkIndex === bound + 1 && stream.outOfOrder.size === 0;
|
|
15716
|
+
if (!complete) {
|
|
15717
|
+
failStream(entry, "close with unresolved chunk gaps");
|
|
15718
|
+
return;
|
|
15719
|
+
}
|
|
15720
|
+
}
|
|
15721
|
+
if (!admitOrderedActivity()) return;
|
|
15722
|
+
routeEvent(serverPubkey, mcp);
|
|
15723
|
+
endStream(entry);
|
|
15724
|
+
return;
|
|
15725
|
+
}
|
|
15726
|
+
case "abort":
|
|
15727
|
+
if (!admitOrderedActivity()) return;
|
|
15728
|
+
routeEvent(serverPubkey, mcp);
|
|
15729
|
+
endStream(entry);
|
|
15730
|
+
return;
|
|
15731
|
+
default:
|
|
15732
|
+
return;
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
function refreshPendingTimeout(entry) {
|
|
15736
|
+
clearTimeout(entry.timer);
|
|
15737
|
+
entry.timer = setTimeout(() => {
|
|
15738
|
+
const settled = settlePending(entry.correlationId);
|
|
15739
|
+
settled?.reject(new Error("relay timeout"));
|
|
15740
|
+
}, entry.timeoutMs);
|
|
15741
|
+
}
|
|
15742
|
+
function routeMessage(serverPubkey, mcp) {
|
|
15364
15743
|
const id = mcp.id;
|
|
15365
15744
|
if (id != null && pending.has(String(id))) {
|
|
15366
15745
|
const entry = pending.get(String(id));
|
|
15367
15746
|
if (entry.serverPubkey !== serverPubkey) return;
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
entry.resolve({ ...mcp, id: entry.originalId });
|
|
15747
|
+
const settled = settlePending(String(id));
|
|
15748
|
+
settled?.resolve({ ...mcp, id: entry.originalId });
|
|
15371
15749
|
return;
|
|
15372
15750
|
}
|
|
15751
|
+
routeEvent(serverPubkey, mcp);
|
|
15752
|
+
}
|
|
15753
|
+
function routeEvent(serverPubkey, mcp) {
|
|
15373
15754
|
if (mcp.method !== void 0 && sessions.has(serverPubkey)) {
|
|
15374
15755
|
const server = { pubkey: serverPubkey };
|
|
15375
15756
|
for (const handler of eventHandlers) handler(server, mcp);
|
|
15376
15757
|
}
|
|
15377
15758
|
}
|
|
15378
15759
|
async function publishMcp(server, relays, message) {
|
|
15760
|
+
const capabilityTags = [
|
|
15761
|
+
...encrypt22 ? [[SUPPORT_ENCRYPTION]] : [],
|
|
15762
|
+
...encrypt22 && wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? [[SUPPORT_ENCRYPTION_EPHEMERAL]] : [],
|
|
15763
|
+
[SUPPORT_OVERSIZED_TRANSFER],
|
|
15764
|
+
[SUPPORT_OPEN_STREAM]
|
|
15765
|
+
];
|
|
15379
15766
|
const inner = finalizeEvent(
|
|
15380
|
-
{
|
|
15767
|
+
{
|
|
15768
|
+
kind: KIND_CVM,
|
|
15769
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
15770
|
+
tags: [["p", server.pubkey], ...capabilityTags],
|
|
15771
|
+
content: JSON.stringify(message)
|
|
15772
|
+
},
|
|
15381
15773
|
clientSecretKey
|
|
15382
15774
|
);
|
|
15383
15775
|
if (!encrypt22) {
|
|
@@ -15401,17 +15793,52 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15401
15793
|
function sendCorrelated(server, relays, message, timeout) {
|
|
15402
15794
|
const correlationId = nextCorrelationId();
|
|
15403
15795
|
const originalId = message.id;
|
|
15404
|
-
|
|
15796
|
+
let outgoing = { ...message, id: correlationId };
|
|
15797
|
+
let issuedToken;
|
|
15798
|
+
const params = message.params && typeof message.params === "object" ? message.params : void 0;
|
|
15799
|
+
const meta = params?._meta && typeof params._meta === "object" ? params._meta : void 0;
|
|
15800
|
+
const explicit = meta?.progressToken;
|
|
15801
|
+
if (message.method === "tools/call") {
|
|
15802
|
+
issuedToken = typeof explicit === "string" || typeof explicit === "number" ? explicit : correlationId;
|
|
15803
|
+
outgoing = {
|
|
15804
|
+
...outgoing,
|
|
15805
|
+
params: {
|
|
15806
|
+
...params,
|
|
15807
|
+
_meta: {
|
|
15808
|
+
...meta,
|
|
15809
|
+
progressToken: issuedToken
|
|
15810
|
+
}
|
|
15811
|
+
}
|
|
15812
|
+
};
|
|
15813
|
+
} else if (typeof explicit === "string" || typeof explicit === "number") {
|
|
15814
|
+
issuedToken = explicit;
|
|
15815
|
+
}
|
|
15816
|
+
const tokenKey = progressTokenKey(issuedToken);
|
|
15817
|
+
const issuedKey = tokenKey ? `${server.pubkey}:${tokenKey}` : null;
|
|
15818
|
+
if (issuedKey && issuedTokens.has(issuedKey)) {
|
|
15819
|
+
return Promise.reject(new Error("duplicate progress token"));
|
|
15820
|
+
}
|
|
15405
15821
|
return new Promise((resolve, reject2) => {
|
|
15406
|
-
const
|
|
15407
|
-
|
|
15408
|
-
reject2
|
|
15409
|
-
|
|
15410
|
-
|
|
15822
|
+
const entry = {
|
|
15823
|
+
resolve,
|
|
15824
|
+
reject: reject2,
|
|
15825
|
+
timer: setTimeout(() => {
|
|
15826
|
+
const settled = settlePending(correlationId);
|
|
15827
|
+
settled?.reject(new Error("relay timeout"));
|
|
15828
|
+
}, timeout),
|
|
15829
|
+
timeoutMs: timeout,
|
|
15830
|
+
originalId,
|
|
15831
|
+
serverPubkey: server.pubkey,
|
|
15832
|
+
correlationId
|
|
15833
|
+
};
|
|
15834
|
+
if (issuedKey) {
|
|
15835
|
+
entry.tokenKey = issuedKey;
|
|
15836
|
+
issuedTokens.set(issuedKey, correlationId);
|
|
15837
|
+
}
|
|
15838
|
+
pending.set(correlationId, entry);
|
|
15411
15839
|
void publishMcp(server, relays, outgoing).catch((err2) => {
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
reject2(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15840
|
+
const settled = settlePending(correlationId);
|
|
15841
|
+
settled?.reject(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15415
15842
|
});
|
|
15416
15843
|
});
|
|
15417
15844
|
}
|
|
@@ -15509,6 +15936,10 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15509
15936
|
if (!session) return;
|
|
15510
15937
|
sessions.delete(server.pubkey);
|
|
15511
15938
|
releaseRelays(session.relays);
|
|
15939
|
+
for (const [correlationId, entry] of pending) {
|
|
15940
|
+
if (entry.serverPubkey !== server.pubkey) continue;
|
|
15941
|
+
settlePending(correlationId)?.reject(new Error("server closed"));
|
|
15942
|
+
}
|
|
15512
15943
|
},
|
|
15513
15944
|
onEvent(handler) {
|
|
15514
15945
|
eventHandlers.add(handler);
|
|
@@ -15523,9 +15954,12 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15523
15954
|
inbound = null;
|
|
15524
15955
|
for (const entry of pending.values()) {
|
|
15525
15956
|
clearTimeout(entry.timer);
|
|
15957
|
+
releaseTransfer(entry);
|
|
15958
|
+
releaseStream(entry);
|
|
15526
15959
|
entry.reject(new Error("transport disposed"));
|
|
15527
15960
|
}
|
|
15528
15961
|
pending.clear();
|
|
15962
|
+
issuedTokens.clear();
|
|
15529
15963
|
sessions.clear();
|
|
15530
15964
|
relayRefcount.clear();
|
|
15531
15965
|
eventHandlers.clear();
|
|
@@ -16430,14 +16864,14 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16430
16864
|
this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;
|
|
16431
16865
|
this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;
|
|
16432
16866
|
}
|
|
16433
|
-
async readBlob(
|
|
16867
|
+
async readBlob(sha256Hex2) {
|
|
16434
16868
|
try {
|
|
16435
16869
|
const cache2 = await this.open();
|
|
16436
|
-
const response = await cache2.match(this.blobUrl(
|
|
16870
|
+
const response = await cache2.match(this.blobUrl(sha256Hex2));
|
|
16437
16871
|
if (!response) return void 0;
|
|
16438
16872
|
const bytes = await responseBytes(response);
|
|
16439
16873
|
const index = await this.readIndex(cache2);
|
|
16440
|
-
const entry = index.blobs[
|
|
16874
|
+
const entry = index.blobs[sha256Hex2];
|
|
16441
16875
|
if (entry) {
|
|
16442
16876
|
entry.lastAccessed = this.now();
|
|
16443
16877
|
index.updatedAt = entry.lastAccessed;
|
|
@@ -16445,20 +16879,20 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16445
16879
|
}
|
|
16446
16880
|
return bytes;
|
|
16447
16881
|
} catch (cause) {
|
|
16448
|
-
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${
|
|
16882
|
+
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${sha256Hex2}`, cause });
|
|
16449
16883
|
return void 0;
|
|
16450
16884
|
}
|
|
16451
16885
|
}
|
|
16452
|
-
async deleteBlob(
|
|
16886
|
+
async deleteBlob(sha256Hex2) {
|
|
16453
16887
|
try {
|
|
16454
16888
|
const cache2 = await this.open();
|
|
16455
|
-
await cache2.delete(this.blobUrl(
|
|
16889
|
+
await cache2.delete(this.blobUrl(sha256Hex2));
|
|
16456
16890
|
const index = await this.readIndex(cache2);
|
|
16457
|
-
delete index.blobs[
|
|
16891
|
+
delete index.blobs[sha256Hex2];
|
|
16458
16892
|
index.updatedAt = this.now();
|
|
16459
16893
|
await this.writeIndex(cache2, index);
|
|
16460
16894
|
} catch (cause) {
|
|
16461
|
-
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${
|
|
16895
|
+
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${sha256Hex2}`, cause });
|
|
16462
16896
|
}
|
|
16463
16897
|
}
|
|
16464
16898
|
async writeVerifiedResolution(input) {
|
|
@@ -16839,22 +17273,22 @@ function verifyManifestSignature(event) {
|
|
|
16839
17273
|
return false;
|
|
16840
17274
|
}
|
|
16841
17275
|
}
|
|
16842
|
-
function verifyBlobHash(bytes,
|
|
16843
|
-
return bytesToHex2(sha256(bytes)) ===
|
|
17276
|
+
function verifyBlobHash(bytes, sha256Hex2) {
|
|
17277
|
+
return bytesToHex2(sha256(bytes)) === sha256Hex2;
|
|
16844
17278
|
}
|
|
16845
17279
|
function stripTrailingSlash(url) {
|
|
16846
17280
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
16847
17281
|
}
|
|
16848
|
-
async function fetchBlob(servers,
|
|
17282
|
+
async function fetchBlob(servers, sha256Hex2, fetchBytes) {
|
|
16849
17283
|
for (const server of servers) {
|
|
16850
|
-
const url = `${stripTrailingSlash(server)}/${
|
|
17284
|
+
const url = `${stripTrailingSlash(server)}/${sha256Hex2}`;
|
|
16851
17285
|
try {
|
|
16852
17286
|
const bytes = await fetchBytes(url);
|
|
16853
|
-
if (verifyBlobHash(bytes,
|
|
17287
|
+
if (verifyBlobHash(bytes, sha256Hex2)) return bytes;
|
|
16854
17288
|
} catch {
|
|
16855
17289
|
}
|
|
16856
17290
|
}
|
|
16857
|
-
throw new NappletResolutionError("blob-unavailable", `no server served blob ${
|
|
17291
|
+
throw new NappletResolutionError("blob-unavailable", `no server served blob ${sha256Hex2}`);
|
|
16858
17292
|
}
|
|
16859
17293
|
var INDEX_PATHS = ["/index.html", "index.html", "/"];
|
|
16860
17294
|
function defaultDecode(bytes) {
|
|
@@ -20466,7 +20900,7 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
20466
20900
|
const resolved = await resolveNapplet({
|
|
20467
20901
|
event,
|
|
20468
20902
|
cache: await openNappletArtifactCache({ requireStorageEstimate: false }),
|
|
20469
|
-
fetchBlob: (
|
|
20903
|
+
fetchBlob: (sha256Hex2, servers) => fetchBlob([...servers, ...blossomServers], sha256Hex2, async (url) => {
|
|
20470
20904
|
const response = await (options.fetcher ?? fetch)(url);
|
|
20471
20905
|
if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);
|
|
20472
20906
|
return new Uint8Array(await response.arrayBuffer());
|