@elisym/sdk 0.25.1 → 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/index.js CHANGED
@@ -73,6 +73,11 @@ 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
83
  BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5,
@@ -1194,6 +1199,17 @@ var BlossomService = class {
1194
1199
  this.serverUrl = serverUrl;
1195
1200
  this.fallback = fallback;
1196
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
+ }
1197
1213
  /**
1198
1214
  * Upload a file to the Blossom server, returning its descriptor. On any failure, falls
1199
1215
  * back to the configured uploader (if any) and returns a normalized descriptor with
@@ -1432,6 +1448,9 @@ function parseCapabilityEvent(event, network) {
1432
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)) {
1433
1449
  return null;
1434
1450
  }
1451
+ if (card.inputText !== void 0 && !["required", "optional", "none"].includes(card.inputText)) {
1452
+ card.inputText = void 0;
1453
+ }
1435
1454
  if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
1436
1455
  return null;
1437
1456
  }
@@ -2062,7 +2081,11 @@ var FileAttachmentSchema = z.object({
2062
2081
  var JobPayloadEnvelopeSchema = z.object({
2063
2082
  v: z.literal(ENVELOPE_VERSION),
2064
2083
  text: z.string().optional(),
2065
- attachment: FileAttachmentSchema.optional()
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()
2066
2089
  });
2067
2090
  var ACCEPT_TRANSPORTS_TAG = "accept";
2068
2091
  var KNOWN_TRANSPORT_KINDS = ["iroh", "blossom"];
@@ -2095,12 +2118,21 @@ function readAcceptedTransports(tags) {
2095
2118
  }
2096
2119
  return out.length > 0 ? out : void 0;
2097
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
+ }
2098
2127
  function encodeJobPayload(payload) {
2099
2128
  const envelope = { v: ENVELOPE_VERSION };
2100
2129
  if (payload.text !== void 0) {
2101
2130
  envelope.text = payload.text;
2102
2131
  }
2103
- if (payload.attachment !== void 0) {
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) {
2104
2136
  envelope.attachment = payload.attachment;
2105
2137
  }
2106
2138
  return JSON.stringify(envelope);
@@ -2128,7 +2160,11 @@ function decodeJobPayload(content) {
2128
2160
  `Invalid elisym job payload (v=${JSON.stringify(version)}): ${result.error.message}`
2129
2161
  );
2130
2162
  }
2131
- return { text: result.data.text, attachment: result.data.attachment };
2163
+ return {
2164
+ text: result.data.text,
2165
+ attachment: result.data.attachment,
2166
+ attachments: result.data.attachments
2167
+ };
2132
2168
  }
2133
2169
 
2134
2170
  // src/services/marketplace.ts
@@ -2293,7 +2329,7 @@ var MarketplaceService = class {
2293
2329
  }
2294
2330
  resultDelivered = true;
2295
2331
  try {
2296
- cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment);
2332
+ cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment, attachmentsOf(decoded));
2297
2333
  } catch {
2298
2334
  } finally {
2299
2335
  done();
@@ -2457,8 +2493,8 @@ var MarketplaceService = class {
2457
2493
  );
2458
2494
  }
2459
2495
  /** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
2460
- async submitJobResult(identity, requestEvent, content, amount, attachment) {
2461
- const hasAttachment = attachment !== void 0;
2496
+ async submitJobResult(identity, requestEvent, content, amount, attachments) {
2497
+ const hasAttachment = attachments !== void 0 && attachments.length > 0;
2462
2498
  if (!content && !hasAttachment) {
2463
2499
  throw new Error("Job result content must not be empty.");
2464
2500
  }
@@ -2472,7 +2508,7 @@ var MarketplaceService = class {
2472
2508
  );
2473
2509
  }
2474
2510
  const shouldEncrypt = isEncrypted(requestEvent);
2475
- const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachment }) : content;
2511
+ const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachments }) : content;
2476
2512
  if (shouldEncrypt) {
2477
2513
  const payloadBytes = utf8ByteLength(payload);
2478
2514
  if (payloadBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
@@ -2513,11 +2549,11 @@ var MarketplaceService = class {
2513
2549
  * With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
2514
2550
  * Jitter: 0.5x-1.0x of calculated delay.
2515
2551
  */
2516
- async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachment) {
2552
+ async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachments) {
2517
2553
  const attempts = Math.max(1, maxAttempts);
2518
2554
  for (let attempt = 0; attempt < attempts; attempt++) {
2519
2555
  try {
2520
- return await this.submitJobResult(identity, requestEvent, content, amount, attachment);
2556
+ return await this.submitJobResult(identity, requestEvent, content, amount, attachments);
2521
2557
  } catch (e) {
2522
2558
  if (attempt >= attempts - 1) {
2523
2559
  throw e;
@@ -3694,6 +3730,10 @@ function createBlossomTransport(opts) {
3694
3730
  }
3695
3731
 
3696
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
+ }
3697
3737
  var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
3698
3738
  ".exe",
3699
3739
  ".dll",
@@ -3719,21 +3759,56 @@ function looksExecutable(name, type) {
3719
3759
  const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
3720
3760
  return EXECUTABLE_EXTENSIONS.has(ext) || EXECUTABLE_MIMES.has(type);
3721
3761
  }
3722
- async function buildEncryptedFileInput(args) {
3762
+ async function prepareEncryptedFileInput(args) {
3723
3763
  const { file, providerPubkey, identity, blossom } = args;
3724
3764
  const name = file.name ?? "upload";
3725
3765
  if (looksExecutable(name, file.type)) {
3726
3766
  throw new Error("Refusing to upload an executable file.");
3727
3767
  }
3728
- const transport = createBlossomTransport({ blossom, identity });
3729
3768
  const bytes = new Uint8Array(await file.arrayBuffer());
3730
- const member = await transport.seedBytes({ bytes, recipientPubkey: providerPubkey });
3731
- return {
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 = {
3732
3787
  name,
3733
3788
  size: bytes.byteLength,
3734
3789
  mime: file.type || "application/octet-stream",
3735
3790
  transports: [member]
3736
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;
3737
3812
  }
3738
3813
  async function fetchEncryptedFileOutput(args) {
3739
3814
  const { attachment, providerPubkey, identity, blossom, maxBytes } = args;
@@ -4323,6 +4398,6 @@ function makeCensor() {
4323
4398
  };
4324
4399
  }
4325
4400
 
4326
- 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, 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, readAcceptedTransports, 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 };
4327
4402
  //# sourceMappingURL=index.js.map
4328
4403
  //# sourceMappingURL=index.js.map