import {
  formatBytes
} from "./chunk-FEUW4G2L.js";

// src/chat-routes/binary-sniff.ts
function bytesStartWith(bytes, offset, signature) {
  if (bytes.length < offset + signature.length) return false;
  for (let i = 0; i < signature.length; i++) {
    if (bytes[offset + i] !== signature[i]) return false;
  }
  return true;
}
function asciiAt(bytes, offset, text) {
  if (bytes.length < offset + text.length) return false;
  for (let i = 0; i < text.length; i++) {
    if (bytes[offset + i] !== text.charCodeAt(i)) return false;
  }
  return true;
}
function sniffRiff(bytes) {
  if (!asciiAt(bytes, 0, "RIFF")) return null;
  if (asciiAt(bytes, 8, "WEBP")) return "image/webp";
  if (asciiAt(bytes, 8, "WAVE")) return "audio/wav";
  return null;
}
function sniffFtyp(bytes) {
  if (!asciiAt(bytes, 4, "ftyp")) return null;
  if (asciiAt(bytes, 8, "qt  ")) return "video/quicktime";
  if (asciiAt(bytes, 8, "avif") || asciiAt(bytes, 8, "avis")) return "image/avif";
  if (asciiAt(bytes, 8, "heic") || asciiAt(bytes, 8, "heix") || asciiAt(bytes, 8, "hevc") || asciiAt(bytes, 8, "hevx")) return "image/heic";
  if (asciiAt(bytes, 8, "mif1") || asciiAt(bytes, 8, "msf1")) return "image/heif";
  return "video/mp4";
}
function sniffBmp(bytes) {
  return asciiAt(bytes, 0, "BM") && bytes.length >= 10 && bytes[6] === 0 && bytes[7] === 0 && bytes[8] === 0 && bytes[9] === 0;
}
function sniffId3(bytes) {
  return asciiAt(bytes, 0, "ID3") && bytes.length >= 10 && bytes[3] < 16 && bytes[6] < 128 && bytes[7] < 128 && bytes[8] < 128 && bytes[9] < 128;
}
function sniffMagicBytes(bytes) {
  if (bytesStartWith(bytes, 0, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
  if (bytesStartWith(bytes, 0, [255, 216, 255])) return "image/jpeg";
  if (asciiAt(bytes, 0, "GIF87a") || asciiAt(bytes, 0, "GIF89a")) return "image/gif";
  if (sniffBmp(bytes)) return "image/bmp";
  if (bytesStartWith(bytes, 0, [73, 73, 42, 0])) return "image/tiff";
  if (bytesStartWith(bytes, 0, [77, 77, 0, 42])) return "image/tiff";
  if (bytesStartWith(bytes, 0, [0, 0, 1, 0])) return "image/x-icon";
  if (asciiAt(bytes, 0, "%PDF-")) return "application/pdf";
  if (bytesStartWith(bytes, 0, [80, 75, 3, 4])) return "application/zip";
  if (bytesStartWith(bytes, 0, [31, 139])) return "application/gzip";
  if (sniffId3(bytes) || bytesStartWith(bytes, 0, [255, 251])) return "audio/mpeg";
  if (asciiAt(bytes, 0, "OggS")) return "audio/ogg";
  const riff = sniffRiff(bytes);
  if (riff) return riff;
  const ftyp = sniffFtyp(bytes);
  if (ftyp) return ftyp;
  return null;
}
function sniffSvgText(decoded) {
  let text = decoded;
  if (text.charCodeAt(0) === 65279) text = text.slice(1);
  text = text.trimStart();
  if (/^<svg[\s>/]/.test(text)) return true;
  if (!text.startsWith("<?xml")) return false;
  return /<svg[\s>/]/.test(text.slice(0, 1024));
}
function sniffBinary(bytes) {
  const mime = sniffMagicBytes(bytes);
  if (mime) return { binary: true, mime };
  if (bytes.includes(0)) return { binary: true, mime: null };
  let decoded;
  try {
    decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  } catch {
    return { binary: true, mime: null };
  }
  if (sniffSvgText(decoded)) return { binary: true, mime: "image/svg+xml" };
  return { binary: false, mime: null };
}

// src/chat-routes/attachment-validation.ts
var MAX_BINARY_ATTACHMENT_BYTES = 10 * 1024 * 1024;
var MAX_TEXT_ATTACHMENT_BYTES = 950 * 1024;
var ATTACHMENT_MAX_COUNT = 10;
var MAX_ATTACHMENT_TOTAL_BYTES = 25 * 1024 * 1024;
var ATTACHMENT_ACCEPT = "image/*,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html";
var ALLOWED_ATTACHMENT_SNIFFED_MIMES = /* @__PURE__ */ new Set([
  "image/png",
  "image/jpeg",
  "image/gif",
  "image/bmp",
  "image/tiff",
  "image/x-icon",
  "image/webp",
  "image/svg+xml",
  "image/avif",
  "image/heic",
  "image/heif",
  "application/pdf"
]);
var EXTENSION_IMPLIES_SNIFFED_MIME = {
  png: "image/png",
  jpg: "image/jpeg",
  jpeg: "image/jpeg",
  gif: "image/gif",
  bmp: "image/bmp",
  tif: "image/tiff",
  tiff: "image/tiff",
  ico: "image/x-icon",
  webp: "image/webp",
  svg: "image/svg+xml",
  pdf: "application/pdf"
};
function checkAttachmentType(fileName, sniff, allowed = ALLOWED_ATTACHMENT_SNIFFED_MIMES) {
  if (sniff.binary === false) return { succeeded: true };
  const extension = fileName.split(".").pop()?.toLowerCase() ?? "";
  const impliedMime = EXTENSION_IMPLIES_SNIFFED_MIME[extension];
  if (impliedMime && sniff.mime && sniff.mime !== impliedMime) {
    return {
      succeeded: false,
      code: "attachment_type_mismatch",
      message: `${fileName} has a .${extension} extension, but its content is ${sniff.mime}`
    };
  }
  if (!sniff.mime || !allowed.has(sniff.mime)) {
    return {
      succeeded: false,
      code: "attachment_type_not_allowed",
      message: sniff.mime ? `${fileName}'s content (${sniff.mime}) is not an allowed attachment type` : `${fileName}'s content is not a recognized attachment type`
    };
  }
  return { succeeded: true };
}
function sanitizeAttachmentFileName(name) {
  const sanitized = name.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "");
  return sanitized || "file";
}
function attachmentSizeErrorMessage(name, actualBytes, limitBytes) {
  return `${name} is ${formatBytes(actualBytes)}; attachments are limited to ${formatBytes(limitBytes)}`;
}
function attachmentTotalSizeErrorMessage(totalBytes, limitBytes) {
  return `Attachments total ${formatBytes(totalBytes)}; each message is limited to ${formatBytes(limitBytes)}`;
}

// src/chat-routes/file-index.ts
var DEFAULT_IGNORE_SEGMENTS = [
  "node_modules",
  "dist",
  "build",
  "out",
  "coverage",
  "target",
  "__pycache__",
  "venv"
];
function isIgnored(relPath, ignoreSegments) {
  for (const segment of relPath.split("/")) {
    if (!segment) continue;
    if (segment.startsWith(".")) return true;
    if (ignoreSegments.has(segment)) return true;
  }
  return false;
}
function relativeTo(root, path) {
  const prefix = root.endsWith("/") ? root : `${root}/`;
  if (path.startsWith(prefix)) return path.slice(prefix.length);
  if (path === root) return "";
  return path;
}
function basename(path) {
  const segments = path.split("/").filter(Boolean);
  return segments[segments.length - 1] ?? path;
}
function isMissingRootError(err, root) {
  if (!(err instanceof Error)) return false;
  if (err.code !== "VALIDATION_ERROR") return false;
  return /ENOENT/.test(err.message) && /no such file or directory/.test(err.message) && new RegExp(`\\blstat '${escapeRegExp(root)}'`).test(err.message);
}
function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function createSandboxFileIndexRoute(options) {
  const maxDepth = options.maxDepth ?? 12;
  const maxEntries = options.maxEntries ?? 5e3;
  const cacheTtlSeconds = options.cacheTtlSeconds ?? 20;
  const staticIgnore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_SEGMENTS, ...options.ignore ?? []]);
  return async function fileIndex(request) {
    const auth = await options.authorize({ request });
    if (auth.status === "denied") return auth.response;
    if (auth.status === "warming") {
      return Response.json({ status: "warming" });
    }
    const cache = options.cache;
    if (cache && auth.cacheKey) {
      const cached = await cache.get(auth.cacheKey);
      if (cached) return Response.json(cached);
    }
    const ignoreSegments = auth.ignore?.length ? /* @__PURE__ */ new Set([...staticIgnore, ...auth.ignore]) : staticIgnore;
    let scan;
    try {
      scan = await auth.fs.tree(auth.root, { maxDepth });
    } catch (err) {
      if (!isMissingRootError(err, auth.root)) throw err;
      return Response.json({ status: "warming" });
    }
    const filtered = scan.files.filter((f) => !isIgnored(relativeTo(scan.root, f.path), ignoreSegments));
    const truncated = scan.stats.truncated || filtered.length > maxEntries;
    const files = filtered.slice(0, maxEntries).map((f) => {
      const path = relativeTo(scan.root, f.path);
      const entry = { path, name: basename(path) };
      if (typeof f.size === "number") entry.size = f.size;
      return entry;
    });
    const body = {
      status: "ready",
      files,
      truncated,
      generatedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    if (cache && auth.cacheKey) await cache.put(auth.cacheKey, body, { ttlSeconds: cacheTtlSeconds });
    return Response.json(body);
  };
}

export {
  sniffBinary,
  MAX_BINARY_ATTACHMENT_BYTES,
  MAX_TEXT_ATTACHMENT_BYTES,
  ATTACHMENT_MAX_COUNT,
  MAX_ATTACHMENT_TOTAL_BYTES,
  ATTACHMENT_ACCEPT,
  ALLOWED_ATTACHMENT_SNIFFED_MIMES,
  checkAttachmentType,
  sanitizeAttachmentFileName,
  attachmentSizeErrorMessage,
  attachmentTotalSizeErrorMessage,
  createSandboxFileIndexRoute
};
//# sourceMappingURL=chunk-5GA3MKOC.js.map