// src/memory-extension-host/host-discovery.ts
import { readdir, readFile, lstat, realpath } from "fs/promises";
import path from "path";
var REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT = 5e3;
var MAX_EXAMPLES_PER_EXTENSION = 10;
var VALID_SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
var VALID_MEMORY_TYPES = /* @__PURE__ */ new Set(["fact", "preference", "procedure", "reference"]);
async function discoverMemoryExtensions(root, log) {
  let rootStat;
  try {
    rootStat = await lstat(root);
  } catch {
    return [];
  }
  if (rootStat.isSymbolicLink()) {
    let resolved;
    try {
      resolved = await realpath(root);
    } catch {
      return [];
    }
    let expectedParent;
    try {
      expectedParent = await realpath(path.resolve(path.dirname(root)));
    } catch {
      return [];
    }
    if (!resolved.startsWith(expectedParent + path.sep) && resolved !== expectedParent) {
      log.warn?.(
        `[memory-extensions] root "${root}" is a symlink resolving outside the expected parent directory, skipping`
      );
      return [];
    }
    try {
      rootStat = await lstat(resolved);
    } catch {
      return [];
    }
  }
  if (!rootStat.isDirectory()) {
    return [];
  }
  let entries;
  try {
    entries = await readdir(root);
  } catch {
    return [];
  }
  const extensions = [];
  for (const entry of entries) {
    const entryPath = path.join(root, entry);
    let entryStat;
    try {
      entryStat = await lstat(entryPath);
    } catch {
      continue;
    }
    if (entryStat.isSymbolicLink()) {
      log.warn?.(
        `[memory-extensions] skipping "${entry}": symlinks are not followed for security`
      );
      continue;
    }
    if (!entryStat.isDirectory()) continue;
    if (!VALID_SLUG_RE.test(entry)) {
      log.warn?.(
        `[memory-extensions] skipping "${entry}": invalid slug (must be lowercase alphanumeric + hyphens, 1-64 chars)`
      );
      continue;
    }
    const instructionsPath = path.join(entryPath, "instructions.md");
    if (await isSymlink(instructionsPath)) {
      log.warn?.(
        `[memory-extensions] skipping "${entry}": instructions.md is a symlink`
      );
      continue;
    }
    let instructions;
    try {
      instructions = await readFile(instructionsPath, "utf-8");
    } catch {
      log.warn?.(
        `[memory-extensions] skipping "${entry}": missing instructions.md`
      );
      continue;
    }
    let schema;
    const schemaPath = path.join(entryPath, "schema.json");
    if (await isSymlink(schemaPath)) {
      log.warn?.(
        `[memory-extensions] "${entry}": schema.json is a symlink, ignoring schema`
      );
    } else {
      try {
        const schemaRaw = await readFile(schemaPath, "utf-8");
        const parsed = JSON.parse(schemaRaw);
        if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
          schema = validateSchema(parsed);
        } else {
          log.warn?.(
            `[memory-extensions] "${entry}": schema.json is not a valid object, ignoring schema`
          );
        }
      } catch (err) {
        if (isFileNotFoundError(err)) {
        } else {
          log.warn?.(
            `[memory-extensions] "${entry}": malformed schema.json, ignoring schema`
          );
        }
      }
    }
    const examplesPaths = [];
    const examplesDir = path.join(entryPath, "examples");
    try {
      const exampleEntries = await readdir(examplesDir);
      const mdFiles = exampleEntries.filter((f) => f.endsWith(".md")).sort().slice(0, MAX_EXAMPLES_PER_EXTENSION);
      for (const f of mdFiles) {
        examplesPaths.push(path.join(examplesDir, f));
      }
    } catch {
    }
    extensions.push({
      name: entry,
      root: entryPath,
      instructionsPath,
      instructions,
      schema,
      examplesPaths
    });
  }
  extensions.sort((a, b) => {
    if (a.name < b.name) return -1;
    if (a.name > b.name) return 1;
    return 0;
  });
  return extensions;
}
function validateSchema(raw) {
  const memoryTypes = (() => {
    if (!Array.isArray(raw.memoryTypes)) return void 0;
    const valid = raw.memoryTypes.filter(
      (t) => typeof t === "string" && VALID_MEMORY_TYPES.has(t)
    );
    return valid.length > 0 ? valid : void 0;
  })();
  const groupingHints = (() => {
    if (!Array.isArray(raw.groupingHints)) return void 0;
    const valid = raw.groupingHints.filter(
      (h) => typeof h === "string" && h.length > 0
    );
    return valid.length > 0 ? valid : void 0;
  })();
  const version = typeof raw.version === "string" && raw.version.length > 0 ? raw.version : void 0;
  return {
    ...memoryTypes ? { memoryTypes } : {},
    ...groupingHints ? { groupingHints } : {},
    ...version ? { version } : {}
  };
}
function isFileNotFoundError(err) {
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
}
async function isSymlink(filePath) {
  try {
    const s = await lstat(filePath);
    return s.isSymbolicLink();
  } catch {
    return false;
  }
}
function resolveExtensionsRoot(config) {
  if (config.memoryExtensionsRoot.length > 0) {
    return config.memoryExtensionsRoot;
  }
  return path.join(path.dirname(config.memoryDir), "memory_extensions");
}

// src/memory-extension-host/render-extensions-block.ts
function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}
function renderExtensionsBlock(extensions) {
  if (extensions.length === 0) return "";
  const header = `## Active memory extensions

You are running with the following third-party memory extensions. Each
extension's \`instructions.md\` tells you how to interpret memories that
extension produces or curates.

`;
  let budget = REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT;
  budget -= estimateTokens(header);
  const inlined = [];
  const omitted = [];
  for (const ext of extensions) {
    const block = `### remnic-extension/${ext.name}
\`\`\`
${ext.instructions}
\`\`\`

`;
    const cost = estimateTokens(block);
    if (cost <= budget) {
      inlined.push(block);
      budget -= cost;
    } else {
      omitted.push(ext.name);
    }
  }
  let result = header;
  result += inlined.join("");
  if (omitted.length > 0) {
    result += `> **Note:** ${omitted.length} extension(s) omitted due to token budget: ${omitted.join(", ")}
`;
  }
  return result;
}
function renderExtensionsFooter(extensions) {
  if (extensions.length === 0) return "";
  const names = extensions.map((ext) => ext.name).join(", ");
  return `Active extensions: ${names}`;
}

export {
  REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT,
  discoverMemoryExtensions,
  resolveExtensionsRoot,
  renderExtensionsBlock,
  renderExtensionsFooter
};
//# sourceMappingURL=chunk-EJI5XIBB.js.map