@langwatch/scenario 0.2.2 → 0.2.9
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/README.md +3 -9
- package/dist/{chunk-NUZZAQV2.mjs → chunk-7H6OGEQ5.mjs} +85 -163
- package/dist/chunk-K7KLHTDI.mjs +146 -0
- package/dist/chunk-YPJZSK4J.mjs +121 -0
- package/dist/index.d.mts +86 -72
- package/dist/index.d.ts +86 -72
- package/dist/index.js +131 -82
- package/dist/index.mjs +40 -24
- package/dist/integrations/vitest/config.d.mts +5 -0
- package/dist/integrations/vitest/config.d.ts +5 -0
- package/dist/integrations/vitest/config.js +324 -0
- package/dist/integrations/vitest/config.mjs +35 -0
- package/dist/integrations/vitest/reporter.js +124 -1
- package/dist/integrations/vitest/reporter.mjs +4 -135
- package/dist/integrations/vitest/setup-global.d.mts +3 -0
- package/dist/integrations/vitest/setup-global.d.ts +3 -0
- package/dist/integrations/vitest/setup-global.js +30 -0
- package/dist/integrations/vitest/setup-global.mjs +11 -0
- package/dist/integrations/vitest/setup.js +97 -67
- package/dist/integrations/vitest/setup.mjs +7 -3
- package/package.json +13 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/integrations/vitest/config.ts
|
|
31
|
+
var config_exports = {};
|
|
32
|
+
__export(config_exports, {
|
|
33
|
+
withScenario: () => withScenario
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(config_exports);
|
|
36
|
+
var import_config = require("vitest/config");
|
|
37
|
+
|
|
38
|
+
// src/integrations/vitest/reporter.ts
|
|
39
|
+
var import_fs = __toESM(require("fs"));
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
|
|
43
|
+
// src/config/env.ts
|
|
44
|
+
var import_zod = require("zod");
|
|
45
|
+
|
|
46
|
+
// src/config/log-levels.ts
|
|
47
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
48
|
+
LogLevel2["ERROR"] = "ERROR";
|
|
49
|
+
LogLevel2["WARN"] = "WARN";
|
|
50
|
+
LogLevel2["INFO"] = "INFO";
|
|
51
|
+
LogLevel2["DEBUG"] = "DEBUG";
|
|
52
|
+
return LogLevel2;
|
|
53
|
+
})(LogLevel || {});
|
|
54
|
+
|
|
55
|
+
// src/config/env.ts
|
|
56
|
+
var envSchema = import_zod.z.object({
|
|
57
|
+
/**
|
|
58
|
+
* LangWatch API key for event reporting.
|
|
59
|
+
* If not provided, events will not be sent to LangWatch.
|
|
60
|
+
*/
|
|
61
|
+
LANGWATCH_API_KEY: import_zod.z.string().optional(),
|
|
62
|
+
/**
|
|
63
|
+
* LangWatch endpoint URL for event reporting.
|
|
64
|
+
* Defaults to the production LangWatch endpoint.
|
|
65
|
+
*/
|
|
66
|
+
LANGWATCH_ENDPOINT: import_zod.z.string().url().default("https://app.langwatch.ai"),
|
|
67
|
+
/**
|
|
68
|
+
* Disables simulation report info messages when set to any truthy value.
|
|
69
|
+
* Useful for CI/CD environments or when you want cleaner output.
|
|
70
|
+
*/
|
|
71
|
+
SCENARIO_DISABLE_SIMULATION_REPORT_INFO: import_zod.z.string().optional().transform((val) => Boolean(val)),
|
|
72
|
+
/**
|
|
73
|
+
* Node environment - affects logging and behavior.
|
|
74
|
+
* Defaults to 'development' if not specified.
|
|
75
|
+
*/
|
|
76
|
+
NODE_ENV: import_zod.z.enum(["development", "production", "test"]).default("development"),
|
|
77
|
+
/**
|
|
78
|
+
* Log level for the scenario package.
|
|
79
|
+
* Defaults to 'info' if not specified.
|
|
80
|
+
*/
|
|
81
|
+
LOG_LEVEL: import_zod.z.nativeEnum(LogLevel).optional(),
|
|
82
|
+
/**
|
|
83
|
+
* Scenario batch run ID.
|
|
84
|
+
* If not provided, a random ID will be generated.
|
|
85
|
+
*/
|
|
86
|
+
SCENARIO_BATCH_RUN_ID: import_zod.z.string().optional()
|
|
87
|
+
});
|
|
88
|
+
var env = envSchema.parse(process.env);
|
|
89
|
+
|
|
90
|
+
// src/utils/logger.ts
|
|
91
|
+
var Logger = class _Logger {
|
|
92
|
+
constructor(context) {
|
|
93
|
+
this.context = context;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Creates a logger with context (e.g., class name)
|
|
97
|
+
*/
|
|
98
|
+
static create(context) {
|
|
99
|
+
return new _Logger(context);
|
|
100
|
+
}
|
|
101
|
+
getLogLevel() {
|
|
102
|
+
return env.LOG_LEVEL ?? "INFO" /* INFO */;
|
|
103
|
+
}
|
|
104
|
+
getLogLevelIndex(level) {
|
|
105
|
+
return Object.values(LogLevel).indexOf(level);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Checks if logging should occur based on LOG_LEVEL env var
|
|
109
|
+
*/
|
|
110
|
+
shouldLog(level) {
|
|
111
|
+
const currentLevelIndex = this.getLogLevelIndex(this.getLogLevel());
|
|
112
|
+
const requestedLevelIndex = this.getLogLevelIndex(level);
|
|
113
|
+
return currentLevelIndex >= 0 && requestedLevelIndex <= currentLevelIndex;
|
|
114
|
+
}
|
|
115
|
+
formatMessage(message) {
|
|
116
|
+
return this.context ? `[${this.context}] ${message}` : message;
|
|
117
|
+
}
|
|
118
|
+
error(message, data) {
|
|
119
|
+
if (this.shouldLog("ERROR" /* ERROR */)) {
|
|
120
|
+
const formattedMessage = this.formatMessage(message);
|
|
121
|
+
if (data) {
|
|
122
|
+
console.error(formattedMessage, data);
|
|
123
|
+
} else {
|
|
124
|
+
console.error(formattedMessage);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
warn(message, data) {
|
|
129
|
+
if (this.shouldLog("WARN" /* WARN */)) {
|
|
130
|
+
const formattedMessage = this.formatMessage(message);
|
|
131
|
+
if (data) {
|
|
132
|
+
console.warn(formattedMessage, data);
|
|
133
|
+
} else {
|
|
134
|
+
console.warn(formattedMessage);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
info(message, data) {
|
|
139
|
+
if (this.shouldLog("INFO" /* INFO */)) {
|
|
140
|
+
const formattedMessage = this.formatMessage(message);
|
|
141
|
+
if (data) {
|
|
142
|
+
console.info(formattedMessage, data);
|
|
143
|
+
} else {
|
|
144
|
+
console.info(formattedMessage);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
debug(message, data) {
|
|
149
|
+
if (this.shouldLog("DEBUG" /* DEBUG */)) {
|
|
150
|
+
const formattedMessage = this.formatMessage(message);
|
|
151
|
+
if (data) {
|
|
152
|
+
console.log(formattedMessage, data);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(formattedMessage);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/integrations/vitest/reporter.ts
|
|
161
|
+
var logger = Logger.create("integrations:vitest:reporter");
|
|
162
|
+
function getProjectRoot() {
|
|
163
|
+
return process.cwd();
|
|
164
|
+
}
|
|
165
|
+
var projectRoot = getProjectRoot();
|
|
166
|
+
var logDir = import_path.default.join(projectRoot, ".scenario");
|
|
167
|
+
if (!import_fs.default.existsSync(logDir)) import_fs.default.mkdirSync(logDir);
|
|
168
|
+
function getLogFilePath(testId) {
|
|
169
|
+
return import_path.default.join(logDir, `${testId}.log`);
|
|
170
|
+
}
|
|
171
|
+
function getFullTestName(task) {
|
|
172
|
+
let name = task.name;
|
|
173
|
+
let parent = task.suite;
|
|
174
|
+
while (parent) {
|
|
175
|
+
name = `${parent.name} > ${name}`;
|
|
176
|
+
parent = parent.suite;
|
|
177
|
+
}
|
|
178
|
+
return name;
|
|
179
|
+
}
|
|
180
|
+
var VitestReporter = class {
|
|
181
|
+
results = [];
|
|
182
|
+
async onTestCaseResult(test) {
|
|
183
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
184
|
+
const fullName = getFullTestName(test);
|
|
185
|
+
const filePath = getLogFilePath(test.id);
|
|
186
|
+
if (!import_fs.default.existsSync(filePath)) {
|
|
187
|
+
logger.warn(
|
|
188
|
+
`No log file found ${filePath} for test ${fullName}`,
|
|
189
|
+
test.id
|
|
190
|
+
);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const lines = import_fs.default.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
194
|
+
const events = lines.map((line) => JSON.parse(line));
|
|
195
|
+
const runs = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const event of events) {
|
|
197
|
+
const runId = event.scenarioRunId ?? "unknown";
|
|
198
|
+
if (!runs.has(runId)) runs.set(runId, []);
|
|
199
|
+
runs.get(runId).push(event);
|
|
200
|
+
}
|
|
201
|
+
for (const [runId, runEvents] of Array.from(runs.entries())) {
|
|
202
|
+
const started = runEvents.find(
|
|
203
|
+
(e) => e.type === "SCENARIO_RUN_STARTED"
|
|
204
|
+
);
|
|
205
|
+
const finished = runEvents.find(
|
|
206
|
+
(e) => e.type === "SCENARIO_RUN_FINISHED"
|
|
207
|
+
);
|
|
208
|
+
const messages = runEvents.filter(
|
|
209
|
+
(e) => e.type === "SCENARIO_MESSAGE_SNAPSHOT"
|
|
210
|
+
);
|
|
211
|
+
this.results.push({
|
|
212
|
+
name: ((_a = started == null ? void 0 : started.metadata) == null ? void 0 : _a.name) ?? fullName,
|
|
213
|
+
status: (finished == null ? void 0 : finished.status) ?? "UNKNOWN",
|
|
214
|
+
duration: started && finished ? finished.timestamp - started.timestamp : 0,
|
|
215
|
+
reasoning: (_b = finished == null ? void 0 : finished.results) == null ? void 0 : _b.reasoning,
|
|
216
|
+
criteria: (finished == null ? void 0 : finished.results) ? `Success Criteria: ${((_c = finished.results.metCriteria) == null ? void 0 : _c.length) ?? 0}/${(((_d = finished.results.metCriteria) == null ? void 0 : _d.length) ?? 0) + (((_e = finished.results.unmetCriteria) == null ? void 0 : _e.length) ?? 0)}` : void 0
|
|
217
|
+
});
|
|
218
|
+
console.log(
|
|
219
|
+
`
|
|
220
|
+
--- Scenario Run: ${((_f = started == null ? void 0 : started.metadata) == null ? void 0 : _f.name) ?? runId} ---`
|
|
221
|
+
);
|
|
222
|
+
if (started) {
|
|
223
|
+
console.log(`Description: ${((_g = started.metadata) == null ? void 0 : _g.description) ?? ""}`);
|
|
224
|
+
}
|
|
225
|
+
if (messages.length) {
|
|
226
|
+
console.log("Chat log:");
|
|
227
|
+
let lastMessageCount = 0;
|
|
228
|
+
for (const msg of messages) {
|
|
229
|
+
const allMessages = msg.messages ?? [];
|
|
230
|
+
for (const m of allMessages.slice(lastMessageCount)) {
|
|
231
|
+
const role = m.role;
|
|
232
|
+
let roleLabel = role;
|
|
233
|
+
if (role.toLowerCase() === "user") roleLabel = import_chalk.default.green("User");
|
|
234
|
+
else if (role.toLowerCase() === "agent")
|
|
235
|
+
roleLabel = import_chalk.default.cyan("Agent");
|
|
236
|
+
else if (role.toLowerCase() === "assistant")
|
|
237
|
+
roleLabel = import_chalk.default.cyan("Assistant");
|
|
238
|
+
else roleLabel = import_chalk.default.yellow(role);
|
|
239
|
+
console.log(`${roleLabel}: ${m.content}`);
|
|
240
|
+
}
|
|
241
|
+
lastMessageCount = allMessages.length;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (finished) {
|
|
245
|
+
console.log("--- Verdict ---");
|
|
246
|
+
console.log(`Status: ${finished.status}`);
|
|
247
|
+
if (finished.results) {
|
|
248
|
+
console.log(`Verdict: ${finished.results.verdict}`);
|
|
249
|
+
if (finished.results.reasoning)
|
|
250
|
+
console.log(`Reasoning: ${finished.results.reasoning}`);
|
|
251
|
+
if ((_h = finished.results.metCriteria) == null ? void 0 : _h.length)
|
|
252
|
+
console.log(
|
|
253
|
+
`Met criteria: ${finished.results.metCriteria.join(", ")}`
|
|
254
|
+
);
|
|
255
|
+
if ((_i = finished.results.unmetCriteria) == null ? void 0 : _i.length)
|
|
256
|
+
console.log(
|
|
257
|
+
`Unmet criteria: ${finished.results.unmetCriteria.join(", ")}`
|
|
258
|
+
);
|
|
259
|
+
if (finished.results.error)
|
|
260
|
+
console.log(`Error: ${finished.results.error}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
console.log("-----------------------------\n");
|
|
264
|
+
}
|
|
265
|
+
import_fs.default.unlinkSync(filePath);
|
|
266
|
+
}
|
|
267
|
+
async onTestRunEnd() {
|
|
268
|
+
if (this.results.length === 0) return;
|
|
269
|
+
const total = this.results.length;
|
|
270
|
+
const passed = this.results.filter((r) => r.status === "SUCCESS").length;
|
|
271
|
+
const failed = this.results.filter((r) => r.status !== "SUCCESS").length;
|
|
272
|
+
const successRate = (passed / total * 100).toFixed(1);
|
|
273
|
+
console.log();
|
|
274
|
+
console.log(import_chalk.default.bold.cyan("=== Scenario Test Report ==="));
|
|
275
|
+
console.log(`Total Scenarios: ${total}`);
|
|
276
|
+
console.log(import_chalk.default.green(`Passed: ${passed}`));
|
|
277
|
+
console.log(import_chalk.default.red(`Failed: ${failed}`));
|
|
278
|
+
console.log(`Success Rate: ${import_chalk.default.bold(`${successRate}%`)}`);
|
|
279
|
+
this.results.forEach((r, i) => {
|
|
280
|
+
const statusColor = r.status === "SUCCESS" ? import_chalk.default.green : import_chalk.default.red;
|
|
281
|
+
console.log();
|
|
282
|
+
console.log(
|
|
283
|
+
`${i + 1}. ${r.name} - ${statusColor(r.status)} in ${(r.duration / 1e3).toFixed(2)}s`
|
|
284
|
+
);
|
|
285
|
+
if (r.reasoning) {
|
|
286
|
+
console.log(import_chalk.default.greenBright(" Reasoning: ") + r.reasoning);
|
|
287
|
+
}
|
|
288
|
+
if (r.criteria) {
|
|
289
|
+
console.log(import_chalk.default.bold(" " + r.criteria));
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
console.log();
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/integrations/vitest/config.ts
|
|
297
|
+
function withScenario(config) {
|
|
298
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
299
|
+
const normalizedSetupFiles = ((_a = config.test) == null ? void 0 : _a.setupFiles) === void 0 ? [] : Array.isArray((_b = config.test) == null ? void 0 : _b.setupFiles) ? (_c = config.test) == null ? void 0 : _c.setupFiles : [(_d = config.test) == null ? void 0 : _d.setupFiles];
|
|
300
|
+
const normalizedGlobalSetup = ((_e = config.test) == null ? void 0 : _e.globalSetup) === void 0 ? [] : Array.isArray((_f = config.test) == null ? void 0 : _f.globalSetup) ? (_g = config.test) == null ? void 0 : _g.globalSetup : [(_h = config.test) == null ? void 0 : _h.globalSetup];
|
|
301
|
+
const normalizedReporters = ((_i = config.test) == null ? void 0 : _i.reporters) === void 0 ? [] : Array.isArray((_j = config.test) == null ? void 0 : _j.reporters) ? (_k = config.test) == null ? void 0 : _k.reporters : [(_l = config.test) == null ? void 0 : _l.reporters];
|
|
302
|
+
return (0, import_config.defineConfig)({
|
|
303
|
+
...config,
|
|
304
|
+
test: {
|
|
305
|
+
...config.test,
|
|
306
|
+
setupFiles: [
|
|
307
|
+
"@langwatch/scenario/integrations/vitest/setup",
|
|
308
|
+
...normalizedSetupFiles
|
|
309
|
+
],
|
|
310
|
+
globalSetup: [
|
|
311
|
+
"@langwatch/scenario/integrations/vitest/setup-global",
|
|
312
|
+
...normalizedGlobalSetup
|
|
313
|
+
],
|
|
314
|
+
reporters: [
|
|
315
|
+
...normalizedReporters,
|
|
316
|
+
new VitestReporter()
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
322
|
+
0 && (module.exports = {
|
|
323
|
+
withScenario
|
|
324
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VitestReporter
|
|
3
|
+
} from "../../chunk-K7KLHTDI.mjs";
|
|
4
|
+
import "../../chunk-YPJZSK4J.mjs";
|
|
5
|
+
import "../../chunk-7P6ASYW6.mjs";
|
|
6
|
+
|
|
7
|
+
// src/integrations/vitest/config.ts
|
|
8
|
+
import { defineConfig } from "vitest/config";
|
|
9
|
+
function withScenario(config) {
|
|
10
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
11
|
+
const normalizedSetupFiles = ((_a = config.test) == null ? void 0 : _a.setupFiles) === void 0 ? [] : Array.isArray((_b = config.test) == null ? void 0 : _b.setupFiles) ? (_c = config.test) == null ? void 0 : _c.setupFiles : [(_d = config.test) == null ? void 0 : _d.setupFiles];
|
|
12
|
+
const normalizedGlobalSetup = ((_e = config.test) == null ? void 0 : _e.globalSetup) === void 0 ? [] : Array.isArray((_f = config.test) == null ? void 0 : _f.globalSetup) ? (_g = config.test) == null ? void 0 : _g.globalSetup : [(_h = config.test) == null ? void 0 : _h.globalSetup];
|
|
13
|
+
const normalizedReporters = ((_i = config.test) == null ? void 0 : _i.reporters) === void 0 ? [] : Array.isArray((_j = config.test) == null ? void 0 : _j.reporters) ? (_k = config.test) == null ? void 0 : _k.reporters : [(_l = config.test) == null ? void 0 : _l.reporters];
|
|
14
|
+
return defineConfig({
|
|
15
|
+
...config,
|
|
16
|
+
test: {
|
|
17
|
+
...config.test,
|
|
18
|
+
setupFiles: [
|
|
19
|
+
"@langwatch/scenario/integrations/vitest/setup",
|
|
20
|
+
...normalizedSetupFiles
|
|
21
|
+
],
|
|
22
|
+
globalSetup: [
|
|
23
|
+
"@langwatch/scenario/integrations/vitest/setup-global",
|
|
24
|
+
...normalizedGlobalSetup
|
|
25
|
+
],
|
|
26
|
+
reporters: [
|
|
27
|
+
...normalizedReporters,
|
|
28
|
+
new VitestReporter()
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
withScenario
|
|
35
|
+
};
|
|
@@ -36,6 +36,126 @@ module.exports = __toCommonJS(reporter_exports);
|
|
|
36
36
|
var import_fs = __toESM(require("fs"));
|
|
37
37
|
var import_path = __toESM(require("path"));
|
|
38
38
|
var import_chalk = __toESM(require("chalk"));
|
|
39
|
+
|
|
40
|
+
// src/config/env.ts
|
|
41
|
+
var import_zod = require("zod");
|
|
42
|
+
|
|
43
|
+
// src/config/log-levels.ts
|
|
44
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
45
|
+
LogLevel2["ERROR"] = "ERROR";
|
|
46
|
+
LogLevel2["WARN"] = "WARN";
|
|
47
|
+
LogLevel2["INFO"] = "INFO";
|
|
48
|
+
LogLevel2["DEBUG"] = "DEBUG";
|
|
49
|
+
return LogLevel2;
|
|
50
|
+
})(LogLevel || {});
|
|
51
|
+
|
|
52
|
+
// src/config/env.ts
|
|
53
|
+
var envSchema = import_zod.z.object({
|
|
54
|
+
/**
|
|
55
|
+
* LangWatch API key for event reporting.
|
|
56
|
+
* If not provided, events will not be sent to LangWatch.
|
|
57
|
+
*/
|
|
58
|
+
LANGWATCH_API_KEY: import_zod.z.string().optional(),
|
|
59
|
+
/**
|
|
60
|
+
* LangWatch endpoint URL for event reporting.
|
|
61
|
+
* Defaults to the production LangWatch endpoint.
|
|
62
|
+
*/
|
|
63
|
+
LANGWATCH_ENDPOINT: import_zod.z.string().url().default("https://app.langwatch.ai"),
|
|
64
|
+
/**
|
|
65
|
+
* Disables simulation report info messages when set to any truthy value.
|
|
66
|
+
* Useful for CI/CD environments or when you want cleaner output.
|
|
67
|
+
*/
|
|
68
|
+
SCENARIO_DISABLE_SIMULATION_REPORT_INFO: import_zod.z.string().optional().transform((val) => Boolean(val)),
|
|
69
|
+
/**
|
|
70
|
+
* Node environment - affects logging and behavior.
|
|
71
|
+
* Defaults to 'development' if not specified.
|
|
72
|
+
*/
|
|
73
|
+
NODE_ENV: import_zod.z.enum(["development", "production", "test"]).default("development"),
|
|
74
|
+
/**
|
|
75
|
+
* Log level for the scenario package.
|
|
76
|
+
* Defaults to 'info' if not specified.
|
|
77
|
+
*/
|
|
78
|
+
LOG_LEVEL: import_zod.z.nativeEnum(LogLevel).optional(),
|
|
79
|
+
/**
|
|
80
|
+
* Scenario batch run ID.
|
|
81
|
+
* If not provided, a random ID will be generated.
|
|
82
|
+
*/
|
|
83
|
+
SCENARIO_BATCH_RUN_ID: import_zod.z.string().optional()
|
|
84
|
+
});
|
|
85
|
+
var env = envSchema.parse(process.env);
|
|
86
|
+
|
|
87
|
+
// src/utils/logger.ts
|
|
88
|
+
var Logger = class _Logger {
|
|
89
|
+
constructor(context) {
|
|
90
|
+
this.context = context;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Creates a logger with context (e.g., class name)
|
|
94
|
+
*/
|
|
95
|
+
static create(context) {
|
|
96
|
+
return new _Logger(context);
|
|
97
|
+
}
|
|
98
|
+
getLogLevel() {
|
|
99
|
+
return env.LOG_LEVEL ?? "INFO" /* INFO */;
|
|
100
|
+
}
|
|
101
|
+
getLogLevelIndex(level) {
|
|
102
|
+
return Object.values(LogLevel).indexOf(level);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Checks if logging should occur based on LOG_LEVEL env var
|
|
106
|
+
*/
|
|
107
|
+
shouldLog(level) {
|
|
108
|
+
const currentLevelIndex = this.getLogLevelIndex(this.getLogLevel());
|
|
109
|
+
const requestedLevelIndex = this.getLogLevelIndex(level);
|
|
110
|
+
return currentLevelIndex >= 0 && requestedLevelIndex <= currentLevelIndex;
|
|
111
|
+
}
|
|
112
|
+
formatMessage(message) {
|
|
113
|
+
return this.context ? `[${this.context}] ${message}` : message;
|
|
114
|
+
}
|
|
115
|
+
error(message, data) {
|
|
116
|
+
if (this.shouldLog("ERROR" /* ERROR */)) {
|
|
117
|
+
const formattedMessage = this.formatMessage(message);
|
|
118
|
+
if (data) {
|
|
119
|
+
console.error(formattedMessage, data);
|
|
120
|
+
} else {
|
|
121
|
+
console.error(formattedMessage);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
warn(message, data) {
|
|
126
|
+
if (this.shouldLog("WARN" /* WARN */)) {
|
|
127
|
+
const formattedMessage = this.formatMessage(message);
|
|
128
|
+
if (data) {
|
|
129
|
+
console.warn(formattedMessage, data);
|
|
130
|
+
} else {
|
|
131
|
+
console.warn(formattedMessage);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
info(message, data) {
|
|
136
|
+
if (this.shouldLog("INFO" /* INFO */)) {
|
|
137
|
+
const formattedMessage = this.formatMessage(message);
|
|
138
|
+
if (data) {
|
|
139
|
+
console.info(formattedMessage, data);
|
|
140
|
+
} else {
|
|
141
|
+
console.info(formattedMessage);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
debug(message, data) {
|
|
146
|
+
if (this.shouldLog("DEBUG" /* DEBUG */)) {
|
|
147
|
+
const formattedMessage = this.formatMessage(message);
|
|
148
|
+
if (data) {
|
|
149
|
+
console.log(formattedMessage, data);
|
|
150
|
+
} else {
|
|
151
|
+
console.log(formattedMessage);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/integrations/vitest/reporter.ts
|
|
158
|
+
var logger = Logger.create("integrations:vitest:reporter");
|
|
39
159
|
function getProjectRoot() {
|
|
40
160
|
return process.cwd();
|
|
41
161
|
}
|
|
@@ -61,7 +181,10 @@ var VitestReporter = class {
|
|
|
61
181
|
const fullName = getFullTestName(test);
|
|
62
182
|
const filePath = getLogFilePath(test.id);
|
|
63
183
|
if (!import_fs.default.existsSync(filePath)) {
|
|
64
|
-
|
|
184
|
+
logger.warn(
|
|
185
|
+
`No log file found ${filePath} for test ${fullName}`,
|
|
186
|
+
test.id
|
|
187
|
+
);
|
|
65
188
|
return;
|
|
66
189
|
}
|
|
67
190
|
const lines = import_fs.default.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
@@ -1,139 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VitestReporter
|
|
3
|
+
} from "../../chunk-K7KLHTDI.mjs";
|
|
4
|
+
import "../../chunk-YPJZSK4J.mjs";
|
|
1
5
|
import "../../chunk-7P6ASYW6.mjs";
|
|
2
|
-
|
|
3
|
-
// src/integrations/vitest/reporter.ts
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
function getProjectRoot() {
|
|
8
|
-
return process.cwd();
|
|
9
|
-
}
|
|
10
|
-
var projectRoot = getProjectRoot();
|
|
11
|
-
var logDir = path.join(projectRoot, ".scenario");
|
|
12
|
-
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);
|
|
13
|
-
function getLogFilePath(testId) {
|
|
14
|
-
return path.join(logDir, `${testId}.log`);
|
|
15
|
-
}
|
|
16
|
-
function getFullTestName(task) {
|
|
17
|
-
let name = task.name;
|
|
18
|
-
let parent = task.suite;
|
|
19
|
-
while (parent) {
|
|
20
|
-
name = `${parent.name} > ${name}`;
|
|
21
|
-
parent = parent.suite;
|
|
22
|
-
}
|
|
23
|
-
return name;
|
|
24
|
-
}
|
|
25
|
-
var VitestReporter = class {
|
|
26
|
-
results = [];
|
|
27
|
-
async onTestCaseResult(test) {
|
|
28
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
29
|
-
const fullName = getFullTestName(test);
|
|
30
|
-
const filePath = getLogFilePath(test.id);
|
|
31
|
-
if (!fs.existsSync(filePath)) {
|
|
32
|
-
console.log(`No log file found ${filePath} for test ${fullName}`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const lines = fs.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
36
|
-
const events = lines.map((line) => JSON.parse(line));
|
|
37
|
-
const runs = /* @__PURE__ */ new Map();
|
|
38
|
-
for (const event of events) {
|
|
39
|
-
const runId = event.scenarioRunId ?? "unknown";
|
|
40
|
-
if (!runs.has(runId)) runs.set(runId, []);
|
|
41
|
-
runs.get(runId).push(event);
|
|
42
|
-
}
|
|
43
|
-
for (const [runId, runEvents] of Array.from(runs.entries())) {
|
|
44
|
-
const started = runEvents.find(
|
|
45
|
-
(e) => e.type === "SCENARIO_RUN_STARTED"
|
|
46
|
-
);
|
|
47
|
-
const finished = runEvents.find(
|
|
48
|
-
(e) => e.type === "SCENARIO_RUN_FINISHED"
|
|
49
|
-
);
|
|
50
|
-
const messages = runEvents.filter(
|
|
51
|
-
(e) => e.type === "SCENARIO_MESSAGE_SNAPSHOT"
|
|
52
|
-
);
|
|
53
|
-
this.results.push({
|
|
54
|
-
name: ((_a = started == null ? void 0 : started.metadata) == null ? void 0 : _a.name) ?? fullName,
|
|
55
|
-
status: (finished == null ? void 0 : finished.status) ?? "UNKNOWN",
|
|
56
|
-
duration: started && finished ? finished.timestamp - started.timestamp : 0,
|
|
57
|
-
reasoning: (_b = finished == null ? void 0 : finished.results) == null ? void 0 : _b.reasoning,
|
|
58
|
-
criteria: (finished == null ? void 0 : finished.results) ? `Success Criteria: ${((_c = finished.results.metCriteria) == null ? void 0 : _c.length) ?? 0}/${(((_d = finished.results.metCriteria) == null ? void 0 : _d.length) ?? 0) + (((_e = finished.results.unmetCriteria) == null ? void 0 : _e.length) ?? 0)}` : void 0
|
|
59
|
-
});
|
|
60
|
-
console.log(
|
|
61
|
-
`
|
|
62
|
-
--- Scenario Run: ${((_f = started == null ? void 0 : started.metadata) == null ? void 0 : _f.name) ?? runId} ---`
|
|
63
|
-
);
|
|
64
|
-
if (started) {
|
|
65
|
-
console.log(`Description: ${((_g = started.metadata) == null ? void 0 : _g.description) ?? ""}`);
|
|
66
|
-
}
|
|
67
|
-
if (messages.length) {
|
|
68
|
-
console.log("Chat log:");
|
|
69
|
-
let lastMessageCount = 0;
|
|
70
|
-
for (const msg of messages) {
|
|
71
|
-
const allMessages = msg.messages ?? [];
|
|
72
|
-
for (const m of allMessages.slice(lastMessageCount)) {
|
|
73
|
-
const role = m.role;
|
|
74
|
-
let roleLabel = role;
|
|
75
|
-
if (role.toLowerCase() === "user") roleLabel = chalk.green("User");
|
|
76
|
-
else if (role.toLowerCase() === "agent")
|
|
77
|
-
roleLabel = chalk.cyan("Agent");
|
|
78
|
-
else if (role.toLowerCase() === "assistant")
|
|
79
|
-
roleLabel = chalk.cyan("Assistant");
|
|
80
|
-
else roleLabel = chalk.yellow(role);
|
|
81
|
-
console.log(`${roleLabel}: ${m.content}`);
|
|
82
|
-
}
|
|
83
|
-
lastMessageCount = allMessages.length;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (finished) {
|
|
87
|
-
console.log("--- Verdict ---");
|
|
88
|
-
console.log(`Status: ${finished.status}`);
|
|
89
|
-
if (finished.results) {
|
|
90
|
-
console.log(`Verdict: ${finished.results.verdict}`);
|
|
91
|
-
if (finished.results.reasoning)
|
|
92
|
-
console.log(`Reasoning: ${finished.results.reasoning}`);
|
|
93
|
-
if ((_h = finished.results.metCriteria) == null ? void 0 : _h.length)
|
|
94
|
-
console.log(
|
|
95
|
-
`Met criteria: ${finished.results.metCriteria.join(", ")}`
|
|
96
|
-
);
|
|
97
|
-
if ((_i = finished.results.unmetCriteria) == null ? void 0 : _i.length)
|
|
98
|
-
console.log(
|
|
99
|
-
`Unmet criteria: ${finished.results.unmetCriteria.join(", ")}`
|
|
100
|
-
);
|
|
101
|
-
if (finished.results.error)
|
|
102
|
-
console.log(`Error: ${finished.results.error}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
console.log("-----------------------------\n");
|
|
106
|
-
}
|
|
107
|
-
fs.unlinkSync(filePath);
|
|
108
|
-
}
|
|
109
|
-
async onTestRunEnd() {
|
|
110
|
-
if (this.results.length === 0) return;
|
|
111
|
-
const total = this.results.length;
|
|
112
|
-
const passed = this.results.filter((r) => r.status === "SUCCESS").length;
|
|
113
|
-
const failed = this.results.filter((r) => r.status !== "SUCCESS").length;
|
|
114
|
-
const successRate = (passed / total * 100).toFixed(1);
|
|
115
|
-
console.log();
|
|
116
|
-
console.log(chalk.bold.cyan("=== Scenario Test Report ==="));
|
|
117
|
-
console.log(`Total Scenarios: ${total}`);
|
|
118
|
-
console.log(chalk.green(`Passed: ${passed}`));
|
|
119
|
-
console.log(chalk.red(`Failed: ${failed}`));
|
|
120
|
-
console.log(`Success Rate: ${chalk.bold(`${successRate}%`)}`);
|
|
121
|
-
this.results.forEach((r, i) => {
|
|
122
|
-
const statusColor = r.status === "SUCCESS" ? chalk.green : chalk.red;
|
|
123
|
-
console.log();
|
|
124
|
-
console.log(
|
|
125
|
-
`${i + 1}. ${r.name} - ${statusColor(r.status)} in ${(r.duration / 1e3).toFixed(2)}s`
|
|
126
|
-
);
|
|
127
|
-
if (r.reasoning) {
|
|
128
|
-
console.log(chalk.greenBright(" Reasoning: ") + r.reasoning);
|
|
129
|
-
}
|
|
130
|
-
if (r.criteria) {
|
|
131
|
-
console.log(chalk.bold(" " + r.criteria));
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
console.log();
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
6
|
export {
|
|
138
7
|
VitestReporter as default
|
|
139
8
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/integrations/vitest/setup-global.ts
|
|
21
|
+
var setup_global_exports = {};
|
|
22
|
+
__export(setup_global_exports, {
|
|
23
|
+
default: () => setup
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(setup_global_exports);
|
|
26
|
+
var import_xksuid = require("xksuid");
|
|
27
|
+
function setup() {
|
|
28
|
+
const scenarioBatchRunId = `scenariobatchrun_${(0, import_xksuid.generate)()}`;
|
|
29
|
+
process.env.SCENARIO_BATCH_RUN_ID = scenarioBatchRunId;
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "../../chunk-7P6ASYW6.mjs";
|
|
2
|
+
|
|
3
|
+
// src/integrations/vitest/setup-global.ts
|
|
4
|
+
import { generate } from "xksuid";
|
|
5
|
+
function setup() {
|
|
6
|
+
const scenarioBatchRunId = `scenariobatchrun_${generate()}`;
|
|
7
|
+
process.env.SCENARIO_BATCH_RUN_ID = scenarioBatchRunId;
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
setup as default
|
|
11
|
+
};
|