@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,374 @@
|
|
|
1
|
+
import { r as logger, v as getEnvBool } from "./logger-C40ZGil9.js";
|
|
2
|
+
import { o as cloudConfig } from "./fetch-DQckpUFz.js";
|
|
3
|
+
import { o as isLoggedIntoCloud } from "./accounts-DjOU8Rm3.js";
|
|
4
|
+
import { n as storeBlob, t as recordBlobReference } from "./blobs-BY8MDmpo.js";
|
|
5
|
+
//#region src/blobs/remoteUpload.ts
|
|
6
|
+
function buildRemoteUrl() {
|
|
7
|
+
const baseUrl = cloudConfig.getApiHost();
|
|
8
|
+
const apiKey = cloudConfig.getApiKey();
|
|
9
|
+
if (!baseUrl || !apiKey || !isLoggedIntoCloud()) return null;
|
|
10
|
+
try {
|
|
11
|
+
return new URL("/api/blobs", baseUrl).toString();
|
|
12
|
+
} catch (error) {
|
|
13
|
+
logger.debug("[RemoteBlob] Invalid remote blob URL", {
|
|
14
|
+
error: error instanceof Error ? error.message : String(error),
|
|
15
|
+
baseUrl
|
|
16
|
+
});
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function shouldAttemptRemoteBlobUpload() {
|
|
21
|
+
return buildRemoteUrl() !== null;
|
|
22
|
+
}
|
|
23
|
+
async function uploadBlobRemote(buffer, mimeType, context) {
|
|
24
|
+
const url = buildRemoteUrl();
|
|
25
|
+
const apiKey = cloudConfig.getApiKey();
|
|
26
|
+
if (!url || !apiKey) return null;
|
|
27
|
+
try {
|
|
28
|
+
const { fetchWithProxy } = await import("./fetch-DQckpUFz.js").then((n) => n.i);
|
|
29
|
+
const response = await fetchWithProxy(url, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
Authorization: `Bearer ${apiKey}`
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
data: buffer.toString("base64"),
|
|
37
|
+
mimeType,
|
|
38
|
+
context
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
if (response.status === 404 || response.status === 400) {
|
|
42
|
+
logger.debug("[RemoteBlob] Remote blob upload unavailable", { status: response.status });
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
logger.debug("[RemoteBlob] Failed to upload blob", {
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
body: text
|
|
51
|
+
});
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
if (!data?.ref?.hash) {
|
|
56
|
+
logger.debug("[RemoteBlob] Remote upload returned malformed response");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.debug("[RemoteBlob] Error uploading blob", { error: error instanceof Error ? error.message : String(error) });
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/blobs/extractor.ts
|
|
67
|
+
const BLOB_URI_REGEX = /^promptfoo:\/\/blob\/([a-f0-9]{64})$/i;
|
|
68
|
+
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
|
69
|
+
function isDataUrl(value) {
|
|
70
|
+
return /^data:(audio|image)\/[^;]+;base64,/.test(value);
|
|
71
|
+
}
|
|
72
|
+
function extractBase64(value) {
|
|
73
|
+
const match = value.match(/^data:([^;]+);base64,(.+)$/);
|
|
74
|
+
if (!match) return null;
|
|
75
|
+
const mimeType = match[1];
|
|
76
|
+
try {
|
|
77
|
+
return {
|
|
78
|
+
buffer: Buffer.from(match[2], "base64"),
|
|
79
|
+
mimeType
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.warn("[BlobExtractor] Failed to parse base64 data URL", { error });
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function shouldExternalize(buffer) {
|
|
87
|
+
const size = buffer.length;
|
|
88
|
+
return size >= 1024 && size <= 52428800;
|
|
89
|
+
}
|
|
90
|
+
function getKindFromMimeType(mimeType) {
|
|
91
|
+
return mimeType.startsWith("audio/") ? "audio" : "image";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Normalize audio format to proper MIME type.
|
|
95
|
+
* Some providers return just 'wav' instead of 'audio/wav'.
|
|
96
|
+
* @internal Exported for testing
|
|
97
|
+
*/
|
|
98
|
+
function normalizeAudioMimeType(format) {
|
|
99
|
+
if (!format) return "audio/wav";
|
|
100
|
+
const trimmedFormat = format.trim();
|
|
101
|
+
if (/^audio\/[a-z0-9_+-]+$/i.test(trimmedFormat)) return trimmedFormat;
|
|
102
|
+
const formatLower = trimmedFormat.toLowerCase();
|
|
103
|
+
const mimeMap = {
|
|
104
|
+
wav: "audio/wav",
|
|
105
|
+
mp3: "audio/mpeg",
|
|
106
|
+
ogg: "audio/ogg",
|
|
107
|
+
flac: "audio/flac",
|
|
108
|
+
aac: "audio/aac",
|
|
109
|
+
m4a: "audio/mp4",
|
|
110
|
+
webm: "audio/webm"
|
|
111
|
+
};
|
|
112
|
+
if (mimeMap[formatLower]) return mimeMap[formatLower];
|
|
113
|
+
if (!/^[a-z0-9_-]+$/i.test(formatLower)) {
|
|
114
|
+
logger.warn("[BlobExtractor] Invalid audio format, using default", { format });
|
|
115
|
+
return "audio/wav";
|
|
116
|
+
}
|
|
117
|
+
return `audio/${formatLower}`;
|
|
118
|
+
}
|
|
119
|
+
function parseBinary(base64OrDataUrl, defaultMimeType) {
|
|
120
|
+
if (isDataUrl(base64OrDataUrl)) {
|
|
121
|
+
const parsed = extractBase64(base64OrDataUrl);
|
|
122
|
+
if (!parsed) return null;
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return {
|
|
127
|
+
buffer: Buffer.from(base64OrDataUrl, "base64"),
|
|
128
|
+
mimeType: defaultMimeType
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logger.warn("[BlobExtractor] Failed to parse base64 data", { error });
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function maybeStore(base64OrDataUrl, defaultMimeType, context, location, kind) {
|
|
136
|
+
const parsed = parseBinary(base64OrDataUrl, defaultMimeType);
|
|
137
|
+
if (!parsed || !shouldExternalize(parsed.buffer)) return null;
|
|
138
|
+
if (!isBlobStorageEnabled()) return null;
|
|
139
|
+
const mimeType = parsed.mimeType || "application/octet-stream";
|
|
140
|
+
const { ref } = await storeBlob(parsed.buffer, mimeType, {
|
|
141
|
+
...context,
|
|
142
|
+
location,
|
|
143
|
+
kind
|
|
144
|
+
});
|
|
145
|
+
if (shouldAttemptRemoteBlobUpload()) uploadBlobRemote(parsed.buffer, mimeType, {
|
|
146
|
+
evalId: context.evalId,
|
|
147
|
+
testIdx: context.testIdx,
|
|
148
|
+
promptIdx: context.promptIdx,
|
|
149
|
+
location,
|
|
150
|
+
kind
|
|
151
|
+
}).catch((error) => {
|
|
152
|
+
logger.debug("[BlobExtractor] Cloud upload failed (non-fatal)", {
|
|
153
|
+
error: error instanceof Error ? error.message : String(error),
|
|
154
|
+
hash: ref.hash
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return ref;
|
|
158
|
+
}
|
|
159
|
+
async function externalizeDataUrls(value, context, location) {
|
|
160
|
+
if (typeof value === "string") {
|
|
161
|
+
if (!isDataUrl(value)) return {
|
|
162
|
+
value,
|
|
163
|
+
mutated: false
|
|
164
|
+
};
|
|
165
|
+
const parsed = extractBase64(value);
|
|
166
|
+
if (!parsed || !shouldExternalize(parsed.buffer)) return {
|
|
167
|
+
value,
|
|
168
|
+
mutated: false
|
|
169
|
+
};
|
|
170
|
+
const storedRef = await maybeStore(parsed.buffer.toString("base64"), parsed.mimeType, context, location, getKindFromMimeType(parsed.mimeType)) || null;
|
|
171
|
+
if (!storedRef) return {
|
|
172
|
+
value,
|
|
173
|
+
mutated: false
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
value: storedRef.uri,
|
|
177
|
+
mutated: true
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
let mutated = false;
|
|
182
|
+
const nextValues = await Promise.all(value.map(async (item, idx) => {
|
|
183
|
+
const { value: nextValue, mutated: childMutated } = await externalizeDataUrls(item, context, `${location}[${idx}]`);
|
|
184
|
+
mutated ||= childMutated;
|
|
185
|
+
return nextValue;
|
|
186
|
+
}));
|
|
187
|
+
return mutated ? {
|
|
188
|
+
value: nextValues,
|
|
189
|
+
mutated
|
|
190
|
+
} : {
|
|
191
|
+
value,
|
|
192
|
+
mutated: false
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (value && typeof value === "object") {
|
|
196
|
+
let mutated = false;
|
|
197
|
+
const nextObject = { ...value };
|
|
198
|
+
for (const [key, child] of Object.entries(value)) {
|
|
199
|
+
const { value: nextValue, mutated: childMutated } = await externalizeDataUrls(child, context, location ? `${location}.${key}` : key);
|
|
200
|
+
if (childMutated) {
|
|
201
|
+
nextObject[key] = nextValue;
|
|
202
|
+
mutated = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return mutated ? {
|
|
206
|
+
value: nextObject,
|
|
207
|
+
mutated: true
|
|
208
|
+
} : {
|
|
209
|
+
value,
|
|
210
|
+
mutated: false
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
value,
|
|
215
|
+
mutated: false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Best-effort extraction of binary data from provider responses.
|
|
220
|
+
* Currently focuses on audio.data fields and data URL outputs.
|
|
221
|
+
*/
|
|
222
|
+
async function extractAndStoreBinaryData(response, context) {
|
|
223
|
+
if (!response) return response;
|
|
224
|
+
let mutated = false;
|
|
225
|
+
const next = { ...response };
|
|
226
|
+
const blobContext = context || {};
|
|
227
|
+
if (response.audio?.data && typeof response.audio.data === "string") {
|
|
228
|
+
const stored = await maybeStore(response.audio.data, normalizeAudioMimeType(response.audio.format), blobContext, "response.audio.data", "audio");
|
|
229
|
+
if (stored) {
|
|
230
|
+
next.audio = {
|
|
231
|
+
...response.audio,
|
|
232
|
+
data: void 0,
|
|
233
|
+
blobRef: stored
|
|
234
|
+
};
|
|
235
|
+
mutated = true;
|
|
236
|
+
logger.debug("[BlobExtractor] Stored audio blob", {
|
|
237
|
+
...context,
|
|
238
|
+
hash: stored.hash
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (response.images?.length) next.images = await Promise.all(response.images.map(async (img, idx) => {
|
|
243
|
+
if (!img.data || typeof img.data !== "string" || !isDataUrl(img.data)) return img;
|
|
244
|
+
const stored = await maybeStore(img.data, img.mimeType || "image/png", blobContext, `response.images[${idx}].data`, "image");
|
|
245
|
+
if (stored) {
|
|
246
|
+
mutated = true;
|
|
247
|
+
logger.debug("[BlobExtractor] Stored image blob", {
|
|
248
|
+
...context,
|
|
249
|
+
hash: stored.hash
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
...img,
|
|
253
|
+
data: void 0,
|
|
254
|
+
blobRef: stored
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return img;
|
|
258
|
+
}));
|
|
259
|
+
const turns = response.turns;
|
|
260
|
+
if (Array.isArray(turns)) next.turns = await Promise.all(turns.map(async (turn, idx) => {
|
|
261
|
+
if (turn?.audio?.data && typeof turn.audio.data === "string") {
|
|
262
|
+
const stored = await maybeStore(turn.audio.data, normalizeAudioMimeType(turn.audio.format), blobContext, `response.turns[${idx}].audio.data`, "audio");
|
|
263
|
+
if (stored) {
|
|
264
|
+
mutated = true;
|
|
265
|
+
return {
|
|
266
|
+
...turn,
|
|
267
|
+
audio: {
|
|
268
|
+
...turn.audio,
|
|
269
|
+
data: void 0,
|
|
270
|
+
blobRef: stored
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return turn;
|
|
276
|
+
}));
|
|
277
|
+
if (typeof response.output === "string" && isDataUrl(response.output)) {
|
|
278
|
+
const parsed = extractBase64(response.output);
|
|
279
|
+
if (parsed && shouldExternalize(parsed.buffer)) {
|
|
280
|
+
const stored = await maybeStore(parsed.buffer.toString("base64"), parsed.mimeType, blobContext, "response.output", getKindFromMimeType(parsed.mimeType));
|
|
281
|
+
if (stored) {
|
|
282
|
+
next.output = stored.uri;
|
|
283
|
+
mutated = true;
|
|
284
|
+
logger.debug("[BlobExtractor] Stored output blob", {
|
|
285
|
+
...context,
|
|
286
|
+
hash: stored.hash
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (typeof response.output === "string" && response.output.trim().startsWith("{") && (response.isBase64 && response.format === "json" || response.output.includes("\"b64_json\"") || response.output.includes("b64_json"))) try {
|
|
292
|
+
const parsed = JSON.parse(response.output);
|
|
293
|
+
if (Array.isArray(parsed.data)) {
|
|
294
|
+
let jsonMutated = false;
|
|
295
|
+
const storedUris = [];
|
|
296
|
+
for (const item of parsed.data) if (item?.b64_json && typeof item.b64_json === "string") {
|
|
297
|
+
const stored = await maybeStore(item.b64_json, "image/png", blobContext, "response.output.data[].b64_json", "image");
|
|
298
|
+
if (stored) {
|
|
299
|
+
item.b64_json = stored.uri;
|
|
300
|
+
storedUris.push(stored.uri);
|
|
301
|
+
jsonMutated = true;
|
|
302
|
+
mutated = true;
|
|
303
|
+
logger.debug("[BlobExtractor] Stored image blob from b64_json", {
|
|
304
|
+
...context,
|
|
305
|
+
hash: stored.hash
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (jsonMutated) {
|
|
310
|
+
if (storedUris.length === 1) next.output = storedUris[0];
|
|
311
|
+
else if (storedUris.length > 1) next.output = JSON.stringify(storedUris);
|
|
312
|
+
else next.output = JSON.stringify(parsed);
|
|
313
|
+
next.metadata = {
|
|
314
|
+
...response.metadata || {},
|
|
315
|
+
blobUris: storedUris,
|
|
316
|
+
originalFormat: response.format
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
logger.debug("[BlobExtractor] Failed to parse base64 JSON output", {
|
|
322
|
+
error: err instanceof Error ? err.message : String(err),
|
|
323
|
+
location: "response.output"
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (response.metadata) {
|
|
327
|
+
const { value, mutated: metadataMutated } = await externalizeDataUrls(response.metadata, blobContext, "response.metadata");
|
|
328
|
+
if (metadataMutated) {
|
|
329
|
+
next.metadata = value;
|
|
330
|
+
mutated = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const finalResponse = mutated ? next : response;
|
|
334
|
+
if (blobContext.evalId) await recordExistingBlobReferences(finalResponse, blobContext, "response");
|
|
335
|
+
return finalResponse;
|
|
336
|
+
}
|
|
337
|
+
function isBlobStorageEnabled() {
|
|
338
|
+
return !getEnvBool("PROMPTFOO_INLINE_MEDIA", false);
|
|
339
|
+
}
|
|
340
|
+
function parseBlobHashFromValue(value) {
|
|
341
|
+
if (!value) return null;
|
|
342
|
+
if (typeof value === "string") {
|
|
343
|
+
const match = value.match(BLOB_URI_REGEX);
|
|
344
|
+
return match ? match[1] : null;
|
|
345
|
+
}
|
|
346
|
+
if (typeof value === "object") {
|
|
347
|
+
const candidate = value;
|
|
348
|
+
if (candidate.hash && BLOB_HASH_REGEX.test(candidate.hash)) return candidate.hash;
|
|
349
|
+
if (candidate.uri && typeof candidate.uri === "string") {
|
|
350
|
+
const match = candidate.uri.match(BLOB_URI_REGEX);
|
|
351
|
+
if (match) return match[1];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
async function recordExistingBlobReferences(value, context, location) {
|
|
357
|
+
const hash = parseBlobHashFromValue(value);
|
|
358
|
+
if (hash) {
|
|
359
|
+
await recordBlobReference(hash, {
|
|
360
|
+
...context,
|
|
361
|
+
location
|
|
362
|
+
});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (Array.isArray(value)) {
|
|
366
|
+
await Promise.all(value.map((child, idx) => recordExistingBlobReferences(child, context, `${location}[${idx}]`)));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (value && typeof value === "object") for (const [key, child] of Object.entries(value)) await recordExistingBlobReferences(child, context, location ? `${location}.${key}` : key);
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
export { isBlobStorageEnabled as n, shouldAttemptRemoteBlobUpload as r, extractAndStoreBinaryData as t };
|
|
373
|
+
|
|
374
|
+
//# sourceMappingURL=extractor-DtlL28vL.js.map
|