@elisym/sdk 0.23.0 → 0.24.1

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
@@ -68,10 +68,35 @@ var DEFAULTS = {
68
68
  RESULT_RETRY_COUNT: 3,
69
69
  RESULT_RETRY_BASE_MS: 1e3,
70
70
  QUERY_MAX_CONCURRENCY: 6,
71
- VERIFY_SIGNATURE_LIMIT: 25
71
+ VERIFY_SIGNATURE_LIMIT: 25,
72
+ // Default ceiling for a single iroh file transfer (seed/fetch). A tunable
73
+ // default, not a protocol constant - the transfer is resumable and its own
74
+ // budget, decoupled from the result-wait window.
75
+ IROH_FETCH_TIMEOUT_MS: 3e5
72
76
  };
73
77
  var LIMITS = {
74
78
  MAX_INPUT_LENGTH: 1e5,
79
+ // NIP-44 v2 hard cap on encrypted plaintext: the pad() length prefix is a u16,
80
+ // so the plaintext can be at most 65_535 BYTES (not chars). Encrypting anything
81
+ // larger throws `invalid plaintext size` inside nip44Encrypt. This is the binding
82
+ // limit for TARGETED (encrypted) jobs - lower than every relay's NIP-11 cap.
83
+ NIP44_MAX_PLAINTEXT_BYTES: 65535,
84
+ // Spill threshold for encrypted content: above this many UTF-8 bytes, callers
85
+ // route the text through iroh out-of-band instead of inlining it. A non-spilled
86
+ // job is encrypted RAW (no envelope - see marketplace.submitJobRequest), so a
87
+ // 60_000-byte input is a 60_000-byte plaintext; the ~5.5KB gap under the hard cap
88
+ // is plain slack, and NIP44_MAX_PLAINTEXT_BYTES is the SDK backstop.
89
+ MAX_ENCRYPTED_INLINE_BYTES: 6e4,
90
+ // Ceiling above which a text/* attachment is NOT materialized back into a string
91
+ // (re-inlined into SkillInput.data) - the consumer gets a filePath / explicit
92
+ // fetch instead. Also bounds the in-memory git-diff buffer (memory-DoS guard).
93
+ MAX_REINLINE_TEXT_BYTES: 4194304,
94
+ // 4 MiB
95
+ // Hard safety cap on a single file transferred via iroh, enforced on the
96
+ // actual streamed bytes (never the sender-declared `size`). A tunable default;
97
+ // providers may lower it per deployment.
98
+ MAX_FILE_SIZE: 1073741824,
99
+ // 1 GiB
75
100
  MAX_TIMEOUT_SECS: 600,
76
101
  // Upper bound for execution budgets (`max_execution_secs` / `execution_timeout_secs`).
77
102
  // Distinct from MAX_TIMEOUT_SECS (the result-wait cap): execution budgets may be
@@ -89,6 +114,10 @@ var LIMITS = {
89
114
  MAX_POLICY_SUMMARY_LENGTH: 280,
90
115
  MAX_POLICY_VERSION_LENGTH: 32
91
116
  };
117
+ var UTF8_ENCODER = new TextEncoder();
118
+ function utf8ByteLength(value) {
119
+ return UTF8_ENCODER.encode(value).length;
120
+ }
92
121
  function getConfigDecoder() {
93
122
  return getStructDecoder([
94
123
  ["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
@@ -1212,6 +1241,12 @@ function parseCapabilityEvent(event, network) {
1212
1241
  if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
1213
1242
  return null;
1214
1243
  }
1244
+ if (card.payment && (card.payment.token !== void 0 && typeof card.payment.token !== "string" || card.payment.symbol !== void 0 && typeof card.payment.symbol !== "string" || card.payment.mint !== void 0 && typeof card.payment.mint !== "string")) {
1245
+ return null;
1246
+ }
1247
+ 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)) {
1248
+ return null;
1249
+ }
1215
1250
  if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
1216
1251
  return null;
1217
1252
  }
@@ -1788,6 +1823,67 @@ function nip44Decrypt(ciphertext, receiverSk, senderPubkey) {
1788
1823
  const conversationKey = nip44.v2.utils.getConversationKey(receiverSk, senderPubkey);
1789
1824
  return nip44.v2.decrypt(ciphertext, conversationKey);
1790
1825
  }
1826
+ var ENVELOPE_VERSION = "elisym-job/1";
1827
+ var ENVELOPE_NAMESPACE_PREFIX = "elisym-job/";
1828
+ var MAX_TICKET_LENGTH = 4096;
1829
+ var FileTransportSchema = z.discriminatedUnion("kind", [
1830
+ z.object({
1831
+ kind: z.literal("iroh"),
1832
+ /** Opaque iroh `BlobTicket` string. Parsed into a real ticket only at fetch time. */
1833
+ ticket: z.string().min(1).max(MAX_TICKET_LENGTH)
1834
+ })
1835
+ ]);
1836
+ var FileAttachmentSchema = z.object({
1837
+ /** Display name only. Never used to derive a filesystem path (callers sanitize). */
1838
+ name: z.string().min(1).max(255),
1839
+ /** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
1840
+ size: z.number().int().nonnegative(),
1841
+ mime: z.string().min(1).max(255),
1842
+ /** Ordered by sender preference; at least one. */
1843
+ transports: z.array(FileTransportSchema).min(1),
1844
+ /** Optional provider hint (unix seconds) for when seeding may stop. */
1845
+ seedingExpiresAt: z.number().int().nonnegative().optional()
1846
+ });
1847
+ var JobPayloadEnvelopeSchema = z.object({
1848
+ v: z.literal(ENVELOPE_VERSION),
1849
+ text: z.string().optional(),
1850
+ attachment: FileAttachmentSchema.optional()
1851
+ });
1852
+ function encodeJobPayload(payload) {
1853
+ const envelope = { v: ENVELOPE_VERSION };
1854
+ if (payload.text !== void 0) {
1855
+ envelope.text = payload.text;
1856
+ }
1857
+ if (payload.attachment !== void 0) {
1858
+ envelope.attachment = payload.attachment;
1859
+ }
1860
+ return JSON.stringify(envelope);
1861
+ }
1862
+ function decodeJobPayload(content) {
1863
+ if (content.length > LIMITS.MAX_INPUT_LENGTH) {
1864
+ return { text: content };
1865
+ }
1866
+ let parsed;
1867
+ try {
1868
+ parsed = JSON.parse(content);
1869
+ } catch {
1870
+ return { text: content };
1871
+ }
1872
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1873
+ return { text: content };
1874
+ }
1875
+ const version = parsed.v;
1876
+ if (typeof version !== "string" || !version.startsWith(ENVELOPE_NAMESPACE_PREFIX)) {
1877
+ return { text: content };
1878
+ }
1879
+ const result = JobPayloadEnvelopeSchema.safeParse(parsed);
1880
+ if (!result.success) {
1881
+ throw new Error(
1882
+ `Invalid elisym job payload (v=${JSON.stringify(version)}): ${result.error.message}`
1883
+ );
1884
+ }
1885
+ return { text: result.data.text, attachment: result.data.attachment };
1886
+ }
1791
1887
 
1792
1888
  // src/services/marketplace.ts
1793
1889
  function isEncrypted(event) {
@@ -1820,7 +1916,8 @@ var MarketplaceService = class {
1820
1916
  }
1821
1917
  /** Submit a job request with NIP-44 encrypted input. Returns the event ID. */
1822
1918
  async submitJobRequest(identity, options) {
1823
- if (!options.input) {
1919
+ const hasAttachment = options.attachment !== void 0;
1920
+ if (!options.input && !hasAttachment) {
1824
1921
  throw new Error("Job input must not be empty.");
1825
1922
  }
1826
1923
  if (options.input.length > LIMITS.MAX_INPUT_LENGTH) {
@@ -1834,7 +1931,15 @@ var MarketplaceService = class {
1834
1931
  if (options.providerPubkey && !/^[0-9a-f]{64}$/.test(options.providerPubkey)) {
1835
1932
  throw new Error("Invalid provider pubkey: expected 64 hex characters.");
1836
1933
  }
1837
- const plaintext = options.input;
1934
+ const plaintext = hasAttachment ? encodeJobPayload({ text: options.input || void 0, attachment: options.attachment }) : options.input;
1935
+ if (options.providerPubkey) {
1936
+ const plaintextBytes = utf8ByteLength(plaintext);
1937
+ if (plaintextBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
1938
+ throw new Error(
1939
+ `Encrypted job input too large: ${plaintextBytes} bytes (max ${LIMITS.NIP44_MAX_PLAINTEXT_BYTES}). Send large input as a file/iroh attachment instead.`
1940
+ );
1941
+ }
1942
+ }
1838
1943
  const encrypted = options.providerPubkey ? nip44Encrypt(plaintext, identity.secretKey, options.providerPubkey) : plaintext;
1839
1944
  const tags = [
1840
1945
  ["i", options.providerPubkey ? "encrypted" : "text", "text"],
@@ -1928,9 +2033,15 @@ var MarketplaceService = class {
1928
2033
  if (content === null) {
1929
2034
  return;
1930
2035
  }
2036
+ let decoded;
2037
+ try {
2038
+ decoded = decodeJobPayload(content);
2039
+ } catch {
2040
+ return;
2041
+ }
1931
2042
  resultDelivered = true;
1932
2043
  try {
1933
- cb.onResult?.(content, ev.id);
2044
+ cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment);
1934
2045
  } catch {
1935
2046
  } finally {
1936
2047
  done();
@@ -2094,8 +2205,9 @@ var MarketplaceService = class {
2094
2205
  );
2095
2206
  }
2096
2207
  /** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
2097
- async submitJobResult(identity, requestEvent, content, amount) {
2098
- if (!content) {
2208
+ async submitJobResult(identity, requestEvent, content, amount, attachment) {
2209
+ const hasAttachment = attachment !== void 0;
2210
+ if (!content && !hasAttachment) {
2099
2211
  throw new Error("Job result content must not be empty.");
2100
2212
  }
2101
2213
  if (!Number.isInteger(requestEvent.kind)) {
@@ -2108,7 +2220,16 @@ var MarketplaceService = class {
2108
2220
  );
2109
2221
  }
2110
2222
  const shouldEncrypt = isEncrypted(requestEvent);
2111
- const resultContent = shouldEncrypt ? nip44Encrypt(content, identity.secretKey, requestEvent.pubkey) : content;
2223
+ const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachment }) : content;
2224
+ if (shouldEncrypt) {
2225
+ const payloadBytes = utf8ByteLength(payload);
2226
+ if (payloadBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
2227
+ throw new Error(
2228
+ `Encrypted job result too large: ${payloadBytes} bytes (max ${LIMITS.NIP44_MAX_PLAINTEXT_BYTES}). Deliver large results as a file/iroh attachment instead.`
2229
+ );
2230
+ }
2231
+ }
2232
+ const resultContent = shouldEncrypt ? nip44Encrypt(payload, identity.secretKey, requestEvent.pubkey) : payload;
2112
2233
  const resultKind = KIND_JOB_RESULT_BASE + offset;
2113
2234
  const tags = [
2114
2235
  ["e", requestEvent.id],
@@ -2140,11 +2261,11 @@ var MarketplaceService = class {
2140
2261
  * With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
2141
2262
  * Jitter: 0.5x-1.0x of calculated delay.
2142
2263
  */
2143
- async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS) {
2264
+ async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachment) {
2144
2265
  const attempts = Math.max(1, maxAttempts);
2145
2266
  for (let attempt = 0; attempt < attempts; attempt++) {
2146
2267
  try {
2147
- return await this.submitJobResult(identity, requestEvent, content, amount);
2268
+ return await this.submitJobResult(identity, requestEvent, content, amount, attachment);
2148
2269
  } catch (e) {
2149
2270
  if (attempt >= attempts - 1) {
2150
2271
  throw e;
@@ -3804,6 +3925,6 @@ function makeCensor() {
3804
3925
  };
3805
3926
  }
3806
3927
 
3807
- export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, 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, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry, verifyJobPaymentQuick };
3928
+ export { 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 };
3808
3929
  //# sourceMappingURL=index.js.map
3809
3930
  //# sourceMappingURL=index.js.map