@use-lattice/litmus 0.121.3
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/LICENSE +19 -0
- package/dist/src/accounts-Bt1oJb1Z.cjs +219 -0
- package/dist/src/accounts-DjOU8Rm3.js +178 -0
- package/dist/src/agentic-utils-D03IiXQc.js +153 -0
- package/dist/src/agentic-utils-Dh7xaMQM.cjs +180 -0
- package/dist/src/agents-C6BIMlZa.js +231 -0
- package/dist/src/agents-DvIpNX1L.cjs +666 -0
- package/dist/src/agents-ZP0RP9vV.cjs +231 -0
- package/dist/src/agents-maJXdjbR.js +665 -0
- package/dist/src/aimlapi-BTbQjG2E.cjs +30 -0
- package/dist/src/aimlapi-CwMxqfXP.js +30 -0
- package/dist/src/audio-BBUdvsde.cjs +97 -0
- package/dist/src/audio-D5DPZ7I-.js +97 -0
- package/dist/src/base-BEysXrkq.cjs +222 -0
- package/dist/src/base-C451JQfq.js +193 -0
- package/dist/src/blobs-BY8MDmpo.js +230 -0
- package/dist/src/blobs-BgcNn97m.cjs +256 -0
- package/dist/src/cache-BBE_lsTA.cjs +4 -0
- package/dist/src/cache-BkrqU5Ba.js +237 -0
- package/dist/src/cache-DsCxFlsZ.cjs +297 -0
- package/dist/src/chat-CPJWDP6a.cjs +289 -0
- package/dist/src/chat-CXX3xzkk.cjs +811 -0
- package/dist/src/chat-CcDgZFJ4.js +787 -0
- package/dist/src/chat-Dz5ZeGO2.js +289 -0
- package/dist/src/chatkit-Dw0mKkML.cjs +1158 -0
- package/dist/src/chatkit-swAIVuea.js +1157 -0
- package/dist/src/chunk-DEq-mXcV.js +15 -0
- package/dist/src/claude-agent-sdk-BXZJtOg6.js +379 -0
- package/dist/src/claude-agent-sdk-CkfyjDoG.cjs +383 -0
- package/dist/src/cloudflare-ai-BzpJcqUH.js +161 -0
- package/dist/src/cloudflare-ai-Cmy_R1y2.cjs +161 -0
- package/dist/src/cloudflare-gateway-B9tVQKok.cjs +272 -0
- package/dist/src/cloudflare-gateway-DrD3ew3H.js +272 -0
- package/dist/src/codex-sdk-Dezj9Nwm.js +1056 -0
- package/dist/src/codex-sdk-Dl9D4k5B.cjs +1060 -0
- package/dist/src/cometapi-C-9YvCHC.js +54 -0
- package/dist/src/cometapi-DHgDKoO2.cjs +54 -0
- package/dist/src/completion-B8Ctyxpr.js +120 -0
- package/dist/src/completion-Cxrt08sj.cjs +131 -0
- package/dist/src/createHash-BwgE13yv.cjs +27 -0
- package/dist/src/createHash-DmPQkvBh.js +15 -0
- package/dist/src/docker-BiqcTwLv.js +80 -0
- package/dist/src/docker-C7tEJnP-.cjs +80 -0
- package/dist/src/esm-C62Zofr1.cjs +409 -0
- package/dist/src/esm-DMVc93eh.js +379 -0
- package/dist/src/evalResult-C3NJPQOo.cjs +301 -0
- package/dist/src/evalResult-C7JJAPBb.js +295 -0
- package/dist/src/evalResult-DoVTZZWI.cjs +2 -0
- package/dist/src/extractor-DnMD3fwt.cjs +391 -0
- package/dist/src/extractor-DtlL28vL.js +374 -0
- package/dist/src/fetch-BTxakTSg.cjs +1133 -0
- package/dist/src/fetch-DQckpUFz.js +928 -0
- package/dist/src/fileExtensions-DnqA1y9x.js +85 -0
- package/dist/src/fileExtensions-bYh77CN8.cjs +114 -0
- package/dist/src/genaiTracer-CyZrmaK0.cjs +268 -0
- package/dist/src/genaiTracer-D3fD9dNV.js +256 -0
- package/dist/src/graders-BNscxFrU.js +13644 -0
- package/dist/src/graders-D2oE9Msq.js +2 -0
- package/dist/src/graders-c0Ez_w-9.cjs +2 -0
- package/dist/src/graders-d0F2M3e9.cjs +14056 -0
- package/dist/src/image-0ZhE0VlR.cjs +280 -0
- package/dist/src/image-CWE1pdNv.js +257 -0
- package/dist/src/image-D9ZK6hwL.js +163 -0
- package/dist/src/image-DKZgZITg.cjs +163 -0
- package/dist/src/index.cjs +11366 -0
- package/dist/src/index.d.cts +19640 -0
- package/dist/src/index.d.ts +19641 -0
- package/dist/src/index.js +11306 -0
- package/dist/src/invariant-Ddh24eXh.js +25 -0
- package/dist/src/invariant-kfQ8Bu82.cjs +30 -0
- package/dist/src/knowledgeBase-BgPyGFUd.cjs +122 -0
- package/dist/src/knowledgeBase-DyHilYaP.js +122 -0
- package/dist/src/litellm-CyMeneHS.js +135 -0
- package/dist/src/litellm-DWDF73yF.cjs +135 -0
- package/dist/src/logger-C40ZGil9.js +717 -0
- package/dist/src/logger-DyfK9PBt.cjs +917 -0
- package/dist/src/luma-ray-BAU9X_ep.cjs +315 -0
- package/dist/src/luma-ray-nwVseBbv.js +313 -0
- package/dist/src/messages-B5ADWTTv.js +245 -0
- package/dist/src/messages-BCnZfqrS.cjs +257 -0
- package/dist/src/meteor-DLZZ3osF.cjs +134 -0
- package/dist/src/meteor-DUiCJRC-.js +134 -0
- package/dist/src/modelslab-00cveB8L.cjs +163 -0
- package/dist/src/modelslab-D9sCU_L7.js +163 -0
- package/dist/src/nova-reel-CTapvqYH.js +276 -0
- package/dist/src/nova-reel-DlWuuroF.cjs +278 -0
- package/dist/src/nova-sonic-5UPWfeMv.cjs +363 -0
- package/dist/src/nova-sonic-BhSwQNym.js +363 -0
- package/dist/src/openai-BWrJK9d8.cjs +52 -0
- package/dist/src/openai-DumO8WQn.js +47 -0
- package/dist/src/openclaw-B8brrjC_.cjs +577 -0
- package/dist/src/openclaw-Bkayww9q.js +571 -0
- package/dist/src/opencode-sdk-7xjoDNiM.cjs +562 -0
- package/dist/src/opencode-sdk-SGwAPxht.js +558 -0
- package/dist/src/otlpReceiver-CoAHfAN9.cjs +15 -0
- package/dist/src/otlpReceiver-oO3EQwI9.js +14 -0
- package/dist/src/providerRegistry-4yjhaEM8.js +45 -0
- package/dist/src/providerRegistry-DhV4rJIc.cjs +50 -0
- package/dist/src/providers-B5RJVG-7.cjs +33609 -0
- package/dist/src/providers-BdmZCLzV.js +33262 -0
- package/dist/src/providers-CxtRxn8e.js +2 -0
- package/dist/src/providers-DnQLNbx1.cjs +3 -0
- package/dist/src/pythonUtils-BD0druiM.cjs +275 -0
- package/dist/src/pythonUtils-IBhn5YGR.js +249 -0
- package/dist/src/quiverai-BDOwZBsM.cjs +213 -0
- package/dist/src/quiverai-D3JTF5lD.js +213 -0
- package/dist/src/responses-B2LCDCXZ.js +667 -0
- package/dist/src/responses-BvNm4Xv9.cjs +685 -0
- package/dist/src/rubyUtils-B0NwnfpY.cjs +245 -0
- package/dist/src/rubyUtils-BroxzZ7c.cjs +2 -0
- package/dist/src/rubyUtils-hqVw5UvJ.js +222 -0
- package/dist/src/sagemaker-Cno2V-Sx.js +689 -0
- package/dist/src/sagemaker-fV_KUgs5.cjs +691 -0
- package/dist/src/server-BOuAXb06.cjs +238 -0
- package/dist/src/server-CtI-EWzm.cjs +2 -0
- package/dist/src/server-Cy3DZymt.js +189 -0
- package/dist/src/slack-CP8xBePa.js +135 -0
- package/dist/src/slack-DSQ1yXVb.cjs +135 -0
- package/dist/src/store-BwDDaBjb.cjs +246 -0
- package/dist/src/store-DcbLC593.cjs +2 -0
- package/dist/src/store-IGpqMIkv.js +240 -0
- package/dist/src/tables-3Q2cL7So.cjs +373 -0
- package/dist/src/tables-Bi2fjr4W.js +288 -0
- package/dist/src/telemetry-Bg2WqF79.js +161 -0
- package/dist/src/telemetry-D0x6u5kX.cjs +166 -0
- package/dist/src/telemetry-DXNimrI0.cjs +2 -0
- package/dist/src/text-B_UCRPp2.js +22 -0
- package/dist/src/text-CW1cyrwj.cjs +33 -0
- package/dist/src/tokenUsageUtils-NYT-WKS6.js +138 -0
- package/dist/src/tokenUsageUtils-bVa1ga6f.cjs +173 -0
- package/dist/src/transcription-Cl_W16Pr.js +122 -0
- package/dist/src/transcription-yt1EecY8.cjs +124 -0
- package/dist/src/transform-BCtGrl_W.cjs +228 -0
- package/dist/src/transform-Bv6gG2MJ.cjs +1688 -0
- package/dist/src/transform-CY1wbpRy.js +1507 -0
- package/dist/src/transform-DU8rUL9P.cjs +2 -0
- package/dist/src/transform-yWaShiKr.js +216 -0
- package/dist/src/transformersAvailability-BGkzavwb.js +35 -0
- package/dist/src/transformersAvailability-DKoRtQLy.cjs +35 -0
- package/dist/src/types-5aqHpBwE.cjs +3769 -0
- package/dist/src/types-Bn6D9c4U.js +3300 -0
- package/dist/src/util-BkKlTkI2.js +293 -0
- package/dist/src/util-CTh0bfOm.cjs +1119 -0
- package/dist/src/util-D17oBwo7.cjs +328 -0
- package/dist/src/util-DsS_-v4p.js +613 -0
- package/dist/src/util-DuntT1Ga.js +951 -0
- package/dist/src/util-aWjdCYMI.cjs +667 -0
- package/dist/src/utils-CisQwpjA.js +94 -0
- package/dist/src/utils-yWamDvmz.cjs +123 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/drizzle/0000_lush_hellion.sql +36 -0
- package/drizzle/0001_wide_calypso.sql +3 -0
- package/drizzle/0002_tidy_juggernaut.sql +1 -0
- package/drizzle/0003_lively_naoko.sql +8 -0
- package/drizzle/0004_minor_peter_quill.sql +19 -0
- package/drizzle/0005_silky_millenium_guard.sql +2 -0
- package/drizzle/0006_harsh_caretaker.sql +42 -0
- package/drizzle/0007_cloudy_wong.sql +1 -0
- package/drizzle/0008_broad_boomer.sql +2 -0
- package/drizzle/0009_strong_marten_broadcloak.sql +19 -0
- package/drizzle/0010_needy_bishop.sql +11 -0
- package/drizzle/0011_moaning_millenium_guard.sql +1 -0
- package/drizzle/0012_late_marten_broadcloak.sql +2 -0
- package/drizzle/0013_previous_dormammu.sql +9 -0
- package/drizzle/0014_lazy_captain_universe.sql +2 -0
- package/drizzle/0015_zippy_wallop.sql +29 -0
- package/drizzle/0016_jazzy_zemo.sql +2 -0
- package/drizzle/0017_reflective_praxagora.sql +4 -0
- package/drizzle/0018_fat_vanisher.sql +22 -0
- package/drizzle/0019_new_clint_barton.sql +8 -0
- package/drizzle/0020_skinny_maverick.sql +1 -0
- package/drizzle/0021_mysterious_madelyne_pryor.sql +13 -0
- package/drizzle/0022_sleepy_ultimo.sql +25 -0
- package/drizzle/0023_wooden_mandrill.sql +2 -0
- package/drizzle/AGENTS.md +68 -0
- package/drizzle/CLAUDE.md +1 -0
- package/drizzle/meta/0000_snapshot.json +221 -0
- package/drizzle/meta/0001_snapshot.json +214 -0
- package/drizzle/meta/0002_snapshot.json +221 -0
- package/drizzle/meta/0005_snapshot.json +369 -0
- package/drizzle/meta/0006_snapshot.json +638 -0
- package/drizzle/meta/0007_snapshot.json +640 -0
- package/drizzle/meta/0008_snapshot.json +649 -0
- package/drizzle/meta/0009_snapshot.json +554 -0
- package/drizzle/meta/0010_snapshot.json +619 -0
- package/drizzle/meta/0011_snapshot.json +627 -0
- package/drizzle/meta/0012_snapshot.json +639 -0
- package/drizzle/meta/0013_snapshot.json +717 -0
- package/drizzle/meta/0014_snapshot.json +717 -0
- package/drizzle/meta/0015_snapshot.json +897 -0
- package/drizzle/meta/0016_snapshot.json +1031 -0
- package/drizzle/meta/0018_snapshot.json +1210 -0
- package/drizzle/meta/0019_snapshot.json +1165 -0
- package/drizzle/meta/0020_snapshot.json +1232 -0
- package/drizzle/meta/0021_snapshot.json +1311 -0
- package/drizzle/meta/0022_snapshot.json +1481 -0
- package/drizzle/meta/0023_snapshot.json +1496 -0
- package/drizzle/meta/_journal.json +174 -0
- package/package.json +240 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { _ as getConfigDirectoryPath, r as logger } from "./logger-C40ZGil9.js";
|
|
2
|
+
import { n as blobReferencesTable, p as getDb, t as blobAssetsTable } from "./tables-Bi2fjr4W.js";
|
|
3
|
+
import * as path$1 from "node:path";
|
|
4
|
+
import * as fs$1 from "node:fs";
|
|
5
|
+
import * as fsPromises$1 from "node:fs/promises";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { and, eq } from "drizzle-orm";
|
|
8
|
+
//#region src/blobs/constants.ts
|
|
9
|
+
const BLOB_MIN_SIZE = 1024;
|
|
10
|
+
const BLOB_MAX_SIZE = 52428800;
|
|
11
|
+
const BLOB_SCHEME = "promptfoo://blob/";
|
|
12
|
+
const DEFAULT_FILESYSTEM_SUBDIR = "blobs";
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/blobs/filesystemProvider.ts
|
|
15
|
+
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
|
16
|
+
function computeHash(data) {
|
|
17
|
+
return createHash("sha256").update(data).digest("hex");
|
|
18
|
+
}
|
|
19
|
+
function buildUri(hash) {
|
|
20
|
+
return `${BLOB_SCHEME}${hash}`;
|
|
21
|
+
}
|
|
22
|
+
var FilesystemBlobStorageProvider = class {
|
|
23
|
+
providerId = "filesystem";
|
|
24
|
+
basePath;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
const defaultBase = path$1.join(getConfigDirectoryPath(true), DEFAULT_FILESYSTEM_SUBDIR);
|
|
27
|
+
this.basePath = path$1.resolve(config?.basePath || defaultBase);
|
|
28
|
+
this.ensureDirectory();
|
|
29
|
+
}
|
|
30
|
+
ensureDirectory() {
|
|
31
|
+
if (!fs$1.existsSync(this.basePath)) {
|
|
32
|
+
fs$1.mkdirSync(this.basePath, { recursive: true });
|
|
33
|
+
logger.debug("[BlobFS] Created blob directory", { basePath: this.basePath });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
assertValidHash(hash) {
|
|
37
|
+
if (!BLOB_HASH_REGEX.test(hash)) throw new Error(`[BlobFS] Invalid blob hash: "${hash}"`);
|
|
38
|
+
}
|
|
39
|
+
resolvePathInBase(unsafePath) {
|
|
40
|
+
const targetPath = path$1.isAbsolute(unsafePath) ? path$1.resolve(unsafePath) : path$1.resolve(this.basePath, unsafePath);
|
|
41
|
+
const safeBase = path$1.resolve(this.basePath) + path$1.sep;
|
|
42
|
+
if (!targetPath.startsWith(safeBase)) throw new Error("[BlobFS] Path traversal attempt detected");
|
|
43
|
+
return targetPath;
|
|
44
|
+
}
|
|
45
|
+
hashToPath(hash) {
|
|
46
|
+
this.assertValidHash(hash);
|
|
47
|
+
const dirRelative = path$1.join(hash.slice(0, 2), hash.slice(2, 4));
|
|
48
|
+
const fileRelative = path$1.join(dirRelative, hash);
|
|
49
|
+
return this.resolvePathInBase(fileRelative);
|
|
50
|
+
}
|
|
51
|
+
async ensureHashDir(hash) {
|
|
52
|
+
this.assertValidHash(hash);
|
|
53
|
+
const dirRelative = path$1.join(hash.slice(0, 2), hash.slice(2, 4));
|
|
54
|
+
const dirPath = this.resolvePathInBase(dirRelative);
|
|
55
|
+
await fsPromises$1.mkdir(dirPath, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
metadataPath(filePath) {
|
|
58
|
+
return `${filePath}.meta.json`;
|
|
59
|
+
}
|
|
60
|
+
async store(data, mimeType) {
|
|
61
|
+
const hash = computeHash(data);
|
|
62
|
+
await this.ensureHashDir(hash);
|
|
63
|
+
const filePath = this.hashToPath(hash);
|
|
64
|
+
try {
|
|
65
|
+
await fsPromises$1.access(filePath);
|
|
66
|
+
const meta = await this.readMetadata(filePath);
|
|
67
|
+
return {
|
|
68
|
+
ref: this.buildRef(hash, meta?.mimeType ?? mimeType, meta?.sizeBytes ?? data.length, meta?.provider ?? this.providerId),
|
|
69
|
+
deduplicated: true
|
|
70
|
+
};
|
|
71
|
+
} catch {}
|
|
72
|
+
await fsPromises$1.writeFile(filePath, data);
|
|
73
|
+
const metadata = {
|
|
74
|
+
mimeType,
|
|
75
|
+
sizeBytes: data.length,
|
|
76
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77
|
+
provider: this.providerId,
|
|
78
|
+
key: filePath
|
|
79
|
+
};
|
|
80
|
+
await fsPromises$1.writeFile(this.metadataPath(filePath), JSON.stringify(metadata, null, 2));
|
|
81
|
+
return {
|
|
82
|
+
ref: this.buildRef(hash, mimeType, data.length, this.providerId),
|
|
83
|
+
deduplicated: false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async getByHash(hash) {
|
|
87
|
+
const filePath = this.hashToPath(hash);
|
|
88
|
+
let data;
|
|
89
|
+
try {
|
|
90
|
+
data = await fsPromises$1.readFile(filePath);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.code === "ENOENT") throw new Error(`Blob not found: ${hash}`);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
const metadata = await this.readMetadata(filePath) || {
|
|
96
|
+
mimeType: "application/octet-stream",
|
|
97
|
+
sizeBytes: data.length,
|
|
98
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
+
provider: this.providerId,
|
|
100
|
+
key: filePath
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
data,
|
|
104
|
+
metadata
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async exists(hash) {
|
|
108
|
+
try {
|
|
109
|
+
const filePath = this.hashToPath(hash);
|
|
110
|
+
await fsPromises$1.access(filePath);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async deleteByHash(hash) {
|
|
117
|
+
try {
|
|
118
|
+
const filePath = this.hashToPath(hash);
|
|
119
|
+
const metaPath = this.metadataPath(filePath);
|
|
120
|
+
try {
|
|
121
|
+
await fsPromises$1.unlink(filePath);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error.code !== "ENOENT") throw error;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await fsPromises$1.unlink(metaPath);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error.code !== "ENOENT") throw error;
|
|
129
|
+
}
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
async getUrl(_hash, _expiresInSeconds) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
buildRef(hash, mimeType, sizeBytes, provider) {
|
|
136
|
+
return {
|
|
137
|
+
uri: buildUri(hash),
|
|
138
|
+
hash,
|
|
139
|
+
mimeType,
|
|
140
|
+
sizeBytes,
|
|
141
|
+
provider
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async readMetadata(filePath) {
|
|
145
|
+
const safeFilePath = this.resolvePathInBase(filePath);
|
|
146
|
+
const metaPath = this.metadataPath(safeFilePath);
|
|
147
|
+
try {
|
|
148
|
+
const raw = await fsPromises$1.readFile(metaPath, "utf8");
|
|
149
|
+
return JSON.parse(raw);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error.code === "ENOENT") return null;
|
|
152
|
+
logger.warn("[BlobFS] Failed to read metadata", { error });
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/blobs/index.ts
|
|
159
|
+
let defaultProvider = null;
|
|
160
|
+
function createDefaultProvider() {
|
|
161
|
+
return new FilesystemBlobStorageProvider();
|
|
162
|
+
}
|
|
163
|
+
function getBlobStorageProvider() {
|
|
164
|
+
if (!defaultProvider) {
|
|
165
|
+
defaultProvider = createDefaultProvider();
|
|
166
|
+
logger.debug("[BlobStorage] Initialized provider", { provider: defaultProvider.providerId });
|
|
167
|
+
}
|
|
168
|
+
return defaultProvider;
|
|
169
|
+
}
|
|
170
|
+
async function storeBlob(data, mimeType, refContext) {
|
|
171
|
+
const provider = getBlobStorageProvider();
|
|
172
|
+
const result = await provider.store(data, mimeType);
|
|
173
|
+
const db = getDb();
|
|
174
|
+
try {
|
|
175
|
+
db.transaction(() => {
|
|
176
|
+
const assetInsert = db.insert(blobAssetsTable).values({
|
|
177
|
+
hash: result.ref.hash,
|
|
178
|
+
sizeBytes: result.ref.sizeBytes,
|
|
179
|
+
mimeType: result.ref.mimeType,
|
|
180
|
+
provider: result.ref.provider
|
|
181
|
+
}).onConflictDoNothing().run();
|
|
182
|
+
return (refContext?.evalId && db.insert(blobReferencesTable).values({
|
|
183
|
+
id: randomUUID(),
|
|
184
|
+
blobHash: result.ref.hash,
|
|
185
|
+
evalId: refContext.evalId,
|
|
186
|
+
testIdx: refContext.testIdx,
|
|
187
|
+
promptIdx: refContext.promptIdx,
|
|
188
|
+
location: refContext.location,
|
|
189
|
+
kind: refContext.kind
|
|
190
|
+
}).onConflictDoNothing().run()) ?? assetInsert;
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
try {
|
|
194
|
+
await provider.deleteByHash(result.ref.hash);
|
|
195
|
+
} catch (cleanupError) {
|
|
196
|
+
logger.warn("[BlobStorage] Failed to rollback blob after DB error", {
|
|
197
|
+
error: cleanupError,
|
|
198
|
+
hash: result.ref.hash
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
async function recordBlobReference(hash, refContext) {
|
|
206
|
+
if (!refContext.evalId) return;
|
|
207
|
+
if (!await getBlobStorageProvider().exists(hash).catch(() => false)) {
|
|
208
|
+
logger.debug("[BlobStorage] Attempted to record reference for missing blob", {
|
|
209
|
+
hash,
|
|
210
|
+
evalId: refContext.evalId,
|
|
211
|
+
location: refContext.location
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const db = getDb();
|
|
216
|
+
if (db.select({ id: blobReferencesTable.id }).from(blobReferencesTable).where(and(eq(blobReferencesTable.blobHash, hash), eq(blobReferencesTable.evalId, refContext.evalId))).get()) return;
|
|
217
|
+
db.insert(blobReferencesTable).values({
|
|
218
|
+
id: randomUUID(),
|
|
219
|
+
blobHash: hash,
|
|
220
|
+
evalId: refContext.evalId,
|
|
221
|
+
testIdx: refContext.testIdx,
|
|
222
|
+
promptIdx: refContext.promptIdx,
|
|
223
|
+
location: refContext.location,
|
|
224
|
+
kind: refContext.kind
|
|
225
|
+
}).run();
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
export { BLOB_MIN_SIZE as i, storeBlob as n, BLOB_MAX_SIZE as r, recordBlobReference as t };
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=blobs-BY8MDmpo.js.map
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const require_logger = require("./logger-DyfK9PBt.cjs");
|
|
2
|
+
const require_tables = require("./tables-3Q2cL7So.cjs");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
node_fs = require_logger.__toESM(node_fs);
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
node_fs_promises = require_logger.__toESM(node_fs_promises);
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
node_path = require_logger.__toESM(node_path);
|
|
9
|
+
let node_crypto = require("node:crypto");
|
|
10
|
+
let drizzle_orm = require("drizzle-orm");
|
|
11
|
+
//#region src/blobs/constants.ts
|
|
12
|
+
const BLOB_MIN_SIZE = 1024;
|
|
13
|
+
const BLOB_MAX_SIZE = 52428800;
|
|
14
|
+
const BLOB_SCHEME = "promptfoo://blob/";
|
|
15
|
+
const DEFAULT_FILESYSTEM_SUBDIR = "blobs";
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/blobs/filesystemProvider.ts
|
|
18
|
+
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
|
19
|
+
function computeHash(data) {
|
|
20
|
+
return (0, node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
function buildUri(hash) {
|
|
23
|
+
return `${BLOB_SCHEME}${hash}`;
|
|
24
|
+
}
|
|
25
|
+
var FilesystemBlobStorageProvider = class {
|
|
26
|
+
providerId = "filesystem";
|
|
27
|
+
basePath;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
const defaultBase = node_path.join(require_logger.getConfigDirectoryPath(true), DEFAULT_FILESYSTEM_SUBDIR);
|
|
30
|
+
this.basePath = node_path.resolve(config?.basePath || defaultBase);
|
|
31
|
+
this.ensureDirectory();
|
|
32
|
+
}
|
|
33
|
+
ensureDirectory() {
|
|
34
|
+
if (!node_fs.existsSync(this.basePath)) {
|
|
35
|
+
node_fs.mkdirSync(this.basePath, { recursive: true });
|
|
36
|
+
require_logger.logger.debug("[BlobFS] Created blob directory", { basePath: this.basePath });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
assertValidHash(hash) {
|
|
40
|
+
if (!BLOB_HASH_REGEX.test(hash)) throw new Error(`[BlobFS] Invalid blob hash: "${hash}"`);
|
|
41
|
+
}
|
|
42
|
+
resolvePathInBase(unsafePath) {
|
|
43
|
+
const targetPath = node_path.isAbsolute(unsafePath) ? node_path.resolve(unsafePath) : node_path.resolve(this.basePath, unsafePath);
|
|
44
|
+
const safeBase = node_path.resolve(this.basePath) + node_path.sep;
|
|
45
|
+
if (!targetPath.startsWith(safeBase)) throw new Error("[BlobFS] Path traversal attempt detected");
|
|
46
|
+
return targetPath;
|
|
47
|
+
}
|
|
48
|
+
hashToPath(hash) {
|
|
49
|
+
this.assertValidHash(hash);
|
|
50
|
+
const dirRelative = node_path.join(hash.slice(0, 2), hash.slice(2, 4));
|
|
51
|
+
const fileRelative = node_path.join(dirRelative, hash);
|
|
52
|
+
return this.resolvePathInBase(fileRelative);
|
|
53
|
+
}
|
|
54
|
+
async ensureHashDir(hash) {
|
|
55
|
+
this.assertValidHash(hash);
|
|
56
|
+
const dirRelative = node_path.join(hash.slice(0, 2), hash.slice(2, 4));
|
|
57
|
+
const dirPath = this.resolvePathInBase(dirRelative);
|
|
58
|
+
await node_fs_promises.mkdir(dirPath, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
metadataPath(filePath) {
|
|
61
|
+
return `${filePath}.meta.json`;
|
|
62
|
+
}
|
|
63
|
+
async store(data, mimeType) {
|
|
64
|
+
const hash = computeHash(data);
|
|
65
|
+
await this.ensureHashDir(hash);
|
|
66
|
+
const filePath = this.hashToPath(hash);
|
|
67
|
+
try {
|
|
68
|
+
await node_fs_promises.access(filePath);
|
|
69
|
+
const meta = await this.readMetadata(filePath);
|
|
70
|
+
return {
|
|
71
|
+
ref: this.buildRef(hash, meta?.mimeType ?? mimeType, meta?.sizeBytes ?? data.length, meta?.provider ?? this.providerId),
|
|
72
|
+
deduplicated: true
|
|
73
|
+
};
|
|
74
|
+
} catch {}
|
|
75
|
+
await node_fs_promises.writeFile(filePath, data);
|
|
76
|
+
const metadata = {
|
|
77
|
+
mimeType,
|
|
78
|
+
sizeBytes: data.length,
|
|
79
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80
|
+
provider: this.providerId,
|
|
81
|
+
key: filePath
|
|
82
|
+
};
|
|
83
|
+
await node_fs_promises.writeFile(this.metadataPath(filePath), JSON.stringify(metadata, null, 2));
|
|
84
|
+
return {
|
|
85
|
+
ref: this.buildRef(hash, mimeType, data.length, this.providerId),
|
|
86
|
+
deduplicated: false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async getByHash(hash) {
|
|
90
|
+
const filePath = this.hashToPath(hash);
|
|
91
|
+
let data;
|
|
92
|
+
try {
|
|
93
|
+
data = await node_fs_promises.readFile(filePath);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error.code === "ENOENT") throw new Error(`Blob not found: ${hash}`);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
const metadata = await this.readMetadata(filePath) || {
|
|
99
|
+
mimeType: "application/octet-stream",
|
|
100
|
+
sizeBytes: data.length,
|
|
101
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
|
+
provider: this.providerId,
|
|
103
|
+
key: filePath
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
data,
|
|
107
|
+
metadata
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async exists(hash) {
|
|
111
|
+
try {
|
|
112
|
+
const filePath = this.hashToPath(hash);
|
|
113
|
+
await node_fs_promises.access(filePath);
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async deleteByHash(hash) {
|
|
120
|
+
try {
|
|
121
|
+
const filePath = this.hashToPath(hash);
|
|
122
|
+
const metaPath = this.metadataPath(filePath);
|
|
123
|
+
try {
|
|
124
|
+
await node_fs_promises.unlink(filePath);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.code !== "ENOENT") throw error;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await node_fs_promises.unlink(metaPath);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error.code !== "ENOENT") throw error;
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
async getUrl(_hash, _expiresInSeconds) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
buildRef(hash, mimeType, sizeBytes, provider) {
|
|
139
|
+
return {
|
|
140
|
+
uri: buildUri(hash),
|
|
141
|
+
hash,
|
|
142
|
+
mimeType,
|
|
143
|
+
sizeBytes,
|
|
144
|
+
provider
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async readMetadata(filePath) {
|
|
148
|
+
const safeFilePath = this.resolvePathInBase(filePath);
|
|
149
|
+
const metaPath = this.metadataPath(safeFilePath);
|
|
150
|
+
try {
|
|
151
|
+
const raw = await node_fs_promises.readFile(metaPath, "utf8");
|
|
152
|
+
return JSON.parse(raw);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === "ENOENT") return null;
|
|
155
|
+
require_logger.logger.warn("[BlobFS] Failed to read metadata", { error });
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/blobs/index.ts
|
|
162
|
+
let defaultProvider = null;
|
|
163
|
+
function createDefaultProvider() {
|
|
164
|
+
return new FilesystemBlobStorageProvider();
|
|
165
|
+
}
|
|
166
|
+
function getBlobStorageProvider() {
|
|
167
|
+
if (!defaultProvider) {
|
|
168
|
+
defaultProvider = createDefaultProvider();
|
|
169
|
+
require_logger.logger.debug("[BlobStorage] Initialized provider", { provider: defaultProvider.providerId });
|
|
170
|
+
}
|
|
171
|
+
return defaultProvider;
|
|
172
|
+
}
|
|
173
|
+
async function storeBlob(data, mimeType, refContext) {
|
|
174
|
+
const provider = getBlobStorageProvider();
|
|
175
|
+
const result = await provider.store(data, mimeType);
|
|
176
|
+
const db = require_tables.getDb();
|
|
177
|
+
try {
|
|
178
|
+
db.transaction(() => {
|
|
179
|
+
const assetInsert = db.insert(require_tables.blobAssetsTable).values({
|
|
180
|
+
hash: result.ref.hash,
|
|
181
|
+
sizeBytes: result.ref.sizeBytes,
|
|
182
|
+
mimeType: result.ref.mimeType,
|
|
183
|
+
provider: result.ref.provider
|
|
184
|
+
}).onConflictDoNothing().run();
|
|
185
|
+
return (refContext?.evalId && db.insert(require_tables.blobReferencesTable).values({
|
|
186
|
+
id: (0, node_crypto.randomUUID)(),
|
|
187
|
+
blobHash: result.ref.hash,
|
|
188
|
+
evalId: refContext.evalId,
|
|
189
|
+
testIdx: refContext.testIdx,
|
|
190
|
+
promptIdx: refContext.promptIdx,
|
|
191
|
+
location: refContext.location,
|
|
192
|
+
kind: refContext.kind
|
|
193
|
+
}).onConflictDoNothing().run()) ?? assetInsert;
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
try {
|
|
197
|
+
await provider.deleteByHash(result.ref.hash);
|
|
198
|
+
} catch (cleanupError) {
|
|
199
|
+
require_logger.logger.warn("[BlobStorage] Failed to rollback blob after DB error", {
|
|
200
|
+
error: cleanupError,
|
|
201
|
+
hash: result.ref.hash
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
async function recordBlobReference(hash, refContext) {
|
|
209
|
+
if (!refContext.evalId) return;
|
|
210
|
+
if (!await getBlobStorageProvider().exists(hash).catch(() => false)) {
|
|
211
|
+
require_logger.logger.debug("[BlobStorage] Attempted to record reference for missing blob", {
|
|
212
|
+
hash,
|
|
213
|
+
evalId: refContext.evalId,
|
|
214
|
+
location: refContext.location
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const db = require_tables.getDb();
|
|
219
|
+
if (db.select({ id: require_tables.blobReferencesTable.id }).from(require_tables.blobReferencesTable).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_tables.blobReferencesTable.blobHash, hash), (0, drizzle_orm.eq)(require_tables.blobReferencesTable.evalId, refContext.evalId))).get()) return;
|
|
220
|
+
db.insert(require_tables.blobReferencesTable).values({
|
|
221
|
+
id: (0, node_crypto.randomUUID)(),
|
|
222
|
+
blobHash: hash,
|
|
223
|
+
evalId: refContext.evalId,
|
|
224
|
+
testIdx: refContext.testIdx,
|
|
225
|
+
promptIdx: refContext.promptIdx,
|
|
226
|
+
location: refContext.location,
|
|
227
|
+
kind: refContext.kind
|
|
228
|
+
}).run();
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
Object.defineProperty(exports, "BLOB_MAX_SIZE", {
|
|
232
|
+
enumerable: true,
|
|
233
|
+
get: function() {
|
|
234
|
+
return BLOB_MAX_SIZE;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
Object.defineProperty(exports, "BLOB_MIN_SIZE", {
|
|
238
|
+
enumerable: true,
|
|
239
|
+
get: function() {
|
|
240
|
+
return BLOB_MIN_SIZE;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
Object.defineProperty(exports, "recordBlobReference", {
|
|
244
|
+
enumerable: true,
|
|
245
|
+
get: function() {
|
|
246
|
+
return recordBlobReference;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
Object.defineProperty(exports, "storeBlob", {
|
|
250
|
+
enumerable: true,
|
|
251
|
+
get: function() {
|
|
252
|
+
return storeBlob;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=blobs-BgcNn97m.cjs.map
|