videodraft 0.20.0 → 0.22.0

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/client.js CHANGED
@@ -643,8 +643,8 @@ var VideoDraftClient = class {
643
643
  * MCP tool by design). Uses the same bearer token + 401 refresh as rpc().
644
644
  * Returns the parsed JSON body; throws on non-2xx with the server's error.
645
645
  */
646
- async restRequest(method, path5, body) {
647
- const doFetch = (token2) => this.fetchImpl(`${this.baseUrl}${path5}`, {
646
+ async restRequest(method, path6, body) {
647
+ const doFetch = (token2) => this.fetchImpl(`${this.baseUrl}${path6}`, {
648
648
  method,
649
649
  headers: {
650
650
  "content-type": "application/json",
@@ -1406,6 +1406,7 @@ var MIME_BY_EXT = {
1406
1406
  jpg: "image/jpeg",
1407
1407
  jpeg: "image/jpeg",
1408
1408
  webp: "image/webp",
1409
+ avif: "image/avif",
1409
1410
  gif: "image/gif",
1410
1411
  heic: "image/heic",
1411
1412
  mp4: "video/mp4",
@@ -1426,11 +1427,30 @@ function guessContentType(filename) {
1426
1427
  return MIME_BY_EXT[ext];
1427
1428
  }
1428
1429
  async function uploadFile(client, localPath, options = {}) {
1430
+ return upload(client, localPath, options, false);
1431
+ }
1432
+ async function upload3DFile(client, localPath, options = {}) {
1433
+ if (path4.extname(localPath).toLowerCase() !== ".glb") {
1434
+ throw new CliError(
1435
+ "3D rigging uploads require a self-contained .glb file."
1436
+ );
1437
+ }
1438
+ return upload(
1439
+ client,
1440
+ localPath,
1441
+ { ...options, contentType: "model/gltf-binary" },
1442
+ true
1443
+ );
1444
+ }
1445
+ async function upload(client, localPath, options, model3d) {
1429
1446
  const fetchImpl = options.fetchImpl ?? fetch;
1430
1447
  const resolved = path4.resolve(localPath);
1431
1448
  if (!fs4.existsSync(resolved)) {
1432
1449
  throw new CliError(`File not found: ${resolved}`);
1433
1450
  }
1451
+ if (!fs4.statSync(resolved).isFile()) {
1452
+ throw new CliError(`Expected a file: ${resolved}`);
1453
+ }
1434
1454
  const filename = path4.basename(resolved);
1435
1455
  const contentType = options.contentType ?? guessContentType(filename);
1436
1456
  if (!contentType) {
@@ -1438,21 +1458,59 @@ async function uploadFile(client, localPath, options = {}) {
1438
1458
  `Cannot infer media type for "${filename}". Pass --content-type (image/*, video/* or audio/*).`
1439
1459
  );
1440
1460
  }
1441
- const created = await client.callTool("create_media_upload", {
1461
+ const createTool = model3d ? "create_3d_upload" : "create_media_upload";
1462
+ const finalizeTool = model3d ? "finalize_3d_upload" : "finalize_media_upload";
1463
+ const created = await client.callTool(createTool, {
1442
1464
  filename,
1443
1465
  content_type: contentType
1444
1466
  });
1445
1467
  const uploadUrl = created?.upload_url;
1446
1468
  const filePath = created?.file_path;
1447
1469
  if (!uploadUrl || !filePath) {
1448
- throw new CliError(
1449
- "create_media_upload did not return upload_url/file_path."
1450
- );
1470
+ throw new CliError(`${createTool} did not return upload_url/file_path.`);
1451
1471
  }
1452
1472
  const { size } = fs4.statSync(resolved);
1473
+ const headers = {
1474
+ "content-type": contentType,
1475
+ "content-length": String(size)
1476
+ };
1477
+ if (model3d) {
1478
+ const maxBytes = created?.max_bytes;
1479
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
1480
+ throw new CliError(
1481
+ "create_3d_upload did not return a valid max_bytes limit."
1482
+ );
1483
+ }
1484
+ if (size > maxBytes) {
1485
+ throw new CliError(
1486
+ `3D upload is ${size} bytes; the server allows at most ${maxBytes} bytes.`
1487
+ );
1488
+ }
1489
+ if (!created.headers || typeof created.headers !== "object" || Array.isArray(created.headers)) {
1490
+ throw new CliError(
1491
+ "create_3d_upload did not return the required signed PUT headers."
1492
+ );
1493
+ }
1494
+ for (const [name, value] of Object.entries(created.headers)) {
1495
+ if (typeof value !== "string")
1496
+ throw new CliError(
1497
+ `create_3d_upload returned an invalid ${name} header.`
1498
+ );
1499
+ headers[name.toLowerCase()] = value;
1500
+ }
1501
+ headers["content-length"] = String(size);
1502
+ const range = /^(\d+),(\d+)$/.exec(
1503
+ headers["x-goog-content-length-range"] ?? ""
1504
+ );
1505
+ if (range && (size < Number(range[1]) || size > Number(range[2]))) {
1506
+ throw new CliError(
1507
+ `3D upload must contain between ${range[1]} and ${range[2]} bytes.`
1508
+ );
1509
+ }
1510
+ }
1453
1511
  const putRes = await fetchImpl(uploadUrl, {
1454
1512
  method: "PUT",
1455
- headers: { "content-type": contentType, "content-length": String(size) },
1513
+ headers,
1456
1514
  body: Readable2.toWeb(
1457
1515
  fs4.createReadStream(resolved)
1458
1516
  ),
@@ -1465,16 +1523,494 @@ async function uploadFile(client, localPath, options = {}) {
1465
1523
  `Upload PUT failed (HTTP ${putRes.status}). The presigned URL may have expired \u2014 retry.`
1466
1524
  );
1467
1525
  }
1468
- const finalized = await client.callTool("finalize_media_upload", {
1526
+ const finalized = await client.callTool(finalizeTool, {
1469
1527
  file_path: filePath,
1470
1528
  original_filename: filename
1471
1529
  });
1472
1530
  const url = finalized?.url ?? finalized?.cdn_url;
1473
1531
  if (!url) {
1474
- throw new CliError("finalize_media_upload did not return a public url.");
1532
+ throw new CliError(`${finalizeTool} did not return a public url.`);
1475
1533
  }
1476
1534
  return { ...finalized, url, file_path: filePath };
1477
1535
  }
1536
+
1537
+ // src/core/model3d.ts
1538
+ import fs5 from "fs";
1539
+ import path5 from "path";
1540
+ import { randomUUID } from "crypto";
1541
+
1542
+ // src/core/media.ts
1543
+ var IMAGE_EXTS = /* @__PURE__ */ new Set([
1544
+ "png",
1545
+ "jpg",
1546
+ "jpeg",
1547
+ "webp",
1548
+ "gif",
1549
+ "bmp",
1550
+ "heic",
1551
+ "heif",
1552
+ "avif",
1553
+ "svg"
1554
+ ]);
1555
+ var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "m4v"]);
1556
+ var AUDIO_EXTS = /* @__PURE__ */ new Set([
1557
+ "mp3",
1558
+ "wav",
1559
+ "pcm",
1560
+ "opus",
1561
+ "m4a",
1562
+ "ogg",
1563
+ "flac",
1564
+ "aac"
1565
+ ]);
1566
+ function kindOf(url, typeHint) {
1567
+ const t = (typeHint ?? "").toLowerCase();
1568
+ if (t.includes("image")) return "image";
1569
+ if (t.includes("video")) return "video";
1570
+ if (t.includes("audio") || t.includes("music") || t.includes("sound") || t.includes("voice") || t.includes("speech") || t.includes("tts")) {
1571
+ return "audio";
1572
+ }
1573
+ const path6 = url.split(/[?#]/)[0]?.toLowerCase() ?? "";
1574
+ if (path6.includes("/img/")) return "image";
1575
+ if (path6.includes("/vid/")) return "video";
1576
+ if (path6.includes("/aud/")) return "audio";
1577
+ const ext = path6.match(/\.([a-z0-9]+)$/)?.[1];
1578
+ if (ext) {
1579
+ if (IMAGE_EXTS.has(ext)) return "image";
1580
+ if (VIDEO_EXTS.has(ext)) return "video";
1581
+ if (AUDIO_EXTS.has(ext)) return "audio";
1582
+ }
1583
+ return null;
1584
+ }
1585
+ function buildMediaDescriptors(urls, typeHint) {
1586
+ if (!Array.isArray(urls)) return [];
1587
+ const seen = /* @__PURE__ */ new Set();
1588
+ const out = [];
1589
+ for (const url of urls) {
1590
+ if (typeof url !== "string" || !/^https?:\/\//i.test(url) || seen.has(url))
1591
+ continue;
1592
+ const kind = kindOf(url, typeHint);
1593
+ if (!kind) continue;
1594
+ seen.add(url);
1595
+ out.push({ kind, url });
1596
+ }
1597
+ return out;
1598
+ }
1599
+
1600
+ // src/core/model3d.ts
1601
+ function isModel3DResult(payload) {
1602
+ return payload?.type === "model3d";
1603
+ }
1604
+ function warningStrings(value) {
1605
+ return Array.isArray(value) ? value.filter(
1606
+ (warning) => typeof warning === "string" && Boolean(warning.trim())
1607
+ ) : [];
1608
+ }
1609
+ function model3DWarnings(payload, localWarnings = []) {
1610
+ return [
1611
+ .../* @__PURE__ */ new Set([
1612
+ ...warningStrings(payload?.artifact_warnings),
1613
+ ...warningStrings(payload?.package_warnings),
1614
+ ...localWarnings
1615
+ ])
1616
+ ];
1617
+ }
1618
+ function model3DArtifacts(payload) {
1619
+ const artifacts = [];
1620
+ const seen = /* @__PURE__ */ new Set();
1621
+ for (const item of Array.isArray(payload?.artifacts) ? payload.artifacts : []) {
1622
+ if (!item || typeof item.url !== "string" || !/^https?:\/\//i.test(item.url))
1623
+ continue;
1624
+ const filename = typeof item.filename === "string" ? item.filename : "";
1625
+ const key = `${item.url}
1626
+ ${filename}`;
1627
+ if (seen.has(key)) continue;
1628
+ seen.add(key);
1629
+ artifacts.push({
1630
+ id: String(item.id ?? `artifact-${artifacts.length}`),
1631
+ role: String(item.role ?? "other"),
1632
+ url: item.url,
1633
+ filename: filename || `artifact-${artifacts.length}.${extFromUrl(item.url)}`,
1634
+ ...typeof item.content_type === "string" ? { content_type: item.content_type } : {},
1635
+ ...typeof item.bytes === "number" ? { bytes: item.bytes } : {},
1636
+ ...Array.isArray(item.aliases) ? {
1637
+ aliases: item.aliases.filter(
1638
+ (alias) => typeof alias === "string"
1639
+ )
1640
+ } : {}
1641
+ });
1642
+ }
1643
+ const urls = Array.isArray(payload?.output_urls) ? payload.output_urls : payload?.outputs;
1644
+ for (const url of Array.isArray(urls) ? urls : []) {
1645
+ if (typeof url !== "string" || !/^https?:\/\//i.test(url) || artifacts.some((a) => a.url === url))
1646
+ continue;
1647
+ artifacts.push({
1648
+ id: `model-${artifacts.length}`,
1649
+ role: "model",
1650
+ url,
1651
+ filename: `model-${artifacts.length}.${extFromUrl(url)}`
1652
+ });
1653
+ }
1654
+ if (typeof payload?.preview_url === "string" && /^https?:\/\//i.test(payload.preview_url) && !artifacts.some((a) => a.url === payload.preview_url)) {
1655
+ artifacts.push({
1656
+ id: "preview",
1657
+ role: "preview",
1658
+ url: payload.preview_url,
1659
+ filename: `preview.${extFromUrl(payload.preview_url, "png")}`
1660
+ });
1661
+ }
1662
+ return artifacts;
1663
+ }
1664
+ function model3DOutputFields(payload) {
1665
+ const artifacts = model3DArtifacts(payload);
1666
+ const previews = artifacts.filter((a) => a.role === "preview");
1667
+ return {
1668
+ output_files: artifacts,
1669
+ artifact_warnings: warningStrings(payload?.artifact_warnings),
1670
+ package_warnings: model3DWarnings(payload),
1671
+ output_media: previews.flatMap(
1672
+ (a) => buildMediaDescriptors([a.url], a.content_type)
1673
+ )
1674
+ };
1675
+ }
1676
+ function safeName(value, fallback) {
1677
+ const name = value.replace(/[^a-zA-Z0-9._ -]/g, "_").replace(/^\.+/, "");
1678
+ return name && name !== "." && name !== ".." ? name : fallback;
1679
+ }
1680
+ function safeRelativeFilename(filename, index) {
1681
+ return normalizeThreeDArtifactFilename(filename || `artifact-${index}.bin`);
1682
+ }
1683
+ function truncateUtf8(value, maximum) {
1684
+ let result = "";
1685
+ let bytes = 0;
1686
+ for (const character of Array.from(value)) {
1687
+ const size = Buffer.byteLength(character);
1688
+ if (bytes + size > maximum) break;
1689
+ result += character;
1690
+ bytes += size;
1691
+ }
1692
+ return result;
1693
+ }
1694
+ function normalizeThreeDArtifactFilename(name, extension, suffix = "") {
1695
+ const cleaned = name.replace(/\\/g, "/").replace(/^[A-Za-z]:\//, "").replace(/^\/+/, "");
1696
+ const parts = cleaned.split("/").filter((part) => part && part !== "." && part !== "..");
1697
+ const safeParts = parts.map((part, index) => {
1698
+ const sanitized = part.replace(/[\x00-\x1f\x7f<>:"|?*]/g, "_");
1699
+ const last = index === parts.length - 1;
1700
+ const originalExtension = path5.posix.extname(sanitized);
1701
+ const keptExtension = last && extension ? `.${extension.replace(/^\./, "")}` : truncateUtf8(originalExtension, 24);
1702
+ const keptSuffix = last ? truncateUtf8(suffix, 64) : "";
1703
+ const base = originalExtension ? sanitized.slice(0, -originalExtension.length) : sanitized;
1704
+ const stem = truncateUtf8(
1705
+ base,
1706
+ 180 - Buffer.byteLength(keptExtension + keptSuffix)
1707
+ );
1708
+ return `${stem || "artifact"}${keptSuffix}${keptExtension}`;
1709
+ });
1710
+ while (safeParts.length > 1 && Buffer.byteLength(safeParts.join("/")) > 700)
1711
+ safeParts.shift();
1712
+ return safeParts.join("/") || `artifact.${extension || "bin"}`;
1713
+ }
1714
+ function model3DPackageDirectory(payload, target) {
1715
+ const id = safeName(
1716
+ String(payload?.asset_id ?? payload?.job_id ?? payload?.id ?? "asset"),
1717
+ "asset"
1718
+ );
1719
+ if (target === true) return path5.join("media", "3d", id);
1720
+ if (/\{(?:index|ext|name)\}/.test(target)) {
1721
+ throw new CliError(
1722
+ "3D downloads need a package directory. Use --download './media/3d/{job_id}' (no {index}, {name}, or {ext}).",
1723
+ EXIT.USAGE
1724
+ );
1725
+ }
1726
+ const expanded = target.replaceAll("{job_id}", safeName(String(payload?.job_id ?? id), "job")).replaceAll("{asset_id}", id);
1727
+ if (/\.(?:glb|gltf|fbx|obj|mtl|stl|usdz|zip|bin)$/i.test(expanded)) {
1728
+ throw new CliError(
1729
+ "3D downloads contain a model, dependencies, and a manifest. Pass a directory to --download, such as ./media/3d.",
1730
+ EXIT.USAGE
1731
+ );
1732
+ }
1733
+ return /\{(?:job_id|asset_id)\}/.test(target) ? expanded : path5.join(expanded, id);
1734
+ }
1735
+ function writeDependencyAliases(directory, artifacts) {
1736
+ const occupied = new Map(
1737
+ artifacts.map((artifact) => [
1738
+ artifact.local_path.toLowerCase(),
1739
+ artifact.url
1740
+ ])
1741
+ );
1742
+ occupied.set("manifest.json", "manifest");
1743
+ const warnings = [];
1744
+ const files = [];
1745
+ for (const artifact of artifacts) {
1746
+ const aliases = [];
1747
+ for (const original of artifact.aliases ?? []) {
1748
+ const relative = original.replace(/\\/g, "/");
1749
+ const segments = relative.split("/");
1750
+ if (!relative || path5.posix.isAbsolute(relative) || segments.some(
1751
+ (segment) => !segment || segment === "." || segment === ".." || /[\x00-\x1f\x7f<>:"|?*]/.test(segment)
1752
+ )) {
1753
+ warnings.push(
1754
+ `${artifact.local_path}: unsafe dependency alias was not saved: ${original}`
1755
+ );
1756
+ continue;
1757
+ }
1758
+ if (relative === artifact.local_path) {
1759
+ aliases.push(relative);
1760
+ continue;
1761
+ }
1762
+ const lower = relative.toLowerCase();
1763
+ const conflict = Array.from(occupied).some(
1764
+ ([name, url]) => name === lower && url !== artifact.url || name.startsWith(lower + "/") || lower.startsWith(name + "/")
1765
+ );
1766
+ if (conflict) {
1767
+ warnings.push(
1768
+ `${artifact.local_path}: dependency alias collides with another artifact: ${relative}`
1769
+ );
1770
+ continue;
1771
+ }
1772
+ const dest = path5.join(directory, relative);
1773
+ if (!fs5.existsSync(dest)) {
1774
+ fs5.mkdirSync(path5.dirname(dest), { recursive: true });
1775
+ fs5.copyFileSync(
1776
+ path5.join(directory, artifact.local_path),
1777
+ dest,
1778
+ fs5.constants.COPYFILE_EXCL
1779
+ );
1780
+ files.push({
1781
+ url: artifact.url,
1782
+ path: dest,
1783
+ bytes: artifact.downloaded_bytes
1784
+ });
1785
+ }
1786
+ aliases.push(relative);
1787
+ occupied.set(lower, artifact.url);
1788
+ }
1789
+ if (aliases.length) artifact.local_aliases = aliases;
1790
+ }
1791
+ return { files, warnings };
1792
+ }
1793
+ function localizeDependencies(directory, artifacts) {
1794
+ const warnings = [];
1795
+ const matching = (source, reference) => {
1796
+ let decoded = reference;
1797
+ try {
1798
+ decoded = decodeURIComponent(reference);
1799
+ } catch {
1800
+ }
1801
+ const sourceRelative = path5.posix.normalize(
1802
+ path5.posix.join(
1803
+ path5.posix.dirname(source.source_filename.replace(/\\/g, "/")),
1804
+ decoded.replace(/\\/g, "/")
1805
+ )
1806
+ );
1807
+ const absoluteUrl = (() => {
1808
+ try {
1809
+ return new URL(reference, source.url).href;
1810
+ } catch {
1811
+ return "";
1812
+ }
1813
+ })();
1814
+ const precise = artifacts.filter(
1815
+ (a) => a.url === reference || a.url === absoluteUrl || a.source_filename.replace(/\\/g, "/") === sourceRelative
1816
+ );
1817
+ if (precise.length === 1) return precise[0];
1818
+ const byFilename = artifacts.filter(
1819
+ (a) => path5.posix.basename(a.source_filename.replace(/\\/g, "/")) === path5.posix.basename(decoded.replace(/\\/g, "/"))
1820
+ );
1821
+ return byFilename.length === 1 ? byFilename[0] : void 0;
1822
+ };
1823
+ const rewrite = (source, reference, encode) => {
1824
+ if (/^data:/i.test(reference)) return reference;
1825
+ const found = matching(source, reference);
1826
+ if (!found) {
1827
+ warnings.push(
1828
+ `${source.local_path}: dependency could not be matched to a saved artifact: ${reference}`
1829
+ );
1830
+ return reference;
1831
+ }
1832
+ const relative = path5.posix.relative(
1833
+ path5.posix.dirname(source.local_path),
1834
+ found.local_path
1835
+ );
1836
+ return encode ? relative.split("/").map(encodeURIComponent).join("/") : relative;
1837
+ };
1838
+ for (const artifact of artifacts) {
1839
+ const filename = path5.join(directory, artifact.local_path);
1840
+ const ext = path5.extname(artifact.local_path).toLowerCase();
1841
+ if (ext === ".gltf") {
1842
+ try {
1843
+ const document = JSON.parse(fs5.readFileSync(filename, "utf8"));
1844
+ const visit = (value) => {
1845
+ if (Array.isArray(value)) {
1846
+ value.forEach(visit);
1847
+ return;
1848
+ }
1849
+ if (!value || typeof value !== "object") return;
1850
+ for (const [key, child] of Object.entries(value)) {
1851
+ if (key === "uri" && typeof child === "string")
1852
+ value[key] = rewrite(
1853
+ artifact,
1854
+ child,
1855
+ true
1856
+ );
1857
+ else visit(child);
1858
+ }
1859
+ };
1860
+ visit(document);
1861
+ fs5.writeFileSync(filename, JSON.stringify(document, null, 2) + "\n");
1862
+ } catch (error) {
1863
+ warnings.push(
1864
+ `${artifact.local_path}: could not resolve glTF dependencies (${error instanceof Error ? error.message : String(error)}).`
1865
+ );
1866
+ }
1867
+ } else if (ext === ".obj" || ext === ".mtl") {
1868
+ const contents = fs5.readFileSync(filename, "utf8");
1869
+ const updated = contents.replace(
1870
+ /^(\s*(?:mtllib|map_[a-z0-9_]+|bump|disp|decal|norm|refl)\s+)(.+)$/gim,
1871
+ (line, prefix, raw) => {
1872
+ const reference = raw.trim();
1873
+ if (reference.startsWith("-")) {
1874
+ const candidates = artifacts.filter(
1875
+ (a) => reference.endsWith(a.url) || reference.endsWith(a.source_filename) || reference.endsWith(path5.posix.basename(a.source_filename))
1876
+ );
1877
+ if (candidates.length === 1) {
1878
+ const candidate = candidates[0];
1879
+ const name = reference.endsWith(candidate.url) ? candidate.url : reference.endsWith(candidate.source_filename) ? candidate.source_filename : path5.posix.basename(candidate.source_filename);
1880
+ return prefix + reference.slice(0, -name.length) + rewrite(artifact, name, false);
1881
+ }
1882
+ warnings.push(
1883
+ `${artifact.local_path}: could not resolve material reference: ${reference}`
1884
+ );
1885
+ return line;
1886
+ }
1887
+ if (/mtllib/i.test(prefix) && !matching(artifact, reference)) {
1888
+ const references = [...reference.matchAll(/"([^"]+)"|(\S+)/g)].map(
1889
+ (match) => match[1] ?? match[2]
1890
+ );
1891
+ if (references.length > 1 && references.every((ref) => matching(artifact, ref))) {
1892
+ return references.map((ref) => prefix + rewrite(artifact, ref, false)).join("\n");
1893
+ }
1894
+ }
1895
+ return prefix + rewrite(artifact, reference, false);
1896
+ }
1897
+ );
1898
+ fs5.writeFileSync(filename, updated);
1899
+ }
1900
+ artifact.downloaded_bytes = fs5.statSync(filename).size;
1901
+ }
1902
+ return [...new Set(warnings)];
1903
+ }
1904
+ async function downloadModel3DPackage(payload, target, fetchImpl = fetch) {
1905
+ try {
1906
+ return await downloadPackage(payload, target, fetchImpl);
1907
+ } catch (error) {
1908
+ const id = payload?.asset_id ?? payload?.job_id ?? payload?.id;
1909
+ throw new CliError(
1910
+ error instanceof Error ? error.message : String(error),
1911
+ error instanceof CliError ? error.exitCode : EXIT.ERROR,
1912
+ id ? `The saved 3D asset remains available. Retry downloading with: videodraft assets 3d get ${id} --download` : void 0,
1913
+ {
1914
+ asset_id: payload?.asset_id,
1915
+ job_id: payload?.job_id,
1916
+ request_id: payload?.request_id
1917
+ }
1918
+ );
1919
+ }
1920
+ }
1921
+ async function downloadPackage(payload, target, fetchImpl) {
1922
+ const artifacts = model3DArtifacts(payload);
1923
+ if (artifacts.length === 0)
1924
+ throw new CliError("The 3D asset has no downloadable artifacts yet.");
1925
+ const preferredDirectory = model3DPackageDirectory(payload, target);
1926
+ fs5.mkdirSync(path5.dirname(preferredDirectory), { recursive: true });
1927
+ let directory = preferredDirectory;
1928
+ for (let suffix = 2; ; suffix++) {
1929
+ try {
1930
+ fs5.mkdirSync(directory);
1931
+ break;
1932
+ } catch (error) {
1933
+ if (error.code !== "EEXIST") throw error;
1934
+ directory = `${preferredDirectory}-${suffix}`;
1935
+ }
1936
+ }
1937
+ const used = /* @__PURE__ */ new Set(["manifest.json"]);
1938
+ const local = artifacts.map((artifact, index) => {
1939
+ const original = safeRelativeFilename(artifact.filename, index);
1940
+ let relative = original;
1941
+ let suffix = 1;
1942
+ while (Array.from(used).some(
1943
+ (name) => name === relative.toLowerCase() || name.startsWith(relative.toLowerCase() + "/") || relative.toLowerCase().startsWith(name + "/")
1944
+ )) {
1945
+ relative = normalizeThreeDArtifactFilename(
1946
+ path5.posix.basename(original),
1947
+ void 0,
1948
+ `-${suffix++}`
1949
+ );
1950
+ }
1951
+ used.add(relative.toLowerCase());
1952
+ return {
1953
+ ...artifact,
1954
+ local_path: relative,
1955
+ source_filename: artifact.filename,
1956
+ downloaded_bytes: 0
1957
+ };
1958
+ });
1959
+ const files = [];
1960
+ for (const artifact of local) {
1961
+ const dest = path5.join(directory, artifact.local_path);
1962
+ const temporary = `${dest}.partial-${randomUUID()}`;
1963
+ try {
1964
+ const file = await downloadUrl(artifact.url, temporary, fetchImpl);
1965
+ fs5.renameSync(temporary, dest);
1966
+ artifact.downloaded_bytes = file.bytes;
1967
+ files.push({ ...file, path: dest });
1968
+ } finally {
1969
+ fs5.rmSync(temporary, { force: true });
1970
+ }
1971
+ }
1972
+ const warnings = localizeDependencies(directory, local);
1973
+ for (let index = 0; index < files.length; index++) {
1974
+ const file = files[index];
1975
+ file.bytes = local[index].downloaded_bytes;
1976
+ if (local[index].role === "preview") {
1977
+ const preview = await writePreview(
1978
+ file.path,
1979
+ file.url,
1980
+ file.bytes,
1981
+ fetchImpl
1982
+ );
1983
+ if (preview) file.preview = preview;
1984
+ }
1985
+ }
1986
+ const aliases = writeDependencyAliases(directory, local);
1987
+ files.push(...aliases.files);
1988
+ warnings.push(...aliases.warnings);
1989
+ const manifestPath = path5.join(directory, "manifest.json");
1990
+ const temporaryManifest = `${manifestPath}.partial-${randomUUID()}`;
1991
+ const manifest = {
1992
+ version: 1,
1993
+ type: "model3d",
1994
+ asset_id: payload?.asset_id ?? payload?.id,
1995
+ job_id: payload?.job_id,
1996
+ model: payload?.model,
1997
+ credits: payload?.credits,
1998
+ byok: payload?.byok,
1999
+ complete: warnings.length === 0,
2000
+ complete_scope: "returned_artifacts",
2001
+ artifact_warnings: warningStrings(payload?.artifact_warnings),
2002
+ warnings: model3DWarnings(payload, warnings),
2003
+ artifacts: local
2004
+ };
2005
+ fs5.writeFileSync(temporaryManifest, JSON.stringify(manifest, null, 2) + "\n");
2006
+ fs5.renameSync(temporaryManifest, manifestPath);
2007
+ return {
2008
+ downloaded_files: files,
2009
+ package_directory: directory,
2010
+ manifest_path: manifestPath,
2011
+ package_warnings: model3DWarnings(payload, warnings)
2012
+ };
2013
+ }
1478
2014
  export {
1479
2015
  AuthError,
1480
2016
  CliError,
@@ -1493,6 +2029,7 @@ export {
1493
2029
  configPath,
1494
2030
  createConnectionSessionStore,
1495
2031
  createPkcePair,
2032
+ downloadModel3DPackage,
1496
2033
  downloadOutputs,
1497
2034
  downloadUrl,
1498
2035
  exchangeCode,
@@ -1500,6 +2037,9 @@ export {
1500
2037
  extractOutputUrls,
1501
2038
  getProfile,
1502
2039
  guessContentType,
2040
+ isModel3DResult,
2041
+ model3DArtifacts,
2042
+ model3DOutputFields,
1503
2043
  nextPollDelay,
1504
2044
  pollExport,
1505
2045
  pollGeneration,
@@ -1517,6 +2057,7 @@ export {
1517
2057
  sessionScope,
1518
2058
  staticTokenProvider,
1519
2059
  updateConfig,
2060
+ upload3DFile,
1520
2061
  uploadFile,
1521
2062
  writeConfig
1522
2063
  };