@tangle-network/agent-eval 0.120.2 → 0.120.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/CHANGELOG.md +6 -0
- package/dist/analyst/index.d.ts +3111 -0
- package/dist/analyst/index.js +403 -0
- package/dist/analyst/index.js.map +1 -0
- package/dist/authenticity/index.d.ts +161 -0
- package/dist/authenticity/index.js +215 -0
- package/dist/authenticity/index.js.map +1 -0
- package/dist/belief-state/index.d.ts +1301 -0
- package/dist/belief-state/index.js +2152 -0
- package/dist/belief-state/index.js.map +1 -0
- package/dist/benchmarks/index.d.ts +974 -0
- package/dist/benchmarks/index.js +60 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/builder-eval/index.d.ts +695 -0
- package/dist/builder-eval/index.js +366 -0
- package/dist/builder-eval/index.js.map +1 -0
- package/dist/campaign/index.d.ts +7454 -0
- package/dist/campaign/index.js +272 -0
- package/dist/campaign/index.js.map +1 -0
- package/dist/chunk-3CDFMEMO.js +3878 -0
- package/dist/chunk-3CDFMEMO.js.map +1 -0
- package/dist/chunk-3RF76KTD.js +84 -0
- package/dist/chunk-3RF76KTD.js.map +1 -0
- package/dist/chunk-3XH4Y2SS.js +750 -0
- package/dist/chunk-3XH4Y2SS.js.map +1 -0
- package/dist/chunk-3YYRZDON.js +45 -0
- package/dist/chunk-3YYRZDON.js.map +1 -0
- package/dist/chunk-5BYTIDZ7.js +550 -0
- package/dist/chunk-5BYTIDZ7.js.map +1 -0
- package/dist/chunk-5CVUPHJ4.js +2668 -0
- package/dist/chunk-5CVUPHJ4.js.map +1 -0
- package/dist/chunk-ARU2PZFM.js +312 -0
- package/dist/chunk-ARU2PZFM.js.map +1 -0
- package/dist/chunk-BOD4O7OF.js +40 -0
- package/dist/chunk-BOD4O7OF.js.map +1 -0
- package/dist/chunk-CVJP5TMD.js +766 -0
- package/dist/chunk-CVJP5TMD.js.map +1 -0
- package/dist/chunk-DPZAEKA6.js +880 -0
- package/dist/chunk-DPZAEKA6.js.map +1 -0
- package/dist/chunk-DTJ6QUQB.js +131 -0
- package/dist/chunk-DTJ6QUQB.js.map +1 -0
- package/dist/chunk-GGE4NNQT.js +65 -0
- package/dist/chunk-GGE4NNQT.js.map +1 -0
- package/dist/chunk-H5UD2323.js +286 -0
- package/dist/chunk-H5UD2323.js.map +1 -0
- package/dist/chunk-HHWE3POT.js +94 -0
- package/dist/chunk-HHWE3POT.js.map +1 -0
- package/dist/chunk-HKUCJ437.js +787 -0
- package/dist/chunk-HKUCJ437.js.map +1 -0
- package/dist/chunk-JHCHEVET.js +274 -0
- package/dist/chunk-JHCHEVET.js.map +1 -0
- package/dist/chunk-K4DBDHLK.js +158 -0
- package/dist/chunk-K4DBDHLK.js.map +1 -0
- package/dist/chunk-K6N6XJJX.js +306 -0
- package/dist/chunk-K6N6XJJX.js.map +1 -0
- package/dist/chunk-MA6HLL3S.js +65 -0
- package/dist/chunk-MA6HLL3S.js.map +1 -0
- package/dist/chunk-MAZ26DC7.js +99 -0
- package/dist/chunk-MAZ26DC7.js.map +1 -0
- package/dist/chunk-MOXWMGPC.js +577 -0
- package/dist/chunk-MOXWMGPC.js.map +1 -0
- package/dist/chunk-NJC7U437.js +626 -0
- package/dist/chunk-NJC7U437.js.map +1 -0
- package/dist/chunk-NMN4WGSJ.js +1030 -0
- package/dist/chunk-NMN4WGSJ.js.map +1 -0
- package/dist/chunk-NPCTHQIO.js +91 -0
- package/dist/chunk-NPCTHQIO.js.map +1 -0
- package/dist/chunk-ONWEPEDO.js +57 -0
- package/dist/chunk-ONWEPEDO.js.map +1 -0
- package/dist/chunk-OYZAPX5G.js +1526 -0
- package/dist/chunk-OYZAPX5G.js.map +1 -0
- package/dist/chunk-P5MGQ2FY.js +7958 -0
- package/dist/chunk-P5MGQ2FY.js.map +1 -0
- package/dist/chunk-PC4UYEBM.js +166 -0
- package/dist/chunk-PC4UYEBM.js.map +1 -0
- package/dist/chunk-PJQFMIOX.js +1182 -0
- package/dist/chunk-PJQFMIOX.js.map +1 -0
- package/dist/chunk-PXD6ZFNY.js +1107 -0
- package/dist/chunk-PXD6ZFNY.js.map +1 -0
- package/dist/chunk-PXE2VKMX.js +140 -0
- package/dist/chunk-PXE2VKMX.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-QBRSJK47.js +622 -0
- package/dist/chunk-QBRSJK47.js.map +1 -0
- package/dist/chunk-S3UZOQ5Y.js +328 -0
- package/dist/chunk-S3UZOQ5Y.js.map +1 -0
- package/dist/chunk-SQQED7ZH.js +998 -0
- package/dist/chunk-SQQED7ZH.js.map +1 -0
- package/dist/chunk-SYV364BL.js +1266 -0
- package/dist/chunk-SYV364BL.js.map +1 -0
- package/dist/chunk-T4SQEITX.js +95 -0
- package/dist/chunk-T4SQEITX.js.map +1 -0
- package/dist/chunk-TT4KNT67.js +124 -0
- package/dist/chunk-TT4KNT67.js.map +1 -0
- package/dist/chunk-U5CHZ5M3.js +357 -0
- package/dist/chunk-U5CHZ5M3.js.map +1 -0
- package/dist/chunk-ULOKLHIQ.js +1937 -0
- package/dist/chunk-ULOKLHIQ.js.map +1 -0
- package/dist/chunk-VI2UW6B6.js +162 -0
- package/dist/chunk-VI2UW6B6.js.map +1 -0
- package/dist/chunk-VQMK5FMP.js +247 -0
- package/dist/chunk-VQMK5FMP.js.map +1 -0
- package/dist/chunk-VSMTAMNK.js +53 -0
- package/dist/chunk-VSMTAMNK.js.map +1 -0
- package/dist/chunk-VZSRQ272.js +149 -0
- package/dist/chunk-VZSRQ272.js.map +1 -0
- package/dist/chunk-WW2A73HW.js +159 -0
- package/dist/chunk-WW2A73HW.js.map +1 -0
- package/dist/chunk-X4UCIOTZ.js +136 -0
- package/dist/chunk-X4UCIOTZ.js.map +1 -0
- package/dist/chunk-XJYR7XFV.js +317 -0
- package/dist/chunk-XJYR7XFV.js.map +1 -0
- package/dist/chunk-ZET2UAYW.js +89 -0
- package/dist/chunk-ZET2UAYW.js.map +1 -0
- package/dist/chunk-ZMXDQ4K7.js +870 -0
- package/dist/chunk-ZMXDQ4K7.js.map +1 -0
- package/dist/chunk-ZZUXHH3R.js +99 -0
- package/dist/chunk-ZZUXHH3R.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract/index.d.ts +4972 -0
- package/dist/contract/index.js +1654 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/control.d.ts +1013 -0
- package/dist/control.js +34 -0
- package/dist/control.js.map +1 -0
- package/dist/fuzz.d.ts +759 -0
- package/dist/fuzz.js +714 -0
- package/dist/fuzz.js.map +1 -0
- package/dist/hosted/index.d.ts +730 -0
- package/dist/hosted/index.js +14 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/index.d.ts +16780 -0
- package/dist/index.js +12168 -0
- package/dist/index.js.map +1 -0
- package/dist/matrix/index.d.ts +155 -0
- package/dist/matrix/index.js +8 -0
- package/dist/matrix/index.js.map +1 -0
- package/dist/meta-eval/index.d.ts +1030 -0
- package/dist/meta-eval/index.js +417 -0
- package/dist/meta-eval/index.js.map +1 -0
- package/dist/multishot/index.d.ts +579 -0
- package/dist/multishot/index.js +589 -0
- package/dist/multishot/index.js.map +1 -0
- package/dist/openapi.json +992 -0
- package/dist/pipelines/index.d.ts +567 -0
- package/dist/pipelines/index.js +515 -0
- package/dist/pipelines/index.js.map +1 -0
- package/dist/reporting.d.ts +1277 -0
- package/dist/reporting.js +48 -0
- package/dist/reporting.js.map +1 -0
- package/dist/rl.d.ts +4092 -0
- package/dist/rl.js +1724 -0
- package/dist/rl.js.map +1 -0
- package/dist/run-campaign-75RTPVV5.js +14 -0
- package/dist/run-campaign-75RTPVV5.js.map +1 -0
- package/dist/storyboard/index.d.ts +279 -0
- package/dist/storyboard/index.js +767 -0
- package/dist/storyboard/index.js.map +1 -0
- package/dist/trace-attributes.d.ts +52 -0
- package/dist/trace-attributes.js +62 -0
- package/dist/trace-attributes.js.map +1 -0
- package/dist/traces.d.ts +2343 -0
- package/dist/traces.js +249 -0
- package/dist/traces.js.map +1 -0
- package/dist/wire/index.d.ts +1252 -0
- package/dist/wire/index.js +81 -0
- package/dist/wire/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultProviderRedactor,
|
|
3
|
+
providerFromBaseUrl
|
|
4
|
+
} from "./chunk-PC4UYEBM.js";
|
|
5
|
+
import {
|
|
6
|
+
AgentEvalError,
|
|
7
|
+
CaptureIntegrityError
|
|
8
|
+
} from "./chunk-ONWEPEDO.js";
|
|
9
|
+
|
|
10
|
+
// src/llm-client.ts
|
|
11
|
+
function maximumChargeForLlmRequest(request, options = {}) {
|
|
12
|
+
if (request.maxTokens === void 0) return void 0;
|
|
13
|
+
if (!Number.isInteger(request.maxTokens) || request.maxTokens <= 0) {
|
|
14
|
+
throw new RangeError(`maximumChargeForLlmRequest: maxTokens must be a positive integer`);
|
|
15
|
+
}
|
|
16
|
+
if (request.messages.some(
|
|
17
|
+
(message) => Array.isArray(message.content) && message.content.some((part) => part.type === "image_url")
|
|
18
|
+
)) {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
const attempts = resolveMaximumAttempts(options.maxRetries);
|
|
22
|
+
const requestBytes = new TextEncoder().encode(
|
|
23
|
+
JSON.stringify(buildBody(request, false))
|
|
24
|
+
).byteLength;
|
|
25
|
+
const batches = request.jsonSchema ? 2 : 1;
|
|
26
|
+
return {
|
|
27
|
+
model: request.model,
|
|
28
|
+
inputTokens: requestBytes * attempts * batches,
|
|
29
|
+
outputTokens: request.maxTokens * attempts * batches
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function costReceiptFromLlm(result) {
|
|
33
|
+
const cachedTokens = result.usage.cachedPromptTokens ?? 0;
|
|
34
|
+
return {
|
|
35
|
+
model: result.model,
|
|
36
|
+
inputTokens: Math.max(0, result.usage.promptTokens - cachedTokens),
|
|
37
|
+
outputTokens: result.usage.completionTokens,
|
|
38
|
+
cachedTokens: cachedTokens > 0 ? cachedTokens : void 0,
|
|
39
|
+
actualCostUsd: result.costUsd ?? void 0,
|
|
40
|
+
usageUnknown: result.usage.captured === false
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function costReceiptFromLlmError(error) {
|
|
44
|
+
return error instanceof LlmResponseError ? costReceiptFromLlm(error.result) : void 0;
|
|
45
|
+
}
|
|
46
|
+
var LlmCallError = class extends AgentEvalError {
|
|
47
|
+
constructor(message, status, body, model) {
|
|
48
|
+
super("judge", message);
|
|
49
|
+
this.status = status;
|
|
50
|
+
this.body = body;
|
|
51
|
+
this.model = model;
|
|
52
|
+
}
|
|
53
|
+
status;
|
|
54
|
+
body;
|
|
55
|
+
model;
|
|
56
|
+
};
|
|
57
|
+
var LlmResponseError = class extends AgentEvalError {
|
|
58
|
+
constructor(message, result, options) {
|
|
59
|
+
super("judge", message, options);
|
|
60
|
+
this.result = result;
|
|
61
|
+
}
|
|
62
|
+
result;
|
|
63
|
+
};
|
|
64
|
+
var DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
65
|
+
var DEFAULT_TIMEOUT_MS = Number(process.env.TANGLE_LLM_TIMEOUT_MS) || 3e5;
|
|
66
|
+
var DEFAULT_MAX_RETRIES = Number(process.env.TANGLE_LLM_MAX_RETRIES) || 3;
|
|
67
|
+
function resolveMaximumAttempts(configured) {
|
|
68
|
+
const attempts = configured ?? DEFAULT_MAX_RETRIES;
|
|
69
|
+
if (!Number.isInteger(attempts) || attempts <= 0) {
|
|
70
|
+
throw new RangeError("LLM maximum attempts must be a positive integer");
|
|
71
|
+
}
|
|
72
|
+
return attempts;
|
|
73
|
+
}
|
|
74
|
+
function providerTokenCount(value) {
|
|
75
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
76
|
+
}
|
|
77
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
78
|
+
var TRANSIENT_ERROR_PATTERNS = [
|
|
79
|
+
/AbortError/i,
|
|
80
|
+
/TimeoutError/i,
|
|
81
|
+
/this operation was aborted/i,
|
|
82
|
+
/fetch failed/i,
|
|
83
|
+
/ECONNRESET/i,
|
|
84
|
+
/ETIMEDOUT/i,
|
|
85
|
+
/EAI_AGAIN/i,
|
|
86
|
+
/socket hang up/i,
|
|
87
|
+
/stream.*ended.*unexpectedly/i,
|
|
88
|
+
/terminated/i,
|
|
89
|
+
/other side closed/i,
|
|
90
|
+
/NGHTTP2/i,
|
|
91
|
+
/UND_ERR/i
|
|
92
|
+
];
|
|
93
|
+
function isTransientLlmError(err) {
|
|
94
|
+
return classifyTransient(err, 0);
|
|
95
|
+
}
|
|
96
|
+
function classifyTransient(err, depth) {
|
|
97
|
+
if (err instanceof LlmCallError) return RETRYABLE_STATUS.has(err.status);
|
|
98
|
+
if (!(err instanceof Error)) return false;
|
|
99
|
+
const status = err.status;
|
|
100
|
+
if (typeof status === "number" && RETRYABLE_STATUS.has(status)) return true;
|
|
101
|
+
const code = err.code;
|
|
102
|
+
const haystack = `${err.name}
|
|
103
|
+
${err.message}
|
|
104
|
+
${typeof code === "string" ? code : ""}`;
|
|
105
|
+
if (TRANSIENT_ERROR_PATTERNS.some((p) => p.test(haystack))) return true;
|
|
106
|
+
const cause = err.cause;
|
|
107
|
+
if (depth < 4 && cause instanceof Error && cause !== err) {
|
|
108
|
+
return classifyTransient(cause, depth + 1);
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function parseRetryAfter(headers) {
|
|
113
|
+
const h = headers.get("retry-after");
|
|
114
|
+
if (!h) return null;
|
|
115
|
+
const asNumber = Number(h);
|
|
116
|
+
if (Number.isFinite(asNumber) && asNumber > 0) return asNumber * 1e3;
|
|
117
|
+
const asDate = Date.parse(h);
|
|
118
|
+
if (Number.isFinite(asDate)) return Math.max(0, asDate - Date.now());
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function backoffMs(attempt) {
|
|
122
|
+
return Math.min(500 * 2 ** attempt, 16e3);
|
|
123
|
+
}
|
|
124
|
+
function buildHeaders(opts) {
|
|
125
|
+
const headers = {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
Accept: "application/json"
|
|
128
|
+
};
|
|
129
|
+
if (opts.authHeader) {
|
|
130
|
+
headers[opts.authHeader.name] = opts.authHeader.value;
|
|
131
|
+
} else if (opts.bearer || opts.apiKey) {
|
|
132
|
+
headers.Authorization = `Bearer ${opts.bearer ?? opts.apiKey}`;
|
|
133
|
+
}
|
|
134
|
+
if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
135
|
+
return headers;
|
|
136
|
+
}
|
|
137
|
+
function isSchemaRejection(status, body) {
|
|
138
|
+
if (status !== 400) return false;
|
|
139
|
+
const lower = body.toLowerCase();
|
|
140
|
+
return lower.includes("response_format") || lower.includes("json_schema") || lower.includes("is unavailable") || lower.includes("not supported");
|
|
141
|
+
}
|
|
142
|
+
function buildBody(req, forceJsonObject) {
|
|
143
|
+
const body = {
|
|
144
|
+
model: req.model,
|
|
145
|
+
messages: req.messages,
|
|
146
|
+
temperature: req.temperature ?? 0
|
|
147
|
+
};
|
|
148
|
+
if (req.maxTokens != null) {
|
|
149
|
+
if (usesMaxCompletionTokens(req.model)) body.max_completion_tokens = req.maxTokens;
|
|
150
|
+
else body.max_tokens = req.maxTokens;
|
|
151
|
+
}
|
|
152
|
+
if (req.jsonSchema && !forceJsonObject) {
|
|
153
|
+
body.response_format = {
|
|
154
|
+
type: "json_schema",
|
|
155
|
+
json_schema: { name: req.jsonSchema.name, schema: req.jsonSchema.schema, strict: true }
|
|
156
|
+
};
|
|
157
|
+
} else if (req.jsonMode || req.jsonSchema) {
|
|
158
|
+
body.response_format = { type: "json_object" };
|
|
159
|
+
}
|
|
160
|
+
return body;
|
|
161
|
+
}
|
|
162
|
+
function usesMaxCompletionTokens(model) {
|
|
163
|
+
return /^gpt-5(?:[.-]|$)/i.test(model);
|
|
164
|
+
}
|
|
165
|
+
async function sleep(ms) {
|
|
166
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
167
|
+
}
|
|
168
|
+
function linkSignals(timeoutController, caller) {
|
|
169
|
+
if (!caller) return timeoutController.signal;
|
|
170
|
+
if (typeof AbortSignal.any === "function") {
|
|
171
|
+
return AbortSignal.any([timeoutController.signal, caller]);
|
|
172
|
+
}
|
|
173
|
+
if (caller.aborted) {
|
|
174
|
+
timeoutController.abort();
|
|
175
|
+
} else {
|
|
176
|
+
caller.addEventListener("abort", () => timeoutController.abort(), { once: true });
|
|
177
|
+
}
|
|
178
|
+
return timeoutController.signal;
|
|
179
|
+
}
|
|
180
|
+
function deadlineExceeded(start, deadlineMs) {
|
|
181
|
+
return deadlineMs != null && Date.now() - start >= deadlineMs;
|
|
182
|
+
}
|
|
183
|
+
function stripFencedJson(raw) {
|
|
184
|
+
const trimmed = raw.trim();
|
|
185
|
+
const m = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
|
|
186
|
+
return m ? m[1].trim() : trimmed;
|
|
187
|
+
}
|
|
188
|
+
function extractJsonPayload(raw) {
|
|
189
|
+
const stripped = stripFencedJson(raw);
|
|
190
|
+
try {
|
|
191
|
+
JSON.parse(stripped);
|
|
192
|
+
return stripped;
|
|
193
|
+
} catch {
|
|
194
|
+
if (stripped.startsWith("{") || stripped.startsWith("[")) return stripped;
|
|
195
|
+
}
|
|
196
|
+
const starts = [...stripped.matchAll(/[[{]/g)].map((match) => match.index).filter((index) => index != null);
|
|
197
|
+
for (const start of starts) {
|
|
198
|
+
const candidate = extractBalancedJson(stripped, start);
|
|
199
|
+
if (!candidate) continue;
|
|
200
|
+
try {
|
|
201
|
+
JSON.parse(candidate);
|
|
202
|
+
return candidate;
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return stripped;
|
|
207
|
+
}
|
|
208
|
+
function extractBalancedJson(input, start) {
|
|
209
|
+
const opener = input[start];
|
|
210
|
+
const closer = opener === "{" ? "}" : opener === "[" ? "]" : null;
|
|
211
|
+
if (!closer) return null;
|
|
212
|
+
const stack = [closer];
|
|
213
|
+
let isInString = false;
|
|
214
|
+
let isEscaped = false;
|
|
215
|
+
for (let i = start + 1; i < input.length; i++) {
|
|
216
|
+
const char = input[i];
|
|
217
|
+
if (isEscaped) {
|
|
218
|
+
isEscaped = false;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (char === "\\") {
|
|
222
|
+
isEscaped = isInString;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (char === '"') {
|
|
226
|
+
isInString = !isInString;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (isInString) continue;
|
|
230
|
+
if (char === "{") stack.push("}");
|
|
231
|
+
else if (char === "[") stack.push("]");
|
|
232
|
+
else if (char === stack[stack.length - 1]) {
|
|
233
|
+
stack.pop();
|
|
234
|
+
if (stack.length === 0) return input.slice(start, i + 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async function callLlm(req, opts = {}) {
|
|
240
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
241
|
+
const url = `${baseUrl}/chat/completions`;
|
|
242
|
+
const endpoint = "/chat/completions";
|
|
243
|
+
const timeoutMs = req.timeoutMs ?? opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
244
|
+
const maximumAttempts = resolveMaximumAttempts(opts.maxRetries);
|
|
245
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
246
|
+
const headers = buildHeaders(opts);
|
|
247
|
+
const provider = opts.provider ?? providerFromBaseUrl(baseUrl);
|
|
248
|
+
const sink = opts.rawSink;
|
|
249
|
+
const redactor = opts.redactor ?? defaultProviderRedactor;
|
|
250
|
+
const traceContext = opts.traceContext;
|
|
251
|
+
const callerSignal = opts.signal;
|
|
252
|
+
const deadlineMs = opts.deadlineMs;
|
|
253
|
+
const deadlineStart = Date.now();
|
|
254
|
+
let lastErr;
|
|
255
|
+
for (let attempt = 0; attempt < maximumAttempts; attempt++) {
|
|
256
|
+
if (callerSignal?.aborted) {
|
|
257
|
+
throw new DOMException("callLlm aborted by caller signal", "AbortError");
|
|
258
|
+
}
|
|
259
|
+
if (attempt > 0 && deadlineExceeded(deadlineStart, deadlineMs)) {
|
|
260
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
261
|
+
}
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
const attemptSignal = linkSignals(controller, callerSignal);
|
|
264
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
265
|
+
const started = Date.now();
|
|
266
|
+
const requestBody = buildBody(req, false);
|
|
267
|
+
let attemptErrorRecorded = false;
|
|
268
|
+
if (sink) {
|
|
269
|
+
await recordRaw(sink, redactor, {
|
|
270
|
+
eventId: cryptoEventId(),
|
|
271
|
+
runId: traceContext?.runId,
|
|
272
|
+
spanId: traceContext?.spanId,
|
|
273
|
+
provider,
|
|
274
|
+
model: req.model,
|
|
275
|
+
endpoint,
|
|
276
|
+
baseUrl,
|
|
277
|
+
attemptIndex: attempt,
|
|
278
|
+
direction: "request",
|
|
279
|
+
timestamp: started,
|
|
280
|
+
requestHeaders: headers,
|
|
281
|
+
requestBody,
|
|
282
|
+
redactedFields: []
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const res = await fetchFn(url, {
|
|
287
|
+
method: "POST",
|
|
288
|
+
headers,
|
|
289
|
+
body: JSON.stringify(requestBody),
|
|
290
|
+
signal: attemptSignal
|
|
291
|
+
});
|
|
292
|
+
clearTimeout(timeoutHandle);
|
|
293
|
+
const responseHeaders = sink ? headersToObject(res.headers) : void 0;
|
|
294
|
+
if (!res.ok) {
|
|
295
|
+
const body = await res.text();
|
|
296
|
+
if (sink) {
|
|
297
|
+
await recordRaw(sink, redactor, {
|
|
298
|
+
eventId: cryptoEventId(),
|
|
299
|
+
runId: traceContext?.runId,
|
|
300
|
+
spanId: traceContext?.spanId,
|
|
301
|
+
provider,
|
|
302
|
+
model: req.model,
|
|
303
|
+
endpoint,
|
|
304
|
+
baseUrl,
|
|
305
|
+
attemptIndex: attempt,
|
|
306
|
+
direction: "error",
|
|
307
|
+
timestamp: Date.now(),
|
|
308
|
+
durationMs: Date.now() - started,
|
|
309
|
+
statusCode: res.status,
|
|
310
|
+
responseHeaders,
|
|
311
|
+
responseBody: body,
|
|
312
|
+
errorMessage: `HTTP ${res.status}`,
|
|
313
|
+
redactedFields: []
|
|
314
|
+
});
|
|
315
|
+
attemptErrorRecorded = true;
|
|
316
|
+
}
|
|
317
|
+
const err = new LlmCallError(
|
|
318
|
+
`LLM call ${res.status}: ${body.slice(0, 300)}`,
|
|
319
|
+
res.status,
|
|
320
|
+
body,
|
|
321
|
+
req.model
|
|
322
|
+
);
|
|
323
|
+
if (RETRYABLE_STATUS.has(res.status) && attempt < maximumAttempts - 1 && !deadlineExceeded(deadlineStart, deadlineMs)) {
|
|
324
|
+
lastErr = err;
|
|
325
|
+
const retryAfter = parseRetryAfter(res.headers);
|
|
326
|
+
await sleep(retryAfter ?? backoffMs(attempt));
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
const text = await res.text();
|
|
332
|
+
let json;
|
|
333
|
+
try {
|
|
334
|
+
json = JSON.parse(text);
|
|
335
|
+
} catch (parseErr) {
|
|
336
|
+
if (sink) {
|
|
337
|
+
await recordRaw(sink, redactor, {
|
|
338
|
+
eventId: cryptoEventId(),
|
|
339
|
+
runId: traceContext?.runId,
|
|
340
|
+
spanId: traceContext?.spanId,
|
|
341
|
+
provider,
|
|
342
|
+
model: req.model,
|
|
343
|
+
endpoint,
|
|
344
|
+
baseUrl,
|
|
345
|
+
attemptIndex: attempt,
|
|
346
|
+
direction: "error",
|
|
347
|
+
timestamp: Date.now(),
|
|
348
|
+
durationMs: Date.now() - started,
|
|
349
|
+
statusCode: res.status,
|
|
350
|
+
responseHeaders,
|
|
351
|
+
responseBody: text,
|
|
352
|
+
errorMessage: `non-JSON response: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,
|
|
353
|
+
redactedFields: []
|
|
354
|
+
});
|
|
355
|
+
attemptErrorRecorded = true;
|
|
356
|
+
}
|
|
357
|
+
throw parseErr;
|
|
358
|
+
}
|
|
359
|
+
if (sink) {
|
|
360
|
+
await recordRaw(sink, redactor, {
|
|
361
|
+
eventId: cryptoEventId(),
|
|
362
|
+
runId: traceContext?.runId,
|
|
363
|
+
spanId: traceContext?.spanId,
|
|
364
|
+
provider,
|
|
365
|
+
model: req.model,
|
|
366
|
+
endpoint,
|
|
367
|
+
baseUrl,
|
|
368
|
+
attemptIndex: attempt,
|
|
369
|
+
direction: "response",
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
durationMs: Date.now() - started,
|
|
372
|
+
statusCode: res.status,
|
|
373
|
+
responseHeaders,
|
|
374
|
+
responseBody: json,
|
|
375
|
+
redactedFields: []
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const choice = json.choices?.[0];
|
|
379
|
+
const usageRaw = json.usage && typeof json.usage === "object" && !Array.isArray(json.usage) ? json.usage : void 0;
|
|
380
|
+
const promptTokens = providerTokenCount(usageRaw?.prompt_tokens);
|
|
381
|
+
const completionTokens = providerTokenCount(usageRaw?.completion_tokens);
|
|
382
|
+
const totalTokens = providerTokenCount(usageRaw?.total_tokens);
|
|
383
|
+
const cachedRaw = usageRaw?.prompt_tokens_details && typeof usageRaw.prompt_tokens_details === "object" && !Array.isArray(usageRaw.prompt_tokens_details) ? usageRaw.prompt_tokens_details.cached_tokens : void 0;
|
|
384
|
+
const cachedPromptTokens = cachedRaw === void 0 ? void 0 : providerTokenCount(cachedRaw);
|
|
385
|
+
const usageCaptured = promptTokens !== void 0 && completionTokens !== void 0 && (cachedRaw === void 0 || cachedPromptTokens !== void 0 && cachedPromptTokens <= promptTokens) && (totalTokens === void 0 || totalTokens === promptTokens + completionTokens);
|
|
386
|
+
const costFromProxy = json._response_cost ?? json.cost_usd;
|
|
387
|
+
const content = choice?.message?.content ?? "";
|
|
388
|
+
return {
|
|
389
|
+
content,
|
|
390
|
+
finishReason: choice?.finish_reason ?? null,
|
|
391
|
+
contentEmpty: content.trim().length === 0,
|
|
392
|
+
usage: {
|
|
393
|
+
promptTokens: promptTokens ?? 0,
|
|
394
|
+
completionTokens: completionTokens ?? 0,
|
|
395
|
+
totalTokens: totalTokens ?? (promptTokens ?? 0) + (completionTokens ?? 0),
|
|
396
|
+
captured: usageCaptured,
|
|
397
|
+
cachedPromptTokens
|
|
398
|
+
},
|
|
399
|
+
costUsd: typeof costFromProxy === "number" ? costFromProxy : null,
|
|
400
|
+
model: json.model ?? req.model,
|
|
401
|
+
durationMs: Date.now() - started,
|
|
402
|
+
raw: json
|
|
403
|
+
};
|
|
404
|
+
} catch (err) {
|
|
405
|
+
clearTimeout(timeoutHandle);
|
|
406
|
+
lastErr = err;
|
|
407
|
+
if (callerSignal?.aborted) {
|
|
408
|
+
if (sink && !attemptErrorRecorded) {
|
|
409
|
+
await recordRaw(sink, redactor, {
|
|
410
|
+
eventId: cryptoEventId(),
|
|
411
|
+
runId: traceContext?.runId,
|
|
412
|
+
spanId: traceContext?.spanId,
|
|
413
|
+
provider,
|
|
414
|
+
model: req.model,
|
|
415
|
+
endpoint,
|
|
416
|
+
baseUrl,
|
|
417
|
+
attemptIndex: attempt,
|
|
418
|
+
direction: "error",
|
|
419
|
+
timestamp: Date.now(),
|
|
420
|
+
durationMs: Date.now() - started,
|
|
421
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
422
|
+
redactedFields: []
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
if (sink && !attemptErrorRecorded) {
|
|
428
|
+
await recordRaw(sink, redactor, {
|
|
429
|
+
eventId: cryptoEventId(),
|
|
430
|
+
runId: traceContext?.runId,
|
|
431
|
+
spanId: traceContext?.spanId,
|
|
432
|
+
provider,
|
|
433
|
+
model: req.model,
|
|
434
|
+
endpoint,
|
|
435
|
+
baseUrl,
|
|
436
|
+
attemptIndex: attempt,
|
|
437
|
+
direction: "error",
|
|
438
|
+
timestamp: Date.now(),
|
|
439
|
+
durationMs: Date.now() - started,
|
|
440
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
441
|
+
redactedFields: []
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (attempt < maximumAttempts - 1 && isTransientLlmError(err) && !deadlineExceeded(deadlineStart, deadlineMs)) {
|
|
445
|
+
await sleep(backoffMs(attempt));
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
452
|
+
}
|
|
453
|
+
async function recordRaw(sink, redactor, event) {
|
|
454
|
+
try {
|
|
455
|
+
await sink.record(redactor(event));
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function headersToObject(h) {
|
|
460
|
+
const out = {};
|
|
461
|
+
h.forEach((value, key) => {
|
|
462
|
+
out[key] = value;
|
|
463
|
+
});
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
function cryptoEventId() {
|
|
467
|
+
if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
|
|
468
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
469
|
+
}
|
|
470
|
+
async function callLlmJson(req, opts = {}) {
|
|
471
|
+
const result = await callLlmStructured(req, opts);
|
|
472
|
+
const value = parseJsonResult(result);
|
|
473
|
+
return { value, result };
|
|
474
|
+
}
|
|
475
|
+
async function callLlmStructured(req, opts = {}) {
|
|
476
|
+
try {
|
|
477
|
+
return await callLlm({ ...req, jsonMode: req.jsonMode ?? !req.jsonSchema }, opts);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
if (err instanceof LlmCallError && isSchemaRejection(err.status, err.body) && req.jsonSchema) {
|
|
480
|
+
const degradedReq = { ...req, jsonMode: true, jsonSchema: void 0 };
|
|
481
|
+
return await callLlm(degradedReq, opts);
|
|
482
|
+
}
|
|
483
|
+
throw err;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function parseJsonResult(result) {
|
|
487
|
+
try {
|
|
488
|
+
if (result.finishReason === "length") {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`LLM returned truncated JSON content (model=${result.model}, finishReason=length)`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
return parseJsonSafely(result.content, result.model);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error instanceof LlmResponseError) throw error;
|
|
496
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
497
|
+
throw new LlmResponseError(cause.message, result, { cause });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function parseJsonSafely(content, model) {
|
|
501
|
+
const stripped = extractJsonPayload(content);
|
|
502
|
+
try {
|
|
503
|
+
return JSON.parse(stripped);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`LLM returned non-JSON content (model=${model}): ${err instanceof Error ? err.message : String(err)}
|
|
507
|
+
--- raw content ---
|
|
508
|
+
${content.slice(0, 800)}`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var LlmRouteAssertionError = class extends CaptureIntegrityError {
|
|
513
|
+
constructor(message, reason, baseUrl) {
|
|
514
|
+
super(message);
|
|
515
|
+
this.reason = reason;
|
|
516
|
+
this.baseUrl = baseUrl;
|
|
517
|
+
}
|
|
518
|
+
reason;
|
|
519
|
+
baseUrl;
|
|
520
|
+
};
|
|
521
|
+
function assertLlmRoute(opts, req = {}) {
|
|
522
|
+
const baseUrlExplicit = opts.baseUrl !== void 0;
|
|
523
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
524
|
+
if (req.requireExplicitBaseUrl && !baseUrlExplicit) {
|
|
525
|
+
throw new LlmRouteAssertionError(
|
|
526
|
+
`assertLlmRoute: requireExplicitBaseUrl set but opts.baseUrl is undefined; would fall back to ${DEFAULT_BASE_URL}.`,
|
|
527
|
+
"no_explicit_base_url",
|
|
528
|
+
baseUrl
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (req.blockedBaseUrls?.some((p) => matchUrl(baseUrl, p))) {
|
|
532
|
+
throw new LlmRouteAssertionError(
|
|
533
|
+
`assertLlmRoute: baseUrl ${baseUrl} matches a blocked pattern.`,
|
|
534
|
+
"base_url_blocked",
|
|
535
|
+
baseUrl
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
if (req.allowedBaseUrls && req.allowedBaseUrls.length > 0) {
|
|
539
|
+
const ok = req.allowedBaseUrls.some((p) => matchUrl(baseUrl, p));
|
|
540
|
+
if (!ok) {
|
|
541
|
+
throw new LlmRouteAssertionError(
|
|
542
|
+
`assertLlmRoute: baseUrl ${baseUrl} is not in the allowed list (${req.allowedBaseUrls.map(describePattern).join(", ")}).`,
|
|
543
|
+
"base_url_not_allowed",
|
|
544
|
+
baseUrl
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (req.requireAuth && !opts.apiKey && !opts.bearer && !opts.authHeader) {
|
|
549
|
+
throw new LlmRouteAssertionError(
|
|
550
|
+
`assertLlmRoute: requireAuth set but no apiKey, bearer, or authHeader was supplied.`,
|
|
551
|
+
"no_auth",
|
|
552
|
+
baseUrl
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
if (req.expectedProvider) {
|
|
556
|
+
const actual = opts.provider ?? providerFromBaseUrl(baseUrl);
|
|
557
|
+
if (actual !== req.expectedProvider) {
|
|
558
|
+
throw new LlmRouteAssertionError(
|
|
559
|
+
`assertLlmRoute: expected provider ${req.expectedProvider} but baseUrl ${baseUrl} resolves to ${actual}.`,
|
|
560
|
+
"wrong_provider",
|
|
561
|
+
baseUrl
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function matchUrl(url, pattern) {
|
|
567
|
+
if (pattern instanceof RegExp) return pattern.test(url);
|
|
568
|
+
return url.toLowerCase().startsWith(pattern.toLowerCase());
|
|
569
|
+
}
|
|
570
|
+
function describePattern(p) {
|
|
571
|
+
return p instanceof RegExp ? p.source : p;
|
|
572
|
+
}
|
|
573
|
+
async function probeLlm(model, opts = {}) {
|
|
574
|
+
const start = Date.now();
|
|
575
|
+
try {
|
|
576
|
+
await callLlm(
|
|
577
|
+
{
|
|
578
|
+
model,
|
|
579
|
+
messages: [{ role: "user", content: "ping" }],
|
|
580
|
+
maxTokens: 64,
|
|
581
|
+
timeoutMs: opts.timeoutMs ?? 3e4
|
|
582
|
+
},
|
|
583
|
+
opts
|
|
584
|
+
);
|
|
585
|
+
return { ok: true, latencyMs: Date.now() - start, error: null };
|
|
586
|
+
} catch (err) {
|
|
587
|
+
return {
|
|
588
|
+
ok: false,
|
|
589
|
+
latencyMs: Date.now() - start,
|
|
590
|
+
error: err instanceof Error ? err.message : String(err)
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
var LlmClient = class {
|
|
595
|
+
maximumAttempts;
|
|
596
|
+
opts;
|
|
597
|
+
constructor(opts = {}) {
|
|
598
|
+
this.opts = opts;
|
|
599
|
+
this.maximumAttempts = resolveMaximumAttempts(opts.maxRetries);
|
|
600
|
+
}
|
|
601
|
+
call(req, per) {
|
|
602
|
+
const options = { ...this.opts, ...per };
|
|
603
|
+
return req.jsonSchema ? callLlmStructured(req, options) : callLlm(req, options);
|
|
604
|
+
}
|
|
605
|
+
callJson(req, per) {
|
|
606
|
+
return callLlmJson(req, { ...this.opts, ...per });
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
export {
|
|
611
|
+
maximumChargeForLlmRequest,
|
|
612
|
+
costReceiptFromLlm,
|
|
613
|
+
costReceiptFromLlmError,
|
|
614
|
+
LlmCallError,
|
|
615
|
+
LlmResponseError,
|
|
616
|
+
isTransientLlmError,
|
|
617
|
+
backoffMs,
|
|
618
|
+
stripFencedJson,
|
|
619
|
+
callLlm,
|
|
620
|
+
callLlmJson,
|
|
621
|
+
LlmRouteAssertionError,
|
|
622
|
+
assertLlmRoute,
|
|
623
|
+
probeLlm,
|
|
624
|
+
LlmClient
|
|
625
|
+
};
|
|
626
|
+
//# sourceMappingURL=chunk-NJC7U437.js.map
|