{"version":3,"sources":["../../src/ipfs/ipfs-uploader.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @agentx/sdk — IPFS Uploader\n// ---------------------------------------------------------------------------\n// Lightweight IPFS upload via Pinata REST API (JWT auth) or custom endpoint.\n// Zero extra deps — works in browser, Node, edge with native fetch.\n// ---------------------------------------------------------------------------\n\nimport type { EncryptedPayload } from '../core/types'\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface IPFSUploaderConfig {\n  /** Pinata JWT token. Required for Pinata uploads. */\n  pinataJwt?: string\n  /** Custom IPFS API endpoint. Falls back to Pinata if not set. */\n  customEndpoint?: string\n  /** Custom API key for non-Pinata endpoints */\n  customApiKey?: string\n  /** Gateway URL for building access URLs (default: ipfs.io) */\n  gatewayUrl?: string\n  /** Request timeout in ms (default: 30_000) */\n  timeoutMs?: number\n  /** Pinata group ID for organizing pinned files */\n  pinataGroupId?: string\n  /** Metadata name prefix for pinned files */\n  namePrefix?: string\n}\n\nexport interface IPFSUploadResult {\n  /** IPFS CID (Content Identifier) */\n  cid: string\n  /** Full IPFS gateway URL */\n  url: string\n  /** Raw response from the upload endpoint */\n  raw: Record<string, unknown>\n}\n\n// ── Implementation ─────────────────────────────────────────────────────────\n\nexport class IPFSUploader {\n  private pinataJwt: string | null\n  private customEndpoint: string | null\n  private customApiKey: string | null\n  private gatewayUrl: string\n  private timeoutMs: number\n  private pinataGroupId: string | null\n  private namePrefix: string\n\n  private static readonly PINATA_JSON_API = 'https://api.pinata.cloud/pinning/pinJSONToIPFS'\n  private static readonly PINATA_FILE_API = 'https://api.pinata.cloud/pinning/pinFileToIPFS'\n\n  constructor(config: IPFSUploaderConfig = {}) {\n    this.pinataJwt = config.pinataJwt ?? null\n    this.customEndpoint = config.customEndpoint ?? null\n    this.customApiKey = config.customApiKey ?? null\n    this.gatewayUrl = config.gatewayUrl ?? 'https://ipfs.io'\n    this.timeoutMs = config.timeoutMs ?? 30_000\n    this.pinataGroupId = config.pinataGroupId ?? null\n    this.namePrefix = config.namePrefix ?? 'agentx-'\n  }\n\n  isConfigured(): boolean {\n    if (this.customEndpoint) return true\n    return !!this.pinataJwt\n  }\n\n  // ── JSON Upload ───────────────────────────────────────────────────────\n\n  /**\n   * Upload JSON-serializable data to IPFS.\n   *\n   * @param data       Any JSON-serializable value\n   * @param metadata   Optional name / keyvalues for Pinata metadata\n   */\n  async uploadJSON(\n    data: unknown,\n    metadata?: { name?: string; keyvalues?: Record<string, string> }\n  ): Promise<IPFSUploadResult> {\n    const endpoint = this.customEndpoint ?? IPFSUploader.PINATA_JSON_API\n\n    const body: Record<string, unknown> = {\n      pinataContent: data,\n      pinataMetadata: {\n        name: this.namePrefix + (metadata?.name ?? `json-${Date.now()}`),\n        keyvalues: metadata?.keyvalues ?? {},\n      },\n    }\n\n    if (this.pinataGroupId) {\n      ;(body.pinataMetadata as Record<string, unknown>).groupId = this.pinataGroupId\n    }\n\n    return this._doFetch(endpoint, body)\n  }\n\n  // ── File Upload ───────────────────────────────────────────────────────\n\n  /**\n   * Upload a file / Blob / Buffer / Uint8Array / string to IPFS.\n   */\n  async uploadFile(\n    content: Blob | Buffer | Uint8Array | string,\n    fileName?: string,\n    mimeType?: string\n  ): Promise<IPFSUploadResult> {\n    const endpoint = this.customEndpoint ?? IPFSUploader.PINATA_FILE_API\n\n    const formData = new FormData()\n\n    const blobPart = (\n      content instanceof Blob ? content\n      : typeof Buffer !== 'undefined' && Buffer.isBuffer(content) ? new Uint8Array(content)\n      : content instanceof Uint8Array ? content\n      : content // string\n    ) as BlobPart\n\n    const blob = new Blob([blobPart], { type: mimeType ?? 'application/octet-stream' })\n\n    formData.append('file', blob, fileName ?? `file-${Date.now()}`)\n\n    const metadata = JSON.stringify({\n      name: this.namePrefix + (fileName ?? `file-${Date.now()}`),\n      ...(this.pinataGroupId ? { groupId: this.pinataGroupId } : {}),\n    })\n    formData.append('pinataMetadata', metadata)\n\n    return this._doFetch(endpoint, formData)\n  }\n\n  // ── Encrypted Payload Upload (AgentX specific) ────────────────────────\n\n  /**\n   * Upload an encrypted agent payload to IPFS.\n   * This is the primary method used by Agent Studio publish flow.\n   */\n  async uploadEncryptedPayload(\n    payload: EncryptedPayload,\n    agentName?: string\n  ): Promise<IPFSUploadResult> {\n    return this.uploadJSON(payload, { name: agentName ?? 'agent-payload' })\n  }\n\n  // ── Convenience ──────────────────────────────────────────────────────────\n\n  async uploadString(content: string, name?: string): Promise<IPFSUploadResult> {\n    return this.uploadJSON({ content }, { name: name ?? 'string-data' })\n  }\n\n  /** Build a public access URL from a CID. */\n  getUrl(cid: string): string {\n    return `${this.gatewayUrl}/ipfs/${cid}`\n  }\n\n  // ── Internal ─────────────────────────────────────────────────────────────\n\n  private async _doFetch(\n    url: string,\n    body: BodyInit | Record<string, unknown>\n  ): Promise<IPFSUploadResult> {\n    const headers: Record<string, string> = {}\n\n    if (url === IPFSUploader.PINATA_JSON_API || url === IPFSUploader.PINATA_FILE_API) {\n      if (!this.pinataJwt) throw new Error('Pinata JWT is not configured')\n      headers['Authorization'] = `Bearer ${this.pinataJwt}`\n    } else if (this.customApiKey) {\n      headers['Authorization'] = `Bearer ${this.customApiKey}`\n    }\n\n    if (!(body instanceof FormData)) {\n      headers['Content-Type'] = 'application/json'\n      // eslint-disable-next-line no-param-reassign\n      body = JSON.stringify(body)\n    }\n\n    const res = await fetch(url, {\n      method: 'POST',\n      headers,\n      body: body as BodyInit,\n      signal: AbortSignal.timeout?.(this.timeoutMs),\n    })\n\n    if (!res.ok) {\n      const errText = await res.text().catch(() => '')\n      throw new Error(`IPFS upload failed: HTTP ${res.status} — ${errText.slice(0, 200)}`)\n    }\n\n    const raw = (await res.json()) as Record<string, unknown>\n\n    const cid = (raw.IpfsHash as string) || (raw.cid as string) || (raw.Hash as string)\n    if (!cid || typeof cid !== 'string') throw new Error('Upload succeeded but no CID returned')\n\n    return { cid, url: this.getUrl(cid), raw }\n  }\n}\n\n/** Shared default instance (unconfigured until pinataJwt is set). */\nexport const defaultIPFSUploader = new IPFSUploader()\n"],"mappings":"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","names":[]}