@elisym/sdk 0.25.0 → 0.25.3
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 +369 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +378 -41
- package/dist/index.d.ts +378 -41
- package/dist/index.js +360 -13
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +83 -22
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +66 -0
- package/dist/node.d.ts +66 -0
- package/dist/node.js +83 -22
- 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.js
CHANGED
|
@@ -73,9 +73,16 @@ var DEFAULTS = {
|
|
|
73
73
|
// default, not a protocol constant - the transfer is resumable and its own
|
|
74
74
|
// budget, decoupled from the result-wait window.
|
|
75
75
|
IROH_FETCH_TIMEOUT_MS: 3e5,
|
|
76
|
+
// Ceiling for a single iroh SEED (addFromPath/addBytes/share). Seeding is local
|
|
77
|
+
// (hash + store-copy + ticket mint), so this is a generous backstop: it bounds
|
|
78
|
+
// the JS await so a wedged native call surfaces as a thrown error (and triggers a
|
|
79
|
+
// node reset) instead of an indefinite hang that stalls file delivery.
|
|
80
|
+
IROH_SEED_TIMEOUT_MS: 12e4,
|
|
76
81
|
// Ceiling for a single Blossom blob upload (PUT /upload). Large blobs (up to
|
|
77
82
|
// LIMITS.MAX_FILE_SIZE) need far more than the 30s used for small media images.
|
|
78
|
-
BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5
|
|
83
|
+
BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5,
|
|
84
|
+
// Ceiling for a single encrypted Blossom blob download (GET). Same budget as upload.
|
|
85
|
+
BLOSSOM_FETCH_TIMEOUT_MS: 3e5
|
|
79
86
|
};
|
|
80
87
|
var LIMITS = {
|
|
81
88
|
MAX_INPUT_LENGTH: 1e5,
|
|
@@ -100,6 +107,12 @@ var LIMITS = {
|
|
|
100
107
|
// providers may lower it per deployment.
|
|
101
108
|
MAX_FILE_SIZE: 1073741824,
|
|
102
109
|
// 1 GiB
|
|
110
|
+
// Cap for the ENCRYPTED Blossom path (web/SDK). The encrypt-then-upload flow is
|
|
111
|
+
// whole-buffer in WebCrypto + BlossomService (~3x file-size peak RAM), so this is
|
|
112
|
+
// deliberately far below MAX_FILE_SIZE to stay safe in a browser tab; larger files
|
|
113
|
+
// use iroh. The relay enforces a ~128 MiB server-side backstop.
|
|
114
|
+
MAX_BLOSSOM_ENCRYPTED_BYTES: 104857600,
|
|
115
|
+
// 100 MiB
|
|
103
116
|
MAX_TIMEOUT_SECS: 600,
|
|
104
117
|
// Upper bound for execution budgets (`max_execution_secs` / `execution_timeout_secs`).
|
|
105
118
|
// Distinct from MAX_TIMEOUT_SECS (the result-wait cap): execution budgets may be
|
|
@@ -1186,6 +1199,17 @@ var BlossomService = class {
|
|
|
1186
1199
|
this.serverUrl = serverUrl;
|
|
1187
1200
|
this.fallback = fallback;
|
|
1188
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* The content-addressed GET URL for a blob, derivable from its sha256 BEFORE
|
|
1204
|
+
* upload (BUD-01: `<serverUrl>/<sha256>`, no extension for our octet-stream
|
|
1205
|
+
* ciphertext uploads - same form `delete` addresses by). Lets a caller build a
|
|
1206
|
+
* complete attachment descriptor and defer the actual byte upload (the descriptor
|
|
1207
|
+
* is submitted first, the bytes PUT later). `upload()` re-verifies the server
|
|
1208
|
+
* returns this exact url.
|
|
1209
|
+
*/
|
|
1210
|
+
contentUrl(sha256) {
|
|
1211
|
+
return `${this.serverUrl}/${sha256}`;
|
|
1212
|
+
}
|
|
1189
1213
|
/**
|
|
1190
1214
|
* Upload a file to the Blossom server, returning its descriptor. On any failure, falls
|
|
1191
1215
|
* back to the configured uploader (if any) and returns a normalized descriptor with
|
|
@@ -1237,6 +1261,62 @@ var BlossomService = class {
|
|
|
1237
1261
|
clearTimeout(timer);
|
|
1238
1262
|
}
|
|
1239
1263
|
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
|
|
1266
|
+
* the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
|
|
1267
|
+
*/
|
|
1268
|
+
async download(url, opts = {}) {
|
|
1269
|
+
const maxBytes = opts.maxBytes ?? LIMITS.MAX_FILE_SIZE;
|
|
1270
|
+
const controller = new AbortController();
|
|
1271
|
+
const timer = setTimeout(
|
|
1272
|
+
() => controller.abort(),
|
|
1273
|
+
opts.timeoutMs ?? DEFAULTS.BLOSSOM_FETCH_TIMEOUT_MS
|
|
1274
|
+
);
|
|
1275
|
+
try {
|
|
1276
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
1277
|
+
if (!res.ok) {
|
|
1278
|
+
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
1279
|
+
}
|
|
1280
|
+
const declared = Number(res.headers.get("content-length"));
|
|
1281
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
1282
|
+
throw new Error(`Blob too large: ${declared} bytes exceeds limit of ${maxBytes}.`);
|
|
1283
|
+
}
|
|
1284
|
+
if (!res.body) {
|
|
1285
|
+
throw new Error("Download response has no body.");
|
|
1286
|
+
}
|
|
1287
|
+
const reader = res.body.getReader();
|
|
1288
|
+
const chunks = [];
|
|
1289
|
+
let total = 0;
|
|
1290
|
+
let chunk = await reader.read();
|
|
1291
|
+
while (!chunk.done) {
|
|
1292
|
+
total += chunk.value.byteLength;
|
|
1293
|
+
if (total > maxBytes) {
|
|
1294
|
+
await reader.cancel();
|
|
1295
|
+
throw new Error(`Blob exceeds limit of ${maxBytes} bytes.`);
|
|
1296
|
+
}
|
|
1297
|
+
chunks.push(chunk.value);
|
|
1298
|
+
chunk = await reader.read();
|
|
1299
|
+
}
|
|
1300
|
+
const bytes = new Uint8Array(total);
|
|
1301
|
+
let offset = 0;
|
|
1302
|
+
for (const c of chunks) {
|
|
1303
|
+
bytes.set(c, offset);
|
|
1304
|
+
offset += c.byteLength;
|
|
1305
|
+
}
|
|
1306
|
+
if (opts.expectedSha256 !== void 0) {
|
|
1307
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1308
|
+
const hashHex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1309
|
+
if (hashHex !== opts.expectedSha256) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
`Download integrity check failed: got ${hashHex}, expected ${opts.expectedSha256}.`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return bytes;
|
|
1316
|
+
} finally {
|
|
1317
|
+
clearTimeout(timer);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1240
1320
|
async uploadToBlossom(identity, bytes, hashHex, mime) {
|
|
1241
1321
|
const contentType = mime || "application/octet-stream";
|
|
1242
1322
|
const authHeader = this.authHeader(identity, "upload", hashHex);
|
|
@@ -1368,6 +1448,9 @@ function parseCapabilityEvent(event, network) {
|
|
|
1368
1448
|
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)) {
|
|
1369
1449
|
return null;
|
|
1370
1450
|
}
|
|
1451
|
+
if (card.inputText !== void 0 && !["required", "optional", "none"].includes(card.inputText)) {
|
|
1452
|
+
card.inputText = void 0;
|
|
1453
|
+
}
|
|
1371
1454
|
if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
|
|
1372
1455
|
return null;
|
|
1373
1456
|
}
|
|
@@ -1952,6 +2035,26 @@ var FileTransportSchema = z.discriminatedUnion("kind", [
|
|
|
1952
2035
|
kind: z.literal("iroh"),
|
|
1953
2036
|
/** Opaque iroh `BlobTicket` string. Parsed into a real ticket only at fetch time. */
|
|
1954
2037
|
ticket: z.string().min(1).max(MAX_TICKET_LENGTH)
|
|
2038
|
+
}),
|
|
2039
|
+
z.object({
|
|
2040
|
+
kind: z.literal("blossom"),
|
|
2041
|
+
/** Public HTTP(S) URL of the CIPHERTEXT blob on a Blossom relay. */
|
|
2042
|
+
url: z.string().url().max(2048),
|
|
2043
|
+
/** sha256 (lowercase hex) of the ciphertext - what the relay stores and addresses. */
|
|
2044
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
2045
|
+
/**
|
|
2046
|
+
* Hybrid-encryption parameters. The file bytes are AES-256-GCM encrypted with a random
|
|
2047
|
+
* content key; that key is NIP-44-wrapped to the recipient. `name`/`mime`/`size` on the
|
|
2048
|
+
* attachment describe the PLAINTEXT and live only inside the (encrypted) envelope - never
|
|
2049
|
+
* sent to the relay (the relay only ever sees opaque ciphertext).
|
|
2050
|
+
*/
|
|
2051
|
+
enc: z.object({
|
|
2052
|
+
alg: z.literal("AES-256-GCM"),
|
|
2053
|
+
/** base64 12-byte GCM IV (non-secret). */
|
|
2054
|
+
iv: z.string().min(1).max(64),
|
|
2055
|
+
/** NIP-44-wrapped content key. */
|
|
2056
|
+
key: z.string().min(1).max(2048)
|
|
2057
|
+
})
|
|
1955
2058
|
})
|
|
1956
2059
|
]);
|
|
1957
2060
|
var FileAttachmentSchema = z.object({
|
|
@@ -1960,22 +2063,76 @@ var FileAttachmentSchema = z.object({
|
|
|
1960
2063
|
/** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
|
|
1961
2064
|
size: z.number().int().nonnegative(),
|
|
1962
2065
|
mime: z.string().min(1).max(255),
|
|
1963
|
-
/**
|
|
1964
|
-
|
|
2066
|
+
/**
|
|
2067
|
+
* Ordered by sender preference; at least one KNOWN transport. Parsed leniently: unknown
|
|
2068
|
+
* transport `kind`s are dropped (not rejected) so adding a new transport never makes an older
|
|
2069
|
+
* decoder throw away the whole envelope - it just ignores the kinds it doesn't know and uses
|
|
2070
|
+
* the ones it does. At least one known transport must survive, else the attachment is invalid.
|
|
2071
|
+
*/
|
|
2072
|
+
transports: z.array(z.unknown()).transform(
|
|
2073
|
+
(arr) => arr.flatMap((t) => {
|
|
2074
|
+
const parsed = FileTransportSchema.safeParse(t);
|
|
2075
|
+
return parsed.success ? [parsed.data] : [];
|
|
2076
|
+
})
|
|
2077
|
+
).refine((arr) => arr.length >= 1, { message: "attachment has no known transport" }),
|
|
1965
2078
|
/** Optional provider hint (unix seconds) for when seeding may stop. */
|
|
1966
2079
|
seedingExpiresAt: z.number().int().nonnegative().optional()
|
|
1967
2080
|
});
|
|
1968
2081
|
var JobPayloadEnvelopeSchema = z.object({
|
|
1969
2082
|
v: z.literal(ENVELOPE_VERSION),
|
|
1970
2083
|
text: z.string().optional(),
|
|
1971
|
-
attachment
|
|
2084
|
+
// Legacy single attachment - kept (and mirrored from `attachments[0]`) so an old
|
|
2085
|
+
// decoder that doesn't know `attachments` still gets the first file.
|
|
2086
|
+
attachment: FileAttachmentSchema.optional(),
|
|
2087
|
+
// Multiple result/input files. Additive; old decoders strip this unknown key.
|
|
2088
|
+
attachments: z.array(FileAttachmentSchema).optional()
|
|
1972
2089
|
});
|
|
2090
|
+
var ACCEPT_TRANSPORTS_TAG = "accept";
|
|
2091
|
+
var KNOWN_TRANSPORT_KINDS = ["iroh", "blossom"];
|
|
2092
|
+
function isKnownTransportKind(value) {
|
|
2093
|
+
return KNOWN_TRANSPORT_KINDS.includes(value);
|
|
2094
|
+
}
|
|
2095
|
+
function buildAcceptTransportsTag(kinds) {
|
|
2096
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2097
|
+
const out = [ACCEPT_TRANSPORTS_TAG];
|
|
2098
|
+
for (const kind of kinds) {
|
|
2099
|
+
if (isKnownTransportKind(kind) && !seen.has(kind)) {
|
|
2100
|
+
seen.add(kind);
|
|
2101
|
+
out.push(kind);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return out;
|
|
2105
|
+
}
|
|
2106
|
+
function readAcceptedTransports(tags) {
|
|
2107
|
+
const tag = tags.find((t) => t[0] === ACCEPT_TRANSPORTS_TAG);
|
|
2108
|
+
if (tag === void 0) {
|
|
2109
|
+
return void 0;
|
|
2110
|
+
}
|
|
2111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2112
|
+
const out = [];
|
|
2113
|
+
for (const value of tag.slice(1)) {
|
|
2114
|
+
if (isKnownTransportKind(value) && !seen.has(value)) {
|
|
2115
|
+
seen.add(value);
|
|
2116
|
+
out.push(value);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return out.length > 0 ? out : void 0;
|
|
2120
|
+
}
|
|
2121
|
+
function attachmentsOf(decoded) {
|
|
2122
|
+
if (decoded.attachments !== void 0 && decoded.attachments.length > 0) {
|
|
2123
|
+
return decoded.attachments;
|
|
2124
|
+
}
|
|
2125
|
+
return decoded.attachment !== void 0 ? [decoded.attachment] : [];
|
|
2126
|
+
}
|
|
1973
2127
|
function encodeJobPayload(payload) {
|
|
1974
2128
|
const envelope = { v: ENVELOPE_VERSION };
|
|
1975
2129
|
if (payload.text !== void 0) {
|
|
1976
2130
|
envelope.text = payload.text;
|
|
1977
2131
|
}
|
|
1978
|
-
if (payload.
|
|
2132
|
+
if (payload.attachments !== void 0 && payload.attachments.length > 0) {
|
|
2133
|
+
envelope.attachments = payload.attachments;
|
|
2134
|
+
envelope.attachment = payload.attachments[0];
|
|
2135
|
+
} else if (payload.attachment !== void 0) {
|
|
1979
2136
|
envelope.attachment = payload.attachment;
|
|
1980
2137
|
}
|
|
1981
2138
|
return JSON.stringify(envelope);
|
|
@@ -2003,7 +2160,11 @@ function decodeJobPayload(content) {
|
|
|
2003
2160
|
`Invalid elisym job payload (v=${JSON.stringify(version)}): ${result.error.message}`
|
|
2004
2161
|
);
|
|
2005
2162
|
}
|
|
2006
|
-
return {
|
|
2163
|
+
return {
|
|
2164
|
+
text: result.data.text,
|
|
2165
|
+
attachment: result.data.attachment,
|
|
2166
|
+
attachments: result.data.attachments
|
|
2167
|
+
};
|
|
2007
2168
|
}
|
|
2008
2169
|
|
|
2009
2170
|
// src/services/marketplace.ts
|
|
@@ -2072,6 +2233,12 @@ var MarketplaceService = class {
|
|
|
2072
2233
|
tags.push(["p", options.providerPubkey]);
|
|
2073
2234
|
tags.push(["encrypted", "nip44"]);
|
|
2074
2235
|
}
|
|
2236
|
+
if (options.acceptTransports && options.acceptTransports.length > 0) {
|
|
2237
|
+
const acceptTag = buildAcceptTransportsTag(options.acceptTransports);
|
|
2238
|
+
if (acceptTag.length > 1) {
|
|
2239
|
+
tags.push(acceptTag);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2075
2242
|
const kind = jobRequestKind(options.kindOffset ?? DEFAULT_KIND_OFFSET);
|
|
2076
2243
|
const event = finalizeEvent(
|
|
2077
2244
|
{
|
|
@@ -2162,7 +2329,7 @@ var MarketplaceService = class {
|
|
|
2162
2329
|
}
|
|
2163
2330
|
resultDelivered = true;
|
|
2164
2331
|
try {
|
|
2165
|
-
cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment);
|
|
2332
|
+
cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment, attachmentsOf(decoded));
|
|
2166
2333
|
} catch {
|
|
2167
2334
|
} finally {
|
|
2168
2335
|
done();
|
|
@@ -2326,8 +2493,8 @@ var MarketplaceService = class {
|
|
|
2326
2493
|
);
|
|
2327
2494
|
}
|
|
2328
2495
|
/** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
|
|
2329
|
-
async submitJobResult(identity, requestEvent, content, amount,
|
|
2330
|
-
const hasAttachment =
|
|
2496
|
+
async submitJobResult(identity, requestEvent, content, amount, attachments) {
|
|
2497
|
+
const hasAttachment = attachments !== void 0 && attachments.length > 0;
|
|
2331
2498
|
if (!content && !hasAttachment) {
|
|
2332
2499
|
throw new Error("Job result content must not be empty.");
|
|
2333
2500
|
}
|
|
@@ -2341,7 +2508,7 @@ var MarketplaceService = class {
|
|
|
2341
2508
|
);
|
|
2342
2509
|
}
|
|
2343
2510
|
const shouldEncrypt = isEncrypted(requestEvent);
|
|
2344
|
-
const payload = hasAttachment ? encodeJobPayload({ text: content || void 0,
|
|
2511
|
+
const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachments }) : content;
|
|
2345
2512
|
if (shouldEncrypt) {
|
|
2346
2513
|
const payloadBytes = utf8ByteLength(payload);
|
|
2347
2514
|
if (payloadBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
|
|
@@ -2382,11 +2549,11 @@ var MarketplaceService = class {
|
|
|
2382
2549
|
* With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
|
|
2383
2550
|
* Jitter: 0.5x-1.0x of calculated delay.
|
|
2384
2551
|
*/
|
|
2385
|
-
async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS,
|
|
2552
|
+
async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachments) {
|
|
2386
2553
|
const attempts = Math.max(1, maxAttempts);
|
|
2387
2554
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
2388
2555
|
try {
|
|
2389
|
-
return await this.submitJobResult(identity, requestEvent, content, amount,
|
|
2556
|
+
return await this.submitJobResult(identity, requestEvent, content, amount, attachments);
|
|
2390
2557
|
} catch (e) {
|
|
2391
2558
|
if (attempt >= attempts - 1) {
|
|
2392
2559
|
throw e;
|
|
@@ -3480,6 +3647,186 @@ var ElisymClient = class {
|
|
|
3480
3647
|
}
|
|
3481
3648
|
};
|
|
3482
3649
|
|
|
3650
|
+
// src/primitives/file-crypto.ts
|
|
3651
|
+
var KEY_BYTES = 32;
|
|
3652
|
+
var IV_BYTES = 12;
|
|
3653
|
+
function bytesToBase64(bytes) {
|
|
3654
|
+
let bin = "";
|
|
3655
|
+
for (const b of bytes) {
|
|
3656
|
+
bin += String.fromCharCode(b);
|
|
3657
|
+
}
|
|
3658
|
+
return btoa(bin);
|
|
3659
|
+
}
|
|
3660
|
+
function base64ToBytes(b64) {
|
|
3661
|
+
const bin = atob(b64);
|
|
3662
|
+
const out = new Uint8Array(bin.length);
|
|
3663
|
+
for (let i = 0; i < bin.length; i += 1) {
|
|
3664
|
+
out[i] = bin.charCodeAt(i);
|
|
3665
|
+
}
|
|
3666
|
+
return out;
|
|
3667
|
+
}
|
|
3668
|
+
async function encryptBytesForRecipient(bytes, senderSk, recipientPubkey) {
|
|
3669
|
+
const rawKey = crypto.getRandomValues(new Uint8Array(KEY_BYTES));
|
|
3670
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
3671
|
+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["encrypt"]);
|
|
3672
|
+
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv, tagLength: 128 }, key, bytes);
|
|
3673
|
+
const wrappedKey = nip44Encrypt(bytesToBase64(rawKey), senderSk, recipientPubkey);
|
|
3674
|
+
return { ciphertext: new Uint8Array(ct), wrappedKey, iv: bytesToBase64(iv) };
|
|
3675
|
+
}
|
|
3676
|
+
async function decryptBytesFromSender(ciphertext, wrappedKey, iv, receiverSk, senderPubkey) {
|
|
3677
|
+
const rawKey = base64ToBytes(nip44Decrypt(wrappedKey, receiverSk, senderPubkey));
|
|
3678
|
+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["decrypt"]);
|
|
3679
|
+
const pt = await crypto.subtle.decrypt(
|
|
3680
|
+
{ name: "AES-GCM", iv: base64ToBytes(iv), tagLength: 128 },
|
|
3681
|
+
key,
|
|
3682
|
+
ciphertext
|
|
3683
|
+
);
|
|
3684
|
+
return new Uint8Array(pt);
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
// src/transport/blossom-transport.ts
|
|
3688
|
+
var AES_GCM_TAG_BYTES = 16;
|
|
3689
|
+
function createBlossomTransport(opts) {
|
|
3690
|
+
const { blossom, identity } = opts;
|
|
3691
|
+
return {
|
|
3692
|
+
async seedBytes({ bytes, recipientPubkey }) {
|
|
3693
|
+
if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
3694
|
+
throw new Error(
|
|
3695
|
+
`File too large for encrypted Blossom: ${bytes.byteLength} bytes exceeds ${LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES}.`
|
|
3696
|
+
);
|
|
3697
|
+
}
|
|
3698
|
+
const { ciphertext, wrappedKey, iv } = await encryptBytesForRecipient(
|
|
3699
|
+
bytes,
|
|
3700
|
+
identity.secretKey,
|
|
3701
|
+
recipientPubkey
|
|
3702
|
+
);
|
|
3703
|
+
const blob = new Blob([ciphertext], { type: "application/octet-stream" });
|
|
3704
|
+
const descriptor = await blossom.upload(identity, blob);
|
|
3705
|
+
if (descriptor.provider !== "blossom") {
|
|
3706
|
+
throw new Error("Blossom upload fell back to a non-content-addressed provider.");
|
|
3707
|
+
}
|
|
3708
|
+
return {
|
|
3709
|
+
kind: "blossom",
|
|
3710
|
+
url: descriptor.url,
|
|
3711
|
+
sha256: descriptor.sha256,
|
|
3712
|
+
enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
|
|
3713
|
+
};
|
|
3714
|
+
},
|
|
3715
|
+
async fetchToBytes({ transport, senderPubkey, maxBytes }) {
|
|
3716
|
+
const plaintextCap = maxBytes ?? LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES;
|
|
3717
|
+
const ciphertext = await blossom.download(transport.url, {
|
|
3718
|
+
maxBytes: plaintextCap + AES_GCM_TAG_BYTES,
|
|
3719
|
+
expectedSha256: transport.sha256
|
|
3720
|
+
});
|
|
3721
|
+
return decryptBytesFromSender(
|
|
3722
|
+
ciphertext,
|
|
3723
|
+
transport.enc.key,
|
|
3724
|
+
transport.enc.iv,
|
|
3725
|
+
identity.secretKey,
|
|
3726
|
+
senderPubkey
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
// src/transport/file-jobs.ts
|
|
3733
|
+
async function sha256Hex(bytes) {
|
|
3734
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
3735
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3736
|
+
}
|
|
3737
|
+
var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3738
|
+
".exe",
|
|
3739
|
+
".dll",
|
|
3740
|
+
".bat",
|
|
3741
|
+
".cmd",
|
|
3742
|
+
".com",
|
|
3743
|
+
".msi",
|
|
3744
|
+
".sh",
|
|
3745
|
+
".app",
|
|
3746
|
+
".scr",
|
|
3747
|
+
".ps1"
|
|
3748
|
+
]);
|
|
3749
|
+
var EXECUTABLE_MIMES = /* @__PURE__ */ new Set([
|
|
3750
|
+
"application/x-msdownload",
|
|
3751
|
+
"application/x-msdos-program",
|
|
3752
|
+
"application/x-sh",
|
|
3753
|
+
"application/x-executable",
|
|
3754
|
+
"application/vnd.microsoft.portable-executable",
|
|
3755
|
+
"application/x-mach-binary"
|
|
3756
|
+
]);
|
|
3757
|
+
function looksExecutable(name, type) {
|
|
3758
|
+
const dot = name.lastIndexOf(".");
|
|
3759
|
+
const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
|
3760
|
+
return EXECUTABLE_EXTENSIONS.has(ext) || EXECUTABLE_MIMES.has(type);
|
|
3761
|
+
}
|
|
3762
|
+
async function prepareEncryptedFileInput(args) {
|
|
3763
|
+
const { file, providerPubkey, identity, blossom } = args;
|
|
3764
|
+
const name = file.name ?? "upload";
|
|
3765
|
+
if (looksExecutable(name, file.type)) {
|
|
3766
|
+
throw new Error("Refusing to upload an executable file.");
|
|
3767
|
+
}
|
|
3768
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
3769
|
+
if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
3770
|
+
throw new Error(
|
|
3771
|
+
`File too large for the encrypted-Blossom transport: ${bytes.byteLength} bytes (max ${LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES}).`
|
|
3772
|
+
);
|
|
3773
|
+
}
|
|
3774
|
+
const { ciphertext, wrappedKey, iv } = await encryptBytesForRecipient(
|
|
3775
|
+
bytes,
|
|
3776
|
+
identity.secretKey,
|
|
3777
|
+
providerPubkey
|
|
3778
|
+
);
|
|
3779
|
+
const sha256 = await sha256Hex(ciphertext);
|
|
3780
|
+
const member = {
|
|
3781
|
+
kind: "blossom",
|
|
3782
|
+
url: blossom.contentUrl(sha256),
|
|
3783
|
+
sha256,
|
|
3784
|
+
enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
|
|
3785
|
+
};
|
|
3786
|
+
const attachment = {
|
|
3787
|
+
name,
|
|
3788
|
+
size: bytes.byteLength,
|
|
3789
|
+
mime: file.type || "application/octet-stream",
|
|
3790
|
+
transports: [member]
|
|
3791
|
+
};
|
|
3792
|
+
const upload = async () => {
|
|
3793
|
+
const descriptor = await blossom.upload(
|
|
3794
|
+
identity,
|
|
3795
|
+
new Blob([ciphertext], { type: "application/octet-stream" })
|
|
3796
|
+
);
|
|
3797
|
+
if (descriptor.provider !== "blossom") {
|
|
3798
|
+
throw new Error("Blossom upload fell back to a non-content-addressed provider.");
|
|
3799
|
+
}
|
|
3800
|
+
if (descriptor.sha256 !== sha256 || descriptor.url !== member.url) {
|
|
3801
|
+
throw new Error(
|
|
3802
|
+
`Blossom upload descriptor mismatch (expected ${member.url} / ${sha256}, got ${descriptor.url} / ${descriptor.sha256}).`
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
return { attachment, upload };
|
|
3807
|
+
}
|
|
3808
|
+
async function buildEncryptedFileInput(args) {
|
|
3809
|
+
const prepared = await prepareEncryptedFileInput(args);
|
|
3810
|
+
await prepared.upload();
|
|
3811
|
+
return prepared.attachment;
|
|
3812
|
+
}
|
|
3813
|
+
async function fetchEncryptedFileOutput(args) {
|
|
3814
|
+
const { attachment, providerPubkey, identity, blossom, maxBytes } = args;
|
|
3815
|
+
const member = attachment.transports.find(
|
|
3816
|
+
(t) => t.kind === "blossom"
|
|
3817
|
+
);
|
|
3818
|
+
if (member === void 0) {
|
|
3819
|
+
throw new Error("Attachment has no blossom transport.");
|
|
3820
|
+
}
|
|
3821
|
+
const transport = createBlossomTransport({ blossom, identity });
|
|
3822
|
+
const bytes = await transport.fetchToBytes({
|
|
3823
|
+
transport: member,
|
|
3824
|
+
senderPubkey: providerPubkey,
|
|
3825
|
+
maxBytes
|
|
3826
|
+
});
|
|
3827
|
+
return { bytes, name: attachment.name, mime: attachment.mime };
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3483
3830
|
// src/services/jobErrors.ts
|
|
3484
3831
|
var AGENT_UNAVAILABLE_MARKERS = [
|
|
3485
3832
|
"agent temporarily unavailable",
|
|
@@ -4051,6 +4398,6 @@ function makeCensor() {
|
|
|
4051
4398
|
};
|
|
4052
4399
|
}
|
|
4053
4400
|
|
|
4054
|
-
export { BlossomService, BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, JobWaitTimeoutError, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_LONG_FORM_ARTICLE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, PaymentRequestSchema, PingService, PoliciesService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, aggregateNetworkStats, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, decodeJobPayload, encodeJobPayload, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
|
|
4401
|
+
export { ACCEPT_TRANSPORTS_TAG, BlossomService, BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, JobWaitTimeoutError, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_LONG_FORM_ARTICLE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, PaymentRequestSchema, PingService, PoliciesService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, aggregateNetworkStats, assertExpiry, assertLamports, assetByKey, assetKey, attachmentsOf, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatAssetAmount, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, prepareEncryptedFileInput, readAcceptedTransports, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
|
|
4055
4402
|
//# sourceMappingURL=index.js.map
|
|
4056
4403
|
//# sourceMappingURL=index.js.map
|