@elisym/sdk 0.25.1 → 0.25.4
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-store.cjs.map +1 -1
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +119 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -18
- package/dist/index.d.ts +158 -18
- package/dist/index.js +118 -19
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +130 -50
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +72 -0
- package/dist/node.d.ts +72 -0
- package/dist/node.js +130 -50
- package/dist/node.js.map +1 -1
- package/dist/skills.cjs +57 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +23 -3
- package/dist/skills.d.ts +23 -3
- package/dist/skills.js +58 -2
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -98,6 +98,11 @@ var DEFAULTS = {
|
|
|
98
98
|
// default, not a protocol constant - the transfer is resumable and its own
|
|
99
99
|
// budget, decoupled from the result-wait window.
|
|
100
100
|
IROH_FETCH_TIMEOUT_MS: 3e5,
|
|
101
|
+
// Ceiling for a single iroh SEED (addFromPath/addBytes/share). Seeding is local
|
|
102
|
+
// (hash + store-copy + ticket mint), so this is a generous backstop: it bounds
|
|
103
|
+
// the JS await so a wedged native call surfaces as a thrown error (and triggers a
|
|
104
|
+
// node reset) instead of an indefinite hang that stalls file delivery.
|
|
105
|
+
IROH_SEED_TIMEOUT_MS: 12e4,
|
|
101
106
|
// Ceiling for a single Blossom blob upload (PUT /upload). Large blobs (up to
|
|
102
107
|
// LIMITS.MAX_FILE_SIZE) need far more than the 30s used for small media images.
|
|
103
108
|
BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5,
|
|
@@ -1219,6 +1224,17 @@ var BlossomService = class {
|
|
|
1219
1224
|
this.serverUrl = serverUrl;
|
|
1220
1225
|
this.fallback = fallback;
|
|
1221
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* The content-addressed GET URL for a blob, derivable from its sha256 BEFORE
|
|
1229
|
+
* upload (BUD-01: `<serverUrl>/<sha256>`, no extension for our octet-stream
|
|
1230
|
+
* ciphertext uploads - same form `delete` addresses by). Lets a caller build a
|
|
1231
|
+
* complete attachment descriptor and defer the actual byte upload (the descriptor
|
|
1232
|
+
* is submitted first, the bytes PUT later). `upload()` re-verifies the server
|
|
1233
|
+
* returns this exact url.
|
|
1234
|
+
*/
|
|
1235
|
+
contentUrl(sha256) {
|
|
1236
|
+
return `${this.serverUrl}/${sha256}`;
|
|
1237
|
+
}
|
|
1222
1238
|
/**
|
|
1223
1239
|
* Upload a file to the Blossom server, returning its descriptor. On any failure, falls
|
|
1224
1240
|
* back to the configured uploader (if any) and returns a normalized descriptor with
|
|
@@ -1271,18 +1287,38 @@ var BlossomService = class {
|
|
|
1271
1287
|
}
|
|
1272
1288
|
}
|
|
1273
1289
|
/**
|
|
1274
|
-
* Download a
|
|
1275
|
-
* the declared Content-Length) and verifies the sha256 when
|
|
1290
|
+
* Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
|
|
1291
|
+
* on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
|
|
1292
|
+
* `expectedSha256` is given. Browser-safe.
|
|
1293
|
+
*
|
|
1294
|
+
* SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
|
|
1295
|
+
* is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
|
|
1296
|
+
* refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
|
|
1297
|
+
* The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
|
|
1298
|
+
* the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
|
|
1299
|
+
* across Blossom servers would replace this single-origin pin with an explicit allowlist.
|
|
1276
1300
|
*/
|
|
1277
1301
|
async download(url, opts = {}) {
|
|
1302
|
+
if (new URL(url).origin !== new URL(this.serverUrl).origin) {
|
|
1303
|
+
throw new Error(`Refusing to download from a non-Blossom origin: ${url}`);
|
|
1304
|
+
}
|
|
1278
1305
|
const maxBytes = opts.maxBytes ?? LIMITS.MAX_FILE_SIZE;
|
|
1279
1306
|
const controller = new AbortController();
|
|
1280
1307
|
const timer = setTimeout(
|
|
1281
1308
|
() => controller.abort(),
|
|
1282
1309
|
opts.timeoutMs ?? DEFAULTS.BLOSSOM_FETCH_TIMEOUT_MS
|
|
1283
1310
|
);
|
|
1311
|
+
const externalSignal = opts.signal;
|
|
1312
|
+
const onExternalAbort = () => controller.abort();
|
|
1313
|
+
if (externalSignal !== void 0) {
|
|
1314
|
+
if (externalSignal.aborted) {
|
|
1315
|
+
controller.abort();
|
|
1316
|
+
} else {
|
|
1317
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1284
1320
|
try {
|
|
1285
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
1321
|
+
const res = await fetch(url, { signal: controller.signal, redirect: "error" });
|
|
1286
1322
|
if (!res.ok) {
|
|
1287
1323
|
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
1288
1324
|
}
|
|
@@ -1324,6 +1360,9 @@ var BlossomService = class {
|
|
|
1324
1360
|
return bytes;
|
|
1325
1361
|
} finally {
|
|
1326
1362
|
clearTimeout(timer);
|
|
1363
|
+
if (externalSignal !== void 0) {
|
|
1364
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
1365
|
+
}
|
|
1327
1366
|
}
|
|
1328
1367
|
}
|
|
1329
1368
|
async uploadToBlossom(identity, bytes, hashHex, mime) {
|
|
@@ -1457,6 +1496,9 @@ function parseCapabilityEvent(event, network) {
|
|
|
1457
1496
|
if (card.inputMime !== void 0 && (typeof card.inputMime !== "string" || card.inputMime.length > 255) || card.outputMime !== void 0 && (typeof card.outputMime !== "string" || card.outputMime.length > 255)) {
|
|
1458
1497
|
return null;
|
|
1459
1498
|
}
|
|
1499
|
+
if (card.inputText !== void 0 && !["required", "optional", "none"].includes(card.inputText)) {
|
|
1500
|
+
card.inputText = void 0;
|
|
1501
|
+
}
|
|
1460
1502
|
if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
|
|
1461
1503
|
return null;
|
|
1462
1504
|
}
|
|
@@ -2087,7 +2129,11 @@ var FileAttachmentSchema = zod.z.object({
|
|
|
2087
2129
|
var JobPayloadEnvelopeSchema = zod.z.object({
|
|
2088
2130
|
v: zod.z.literal(ENVELOPE_VERSION),
|
|
2089
2131
|
text: zod.z.string().optional(),
|
|
2090
|
-
attachment
|
|
2132
|
+
// Legacy single attachment - kept (and mirrored from `attachments[0]`) so an old
|
|
2133
|
+
// decoder that doesn't know `attachments` still gets the first file.
|
|
2134
|
+
attachment: FileAttachmentSchema.optional(),
|
|
2135
|
+
// Multiple result/input files. Additive; old decoders strip this unknown key.
|
|
2136
|
+
attachments: zod.z.array(FileAttachmentSchema).optional()
|
|
2091
2137
|
});
|
|
2092
2138
|
var ACCEPT_TRANSPORTS_TAG = "accept";
|
|
2093
2139
|
var KNOWN_TRANSPORT_KINDS = ["iroh", "blossom"];
|
|
@@ -2120,12 +2166,21 @@ function readAcceptedTransports(tags) {
|
|
|
2120
2166
|
}
|
|
2121
2167
|
return out.length > 0 ? out : void 0;
|
|
2122
2168
|
}
|
|
2169
|
+
function attachmentsOf(decoded) {
|
|
2170
|
+
if (decoded.attachments !== void 0 && decoded.attachments.length > 0) {
|
|
2171
|
+
return decoded.attachments;
|
|
2172
|
+
}
|
|
2173
|
+
return decoded.attachment !== void 0 ? [decoded.attachment] : [];
|
|
2174
|
+
}
|
|
2123
2175
|
function encodeJobPayload(payload) {
|
|
2124
2176
|
const envelope = { v: ENVELOPE_VERSION };
|
|
2125
2177
|
if (payload.text !== void 0) {
|
|
2126
2178
|
envelope.text = payload.text;
|
|
2127
2179
|
}
|
|
2128
|
-
if (payload.
|
|
2180
|
+
if (payload.attachments !== void 0 && payload.attachments.length > 0) {
|
|
2181
|
+
envelope.attachments = payload.attachments;
|
|
2182
|
+
envelope.attachment = payload.attachments[0];
|
|
2183
|
+
} else if (payload.attachment !== void 0) {
|
|
2129
2184
|
envelope.attachment = payload.attachment;
|
|
2130
2185
|
}
|
|
2131
2186
|
return JSON.stringify(envelope);
|
|
@@ -2153,7 +2208,11 @@ function decodeJobPayload(content) {
|
|
|
2153
2208
|
`Invalid elisym job payload (v=${JSON.stringify(version)}): ${result.error.message}`
|
|
2154
2209
|
);
|
|
2155
2210
|
}
|
|
2156
|
-
return {
|
|
2211
|
+
return {
|
|
2212
|
+
text: result.data.text,
|
|
2213
|
+
attachment: result.data.attachment,
|
|
2214
|
+
attachments: result.data.attachments
|
|
2215
|
+
};
|
|
2157
2216
|
}
|
|
2158
2217
|
|
|
2159
2218
|
// src/services/marketplace.ts
|
|
@@ -2318,7 +2377,7 @@ var MarketplaceService = class {
|
|
|
2318
2377
|
}
|
|
2319
2378
|
resultDelivered = true;
|
|
2320
2379
|
try {
|
|
2321
|
-
cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment);
|
|
2380
|
+
cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment, attachmentsOf(decoded));
|
|
2322
2381
|
} catch {
|
|
2323
2382
|
} finally {
|
|
2324
2383
|
done();
|
|
@@ -2482,8 +2541,8 @@ var MarketplaceService = class {
|
|
|
2482
2541
|
);
|
|
2483
2542
|
}
|
|
2484
2543
|
/** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
|
|
2485
|
-
async submitJobResult(identity, requestEvent, content, amount,
|
|
2486
|
-
const hasAttachment =
|
|
2544
|
+
async submitJobResult(identity, requestEvent, content, amount, attachments) {
|
|
2545
|
+
const hasAttachment = attachments !== void 0 && attachments.length > 0;
|
|
2487
2546
|
if (!content && !hasAttachment) {
|
|
2488
2547
|
throw new Error("Job result content must not be empty.");
|
|
2489
2548
|
}
|
|
@@ -2497,7 +2556,7 @@ var MarketplaceService = class {
|
|
|
2497
2556
|
);
|
|
2498
2557
|
}
|
|
2499
2558
|
const shouldEncrypt = isEncrypted(requestEvent);
|
|
2500
|
-
const payload = hasAttachment ? encodeJobPayload({ text: content || void 0,
|
|
2559
|
+
const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachments }) : content;
|
|
2501
2560
|
if (shouldEncrypt) {
|
|
2502
2561
|
const payloadBytes = utf8ByteLength(payload);
|
|
2503
2562
|
if (payloadBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
|
|
@@ -2538,11 +2597,11 @@ var MarketplaceService = class {
|
|
|
2538
2597
|
* With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
|
|
2539
2598
|
* Jitter: 0.5x-1.0x of calculated delay.
|
|
2540
2599
|
*/
|
|
2541
|
-
async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS,
|
|
2600
|
+
async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachments) {
|
|
2542
2601
|
const attempts = Math.max(1, maxAttempts);
|
|
2543
2602
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
2544
2603
|
try {
|
|
2545
|
-
return await this.submitJobResult(identity, requestEvent, content, amount,
|
|
2604
|
+
return await this.submitJobResult(identity, requestEvent, content, amount, attachments);
|
|
2546
2605
|
} catch (e) {
|
|
2547
2606
|
if (attempt >= attempts - 1) {
|
|
2548
2607
|
throw e;
|
|
@@ -3701,11 +3760,12 @@ function createBlossomTransport(opts) {
|
|
|
3701
3760
|
enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
|
|
3702
3761
|
};
|
|
3703
3762
|
},
|
|
3704
|
-
async fetchToBytes({ transport, senderPubkey, maxBytes }) {
|
|
3763
|
+
async fetchToBytes({ transport, senderPubkey, maxBytes, signal }) {
|
|
3705
3764
|
const plaintextCap = maxBytes ?? LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES;
|
|
3706
3765
|
const ciphertext = await blossom.download(transport.url, {
|
|
3707
3766
|
maxBytes: plaintextCap + AES_GCM_TAG_BYTES,
|
|
3708
|
-
expectedSha256: transport.sha256
|
|
3767
|
+
expectedSha256: transport.sha256,
|
|
3768
|
+
signal
|
|
3709
3769
|
});
|
|
3710
3770
|
return decryptBytesFromSender(
|
|
3711
3771
|
ciphertext,
|
|
@@ -3719,6 +3779,10 @@ function createBlossomTransport(opts) {
|
|
|
3719
3779
|
}
|
|
3720
3780
|
|
|
3721
3781
|
// src/transport/file-jobs.ts
|
|
3782
|
+
async function sha256Hex(bytes) {
|
|
3783
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
3784
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3785
|
+
}
|
|
3722
3786
|
var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3723
3787
|
".exe",
|
|
3724
3788
|
".dll",
|
|
@@ -3744,21 +3808,56 @@ function looksExecutable(name, type) {
|
|
|
3744
3808
|
const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
|
3745
3809
|
return EXECUTABLE_EXTENSIONS.has(ext) || EXECUTABLE_MIMES.has(type);
|
|
3746
3810
|
}
|
|
3747
|
-
async function
|
|
3811
|
+
async function prepareEncryptedFileInput(args) {
|
|
3748
3812
|
const { file, providerPubkey, identity, blossom } = args;
|
|
3749
3813
|
const name = file.name ?? "upload";
|
|
3750
3814
|
if (looksExecutable(name, file.type)) {
|
|
3751
3815
|
throw new Error("Refusing to upload an executable file.");
|
|
3752
3816
|
}
|
|
3753
|
-
const transport = createBlossomTransport({ blossom, identity });
|
|
3754
3817
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
3755
|
-
|
|
3756
|
-
|
|
3818
|
+
if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
3819
|
+
throw new Error(
|
|
3820
|
+
`File too large for the encrypted-Blossom transport: ${bytes.byteLength} bytes (max ${LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES}).`
|
|
3821
|
+
);
|
|
3822
|
+
}
|
|
3823
|
+
const { ciphertext, wrappedKey, iv } = await encryptBytesForRecipient(
|
|
3824
|
+
bytes,
|
|
3825
|
+
identity.secretKey,
|
|
3826
|
+
providerPubkey
|
|
3827
|
+
);
|
|
3828
|
+
const sha256 = await sha256Hex(ciphertext);
|
|
3829
|
+
const member = {
|
|
3830
|
+
kind: "blossom",
|
|
3831
|
+
url: blossom.contentUrl(sha256),
|
|
3832
|
+
sha256,
|
|
3833
|
+
enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
|
|
3834
|
+
};
|
|
3835
|
+
const attachment = {
|
|
3757
3836
|
name,
|
|
3758
3837
|
size: bytes.byteLength,
|
|
3759
3838
|
mime: file.type || "application/octet-stream",
|
|
3760
3839
|
transports: [member]
|
|
3761
3840
|
};
|
|
3841
|
+
const upload = async () => {
|
|
3842
|
+
const descriptor = await blossom.upload(
|
|
3843
|
+
identity,
|
|
3844
|
+
new Blob([ciphertext], { type: "application/octet-stream" })
|
|
3845
|
+
);
|
|
3846
|
+
if (descriptor.provider !== "blossom") {
|
|
3847
|
+
throw new Error("Blossom upload fell back to a non-content-addressed provider.");
|
|
3848
|
+
}
|
|
3849
|
+
if (descriptor.sha256 !== sha256 || descriptor.url !== member.url) {
|
|
3850
|
+
throw new Error(
|
|
3851
|
+
`Blossom upload descriptor mismatch (expected ${member.url} / ${sha256}, got ${descriptor.url} / ${descriptor.sha256}).`
|
|
3852
|
+
);
|
|
3853
|
+
}
|
|
3854
|
+
};
|
|
3855
|
+
return { attachment, upload };
|
|
3856
|
+
}
|
|
3857
|
+
async function buildEncryptedFileInput(args) {
|
|
3858
|
+
const prepared = await prepareEncryptedFileInput(args);
|
|
3859
|
+
await prepared.upload();
|
|
3860
|
+
return prepared.attachment;
|
|
3762
3861
|
}
|
|
3763
3862
|
async function fetchEncryptedFileOutput(args) {
|
|
3764
3863
|
const { attachment, providerPubkey, identity, blossom, maxBytes } = args;
|
|
@@ -4395,6 +4494,7 @@ exports.assertExpiry = assertExpiry;
|
|
|
4395
4494
|
exports.assertLamports = assertLamports;
|
|
4396
4495
|
exports.assetByKey = assetByKey;
|
|
4397
4496
|
exports.assetKey = assetKey;
|
|
4497
|
+
exports.attachmentsOf = attachmentsOf;
|
|
4398
4498
|
exports.buildAcceptTransportsTag = buildAcceptTransportsTag;
|
|
4399
4499
|
exports.buildEncryptedFileInput = buildEncryptedFileInput;
|
|
4400
4500
|
exports.buildPaymentInstructions = buildPaymentInstructions;
|
|
@@ -4431,6 +4531,7 @@ exports.nip44Encrypt = nip44Encrypt;
|
|
|
4431
4531
|
exports.parseAssetAmount = parseAssetAmount;
|
|
4432
4532
|
exports.parsePaymentRequest = parsePaymentRequest;
|
|
4433
4533
|
exports.pickPercentileFee = pickPercentileFee;
|
|
4534
|
+
exports.prepareEncryptedFileInput = prepareEncryptedFileInput;
|
|
4434
4535
|
exports.readAcceptedTransports = readAcceptedTransports;
|
|
4435
4536
|
exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
|
|
4436
4537
|
exports.resolveKnownAsset = resolveKnownAsset;
|