{"version":3,"sources":["../src/download-file.ts"],"names":["HostCapabilityError","internalsFor","DOWNLOAD_FILE_METHOD"],"mappings":";;;;;;;;AAsBA,IAAM,YAAA,GAAe,KAAA;AAqBrB,eAAsB,YAAA,CACpB,GAAA,EACA,QAAA,EACA,OAAA,EACA,QAAA,EACkC;AAClC,EAAA,IAAI,CAAC,GAAA,CAAI,gBAAA,CAAiB,YAAA,EAAc;AACtC,IAAA,MAAM,IAAIA,qCAAA,CAAoB,cAAA,EAAgB,cAAc,CAAA;AAAA,EAC9D;AACA,EAAA,MAAM,SAAA,GAAYC,+BAAa,GAAG,CAAA;AAClC,EAAA,MAAM,eACJ,QAAA,KAAa,OAAA,YAAmB,IAAA,GAAO,OAAA,CAAQ,OAAO,EAAA,CAAA,IAAO,0BAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,WAAW,QAAQ,CAAA,CAAA;AAC/B,EAAA,MAAM,WACJ,OAAO,OAAA,KAAY,WACf,EAAE,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,SAAQ,GAC7C,EAAE,KAAK,QAAA,EAAU,YAAA,EAAc,MAAM,MAAM,QAAA,CAAS,OAAO,CAAA,EAAE;AACnE,EAAA,MAAM,MAAA,GAA6C;AAAA,IACjD,UAAU,CAAC,EAAE,IAAA,EAAM,UAAA,EAAY,UAAU;AAAA,GAC3C;AACA,EAAA,OAAQ,MAAM,SAAA,CAAU,OAAA;AAAA,IACtBC,4BAAA;AAAA,IACA;AAAA,GACF;AACF;AAEA,eAAe,SAAS,IAAA,EAA6B;AACnD,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,aAAa,CAAA;AACrD,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,YAAA,EAAc;AACnD,IAAA,MAAA,IAAU,MAAA,CAAO,aAAa,GAAG,KAAA,CAAM,SAAS,CAAA,EAAG,CAAA,GAAI,YAAY,CAAC,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"index.cjs","sourcesContent":["/**\n * Hand the user a file to save, over the ext-apps `ui/download-file` request.\n *\n * A function over {@link App} rather than a method on it, like the helpers in\n * `extensions.ts` — but unlike them this is spec surface, so it is not gated on\n * the host identity. Any host that advertises `downloadFile` can answer it.\n *\n * The bytes always travel embedded. The spec also allows a `ResourceLink`, which\n * asks the host to fetch a URI the app names; a host holding the user's session\n * has every reason to refuse that, and the NimbleBrain host does.\n */\nimport {\n  DOWNLOAD_FILE_METHOD,\n  type McpUiDownloadFileRequest,\n  type McpUiDownloadFileResult,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { EmbeddedResource } from \"@modelcontextprotocol/sdk/types.js\";\nimport { HostCapabilityError } from \"./errors.js\";\nimport { internalsFor } from \"./internals.js\";\nimport type { App } from \"./types.js\";\n\n/** Bytes per `String.fromCharCode` spread — well under any engine's argument limit. */\nconst BASE64_CHUNK = 0x8000;\n\n/**\n * Hand the user a file to save.\n *\n * A string is sent as the resource's `text`; a `Blob` is read and sent as\n * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a\n * promise.\n *\n * Precedence for the MIME type: the explicit argument, then the Blob's own\n * type, then `application/octet-stream`. An empty-string argument falls\n * through — a `\"\"` MIME is effectively \"no type\".\n *\n * The filename is the last segment of the resource's `file:///` URI, because an\n * embedded resource has no name field of its own.\n *\n * Resolves with the host's result: `{ isError: true }` when the host declined\n * or the user cancelled. Rejects with `HostCapabilityError`, without sending,\n * when the host did not declare `downloadFile` — a host that does not implement\n * the request may never answer it, and a request has no deadline.\n */\nexport async function downloadFile(\n  app: App,\n  filename: string,\n  content: string | Blob,\n  mimeType?: string,\n): Promise<McpUiDownloadFileResult> {\n  if (!app.hostCapabilities.downloadFile) {\n    throw new HostCapabilityError(\"downloadFile\", \"downloadFile\");\n  }\n  const internals = internalsFor(app);\n  const resolvedMime =\n    mimeType || (content instanceof Blob ? content.type : \"\") || \"application/octet-stream\";\n  const uri = `file:///${filename}`;\n  const resource: EmbeddedResource[\"resource\"] =\n    typeof content === \"string\"\n      ? { uri, mimeType: resolvedMime, text: content }\n      : { uri, mimeType: resolvedMime, blob: await toBase64(content) };\n  const params: McpUiDownloadFileRequest[\"params\"] = {\n    contents: [{ type: \"resource\", resource }],\n  };\n  return (await internals.request(\n    DOWNLOAD_FILE_METHOD,\n    params as unknown as Record<string, unknown>,\n  )) as McpUiDownloadFileResult;\n}\n\nasync function toBase64(blob: Blob): Promise<string> {\n  const bytes = new Uint8Array(await blob.arrayBuffer());\n  let binary = \"\";\n  for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {\n    binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));\n  }\n  return btoa(binary);\n}\n"]}