@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/index.d.cts CHANGED
@@ -227,6 +227,70 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
227
227
  transports: unknown[];
228
228
  seedingExpiresAt?: number | undefined;
229
229
  }>>;
230
+ attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
231
+ /** Display name only. Never used to derive a filesystem path (callers sanitize). */
232
+ name: z.ZodString;
233
+ /** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
234
+ size: z.ZodNumber;
235
+ mime: z.ZodString;
236
+ /**
237
+ * Ordered by sender preference; at least one KNOWN transport. Parsed leniently: unknown
238
+ * transport `kind`s are dropped (not rejected) so adding a new transport never makes an older
239
+ * decoder throw away the whole envelope - it just ignores the kinds it doesn't know and uses
240
+ * the ones it does. At least one known transport must survive, else the attachment is invalid.
241
+ */
242
+ transports: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodUnknown, "many">, ({
243
+ kind: "iroh";
244
+ ticket: string;
245
+ } | {
246
+ kind: "blossom";
247
+ url: string;
248
+ sha256: string;
249
+ enc: {
250
+ alg: "AES-256-GCM";
251
+ iv: string;
252
+ key: string;
253
+ };
254
+ })[], unknown[]>, ({
255
+ kind: "iroh";
256
+ ticket: string;
257
+ } | {
258
+ kind: "blossom";
259
+ url: string;
260
+ sha256: string;
261
+ enc: {
262
+ alg: "AES-256-GCM";
263
+ iv: string;
264
+ key: string;
265
+ };
266
+ })[], unknown[]>;
267
+ /** Optional provider hint (unix seconds) for when seeding may stop. */
268
+ seedingExpiresAt: z.ZodOptional<z.ZodNumber>;
269
+ }, "strip", z.ZodTypeAny, {
270
+ name: string;
271
+ size: number;
272
+ mime: string;
273
+ transports: ({
274
+ kind: "iroh";
275
+ ticket: string;
276
+ } | {
277
+ kind: "blossom";
278
+ url: string;
279
+ sha256: string;
280
+ enc: {
281
+ alg: "AES-256-GCM";
282
+ iv: string;
283
+ key: string;
284
+ };
285
+ })[];
286
+ seedingExpiresAt?: number | undefined;
287
+ }, {
288
+ name: string;
289
+ size: number;
290
+ mime: string;
291
+ transports: unknown[];
292
+ seedingExpiresAt?: number | undefined;
293
+ }>, "many">>;
230
294
  }, "strip", z.ZodTypeAny, {
231
295
  v: "elisym-job/1";
232
296
  text?: string | undefined;
@@ -249,6 +313,25 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
249
313
  })[];
250
314
  seedingExpiresAt?: number | undefined;
251
315
  } | undefined;
316
+ attachments?: {
317
+ name: string;
318
+ size: number;
319
+ mime: string;
320
+ transports: ({
321
+ kind: "iroh";
322
+ ticket: string;
323
+ } | {
324
+ kind: "blossom";
325
+ url: string;
326
+ sha256: string;
327
+ enc: {
328
+ alg: "AES-256-GCM";
329
+ iv: string;
330
+ key: string;
331
+ };
332
+ })[];
333
+ seedingExpiresAt?: number | undefined;
334
+ }[] | undefined;
252
335
  }, {
253
336
  v: "elisym-job/1";
254
337
  text?: string | undefined;
@@ -259,6 +342,13 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
259
342
  transports: unknown[];
260
343
  seedingExpiresAt?: number | undefined;
261
344
  } | undefined;
345
+ attachments?: {
346
+ name: string;
347
+ size: number;
348
+ mime: string;
349
+ transports: unknown[];
350
+ seedingExpiresAt?: number | undefined;
351
+ }[] | undefined;
262
352
  }>;
263
353
  type FileTransport = z.infer<typeof FileTransportSchema>;
264
354
  type FileAttachment = z.infer<typeof FileAttachmentSchema>;
@@ -279,11 +369,20 @@ declare function buildAcceptTransportsTag(kinds: TransportKind[]): string[];
279
369
  * so this never strands a job.
280
370
  */
281
371
  declare function readAcceptedTransports(tags: string[][]): TransportKind[] | undefined;
282
- /** Decoded job payload: a free-text note and/or a file attachment. */
372
+ /** Decoded job payload: a free-text note and/or file attachment(s). */
283
373
  interface DecodedJobPayload {
284
374
  text?: string;
375
+ /** Legacy single attachment (also mirrors `attachments[0]`). */
285
376
  attachment?: FileAttachment;
377
+ /** All attachments when a job carries multiple files. */
378
+ attachments?: FileAttachment[];
286
379
  }
380
+ /**
381
+ * Normalize a decoded payload to the full attachment list, treating the legacy
382
+ * single `attachment` as a 1-element list. Use this everywhere instead of reading
383
+ * `.attachment`/`.attachments` directly, so single- and multi-file are uniform.
384
+ */
385
+ declare function attachmentsOf(decoded: DecodedJobPayload): FileAttachment[];
287
386
  /**
288
387
  * Serialize a job payload into the envelope string that goes (encrypted) into a
289
388
  * Nostr event's `content`. Used only when an attachment is present; plain-text
@@ -325,6 +424,13 @@ interface CapabilityCard {
325
424
  inputMime?: string;
326
425
  /** MIME of a file result the capability produces (from `output_mime`). */
327
426
  outputMime?: string;
427
+ /**
428
+ * Whether a file-input capability ALSO accepts a text prompt (from `input_text`):
429
+ * `'none'` = file only, `'optional'` = file + optional note, `'required'` = both.
430
+ * Discovery hint; the web app shows/hides its text box accordingly. Only meaningful
431
+ * with `inputMime`. Untrusted - gate on it, never render the raw value.
432
+ */
433
+ inputText?: 'required' | 'optional' | 'none';
328
434
  }
329
435
  /** Payment info embedded in capability card (legacy format for on-network events). */
330
436
  interface PaymentInfo {
@@ -457,11 +563,12 @@ interface JobUpdateCallbacks {
457
563
  onFeedback?: (status: string, amount?: number, paymentRequest?: string, senderPubkey?: string) => void;
458
564
  /**
459
565
  * Fired on a job result. `content` is the result text (for a file result, the
460
- * envelope's text note, or `''`); `attachment` is the file descriptor when the
461
- * result carries a file. The file is fetched separately (P2P via iroh), never
462
- * inlined here.
566
+ * envelope's text note, or `''`); `attachment` is the FIRST file descriptor
567
+ * (= `attachments[0]`, kept for back-compat); `attachments` is the full list for
568
+ * a multi-file result. Files are fetched separately (P2P via iroh / Blossom),
569
+ * never inlined here.
463
570
  */
464
- onResult?: (content: string, eventId: string, attachment?: FileAttachment) => void;
571
+ onResult?: (content: string, eventId: string, attachment?: FileAttachment, attachments?: FileAttachment[]) => void;
465
572
  onError?: (error: string) => void;
466
573
  /**
467
574
  * Fired when the result wait window expires without a result - a distinct,
@@ -676,6 +783,15 @@ declare class BlossomService {
676
783
  private serverUrl;
677
784
  private fallback?;
678
785
  constructor(serverUrl?: string, fallback?: BlossomUploadFallback | undefined);
786
+ /**
787
+ * The content-addressed GET URL for a blob, derivable from its sha256 BEFORE
788
+ * upload (BUD-01: `<serverUrl>/<sha256>`, no extension for our octet-stream
789
+ * ciphertext uploads - same form `delete` addresses by). Lets a caller build a
790
+ * complete attachment descriptor and defer the actual byte upload (the descriptor
791
+ * is submitted first, the bytes PUT later). `upload()` re-verifies the server
792
+ * returns this exact url.
793
+ */
794
+ contentUrl(sha256: string): string;
679
795
  /**
680
796
  * Upload a file to the Blossom server, returning its descriptor. On any failure, falls
681
797
  * back to the configured uploader (if any) and returns a normalized descriptor with
@@ -685,13 +801,22 @@ declare class BlossomService {
685
801
  /** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
686
802
  delete(identity: ElisymIdentity, sha256: string): Promise<void>;
687
803
  /**
688
- * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
689
- * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
804
+ * Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
805
+ * on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
806
+ * `expectedSha256` is given. Browser-safe.
807
+ *
808
+ * SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
809
+ * is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
810
+ * refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
811
+ * The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
812
+ * the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
813
+ * across Blossom servers would replace this single-origin pin with an explicit allowlist.
690
814
  */
691
815
  download(url: string, opts?: {
692
816
  maxBytes?: number;
693
817
  timeoutMs?: number;
694
818
  expectedSha256?: string;
819
+ signal?: AbortSignal;
695
820
  }): Promise<Uint8Array>;
696
821
  private uploadToBlossom;
697
822
  private authHeader;
@@ -895,14 +1020,14 @@ declare class MarketplaceService {
895
1020
  */
896
1021
  subscribeToJobRequests(identity: ElisymIdentity, kinds: number[], onRequest: (event: Event) => void): SubCloser;
897
1022
  /** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
898
- submitJobResult(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, attachment?: FileAttachment): Promise<string>;
1023
+ submitJobResult(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, attachments?: FileAttachment[]): Promise<string>;
899
1024
  /**
900
1025
  * Submit a job result with retry and exponential backoff.
901
1026
  * Retries on publish failures (e.g. relay disconnects).
902
1027
  * With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
903
1028
  * Jitter: 0.5x-1.0x of calculated delay.
904
1029
  */
905
- submitJobResultWithRetry(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, maxAttempts?: number, baseDelayMs?: number, attachment?: FileAttachment): Promise<string>;
1030
+ submitJobResultWithRetry(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, maxAttempts?: number, baseDelayMs?: number, attachments?: FileAttachment[]): Promise<string>;
906
1031
  /** Submit payment-required feedback with a payment request. */
907
1032
  submitPaymentRequiredFeedback(identity: ElisymIdentity, requestEvent: Event, amount: number, paymentRequestJson: string): Promise<void>;
908
1033
  /** Submit processing feedback to notify customer that work has started. */
@@ -1055,6 +1180,8 @@ interface BlossomBlobTransport {
1055
1180
  transport: BlossomTransport;
1056
1181
  senderPubkey: string;
1057
1182
  maxBytes?: number;
1183
+ /** Abort the in-flight download (e.g. job stop() / input-fetch budget). */
1184
+ signal?: AbortSignal;
1058
1185
  }): Promise<Uint8Array>;
1059
1186
  }
1060
1187
  declare function createBlossomTransport(opts: {
@@ -1063,17 +1190,29 @@ declare function createBlossomTransport(opts: {
1063
1190
  }): BlossomBlobTransport;
1064
1191
 
1065
1192
  /**
1066
- * Customer-side helpers to send/receive ENCRYPTED file jobs over Blossom with no iroh/Node dependency
1067
- * (browser-safe). These are the seams a web app calls.
1193
+ * Encrypt `file` to `providerPubkey` and build a complete `FileAttachment` WITHOUT uploading the bytes
1194
+ * yet. Because Blossom is content-addressed, the blob URL is derivable from the ciphertext sha256, so the
1195
+ * caller can submit the job request with this descriptor and DEFER the byte upload (via the returned
1196
+ * `upload()`) until the customer commits - e.g. after the provider quotes a price, so an unresponsive
1197
+ * provider never costs a wasted upload. TARGETED jobs only (a recipient pubkey is required to encrypt).
1068
1198
  *
1069
- * Encrypted-Blossom needs a recipient pubkey, so a file INPUT is only meaningful on a TARGETED job (a
1070
- * chosen provider) - hence `buildEncryptedFileInput` requires `providerPubkey`. Broadcast file inputs
1071
- * are not supported here (no recipient to encrypt to); those stay on iroh.
1199
+ * `upload()` PUTs the ciphertext and verifies the server returns the precomputed url/sha256 (the request
1200
+ * already carries them, so a mismatch must fail loudly - pre-commit - rather than 404 the provider).
1072
1201
  */
1073
-
1202
+ declare function prepareEncryptedFileInput(args: {
1203
+ file: Blob & {
1204
+ name?: string;
1205
+ };
1206
+ providerPubkey: string;
1207
+ identity: ElisymIdentity;
1208
+ blossom: BlossomService;
1209
+ }): Promise<{
1210
+ attachment: FileAttachment;
1211
+ upload: () => Promise<void>;
1212
+ }>;
1074
1213
  /**
1075
- * Encrypt `file` to `providerPubkey` and upload the ciphertext to Blossom, returning a `FileAttachment`
1076
- * with a single `blossom` transport. TARGETED jobs only (a recipient pubkey is required to encrypt).
1214
+ * Encrypt `file` to `providerPubkey` AND upload it immediately, returning the `FileAttachment`
1215
+ * (prepare + upload in one step). Use `prepareEncryptedFileInput` when you want to defer the upload.
1077
1216
  */
1078
1217
  declare function buildEncryptedFileInput(args: {
1079
1218
  file: Blob & {
@@ -1725,6 +1864,7 @@ declare const DEFAULTS: {
1725
1864
  readonly QUERY_MAX_CONCURRENCY: 6;
1726
1865
  readonly VERIFY_SIGNATURE_LIMIT: 25;
1727
1866
  readonly IROH_FETCH_TIMEOUT_MS: 300000;
1867
+ readonly IROH_SEED_TIMEOUT_MS: 120000;
1728
1868
  readonly BLOSSOM_UPLOAD_TIMEOUT_MS: 300000;
1729
1869
  readonly BLOSSOM_FETCH_TIMEOUT_MS: 300000;
1730
1870
  };
@@ -1756,4 +1896,4 @@ declare const LIMITS: {
1756
1896
  */
1757
1897
  declare function utf8ByteLength(value: string): number;
1758
1898
 
1759
- export { ACCEPT_TRANSPORTS_TAG, type Agent, type AgentPolicy, type AggregateNetworkStatsOptions, Asset, type BlobDescriptor, type BlossomBlobTransport, BlossomService, type BlossomUploadFallback, BoundedSet, type BuildTransactionOptions, type CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, type DecodedJobPayload, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, type ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type EncryptedBytes, type EstimatePriorityFeeOptions, type EstimateSolFeeOptions, type FileAttachment, type FileTransport, type GetProtocolConfigOptions, INPUT_REDACT_PATHS, type Job, type JobErrorKind, type JobPayloadEnvelope, type JobStatus, type JobSubscriptionOptions, type JobUpdateCallbacks, 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, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, type Network, type NetworkBaselineEstimate, type NetworkBaselineOptions, type NetworkStats, type NetworkStatsResult, NostrPool, type OnchainNetworkStats, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, type ParseOptions, type ParseResult, type ParsedPaymentRequest, type PaymentAssetRef, type PaymentInfo, type PaymentRequestData, PaymentRequestSchema, type PaymentStrategy, type PaymentValidationCode, type PaymentValidationError, type PingResult, PingService, PoliciesService, type PolicyInput, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, type QuickVerifyReason, type QuickVerifyResult, RELAYS, type RankKey, SECRET_REDACT_PATHS, type Signer, type SolFeeEstimate, SolanaPaymentStrategy, type SubCloser, type SubmitJobOptions, type TransportKind, type VerifyOptions, type VerifyResult, aggregateNetworkStats, assertExpiry, assertLamports, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, readAcceptedTransports, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
1899
+ export { ACCEPT_TRANSPORTS_TAG, type Agent, type AgentPolicy, type AggregateNetworkStatsOptions, Asset, type BlobDescriptor, type BlossomBlobTransport, BlossomService, type BlossomUploadFallback, BoundedSet, type BuildTransactionOptions, type CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, type DecodedJobPayload, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, type ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type EncryptedBytes, type EstimatePriorityFeeOptions, type EstimateSolFeeOptions, type FileAttachment, type FileTransport, type GetProtocolConfigOptions, INPUT_REDACT_PATHS, type Job, type JobErrorKind, type JobPayloadEnvelope, type JobStatus, type JobSubscriptionOptions, type JobUpdateCallbacks, 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, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, type Network, type NetworkBaselineEstimate, type NetworkBaselineOptions, type NetworkStats, type NetworkStatsResult, NostrPool, type OnchainNetworkStats, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, type ParseOptions, type ParseResult, type ParsedPaymentRequest, type PaymentAssetRef, type PaymentInfo, type PaymentRequestData, PaymentRequestSchema, type PaymentStrategy, type PaymentValidationCode, type PaymentValidationError, type PingResult, PingService, PoliciesService, type PolicyInput, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, type QuickVerifyReason, type QuickVerifyResult, RELAYS, type RankKey, SECRET_REDACT_PATHS, type Signer, type SolFeeEstimate, SolanaPaymentStrategy, type SubCloser, type SubmitJobOptions, type TransportKind, type VerifyOptions, type VerifyResult, aggregateNetworkStats, assertExpiry, assertLamports, attachmentsOf, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, prepareEncryptedFileInput, readAcceptedTransports, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
package/dist/index.d.ts CHANGED
@@ -227,6 +227,70 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
227
227
  transports: unknown[];
228
228
  seedingExpiresAt?: number | undefined;
229
229
  }>>;
230
+ attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
231
+ /** Display name only. Never used to derive a filesystem path (callers sanitize). */
232
+ name: z.ZodString;
233
+ /** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
234
+ size: z.ZodNumber;
235
+ mime: z.ZodString;
236
+ /**
237
+ * Ordered by sender preference; at least one KNOWN transport. Parsed leniently: unknown
238
+ * transport `kind`s are dropped (not rejected) so adding a new transport never makes an older
239
+ * decoder throw away the whole envelope - it just ignores the kinds it doesn't know and uses
240
+ * the ones it does. At least one known transport must survive, else the attachment is invalid.
241
+ */
242
+ transports: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodUnknown, "many">, ({
243
+ kind: "iroh";
244
+ ticket: string;
245
+ } | {
246
+ kind: "blossom";
247
+ url: string;
248
+ sha256: string;
249
+ enc: {
250
+ alg: "AES-256-GCM";
251
+ iv: string;
252
+ key: string;
253
+ };
254
+ })[], unknown[]>, ({
255
+ kind: "iroh";
256
+ ticket: string;
257
+ } | {
258
+ kind: "blossom";
259
+ url: string;
260
+ sha256: string;
261
+ enc: {
262
+ alg: "AES-256-GCM";
263
+ iv: string;
264
+ key: string;
265
+ };
266
+ })[], unknown[]>;
267
+ /** Optional provider hint (unix seconds) for when seeding may stop. */
268
+ seedingExpiresAt: z.ZodOptional<z.ZodNumber>;
269
+ }, "strip", z.ZodTypeAny, {
270
+ name: string;
271
+ size: number;
272
+ mime: string;
273
+ transports: ({
274
+ kind: "iroh";
275
+ ticket: string;
276
+ } | {
277
+ kind: "blossom";
278
+ url: string;
279
+ sha256: string;
280
+ enc: {
281
+ alg: "AES-256-GCM";
282
+ iv: string;
283
+ key: string;
284
+ };
285
+ })[];
286
+ seedingExpiresAt?: number | undefined;
287
+ }, {
288
+ name: string;
289
+ size: number;
290
+ mime: string;
291
+ transports: unknown[];
292
+ seedingExpiresAt?: number | undefined;
293
+ }>, "many">>;
230
294
  }, "strip", z.ZodTypeAny, {
231
295
  v: "elisym-job/1";
232
296
  text?: string | undefined;
@@ -249,6 +313,25 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
249
313
  })[];
250
314
  seedingExpiresAt?: number | undefined;
251
315
  } | undefined;
316
+ attachments?: {
317
+ name: string;
318
+ size: number;
319
+ mime: string;
320
+ transports: ({
321
+ kind: "iroh";
322
+ ticket: string;
323
+ } | {
324
+ kind: "blossom";
325
+ url: string;
326
+ sha256: string;
327
+ enc: {
328
+ alg: "AES-256-GCM";
329
+ iv: string;
330
+ key: string;
331
+ };
332
+ })[];
333
+ seedingExpiresAt?: number | undefined;
334
+ }[] | undefined;
252
335
  }, {
253
336
  v: "elisym-job/1";
254
337
  text?: string | undefined;
@@ -259,6 +342,13 @@ declare const JobPayloadEnvelopeSchema: z.ZodObject<{
259
342
  transports: unknown[];
260
343
  seedingExpiresAt?: number | undefined;
261
344
  } | undefined;
345
+ attachments?: {
346
+ name: string;
347
+ size: number;
348
+ mime: string;
349
+ transports: unknown[];
350
+ seedingExpiresAt?: number | undefined;
351
+ }[] | undefined;
262
352
  }>;
263
353
  type FileTransport = z.infer<typeof FileTransportSchema>;
264
354
  type FileAttachment = z.infer<typeof FileAttachmentSchema>;
@@ -279,11 +369,20 @@ declare function buildAcceptTransportsTag(kinds: TransportKind[]): string[];
279
369
  * so this never strands a job.
280
370
  */
281
371
  declare function readAcceptedTransports(tags: string[][]): TransportKind[] | undefined;
282
- /** Decoded job payload: a free-text note and/or a file attachment. */
372
+ /** Decoded job payload: a free-text note and/or file attachment(s). */
283
373
  interface DecodedJobPayload {
284
374
  text?: string;
375
+ /** Legacy single attachment (also mirrors `attachments[0]`). */
285
376
  attachment?: FileAttachment;
377
+ /** All attachments when a job carries multiple files. */
378
+ attachments?: FileAttachment[];
286
379
  }
380
+ /**
381
+ * Normalize a decoded payload to the full attachment list, treating the legacy
382
+ * single `attachment` as a 1-element list. Use this everywhere instead of reading
383
+ * `.attachment`/`.attachments` directly, so single- and multi-file are uniform.
384
+ */
385
+ declare function attachmentsOf(decoded: DecodedJobPayload): FileAttachment[];
287
386
  /**
288
387
  * Serialize a job payload into the envelope string that goes (encrypted) into a
289
388
  * Nostr event's `content`. Used only when an attachment is present; plain-text
@@ -325,6 +424,13 @@ interface CapabilityCard {
325
424
  inputMime?: string;
326
425
  /** MIME of a file result the capability produces (from `output_mime`). */
327
426
  outputMime?: string;
427
+ /**
428
+ * Whether a file-input capability ALSO accepts a text prompt (from `input_text`):
429
+ * `'none'` = file only, `'optional'` = file + optional note, `'required'` = both.
430
+ * Discovery hint; the web app shows/hides its text box accordingly. Only meaningful
431
+ * with `inputMime`. Untrusted - gate on it, never render the raw value.
432
+ */
433
+ inputText?: 'required' | 'optional' | 'none';
328
434
  }
329
435
  /** Payment info embedded in capability card (legacy format for on-network events). */
330
436
  interface PaymentInfo {
@@ -457,11 +563,12 @@ interface JobUpdateCallbacks {
457
563
  onFeedback?: (status: string, amount?: number, paymentRequest?: string, senderPubkey?: string) => void;
458
564
  /**
459
565
  * Fired on a job result. `content` is the result text (for a file result, the
460
- * envelope's text note, or `''`); `attachment` is the file descriptor when the
461
- * result carries a file. The file is fetched separately (P2P via iroh), never
462
- * inlined here.
566
+ * envelope's text note, or `''`); `attachment` is the FIRST file descriptor
567
+ * (= `attachments[0]`, kept for back-compat); `attachments` is the full list for
568
+ * a multi-file result. Files are fetched separately (P2P via iroh / Blossom),
569
+ * never inlined here.
463
570
  */
464
- onResult?: (content: string, eventId: string, attachment?: FileAttachment) => void;
571
+ onResult?: (content: string, eventId: string, attachment?: FileAttachment, attachments?: FileAttachment[]) => void;
465
572
  onError?: (error: string) => void;
466
573
  /**
467
574
  * Fired when the result wait window expires without a result - a distinct,
@@ -676,6 +783,15 @@ declare class BlossomService {
676
783
  private serverUrl;
677
784
  private fallback?;
678
785
  constructor(serverUrl?: string, fallback?: BlossomUploadFallback | undefined);
786
+ /**
787
+ * The content-addressed GET URL for a blob, derivable from its sha256 BEFORE
788
+ * upload (BUD-01: `<serverUrl>/<sha256>`, no extension for our octet-stream
789
+ * ciphertext uploads - same form `delete` addresses by). Lets a caller build a
790
+ * complete attachment descriptor and defer the actual byte upload (the descriptor
791
+ * is submitted first, the bytes PUT later). `upload()` re-verifies the server
792
+ * returns this exact url.
793
+ */
794
+ contentUrl(sha256: string): string;
679
795
  /**
680
796
  * Upload a file to the Blossom server, returning its descriptor. On any failure, falls
681
797
  * back to the configured uploader (if any) and returns a normalized descriptor with
@@ -685,13 +801,22 @@ declare class BlossomService {
685
801
  /** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
686
802
  delete(identity: ElisymIdentity, sha256: string): Promise<void>;
687
803
  /**
688
- * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
689
- * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
804
+ * Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
805
+ * on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
806
+ * `expectedSha256` is given. Browser-safe.
807
+ *
808
+ * SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
809
+ * is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
810
+ * refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
811
+ * The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
812
+ * the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
813
+ * across Blossom servers would replace this single-origin pin with an explicit allowlist.
690
814
  */
691
815
  download(url: string, opts?: {
692
816
  maxBytes?: number;
693
817
  timeoutMs?: number;
694
818
  expectedSha256?: string;
819
+ signal?: AbortSignal;
695
820
  }): Promise<Uint8Array>;
696
821
  private uploadToBlossom;
697
822
  private authHeader;
@@ -895,14 +1020,14 @@ declare class MarketplaceService {
895
1020
  */
896
1021
  subscribeToJobRequests(identity: ElisymIdentity, kinds: number[], onRequest: (event: Event) => void): SubCloser;
897
1022
  /** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
898
- submitJobResult(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, attachment?: FileAttachment): Promise<string>;
1023
+ submitJobResult(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, attachments?: FileAttachment[]): Promise<string>;
899
1024
  /**
900
1025
  * Submit a job result with retry and exponential backoff.
901
1026
  * Retries on publish failures (e.g. relay disconnects).
902
1027
  * With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
903
1028
  * Jitter: 0.5x-1.0x of calculated delay.
904
1029
  */
905
- submitJobResultWithRetry(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, maxAttempts?: number, baseDelayMs?: number, attachment?: FileAttachment): Promise<string>;
1030
+ submitJobResultWithRetry(identity: ElisymIdentity, requestEvent: Event, content: string, amount?: number, maxAttempts?: number, baseDelayMs?: number, attachments?: FileAttachment[]): Promise<string>;
906
1031
  /** Submit payment-required feedback with a payment request. */
907
1032
  submitPaymentRequiredFeedback(identity: ElisymIdentity, requestEvent: Event, amount: number, paymentRequestJson: string): Promise<void>;
908
1033
  /** Submit processing feedback to notify customer that work has started. */
@@ -1055,6 +1180,8 @@ interface BlossomBlobTransport {
1055
1180
  transport: BlossomTransport;
1056
1181
  senderPubkey: string;
1057
1182
  maxBytes?: number;
1183
+ /** Abort the in-flight download (e.g. job stop() / input-fetch budget). */
1184
+ signal?: AbortSignal;
1058
1185
  }): Promise<Uint8Array>;
1059
1186
  }
1060
1187
  declare function createBlossomTransport(opts: {
@@ -1063,17 +1190,29 @@ declare function createBlossomTransport(opts: {
1063
1190
  }): BlossomBlobTransport;
1064
1191
 
1065
1192
  /**
1066
- * Customer-side helpers to send/receive ENCRYPTED file jobs over Blossom with no iroh/Node dependency
1067
- * (browser-safe). These are the seams a web app calls.
1193
+ * Encrypt `file` to `providerPubkey` and build a complete `FileAttachment` WITHOUT uploading the bytes
1194
+ * yet. Because Blossom is content-addressed, the blob URL is derivable from the ciphertext sha256, so the
1195
+ * caller can submit the job request with this descriptor and DEFER the byte upload (via the returned
1196
+ * `upload()`) until the customer commits - e.g. after the provider quotes a price, so an unresponsive
1197
+ * provider never costs a wasted upload. TARGETED jobs only (a recipient pubkey is required to encrypt).
1068
1198
  *
1069
- * Encrypted-Blossom needs a recipient pubkey, so a file INPUT is only meaningful on a TARGETED job (a
1070
- * chosen provider) - hence `buildEncryptedFileInput` requires `providerPubkey`. Broadcast file inputs
1071
- * are not supported here (no recipient to encrypt to); those stay on iroh.
1199
+ * `upload()` PUTs the ciphertext and verifies the server returns the precomputed url/sha256 (the request
1200
+ * already carries them, so a mismatch must fail loudly - pre-commit - rather than 404 the provider).
1072
1201
  */
1073
-
1202
+ declare function prepareEncryptedFileInput(args: {
1203
+ file: Blob & {
1204
+ name?: string;
1205
+ };
1206
+ providerPubkey: string;
1207
+ identity: ElisymIdentity;
1208
+ blossom: BlossomService;
1209
+ }): Promise<{
1210
+ attachment: FileAttachment;
1211
+ upload: () => Promise<void>;
1212
+ }>;
1074
1213
  /**
1075
- * Encrypt `file` to `providerPubkey` and upload the ciphertext to Blossom, returning a `FileAttachment`
1076
- * with a single `blossom` transport. TARGETED jobs only (a recipient pubkey is required to encrypt).
1214
+ * Encrypt `file` to `providerPubkey` AND upload it immediately, returning the `FileAttachment`
1215
+ * (prepare + upload in one step). Use `prepareEncryptedFileInput` when you want to defer the upload.
1077
1216
  */
1078
1217
  declare function buildEncryptedFileInput(args: {
1079
1218
  file: Blob & {
@@ -1725,6 +1864,7 @@ declare const DEFAULTS: {
1725
1864
  readonly QUERY_MAX_CONCURRENCY: 6;
1726
1865
  readonly VERIFY_SIGNATURE_LIMIT: 25;
1727
1866
  readonly IROH_FETCH_TIMEOUT_MS: 300000;
1867
+ readonly IROH_SEED_TIMEOUT_MS: 120000;
1728
1868
  readonly BLOSSOM_UPLOAD_TIMEOUT_MS: 300000;
1729
1869
  readonly BLOSSOM_FETCH_TIMEOUT_MS: 300000;
1730
1870
  };
@@ -1756,4 +1896,4 @@ declare const LIMITS: {
1756
1896
  */
1757
1897
  declare function utf8ByteLength(value: string): number;
1758
1898
 
1759
- export { ACCEPT_TRANSPORTS_TAG, type Agent, type AgentPolicy, type AggregateNetworkStatsOptions, Asset, type BlobDescriptor, type BlossomBlobTransport, BlossomService, type BlossomUploadFallback, BoundedSet, type BuildTransactionOptions, type CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, type DecodedJobPayload, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, type ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type EncryptedBytes, type EstimatePriorityFeeOptions, type EstimateSolFeeOptions, type FileAttachment, type FileTransport, type GetProtocolConfigOptions, INPUT_REDACT_PATHS, type Job, type JobErrorKind, type JobPayloadEnvelope, type JobStatus, type JobSubscriptionOptions, type JobUpdateCallbacks, 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, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, type Network, type NetworkBaselineEstimate, type NetworkBaselineOptions, type NetworkStats, type NetworkStatsResult, NostrPool, type OnchainNetworkStats, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, type ParseOptions, type ParseResult, type ParsedPaymentRequest, type PaymentAssetRef, type PaymentInfo, type PaymentRequestData, PaymentRequestSchema, type PaymentStrategy, type PaymentValidationCode, type PaymentValidationError, type PingResult, PingService, PoliciesService, type PolicyInput, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, type QuickVerifyReason, type QuickVerifyResult, RELAYS, type RankKey, SECRET_REDACT_PATHS, type Signer, type SolFeeEstimate, SolanaPaymentStrategy, type SubCloser, type SubmitJobOptions, type TransportKind, type VerifyOptions, type VerifyResult, aggregateNetworkStats, assertExpiry, assertLamports, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, readAcceptedTransports, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
1899
+ export { ACCEPT_TRANSPORTS_TAG, type Agent, type AgentPolicy, type AggregateNetworkStatsOptions, Asset, type BlobDescriptor, type BlossomBlobTransport, BlossomService, type BlossomUploadFallback, BoundedSet, type BuildTransactionOptions, type CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, type DecodedJobPayload, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, type ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type EncryptedBytes, type EstimatePriorityFeeOptions, type EstimateSolFeeOptions, type FileAttachment, type FileTransport, type GetProtocolConfigOptions, INPUT_REDACT_PATHS, type Job, type JobErrorKind, type JobPayloadEnvelope, type JobStatus, type JobSubscriptionOptions, type JobUpdateCallbacks, 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, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, type Network, type NetworkBaselineEstimate, type NetworkBaselineOptions, type NetworkStats, type NetworkStatsResult, NostrPool, type OnchainNetworkStats, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, type ParseOptions, type ParseResult, type ParsedPaymentRequest, type PaymentAssetRef, type PaymentInfo, type PaymentRequestData, PaymentRequestSchema, type PaymentStrategy, type PaymentValidationCode, type PaymentValidationError, type PingResult, PingService, PoliciesService, type PolicyInput, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, type QuickVerifyReason, type QuickVerifyResult, RELAYS, type RankKey, SECRET_REDACT_PATHS, type Signer, type SolFeeEstimate, SolanaPaymentStrategy, type SubCloser, type SubmitJobOptions, type TransportKind, type VerifyOptions, type VerifyResult, aggregateNetworkStats, assertExpiry, assertLamports, attachmentsOf, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, prepareEncryptedFileInput, readAcceptedTransports, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };