@ls-stack/agent-eval 0.42.3 → 0.45.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.
@@ -4,8 +4,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { z, z as z$1 } from "zod/v4";
5
5
  import dayjs from "dayjs";
6
6
  import { Blob as Blob$1, Buffer as Buffer$1, File as File$1 } from "node:buffer";
7
- import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
8
- import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
8
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
9
  import { createHash, randomUUID } from "node:crypto";
10
10
  import { getCompositeKey } from "@ls-stack/utils/getCompositeKey";
11
11
  import { existsSync } from "node:fs";
@@ -344,7 +344,12 @@ const cacheEntrySchema = z.object({
344
344
  storedAt: z.string(),
345
345
  recording: cacheRecordingSchema
346
346
  });
347
- /** Debug-only raw key metadata stored outside the reusable cache entry. */
347
+ /**
348
+ * Debug-only raw key metadata stored outside the reusable cache entry.
349
+ *
350
+ * Debug entries mirror the serialized cache entry so inspecting one debug file
351
+ * shows both the authored raw key and the persisted payload for that key.
352
+ */
348
353
  const cacheDebugKeyEntrySchema = z.object({
349
354
  version: z.literal(1),
350
355
  key: z.string(),
@@ -352,17 +357,16 @@ const cacheDebugKeyEntrySchema = z.object({
352
357
  operationType: cacheOperationTypeSchema,
353
358
  operationName: z.string(),
354
359
  storedAt: z.string(),
355
- rawKey: z.unknown()
360
+ rawKey: z.unknown(),
361
+ entry: cacheEntrySchema
356
362
  });
357
363
  cacheEntrySchema.extend({ debugKey: cacheDebugKeyEntrySchema.optional() });
358
- /** Persisted per-owner cache file containing multiple cache entries. */
359
- const cacheFileSchema = z.object({
364
+ z.object({
360
365
  version: z.literal(1),
361
366
  owner: z.string(),
362
367
  entries: z.record(z.string(), cacheEntrySchema)
363
368
  });
364
- /** Persisted per-owner debug file containing raw cache key metadata. */
365
- const cacheDebugKeyFileSchema = z.object({
369
+ z.object({
366
370
  version: z.literal(1),
367
371
  owner: z.string(),
368
372
  entries: z.record(z.string(), cacheDebugKeyEntrySchema)
@@ -4176,7 +4180,7 @@ function createTraceCache(generateSpanId) {
4176
4180
  if (!scope) return await fn();
4177
4181
  const cacheCtx = scope.cacheContext;
4178
4182
  if (cacheCtx === void 0 || scope.replayingDepth > 0) return await fn();
4179
- const namespace = info.namespace ?? `${cacheCtx.evalId}__${info.name}`;
4183
+ const namespace = info.namespace ?? `${cacheCtx.evalId}.${info.name}`;
4180
4184
  const keyHash = await hashCacheKey({
4181
4185
  namespace,
4182
4186
  key: info.key
@@ -4835,12 +4839,14 @@ const cacheSerializationMarker = "__aecs";
4835
4839
  const supportedCacheSerializationPrefix = "v1:";
4836
4840
  const externalJsonCacheSerializationMarker = "v1:ExternalJson";
4837
4841
  const externalJsonBlobExtension = ".json.br";
4842
+ const cacheEntryExtension = ".json.br";
4843
+ const debugEntryExtension = ".json";
4838
4844
  /**
4839
4845
  * Create a filesystem-backed cache adapter rooted at `<workspaceRoot>/<dir>`.
4840
4846
  *
4841
- * Cache entries are grouped into one inspectable JSON file per cache owner.
4842
- * Writes use a short-lived lock directory plus `<name>.tmp` + atomic
4843
- * `rename` to avoid partial reads and lost updates under concurrent access.
4847
+ * Cache entries are stored as one Brotli-compressed JSON file per entry, nested
4848
+ * under a sanitized namespace directory. Debug sidecars mirror one key per file
4849
+ * and include the authored raw key plus the serialized cache entry.
4844
4850
  */
4845
4851
  function createFsCacheStore(options) {
4846
4852
  const cacheDir = resolve(options.workspaceRoot, options.dir ?? ".agent-evals/cache");
@@ -4860,76 +4866,64 @@ function createFsCacheStore(options) {
4860
4866
  return blobDir;
4861
4867
  },
4862
4868
  async lookup(namespace, keyHash) {
4863
- const entry = (await readCacheFile(cacheDir, ownerFromNamespace(namespace)))?.entries[keyHash] ?? null;
4869
+ const entry = await readCacheEntry(cacheDir, namespace, keyHash);
4864
4870
  return entry === null ? null : await materializeExternalJsonCacheEntry(entry, externalJsonStore);
4865
4871
  },
4866
4872
  async lookupWithDebug(namespace, keyHash) {
4867
- const owner = ownerFromNamespace(namespace);
4868
- const rawEntry = (await readCacheFile(cacheDir, owner))?.entries[keyHash] ?? null;
4869
- const entry = rawEntry === null ? null : await materializeExternalJsonCacheEntry(rawEntry, externalJsonStore);
4870
- if (entry === null) return null;
4871
- const debugKey = (await readDebugKeyFile(debugDir, owner))?.entries[keyHash];
4873
+ const rawEntry = await readCacheEntry(cacheDir, namespace, keyHash);
4874
+ if (rawEntry === null) return null;
4875
+ const entry = await materializeExternalJsonCacheEntry(rawEntry, externalJsonStore);
4876
+ const debugKey = await readDebugEntry(debugDir, namespace, keyHash);
4872
4877
  const deserializedEntry = {
4873
4878
  ...entry,
4874
4879
  recording: deserializeCacheRecording(entry.recording)
4875
4880
  };
4876
- return debugKey === void 0 ? deserializedEntry : {
4881
+ return debugKey === null ? deserializedEntry : {
4877
4882
  ...deserializedEntry,
4878
4883
  debugKey
4879
4884
  };
4880
4885
  },
4881
4886
  async write(entry, debugKey) {
4882
- const owner = ownerFromNamespace(entry.namespace);
4883
- const filePath = ownerPath(cacheDir, owner);
4884
- await mkdir(cacheDir, { recursive: true });
4885
- await withCacheFileLock(filePath, async () => {
4886
- await writeCacheFile(cacheDir, {
4887
- version: 1,
4888
- owner,
4889
- entries: pruneEntries({
4890
- ...(await readCacheFile(cacheDir, owner))?.entries ?? {},
4891
- [entry.key]: entry
4892
- }, entry.namespace, maxEntriesForNamespace(entry.namespace, defaultMaxEntries, options.maxEntriesByNamespace), entry.key)
4893
- });
4894
- });
4895
- await pruneExternalJsonBlobs(cacheDir, blobDir);
4887
+ const maxEntries = maxEntriesForNamespace(entry.namespace, defaultMaxEntries, options.maxEntriesByNamespace);
4888
+ await withCacheFileLock(namespaceLockPath(cacheDir, entry.namespace), () => writeCompressedCacheEntry(cacheDir, entry));
4896
4889
  if (debugKey !== void 0) {
4897
4890
  if ((await resultify(() => writeDebugKeyEntry({
4898
4891
  debugDir,
4899
4892
  entry,
4900
- debugKey,
4901
- maxEntries: maxEntriesForNamespace(entry.namespace, defaultMaxEntries, options.maxEntriesByNamespace)
4893
+ debugKey
4902
4894
  }))).error) await resultify(() => clearDebugEntries(debugDir, {
4903
4895
  namespace: entry.namespace,
4904
4896
  key: entry.key
4905
4897
  }));
4906
4898
  }
4899
+ await pruneEntriesForNamespace({
4900
+ cacheDir,
4901
+ debugDir,
4902
+ namespace: entry.namespace,
4903
+ maxEntries,
4904
+ protectedKey: entry.key
4905
+ });
4906
+ await pruneExternalJsonBlobs(cacheDir, blobDir);
4907
4907
  },
4908
4908
  async list() {
4909
- if (!existsSync(cacheDir)) return [];
4910
- const files = await readdir(cacheDir);
4909
+ const files = await listCacheEntryFiles(cacheDir);
4911
4910
  const items = [];
4912
- for (const fileName of files) {
4913
- if (!fileName.endsWith(".json")) continue;
4914
- const filePath = join(cacheDir, fileName);
4915
- const fileStatResult = await resultify(() => stat(filePath));
4916
- if (fileStatResult.error || !fileStatResult.value.isFile()) continue;
4917
- const cacheFile = await readCacheFilePath(filePath);
4918
- if (cacheFile === null) continue;
4919
- for (const entry of Object.values(cacheFile.entries)) {
4920
- const operationType = entry.operationType ?? "span";
4921
- const operationName = entry.operationName ?? entry.spanName ?? entry.namespace;
4922
- items.push({
4923
- key: entry.key,
4924
- namespace: entry.namespace,
4925
- operationType,
4926
- operationName,
4927
- spanName: entry.spanName,
4928
- spanKind: entry.spanKind,
4929
- storedAt: entry.storedAt,
4930
- sizeBytes: Buffer.byteLength(JSON.stringify(entry), "utf8")
4931
- });
4932
- }
4911
+ for (const filePath of files) {
4912
+ const fileEntry = await readCacheEntryFilePath(filePath);
4913
+ if (fileEntry === null || !entryMatchesPath(filePath, fileEntry.entry)) continue;
4914
+ const entry = fileEntry.entry;
4915
+ const operationType = entry.operationType ?? "span";
4916
+ const operationName = entry.operationName ?? entry.spanName ?? entry.namespace;
4917
+ items.push({
4918
+ key: entry.key,
4919
+ namespace: entry.namespace,
4920
+ operationType,
4921
+ operationName,
4922
+ spanName: entry.spanName,
4923
+ spanKind: entry.spanKind,
4924
+ storedAt: entry.storedAt,
4925
+ sizeBytes: fileEntry.sizeBytes
4926
+ });
4933
4927
  }
4934
4928
  items.sort((a, b) => a.storedAt < b.storedAt ? 1 : -1);
4935
4929
  return items;
@@ -4951,41 +4945,12 @@ function createFsCacheStore(options) {
4951
4945
  return;
4952
4946
  }
4953
4947
  if (filter.namespace !== void 0) {
4954
- const owner = ownerFromNamespace(filter.namespace);
4955
- const filePath = ownerPath(cacheDir, owner);
4956
- if (existsSync(cacheDir)) await withCacheFileLock(filePath, async () => {
4957
- const cacheFile = await readCacheFile(cacheDir, owner);
4958
- if (cacheFile === null) return;
4959
- await writeOrRemoveCacheFile(cacheDir, {
4960
- version: 1,
4961
- owner,
4962
- entries: Object.fromEntries(Object.entries(cacheFile.entries).filter(([key, entry]) => {
4963
- if (filter.key !== void 0) return key !== filter.key;
4964
- return entry.namespace !== filter.namespace;
4965
- }))
4966
- });
4967
- });
4948
+ await clearCacheEntries(cacheDir, filter);
4968
4949
  await clearDebugEntries(debugDir, filter);
4969
4950
  await pruneExternalJsonBlobs(cacheDir, blobDir);
4970
4951
  return;
4971
4952
  }
4972
- if (existsSync(cacheDir)) {
4973
- const files = await readdir(cacheDir);
4974
- for (const fileName of files) {
4975
- if (!fileName.endsWith(".json")) continue;
4976
- const filePath = join(cacheDir, fileName);
4977
- await withCacheFileLock(filePath, async () => {
4978
- const cacheFile = await readCacheFilePath(filePath);
4979
- if (cacheFile === null) return;
4980
- const entries = Object.fromEntries(Object.entries(cacheFile.entries).filter(([key]) => key !== filter.key));
4981
- await writeOrRemoveCacheFile(cacheDir, {
4982
- version: 1,
4983
- owner: cacheFile.owner,
4984
- entries
4985
- });
4986
- });
4987
- }
4988
- }
4953
+ await clearCacheEntries(cacheDir, filter);
4989
4954
  await clearDebugEntries(debugDir, filter);
4990
4955
  await pruneExternalJsonBlobs(cacheDir, blobDir);
4991
4956
  }
@@ -5030,19 +4995,185 @@ function maxEntriesForNamespace(namespace, defaultMaxEntries, maxEntriesByNamesp
5030
4995
  const namespaceMaxEntries = maxEntriesByNamespace?.[namespace];
5031
4996
  return namespaceMaxEntries === void 0 ? defaultMaxEntries : normalizeMaxEntries(namespaceMaxEntries, defaultMaxEntries);
5032
4997
  }
5033
- function ownerFromNamespace(namespace) {
5034
- const [owner] = namespace.split("__");
5035
- return owner === void 0 || owner.length === 0 ? namespace : owner;
5036
- }
5037
- function ownerPath(cacheDir, owner) {
5038
- return join(cacheDir, `${sanitizeSegment$1(owner)}.json`);
5039
- }
5040
4998
  function toPendingKey(namespace, keyHash) {
5041
4999
  return `${namespace}::${keyHash}`;
5042
5000
  }
5043
5001
  function sanitizeSegment$1(segment) {
5044
5002
  return segment.replace(/[^a-zA-Z0-9_.-]/g, "_");
5045
5003
  }
5004
+ function namespaceDirPath(rootDir, namespace) {
5005
+ return join(rootDir, sanitizeSegment$1(namespace));
5006
+ }
5007
+ function namespaceLockPath(rootDir, namespace) {
5008
+ return join(rootDir, `${sanitizeSegment$1(namespace)}.namespace`);
5009
+ }
5010
+ function cacheEntryPath(cacheDir, namespace, key) {
5011
+ return entryPath({
5012
+ rootDir: cacheDir,
5013
+ namespace,
5014
+ key,
5015
+ extension: cacheEntryExtension
5016
+ });
5017
+ }
5018
+ function debugEntryPath(debugDir, namespace, key) {
5019
+ return entryPath({
5020
+ rootDir: debugDir,
5021
+ namespace,
5022
+ key,
5023
+ extension: debugEntryExtension
5024
+ });
5025
+ }
5026
+ function entryPath(params) {
5027
+ const namespaceDir = resolve(namespaceDirPath(params.rootDir, params.namespace));
5028
+ const filePath = resolve(namespaceDir, `${params.key}${params.extension}`);
5029
+ if (filePath !== namespaceDir && !filePath.startsWith(`${namespaceDir}${sep}`)) throw new Error(`Cache entry key escapes namespace directory: ${params.key}`);
5030
+ return filePath;
5031
+ }
5032
+ async function readCacheEntry(cacheDir, namespace, key) {
5033
+ return (await readCacheEntryFilePath(cacheEntryPath(cacheDir, namespace, key), {
5034
+ namespace,
5035
+ key
5036
+ }))?.entry ?? null;
5037
+ }
5038
+ async function readCacheEntryFilePath(filePath, expected) {
5039
+ if (!existsSync(filePath)) return null;
5040
+ const compressedResult = await resultify(() => readFile(filePath));
5041
+ if (compressedResult.error) return null;
5042
+ const rawResult = resultify(() => brotliDecompressSync(compressedResult.value).toString("utf8"));
5043
+ if (rawResult.error) return null;
5044
+ const json = safeJsonParse(rawResult.value);
5045
+ if (json === null) return null;
5046
+ const parsed = cacheEntrySchema.safeParse(json);
5047
+ if (!parsed.success) return null;
5048
+ const entry = parsed.data;
5049
+ if (!usesSupportedCacheSerialization(entry.recording)) return null;
5050
+ if (expected !== void 0 && (entry.namespace !== expected.namespace || entry.key !== expected.key)) return null;
5051
+ return {
5052
+ entry,
5053
+ sizeBytes: compressedResult.value.byteLength
5054
+ };
5055
+ }
5056
+ async function writeCompressedCacheEntry(cacheDir, entry) {
5057
+ const filePath = cacheEntryPath(cacheDir, entry.namespace, entry.key);
5058
+ const rawJson = JSON.stringify(entry);
5059
+ await writeAtomicFile(filePath, brotliCompressSync(Buffer.from(rawJson, "utf8")));
5060
+ }
5061
+ async function readDebugEntry(debugDir, namespace, key) {
5062
+ return readDebugEntryFilePath(debugEntryPath(debugDir, namespace, key), {
5063
+ namespace,
5064
+ key
5065
+ });
5066
+ }
5067
+ async function readDebugEntryFilePath(filePath, expected) {
5068
+ if (!existsSync(filePath)) return null;
5069
+ const rawResult = await resultify(() => readFile(filePath, "utf8"));
5070
+ if (rawResult.error) return null;
5071
+ const json = safeJsonParse(rawResult.value);
5072
+ if (json === null) return null;
5073
+ const parsed = cacheDebugKeyEntrySchema.safeParse(json);
5074
+ if (!parsed.success) return null;
5075
+ const entry = parsed.data;
5076
+ if (entry.entry.namespace !== entry.namespace || entry.entry.key !== entry.key || !usesSupportedCacheSerialization(entry.entry.recording)) return null;
5077
+ if (expected !== void 0 && (entry.namespace !== expected.namespace || entry.key !== expected.key)) return null;
5078
+ return entry;
5079
+ }
5080
+ async function writeDebugKeyEntry(params) {
5081
+ const { debugDir, entry, debugKey } = params;
5082
+ const debugEntry = {
5083
+ version: 1,
5084
+ key: entry.key,
5085
+ namespace: entry.namespace,
5086
+ operationType: debugKey.operationType,
5087
+ operationName: debugKey.operationName,
5088
+ storedAt: entry.storedAt,
5089
+ rawKey: debugKey.rawKey,
5090
+ entry
5091
+ };
5092
+ await withCacheFileLock(namespaceLockPath(debugDir, entry.namespace), () => writePrettyDebugEntry(debugDir, debugEntry));
5093
+ }
5094
+ async function writePrettyDebugEntry(debugDir, entry) {
5095
+ await writeAtomicFile(debugEntryPath(debugDir, entry.namespace, entry.key), JSON.stringify(entry, null, 2));
5096
+ }
5097
+ async function writeAtomicFile(filePath, contents) {
5098
+ await mkdir(dirname(filePath), { recursive: true });
5099
+ const tmpPath = `${filePath}.${process.pid.toString()}.${randomUUID()}.tmp`;
5100
+ await writeFile(tmpPath, contents);
5101
+ await rename(tmpPath, filePath);
5102
+ }
5103
+ async function clearCacheEntries(cacheDir, filter) {
5104
+ const files = filter.namespace === void 0 ? await listCacheEntryFiles(cacheDir) : await listCacheEntryFiles(namespaceDirPath(cacheDir, filter.namespace));
5105
+ for (const filePath of files) {
5106
+ const fileEntry = await readCacheEntryFilePath(filePath);
5107
+ if (fileEntry === null) continue;
5108
+ const entry = fileEntry.entry;
5109
+ if (!entryMatchesFilter(entry, filter)) continue;
5110
+ await withCacheFileLock(namespaceLockPath(cacheDir, entry.namespace), () => rm(filePath, { force: true }));
5111
+ }
5112
+ if (filter.namespace !== void 0) await removeDirIfEmpty(namespaceDirPath(cacheDir, filter.namespace));
5113
+ }
5114
+ async function clearDebugEntries(debugDir, filter) {
5115
+ const files = filter.namespace === void 0 ? await listDebugEntryFiles(debugDir) : await listDebugEntryFiles(namespaceDirPath(debugDir, filter.namespace));
5116
+ for (const filePath of files) {
5117
+ const entry = await readDebugEntryFilePath(filePath);
5118
+ if (entry === null || !entryMatchesFilter(entry, filter)) continue;
5119
+ await withCacheFileLock(namespaceLockPath(debugDir, entry.namespace), () => rm(filePath, { force: true }));
5120
+ }
5121
+ if (filter.namespace !== void 0) await removeDirIfEmpty(namespaceDirPath(debugDir, filter.namespace));
5122
+ }
5123
+ function entryMatchesFilter(entry, filter) {
5124
+ if (filter.namespace !== void 0 && entry.namespace !== filter.namespace) return false;
5125
+ return filter.key === void 0 || entry.key === filter.key;
5126
+ }
5127
+ async function pruneEntriesForNamespace(params) {
5128
+ const { cacheDir, debugDir, namespace, maxEntries, protectedKey } = params;
5129
+ await withCacheFileLock(namespaceLockPath(cacheDir, namespace), async () => {
5130
+ const keptKeys = await pruneCacheEntriesForNamespace(cacheDir, namespace, maxEntries, protectedKey);
5131
+ await withCacheFileLock(namespaceLockPath(debugDir, namespace), () => pruneDebugEntriesForNamespace(debugDir, namespace, keptKeys));
5132
+ });
5133
+ }
5134
+ async function pruneCacheEntriesForNamespace(cacheDir, namespace, maxEntries, protectedKey) {
5135
+ const entries = await listCacheEntriesForNamespace(cacheDir, namespace);
5136
+ const sorted = entries.toSorted((a, b) => a.entry.storedAt < b.entry.storedAt ? 1 : -1);
5137
+ const keptKeys = /* @__PURE__ */ new Set();
5138
+ const protectedEntry = entries.find((item) => item.entry.key === protectedKey);
5139
+ if (protectedEntry !== void 0) keptKeys.add(protectedEntry.entry.key);
5140
+ for (const item of sorted) {
5141
+ if (keptKeys.size >= maxEntries) break;
5142
+ keptKeys.add(item.entry.key);
5143
+ }
5144
+ for (const item of entries) if (!keptKeys.has(item.entry.key)) await rm(item.filePath, { force: true });
5145
+ await removeDirIfEmpty(namespaceDirPath(cacheDir, namespace));
5146
+ return keptKeys;
5147
+ }
5148
+ async function pruneDebugEntriesForNamespace(debugDir, namespace, keptKeys) {
5149
+ const files = await listDebugEntryFiles(namespaceDirPath(debugDir, namespace));
5150
+ for (const filePath of files) {
5151
+ const entry = await readDebugEntryFilePath(filePath);
5152
+ if (entry !== null && entry.namespace === namespace && !keptKeys.has(entry.key)) await rm(filePath, { force: true });
5153
+ }
5154
+ await removeDirIfEmpty(namespaceDirPath(debugDir, namespace));
5155
+ }
5156
+ async function listCacheEntriesForNamespace(cacheDir, namespace) {
5157
+ const files = await listCacheEntryFiles(namespaceDirPath(cacheDir, namespace));
5158
+ const entries = [];
5159
+ for (const filePath of files) {
5160
+ const fileEntry = await readCacheEntryFilePath(filePath);
5161
+ if (fileEntry !== null && fileEntry.entry.namespace === namespace && entryMatchesPath(filePath, fileEntry.entry)) entries.push({
5162
+ filePath,
5163
+ entry: fileEntry.entry
5164
+ });
5165
+ }
5166
+ return entries;
5167
+ }
5168
+ function entryMatchesPath(filePath, entry) {
5169
+ return basename(filePath) === `${entry.key}${cacheEntryExtension}` && basename(dirname(filePath)) === sanitizeSegment$1(entry.namespace);
5170
+ }
5171
+ function usesSupportedCacheSerialization(value) {
5172
+ if (Array.isArray(value)) return value.every(usesSupportedCacheSerialization);
5173
+ if (!isRecordLike(value)) return true;
5174
+ if (Object.hasOwn(value, cacheSerializationMarker) && (typeof value[cacheSerializationMarker] !== "string" || !value[cacheSerializationMarker].startsWith(supportedCacheSerializationPrefix))) return false;
5175
+ return Object.values(value).every(usesSupportedCacheSerialization);
5176
+ }
5046
5177
  function createExternalJsonBlobStore(blobDir) {
5047
5178
  return {
5048
5179
  async write(rawJson) {
@@ -5051,12 +5182,7 @@ function createExternalJsonBlobStore(blobDir) {
5051
5182
  const path = externalJsonBlobPath(hash);
5052
5183
  const compressed = brotliCompressSync(rawBytes);
5053
5184
  const filePath = resolveStorePath(blobDir, path);
5054
- if (!existsSync(filePath)) {
5055
- await mkdir(dirname(filePath), { recursive: true });
5056
- const tmpPath = `${filePath}.${process.pid.toString()}.tmp`;
5057
- await writeFile(tmpPath, compressed);
5058
- await rename(tmpPath, filePath);
5059
- }
5185
+ if (!existsSync(filePath)) await writeAtomicFile(filePath, compressed);
5060
5186
  return {
5061
5187
  compressedLength: compressed.byteLength,
5062
5188
  hash,
@@ -5097,13 +5223,10 @@ async function pruneExternalJsonBlobs(cacheDir, blobDir) {
5097
5223
  }
5098
5224
  async function collectReferencedExternalJsonBlobPaths(cacheDir) {
5099
5225
  const paths = /* @__PURE__ */ new Set();
5100
- if (!existsSync(cacheDir)) return paths;
5101
- const files = await readdir(cacheDir);
5102
- for (const fileName of files) {
5103
- if (!fileName.endsWith(".json")) continue;
5104
- const cacheFile = await readCacheFilePath(join(cacheDir, fileName));
5105
- if (cacheFile === null) continue;
5106
- collectExternalJsonBlobPaths(cacheFile, paths);
5226
+ for (const filePath of await listCacheEntryFiles(cacheDir)) {
5227
+ const fileEntry = await readCacheEntryFilePath(filePath);
5228
+ if (fileEntry === null || !entryMatchesPath(filePath, fileEntry.entry)) continue;
5229
+ collectExternalJsonBlobPaths(fileEntry.entry, paths);
5107
5230
  }
5108
5231
  return paths;
5109
5232
  }
@@ -5122,8 +5245,9 @@ async function listExternalJsonBlobPaths(blobDir) {
5122
5245
  return paths;
5123
5246
  }
5124
5247
  async function collectExternalJsonBlobFilePaths(root, dir, paths) {
5125
- const entries = await readdir(dir, { withFileTypes: true });
5126
- for (const entry of entries) {
5248
+ const entriesResult = await resultify(() => readdir(dir, { withFileTypes: true }));
5249
+ if (entriesResult.error) return;
5250
+ for (const entry of entriesResult.value) {
5127
5251
  const path = join(dir, entry.name);
5128
5252
  if (entry.isDirectory()) {
5129
5253
  await collectExternalJsonBlobFilePaths(root, path, paths);
@@ -5132,153 +5256,38 @@ async function collectExternalJsonBlobFilePaths(root, dir, paths) {
5132
5256
  if (entry.isFile() && entry.name.endsWith(externalJsonBlobExtension)) paths.push(relative(root, path));
5133
5257
  }
5134
5258
  }
5135
- async function readCacheFile(cacheDir, owner) {
5136
- return readCacheFilePath(ownerPath(cacheDir, owner));
5259
+ async function listCacheEntryFiles(rootDir) {
5260
+ return listFilesWithExtension(rootDir, cacheEntryExtension);
5137
5261
  }
5138
- async function readCacheFilePath(filePath) {
5139
- if (!existsSync(filePath)) return null;
5140
- const rawResult = await resultify(() => readFile(filePath, "utf-8"));
5141
- if (rawResult.error) return null;
5142
- const json = safeJsonParse(rawResult.value);
5143
- if (json === null) return null;
5144
- const parsed = cacheFileSchema.safeParse(json);
5145
- if (!parsed.success) return null;
5146
- return {
5147
- ...parsed.data,
5148
- entries: Object.fromEntries(Object.entries(parsed.data.entries).filter(([, entry]) => usesSupportedCacheSerialization(entry.recording)))
5149
- };
5150
- }
5151
- function usesSupportedCacheSerialization(value) {
5152
- if (Array.isArray(value)) return value.every(usesSupportedCacheSerialization);
5153
- if (!isRecordLike(value)) return true;
5154
- if (Object.hasOwn(value, cacheSerializationMarker) && (typeof value[cacheSerializationMarker] !== "string" || !value[cacheSerializationMarker].startsWith(supportedCacheSerializationPrefix))) return false;
5155
- return Object.values(value).every(usesSupportedCacheSerialization);
5262
+ async function listDebugEntryFiles(rootDir) {
5263
+ return listFilesWithExtension(rootDir, debugEntryExtension);
5156
5264
  }
5157
- async function writeOrRemoveCacheFile(cacheDir, cacheFile) {
5158
- if (Object.keys(cacheFile.entries).length === 0) {
5159
- await rm(ownerPath(cacheDir, cacheFile.owner), { force: true });
5160
- return;
5265
+ async function listFilesWithExtension(rootDir, extension) {
5266
+ if (!existsSync(rootDir)) return [];
5267
+ const entriesResult = await resultify(() => readdir(rootDir, { withFileTypes: true }));
5268
+ if (entriesResult.error) return [];
5269
+ const files = [];
5270
+ for (const entry of entriesResult.value) {
5271
+ const filePath = join(rootDir, entry.name);
5272
+ if (entry.isDirectory()) {
5273
+ files.push(...await listFilesWithExtension(filePath, extension));
5274
+ continue;
5275
+ }
5276
+ if (entry.isFile() && entry.name.endsWith(extension)) files.push(filePath);
5161
5277
  }
5162
- await writeCacheFile(cacheDir, cacheFile);
5163
- }
5164
- async function writeCacheFile(cacheDir, cacheFile) {
5165
- await mkdir(cacheDir, { recursive: true });
5166
- const filePath = ownerPath(cacheDir, cacheFile.owner);
5167
- const tmpPath = `${filePath}.${process.pid.toString()}.tmp`;
5168
- await writeFile(tmpPath, JSON.stringify(cacheFile, null, 2));
5169
- await rename(tmpPath, filePath);
5170
- }
5171
- async function readDebugKeyFile(debugDir, owner) {
5172
- return readDebugKeyFilePath(ownerPath(debugDir, owner));
5278
+ return files;
5173
5279
  }
5174
- async function readDebugKeyFilePath(filePath) {
5175
- if (!existsSync(filePath)) return null;
5176
- const rawResult = await resultify(() => readFile(filePath, "utf-8"));
5177
- if (rawResult.error) return null;
5178
- const json = safeJsonParse(rawResult.value);
5179
- if (json === null) return null;
5180
- const parsed = cacheDebugKeyFileSchema.safeParse(json);
5181
- if (!parsed.success) return null;
5182
- return parsed.data;
5183
- }
5184
- async function writeDebugKeyEntry(params) {
5185
- const { debugDir, entry, debugKey, maxEntries } = params;
5186
- const owner = ownerFromNamespace(entry.namespace);
5187
- const filePath = ownerPath(debugDir, owner);
5188
- await mkdir(debugDir, { recursive: true });
5189
- await withCacheFileLock(filePath, async () => {
5190
- const entries = (await readDebugKeyFile(debugDir, owner))?.entries ?? {};
5191
- const debugEntry = {
5192
- version: 1,
5193
- key: entry.key,
5194
- namespace: entry.namespace,
5195
- operationType: debugKey.operationType,
5196
- operationName: debugKey.operationName,
5197
- storedAt: entry.storedAt,
5198
- rawKey: debugKey.rawKey
5199
- };
5200
- await writeDebugKeyFile(debugDir, {
5201
- version: 1,
5202
- owner,
5203
- entries: pruneDebugKeyEntries({
5204
- ...entries,
5205
- [entry.key]: debugEntry
5206
- }, entry.namespace, maxEntries, entry.key)
5207
- });
5280
+ async function removeDirIfEmpty(dirPath) {
5281
+ const entriesResult = await resultify(() => readdir(dirPath));
5282
+ if (entriesResult.error || entriesResult.value.length > 0) return;
5283
+ await rm(dirPath, {
5284
+ recursive: true,
5285
+ force: true
5208
5286
  });
5209
5287
  }
5210
- async function clearDebugEntries(debugDir, filter) {
5211
- if (!existsSync(debugDir)) return;
5212
- if (filter.namespace !== void 0) {
5213
- const owner = ownerFromNamespace(filter.namespace);
5214
- await withCacheFileLock(ownerPath(debugDir, owner), async () => {
5215
- const debugFile = await readDebugKeyFile(debugDir, owner);
5216
- if (debugFile === null) return;
5217
- await writeOrRemoveDebugKeyFile(debugDir, {
5218
- version: 1,
5219
- owner,
5220
- entries: Object.fromEntries(Object.entries(debugFile.entries).filter(([key, entry]) => {
5221
- if (filter.key !== void 0) return key !== filter.key;
5222
- return entry.namespace !== filter.namespace;
5223
- }))
5224
- });
5225
- });
5226
- return;
5227
- }
5228
- const files = await readdir(debugDir);
5229
- for (const fileName of files) {
5230
- if (!fileName.endsWith(".json")) continue;
5231
- const filePath = join(debugDir, fileName);
5232
- await withCacheFileLock(filePath, async () => {
5233
- const debugFile = await readDebugKeyFilePath(filePath);
5234
- if (debugFile === null) return;
5235
- const entries = Object.fromEntries(Object.entries(debugFile.entries).filter(([key]) => key !== filter.key));
5236
- await writeOrRemoveDebugKeyFile(debugDir, {
5237
- version: 1,
5238
- owner: debugFile.owner,
5239
- entries
5240
- });
5241
- });
5242
- }
5243
- }
5244
- async function writeOrRemoveDebugKeyFile(debugDir, debugFile) {
5245
- if (Object.keys(debugFile.entries).length === 0) {
5246
- await rm(ownerPath(debugDir, debugFile.owner), { force: true });
5247
- return;
5248
- }
5249
- await writeDebugKeyFile(debugDir, debugFile);
5250
- }
5251
- async function writeDebugKeyFile(debugDir, debugFile) {
5252
- await mkdir(debugDir, { recursive: true });
5253
- const filePath = ownerPath(debugDir, debugFile.owner);
5254
- const tmpPath = `${filePath}.${process.pid.toString()}.tmp`;
5255
- await writeFile(tmpPath, JSON.stringify(debugFile, null, 2));
5256
- await rename(tmpPath, filePath);
5257
- }
5258
- function pruneEntries(entries, namespace, maxEntries, protectedKey) {
5259
- const sorted = Object.values(entries).filter((entry) => entry.namespace === namespace).toSorted((a, b) => a.storedAt < b.storedAt ? 1 : -1);
5260
- const kept = /* @__PURE__ */ new Map();
5261
- const protectedEntry = entries[protectedKey];
5262
- if (protectedEntry?.namespace === namespace) kept.set(protectedEntry.key, protectedEntry);
5263
- for (const entry of sorted) {
5264
- if (kept.size >= maxEntries) break;
5265
- kept.set(entry.key, entry);
5266
- }
5267
- return Object.fromEntries(Object.values(entries).filter((entry) => entry.namespace !== namespace || kept.has(entry.key)).toSorted((a, b) => a.key < b.key ? -1 : 1).map((entry) => [entry.key, entry]));
5268
- }
5269
- function pruneDebugKeyEntries(entries, namespace, maxEntries, protectedKey) {
5270
- const sorted = Object.values(entries).filter((entry) => entry.namespace === namespace).toSorted((a, b) => a.storedAt < b.storedAt ? 1 : -1);
5271
- const kept = /* @__PURE__ */ new Map();
5272
- const protectedEntry = entries[protectedKey];
5273
- if (protectedEntry?.namespace === namespace) kept.set(protectedEntry.key, protectedEntry);
5274
- for (const entry of sorted) {
5275
- if (kept.size >= maxEntries) break;
5276
- kept.set(entry.key, entry);
5277
- }
5278
- return Object.fromEntries(Object.values(entries).filter((entry) => entry.namespace !== namespace || kept.has(entry.key)).toSorted((a, b) => a.key < b.key ? -1 : 1).map((entry) => [entry.key, entry]));
5279
- }
5280
5288
  async function withCacheFileLock(filePath, fn) {
5281
5289
  const lockPath = `${filePath}.lock`;
5290
+ await mkdir(dirname(lockPath), { recursive: true });
5282
5291
  await acquireLock(lockPath);
5283
5292
  const result = await resultify(fn);
5284
5293
  await rm(lockPath, {
@@ -1,2 +1,2 @@
1
- import { n as initRunner, t as getRunnerInstance } from "./runner-B1KygirW.mjs";
1
+ import { n as initRunner, t as getRunnerInstance } from "./runner-DJWn_7p0.mjs";
2
2
  export { getRunnerInstance, initRunner };
@@ -1,5 +1,5 @@
1
- import { n as createRunner } from "./cli-BeJCJMQo.mjs";
2
- import "./src-D7_xKo7h.mjs";
1
+ import { n as createRunner } from "./cli-vdJYkEVk.mjs";
2
+ import "./src-BRqs3kSA.mjs";
3
3
  //#region ../../apps/server/src/runner.ts
4
4
  let runnerInstance = null;
5
5
  function getRunnerInstance() {
@@ -1,5 +1,5 @@
1
- import { It as defineEval$1, rt as matchesEvalTags$1 } from "./runOrchestration-OVUFw1fL.mjs";
2
- import "./cli-BeJCJMQo.mjs";
1
+ import { It as defineEval$1, rt as matchesEvalTags$1 } from "./runOrchestration-BFdxG9ws.mjs";
2
+ import "./cli-vdJYkEVk.mjs";
3
3
  //#region src/index.ts
4
4
  /** Register an eval definition with typed tag support. */
5
5
  function defineEval(definition) {