@langwatch/scenario 0.2.0 → 0.2.1
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/dist/chunk-ZMHTHRDR.mjs +618 -0
- package/dist/index.js +232 -87
- package/dist/index.mjs +44 -167
- package/dist/integrations/vitest/setup.js +280 -105
- package/dist/integrations/vitest/setup.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-ORWSJC5F.mjs +0 -309
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-7P6ASYW6.mjs";
|
|
4
|
+
|
|
5
|
+
// src/domain/core/config.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var scenarioProjectConfigSchema = z.object({
|
|
8
|
+
defaultModel: z.object({
|
|
9
|
+
model: z.custom(),
|
|
10
|
+
temperature: z.number().min(0).max(1).optional().default(0),
|
|
11
|
+
maxTokens: z.number().optional()
|
|
12
|
+
}).optional(),
|
|
13
|
+
langwatchEndpoint: z.string().optional(),
|
|
14
|
+
langwatchApiKey: z.string().optional()
|
|
15
|
+
}).strict();
|
|
16
|
+
function defineConfig(config2) {
|
|
17
|
+
return config2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/domain/agents/index.ts
|
|
21
|
+
var AgentRole = /* @__PURE__ */ ((AgentRole2) => {
|
|
22
|
+
AgentRole2["USER"] = "User";
|
|
23
|
+
AgentRole2["AGENT"] = "Agent";
|
|
24
|
+
AgentRole2["JUDGE"] = "Judge";
|
|
25
|
+
return AgentRole2;
|
|
26
|
+
})(AgentRole || {});
|
|
27
|
+
var allAgentRoles = ["User" /* USER */, "Agent" /* AGENT */, "Judge" /* JUDGE */];
|
|
28
|
+
var AgentAdapter = class {
|
|
29
|
+
role = "Agent" /* AGENT */;
|
|
30
|
+
constructor(input) {
|
|
31
|
+
void input;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var UserSimulatorAgentAdapter = class {
|
|
35
|
+
role = "User" /* USER */;
|
|
36
|
+
constructor(input) {
|
|
37
|
+
void input;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var JudgeAgentAdapter = class {
|
|
41
|
+
role = "Judge" /* JUDGE */;
|
|
42
|
+
constructor(input) {
|
|
43
|
+
void input;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/domain/index.ts
|
|
48
|
+
var domain_exports = {};
|
|
49
|
+
__export(domain_exports, {
|
|
50
|
+
AgentAdapter: () => AgentAdapter,
|
|
51
|
+
AgentRole: () => AgentRole,
|
|
52
|
+
JudgeAgentAdapter: () => JudgeAgentAdapter,
|
|
53
|
+
UserSimulatorAgentAdapter: () => UserSimulatorAgentAdapter,
|
|
54
|
+
allAgentRoles: () => allAgentRoles,
|
|
55
|
+
defineConfig: () => defineConfig,
|
|
56
|
+
scenarioProjectConfigSchema: () => scenarioProjectConfigSchema
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// src/events/event-bus.ts
|
|
60
|
+
import {
|
|
61
|
+
concatMap,
|
|
62
|
+
EMPTY,
|
|
63
|
+
catchError,
|
|
64
|
+
Subject,
|
|
65
|
+
tap,
|
|
66
|
+
map
|
|
67
|
+
} from "rxjs";
|
|
68
|
+
|
|
69
|
+
// src/config/load.ts
|
|
70
|
+
import fs from "node:fs/promises";
|
|
71
|
+
import path from "node:path";
|
|
72
|
+
import { pathToFileURL } from "node:url";
|
|
73
|
+
async function loadScenarioProjectConfig() {
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
const configNames = [
|
|
76
|
+
"scenario.config.js",
|
|
77
|
+
"scenario.config.mjs"
|
|
78
|
+
];
|
|
79
|
+
for (const name of configNames) {
|
|
80
|
+
const fullPath = path.join(cwd, name);
|
|
81
|
+
try {
|
|
82
|
+
await fs.access(fullPath);
|
|
83
|
+
const configModule = await import(pathToFileURL(fullPath).href);
|
|
84
|
+
const config2 = configModule.default || configModule;
|
|
85
|
+
const parsed = scenarioProjectConfigSchema.safeParse(config2);
|
|
86
|
+
if (!parsed.success) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Invalid config file ${name}: ${JSON.stringify(parsed.error.format(), null, 2)}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return parsed.data;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return await scenarioProjectConfigSchema.parseAsync({});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/utils/logger.ts
|
|
103
|
+
var Logger = class _Logger {
|
|
104
|
+
constructor(context) {
|
|
105
|
+
this.context = context;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates a logger with context (e.g., class name)
|
|
109
|
+
*/
|
|
110
|
+
static create(context) {
|
|
111
|
+
return new _Logger(context);
|
|
112
|
+
}
|
|
113
|
+
getLogLevel() {
|
|
114
|
+
return env.SCENARIO_LOG_LEVEL ?? "INFO" /* INFO */;
|
|
115
|
+
}
|
|
116
|
+
getLogLevelIndex(level) {
|
|
117
|
+
return Object.values(LogLevel).indexOf(level);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Checks if logging should occur based on LOG_LEVEL env var
|
|
121
|
+
*/
|
|
122
|
+
shouldLog(level) {
|
|
123
|
+
const currentLevelIndex = this.getLogLevelIndex(this.getLogLevel());
|
|
124
|
+
const requestedLevelIndex = this.getLogLevelIndex(level);
|
|
125
|
+
return currentLevelIndex >= 0 && requestedLevelIndex <= currentLevelIndex;
|
|
126
|
+
}
|
|
127
|
+
formatMessage(message) {
|
|
128
|
+
return this.context ? `[${this.context}] ${message}` : message;
|
|
129
|
+
}
|
|
130
|
+
error(message, data) {
|
|
131
|
+
if (this.shouldLog("ERROR" /* ERROR */)) {
|
|
132
|
+
const formattedMessage = this.formatMessage(message);
|
|
133
|
+
if (data) {
|
|
134
|
+
console.error(formattedMessage, data);
|
|
135
|
+
} else {
|
|
136
|
+
console.error(formattedMessage);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
warn(message, data) {
|
|
141
|
+
if (this.shouldLog("WARN" /* WARN */)) {
|
|
142
|
+
const formattedMessage = this.formatMessage(message);
|
|
143
|
+
if (data) {
|
|
144
|
+
console.warn(formattedMessage, data);
|
|
145
|
+
} else {
|
|
146
|
+
console.warn(formattedMessage);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
info(message, data) {
|
|
151
|
+
if (this.shouldLog("INFO" /* INFO */)) {
|
|
152
|
+
const formattedMessage = this.formatMessage(message);
|
|
153
|
+
if (data) {
|
|
154
|
+
console.info(formattedMessage, data);
|
|
155
|
+
} else {
|
|
156
|
+
console.info(formattedMessage);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
debug(message, data) {
|
|
161
|
+
if (this.shouldLog("DEBUG" /* DEBUG */)) {
|
|
162
|
+
const formattedMessage = this.formatMessage(message);
|
|
163
|
+
if (data) {
|
|
164
|
+
console.log(formattedMessage, data);
|
|
165
|
+
} else {
|
|
166
|
+
console.log(formattedMessage);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/config/env.ts
|
|
173
|
+
import { z as z2 } from "zod";
|
|
174
|
+
|
|
175
|
+
// src/config/log-levels.ts
|
|
176
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
177
|
+
LogLevel2["ERROR"] = "ERROR";
|
|
178
|
+
LogLevel2["WARN"] = "WARN";
|
|
179
|
+
LogLevel2["INFO"] = "INFO";
|
|
180
|
+
LogLevel2["DEBUG"] = "DEBUG";
|
|
181
|
+
return LogLevel2;
|
|
182
|
+
})(LogLevel || {});
|
|
183
|
+
|
|
184
|
+
// src/config/env.ts
|
|
185
|
+
var envSchema = z2.object({
|
|
186
|
+
/**
|
|
187
|
+
* LangWatch API key for event reporting.
|
|
188
|
+
* If not provided, events will not be sent to LangWatch.
|
|
189
|
+
*/
|
|
190
|
+
LANGWATCH_API_KEY: z2.string().optional(),
|
|
191
|
+
/**
|
|
192
|
+
* LangWatch endpoint URL for event reporting.
|
|
193
|
+
* Defaults to the production LangWatch endpoint.
|
|
194
|
+
*/
|
|
195
|
+
LANGWATCH_ENDPOINT: z2.string().url().default("https://app.langwatch.ai"),
|
|
196
|
+
/**
|
|
197
|
+
* Disables simulation report info messages when set to any truthy value.
|
|
198
|
+
* Useful for CI/CD environments or when you want cleaner output.
|
|
199
|
+
*/
|
|
200
|
+
SCENARIO_DISABLE_SIMULATION_REPORT_INFO: z2.string().optional().transform((val) => Boolean(val)),
|
|
201
|
+
/**
|
|
202
|
+
* Node environment - affects logging and behavior.
|
|
203
|
+
* Defaults to 'development' if not specified.
|
|
204
|
+
*/
|
|
205
|
+
NODE_ENV: z2.enum(["development", "production", "test"]).default("development"),
|
|
206
|
+
/**
|
|
207
|
+
* Log level for the scenario package.
|
|
208
|
+
* Defaults to 'info' if not specified.
|
|
209
|
+
*/
|
|
210
|
+
SCENARIO_LOG_LEVEL: z2.nativeEnum(LogLevel).optional(),
|
|
211
|
+
/**
|
|
212
|
+
* Scenario batch run ID.
|
|
213
|
+
* If not provided, a random ID will be generated.
|
|
214
|
+
*/
|
|
215
|
+
SCENARIO_BATCH_RUN_ID: z2.string().optional()
|
|
216
|
+
});
|
|
217
|
+
var env = envSchema.parse(process.env);
|
|
218
|
+
|
|
219
|
+
// src/config/index.ts
|
|
220
|
+
var logger = new Logger("scenario.config");
|
|
221
|
+
var configLoaded = false;
|
|
222
|
+
var config = null;
|
|
223
|
+
var configLoadPromise = null;
|
|
224
|
+
async function loadProjectConfig() {
|
|
225
|
+
if (configLoaded) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (configLoadPromise) {
|
|
229
|
+
return configLoadPromise;
|
|
230
|
+
}
|
|
231
|
+
configLoadPromise = (async () => {
|
|
232
|
+
try {
|
|
233
|
+
config = await loadScenarioProjectConfig();
|
|
234
|
+
logger.info("loaded scenario project config", { config });
|
|
235
|
+
} catch (error) {
|
|
236
|
+
logger.error("error loading scenario project config", { error });
|
|
237
|
+
} finally {
|
|
238
|
+
configLoaded = true;
|
|
239
|
+
}
|
|
240
|
+
})();
|
|
241
|
+
return configLoadPromise;
|
|
242
|
+
}
|
|
243
|
+
async function getProjectConfig() {
|
|
244
|
+
await loadProjectConfig();
|
|
245
|
+
return config;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/utils/ids.ts
|
|
249
|
+
import { generate, parse } from "xksuid";
|
|
250
|
+
function generateThreadId() {
|
|
251
|
+
return `thread_${generate()}`;
|
|
252
|
+
}
|
|
253
|
+
function generateScenarioRunId() {
|
|
254
|
+
return `scenariorun_${generate()}`;
|
|
255
|
+
}
|
|
256
|
+
function generateScenarioId() {
|
|
257
|
+
return `scenario_${generate()}`;
|
|
258
|
+
}
|
|
259
|
+
function getBatchRunId() {
|
|
260
|
+
if (!env.SCENARIO_BATCH_RUN_ID) {
|
|
261
|
+
env.SCENARIO_BATCH_RUN_ID = `scenariobatchrun_${generate()}`;
|
|
262
|
+
}
|
|
263
|
+
return env.SCENARIO_BATCH_RUN_ID;
|
|
264
|
+
}
|
|
265
|
+
function generateMessageId() {
|
|
266
|
+
return `scenariomsg_${generate()}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/events/event-alert-message-logger.ts
|
|
270
|
+
var EventAlertMessageLogger = class _EventAlertMessageLogger {
|
|
271
|
+
static shownBatchIds = /* @__PURE__ */ new Set();
|
|
272
|
+
/**
|
|
273
|
+
* Shows a fancy greeting message about simulation reporting status.
|
|
274
|
+
* Only shows once per batch run to avoid spam.
|
|
275
|
+
*/
|
|
276
|
+
handleGreeting() {
|
|
277
|
+
if (this.isGreetingDisabled()) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const batchRunId = getBatchRunId();
|
|
281
|
+
if (_EventAlertMessageLogger.shownBatchIds.has(batchRunId)) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
_EventAlertMessageLogger.shownBatchIds.add(batchRunId);
|
|
285
|
+
this.displayGreeting(batchRunId);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Shows a fancy message about how to watch the simulation.
|
|
289
|
+
* Called when a run started event is received with a session ID.
|
|
290
|
+
*/
|
|
291
|
+
handleWatchMessage(params) {
|
|
292
|
+
if (this.isGreetingDisabled()) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.displayWatchMessage(params);
|
|
296
|
+
}
|
|
297
|
+
isGreetingDisabled() {
|
|
298
|
+
return env.SCENARIO_DISABLE_SIMULATION_REPORT_INFO === true;
|
|
299
|
+
}
|
|
300
|
+
displayGreeting(batchRunId) {
|
|
301
|
+
const separator = "\u2500".repeat(60);
|
|
302
|
+
if (!env.LANGWATCH_API_KEY) {
|
|
303
|
+
console.log(`
|
|
304
|
+
${separator}`);
|
|
305
|
+
console.log("\u{1F680} LangWatch Simulation Reporting");
|
|
306
|
+
console.log(`${separator}`);
|
|
307
|
+
console.log("\u27A1\uFE0F API key not configured");
|
|
308
|
+
console.log(" Simulations will only output final results");
|
|
309
|
+
console.log("");
|
|
310
|
+
console.log("\u{1F4A1} To visualize conversations in real time:");
|
|
311
|
+
console.log(" \u2022 Set LANGWATCH_API_KEY environment variable");
|
|
312
|
+
console.log(" \u2022 Or configure apiKey in scenario.config.js");
|
|
313
|
+
console.log("");
|
|
314
|
+
console.log(`\u{1F4E6} Batch Run ID: ${batchRunId}`);
|
|
315
|
+
console.log(`${separator}
|
|
316
|
+
`);
|
|
317
|
+
} else {
|
|
318
|
+
console.log(`
|
|
319
|
+
${separator}`);
|
|
320
|
+
console.log("\u{1F680} LangWatch Simulation Reporting");
|
|
321
|
+
console.log(`${separator}`);
|
|
322
|
+
console.log("\u2705 Simulation reporting enabled");
|
|
323
|
+
console.log(` Endpoint: ${env.LANGWATCH_ENDPOINT}`);
|
|
324
|
+
console.log(
|
|
325
|
+
` API Key: ${env.LANGWATCH_API_KEY.length > 0 ? "Configured" : "Not configured"}`
|
|
326
|
+
);
|
|
327
|
+
console.log("");
|
|
328
|
+
console.log(`\u{1F4E6} Batch Run ID: ${batchRunId}`);
|
|
329
|
+
console.log(`${separator}
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
displayWatchMessage(params) {
|
|
334
|
+
const separator = "\u2500".repeat(60);
|
|
335
|
+
const setUrl = params.setUrl;
|
|
336
|
+
const batchUrl = `${setUrl}/${getBatchRunId()}`;
|
|
337
|
+
console.log(`
|
|
338
|
+
${separator}`);
|
|
339
|
+
console.log("\u{1F440} Watch Your Simulation Live");
|
|
340
|
+
console.log(`${separator}`);
|
|
341
|
+
console.log("\u{1F310} Open in your browser:");
|
|
342
|
+
console.log(` Scenario Set: ${setUrl}`);
|
|
343
|
+
console.log(` Batch Run: ${batchUrl}`);
|
|
344
|
+
console.log("");
|
|
345
|
+
console.log(`${separator}
|
|
346
|
+
`);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/events/schema.ts
|
|
351
|
+
import { EventType, MessagesSnapshotEventSchema } from "@ag-ui/core";
|
|
352
|
+
import { z as z3 } from "zod";
|
|
353
|
+
var Verdict = /* @__PURE__ */ ((Verdict2) => {
|
|
354
|
+
Verdict2["SUCCESS"] = "success";
|
|
355
|
+
Verdict2["FAILURE"] = "failure";
|
|
356
|
+
Verdict2["INCONCLUSIVE"] = "inconclusive";
|
|
357
|
+
return Verdict2;
|
|
358
|
+
})(Verdict || {});
|
|
359
|
+
var ScenarioRunStatus = /* @__PURE__ */ ((ScenarioRunStatus2) => {
|
|
360
|
+
ScenarioRunStatus2["SUCCESS"] = "SUCCESS";
|
|
361
|
+
ScenarioRunStatus2["ERROR"] = "ERROR";
|
|
362
|
+
ScenarioRunStatus2["CANCELLED"] = "CANCELLED";
|
|
363
|
+
ScenarioRunStatus2["IN_PROGRESS"] = "IN_PROGRESS";
|
|
364
|
+
ScenarioRunStatus2["PENDING"] = "PENDING";
|
|
365
|
+
ScenarioRunStatus2["FAILED"] = "FAILED";
|
|
366
|
+
return ScenarioRunStatus2;
|
|
367
|
+
})(ScenarioRunStatus || {});
|
|
368
|
+
var baseEventSchema = z3.object({
|
|
369
|
+
type: z3.nativeEnum(EventType),
|
|
370
|
+
timestamp: z3.number(),
|
|
371
|
+
rawEvent: z3.any().optional()
|
|
372
|
+
});
|
|
373
|
+
var batchRunIdSchema = z3.string();
|
|
374
|
+
var scenarioRunIdSchema = z3.string();
|
|
375
|
+
var scenarioIdSchema = z3.string();
|
|
376
|
+
var baseScenarioEventSchema = baseEventSchema.extend({
|
|
377
|
+
batchRunId: batchRunIdSchema,
|
|
378
|
+
scenarioId: scenarioIdSchema,
|
|
379
|
+
scenarioRunId: scenarioRunIdSchema,
|
|
380
|
+
scenarioSetId: z3.string().optional().default("default")
|
|
381
|
+
});
|
|
382
|
+
var scenarioRunStartedSchema = baseScenarioEventSchema.extend({
|
|
383
|
+
type: z3.literal("SCENARIO_RUN_STARTED" /* RUN_STARTED */),
|
|
384
|
+
metadata: z3.object({
|
|
385
|
+
name: z3.string().optional(),
|
|
386
|
+
description: z3.string().optional()
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
var scenarioResultsSchema = z3.object({
|
|
390
|
+
verdict: z3.nativeEnum(Verdict),
|
|
391
|
+
reasoning: z3.string().optional(),
|
|
392
|
+
metCriteria: z3.array(z3.string()),
|
|
393
|
+
unmetCriteria: z3.array(z3.string()),
|
|
394
|
+
error: z3.string().optional()
|
|
395
|
+
});
|
|
396
|
+
var scenarioRunFinishedSchema = baseScenarioEventSchema.extend({
|
|
397
|
+
type: z3.literal("SCENARIO_RUN_FINISHED" /* RUN_FINISHED */),
|
|
398
|
+
status: z3.nativeEnum(ScenarioRunStatus),
|
|
399
|
+
results: scenarioResultsSchema.optional().nullable()
|
|
400
|
+
});
|
|
401
|
+
var scenarioMessageSnapshotSchema = MessagesSnapshotEventSchema.merge(
|
|
402
|
+
baseScenarioEventSchema.extend({
|
|
403
|
+
type: z3.literal("SCENARIO_MESSAGE_SNAPSHOT" /* MESSAGE_SNAPSHOT */)
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
var scenarioEventSchema = z3.discriminatedUnion("type", [
|
|
407
|
+
scenarioRunStartedSchema,
|
|
408
|
+
scenarioRunFinishedSchema,
|
|
409
|
+
scenarioMessageSnapshotSchema
|
|
410
|
+
]);
|
|
411
|
+
var successSchema = z3.object({ success: z3.boolean() });
|
|
412
|
+
var errorSchema = z3.object({ error: z3.string() });
|
|
413
|
+
var stateSchema = z3.object({
|
|
414
|
+
state: z3.object({
|
|
415
|
+
messages: z3.array(z3.any()),
|
|
416
|
+
status: z3.string()
|
|
417
|
+
})
|
|
418
|
+
});
|
|
419
|
+
var runsSchema = z3.object({ runs: z3.array(z3.string()) });
|
|
420
|
+
var eventsSchema = z3.object({ events: z3.array(scenarioEventSchema) });
|
|
421
|
+
|
|
422
|
+
// src/events/event-reporter.ts
|
|
423
|
+
var EventReporter = class {
|
|
424
|
+
apiKey;
|
|
425
|
+
eventsEndpoint;
|
|
426
|
+
eventAlertMessageLogger;
|
|
427
|
+
logger = new Logger("scenario.events.EventReporter");
|
|
428
|
+
constructor(config2) {
|
|
429
|
+
this.apiKey = config2.apiKey ?? "";
|
|
430
|
+
this.eventsEndpoint = new URL("/api/scenario-events", config2.endpoint);
|
|
431
|
+
this.eventAlertMessageLogger = new EventAlertMessageLogger();
|
|
432
|
+
this.eventAlertMessageLogger.handleGreeting();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Posts an event to the configured endpoint.
|
|
436
|
+
* Logs success/failure but doesn't throw - event posting shouldn't break scenario execution.
|
|
437
|
+
*/
|
|
438
|
+
async postEvent(event) {
|
|
439
|
+
const result = {};
|
|
440
|
+
this.logger.debug(`[${event.type}] Posting event`, { event });
|
|
441
|
+
const processedEvent = this.processEventForApi(event);
|
|
442
|
+
try {
|
|
443
|
+
const response = await fetch(this.eventsEndpoint.href, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
body: JSON.stringify(processedEvent),
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json",
|
|
448
|
+
"X-Auth-Token": this.apiKey
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
this.logger.debug(
|
|
452
|
+
`[${event.type}] Event POST response status: ${response.status}`
|
|
453
|
+
);
|
|
454
|
+
if (response.ok) {
|
|
455
|
+
const data = await response.json();
|
|
456
|
+
this.logger.debug(`[${event.type}] Event POST response:`, data);
|
|
457
|
+
result.setUrl = data.url;
|
|
458
|
+
} else {
|
|
459
|
+
const errorText = await response.text();
|
|
460
|
+
this.logger.error(`[${event.type}] Event POST failed:`, {
|
|
461
|
+
status: response.status,
|
|
462
|
+
statusText: response.statusText,
|
|
463
|
+
error: errorText,
|
|
464
|
+
event: JSON.stringify(processedEvent)
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
this.logger.error(`[${event.type}] Event POST error:`, {
|
|
469
|
+
error,
|
|
470
|
+
event: JSON.stringify(processedEvent),
|
|
471
|
+
endpoint: this.eventsEndpoint.href
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Processes event data to ensure API compatibility.
|
|
478
|
+
* Converts message content objects to strings when needed.
|
|
479
|
+
*/
|
|
480
|
+
processEventForApi(event) {
|
|
481
|
+
if (event.type === "SCENARIO_MESSAGE_SNAPSHOT" /* MESSAGE_SNAPSHOT */) {
|
|
482
|
+
return {
|
|
483
|
+
...event,
|
|
484
|
+
messages: event.messages.map((message) => ({
|
|
485
|
+
...message,
|
|
486
|
+
content: typeof message.content !== "string" ? JSON.stringify(message.content) : message.content
|
|
487
|
+
}))
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return event;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/events/event-bus.ts
|
|
495
|
+
var EventBus = class _EventBus {
|
|
496
|
+
static registry = /* @__PURE__ */ new Set();
|
|
497
|
+
events$ = new Subject();
|
|
498
|
+
eventReporter;
|
|
499
|
+
eventAlertMessageLogger;
|
|
500
|
+
processingPromise = null;
|
|
501
|
+
logger = new Logger("scenario.events.EventBus");
|
|
502
|
+
static globalListeners = [];
|
|
503
|
+
constructor(config2) {
|
|
504
|
+
this.eventReporter = new EventReporter(config2);
|
|
505
|
+
this.eventAlertMessageLogger = new EventAlertMessageLogger();
|
|
506
|
+
_EventBus.registry.add(this);
|
|
507
|
+
for (const listener of _EventBus.globalListeners) {
|
|
508
|
+
listener(this);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
static getAllBuses() {
|
|
512
|
+
return _EventBus.registry;
|
|
513
|
+
}
|
|
514
|
+
static addGlobalListener(listener) {
|
|
515
|
+
_EventBus.globalListeners.push(listener);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Publishes an event into the processing pipeline.
|
|
519
|
+
*/
|
|
520
|
+
publish(event) {
|
|
521
|
+
this.logger.debug(`[${event.type}] Publishing event`, {
|
|
522
|
+
event
|
|
523
|
+
});
|
|
524
|
+
this.events$.next(event);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Begins listening for and processing events.
|
|
528
|
+
* Returns a promise that resolves when a RUN_FINISHED event is fully processed.
|
|
529
|
+
*/
|
|
530
|
+
listen() {
|
|
531
|
+
this.logger.debug("Listening for events");
|
|
532
|
+
if (this.processingPromise) {
|
|
533
|
+
return this.processingPromise;
|
|
534
|
+
}
|
|
535
|
+
this.processingPromise = new Promise((resolve, reject) => {
|
|
536
|
+
this.events$.pipe(
|
|
537
|
+
// Post events and get results
|
|
538
|
+
concatMap(async (event) => {
|
|
539
|
+
this.logger.debug(`[${event.type}] Processing event`, { event });
|
|
540
|
+
const result = await this.eventReporter.postEvent(event);
|
|
541
|
+
return { event, result };
|
|
542
|
+
}),
|
|
543
|
+
// Handle watch messages reactively
|
|
544
|
+
tap(({ event, result }) => {
|
|
545
|
+
if (event.type === "SCENARIO_RUN_STARTED" /* RUN_STARTED */ && result.setUrl) {
|
|
546
|
+
this.eventAlertMessageLogger.handleWatchMessage({
|
|
547
|
+
scenarioSetId: event.scenarioSetId,
|
|
548
|
+
scenarioRunId: event.scenarioRunId,
|
|
549
|
+
setUrl: result.setUrl
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}),
|
|
553
|
+
// Extract just the event for downstream processing
|
|
554
|
+
map(({ event }) => event),
|
|
555
|
+
catchError((error) => {
|
|
556
|
+
this.logger.error("Error in event stream:", error);
|
|
557
|
+
return EMPTY;
|
|
558
|
+
})
|
|
559
|
+
).subscribe({
|
|
560
|
+
next: (event) => {
|
|
561
|
+
this.logger.debug(`[${event.type}] Event processed`, { event });
|
|
562
|
+
if (event.type === "SCENARIO_RUN_FINISHED" /* RUN_FINISHED */) {
|
|
563
|
+
resolve();
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
error: (error) => {
|
|
567
|
+
this.logger.error("Error in event stream:", error);
|
|
568
|
+
reject(error);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
return this.processingPromise;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Stops accepting new events and drains the processing queue.
|
|
576
|
+
*/
|
|
577
|
+
async drain() {
|
|
578
|
+
this.logger.debug("Draining event stream");
|
|
579
|
+
this.events$.complete();
|
|
580
|
+
if (this.processingPromise) {
|
|
581
|
+
await this.processingPromise;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Subscribes to an event stream.
|
|
586
|
+
* @param source$ - The event stream to subscribe to.
|
|
587
|
+
*/
|
|
588
|
+
subscribeTo(source$) {
|
|
589
|
+
this.logger.debug("Subscribing to event stream");
|
|
590
|
+
return source$.subscribe(this.events$);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Expose the events$ observable for external subscription (read-only).
|
|
594
|
+
*/
|
|
595
|
+
get eventsObservable() {
|
|
596
|
+
return this.events$.asObservable();
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
export {
|
|
601
|
+
scenarioProjectConfigSchema,
|
|
602
|
+
defineConfig,
|
|
603
|
+
AgentRole,
|
|
604
|
+
allAgentRoles,
|
|
605
|
+
AgentAdapter,
|
|
606
|
+
UserSimulatorAgentAdapter,
|
|
607
|
+
JudgeAgentAdapter,
|
|
608
|
+
domain_exports,
|
|
609
|
+
loadScenarioProjectConfig,
|
|
610
|
+
Logger,
|
|
611
|
+
getProjectConfig,
|
|
612
|
+
generateThreadId,
|
|
613
|
+
generateScenarioRunId,
|
|
614
|
+
generateScenarioId,
|
|
615
|
+
getBatchRunId,
|
|
616
|
+
generateMessageId,
|
|
617
|
+
EventBus
|
|
618
|
+
};
|