@langwatch/scenario 0.2.12 → 0.3.0

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 CHANGED
@@ -78,7 +78,7 @@ import { describe, it, expect } from "vitest";
78
78
  import { openai } from "@ai-sdk/openai";
79
79
  import scenario, { type AgentAdapter, AgentRole } from "@langwatch/scenario";
80
80
  import { generateText, tool } from "ai";
81
- import { z } from "zod";
81
+ import { z } from "zod/v4";
82
82
 
83
83
  describe("Weather Agent", () => {
84
84
  it("should get the weather for a city", async () => {
@@ -103,13 +103,40 @@ describe("Weather Agent", () => {
103
103
  tools: { get_current_weather: getCurrentWeather },
104
104
  });
105
105
 
106
- if (response.toolCalls?.length) {
107
- // For simplicity, we'll just return the arguments of the first tool call
108
- const { toolName, args } = response.toolCalls[0];
109
- return {
110
- role: "tool",
111
- content: [{ type: "tool-result", toolName, result: args }],
112
- };
106
+ if (response.toolCalls && response.toolCalls.length > 0) {
107
+ const toolCall = response.toolCalls[0];
108
+ // Agent executes the tool directly and returns both messages
109
+ const toolResult = await getCurrentWeather.execute(
110
+ toolCall.input as { city: string },
111
+ {
112
+ toolCallId: toolCall.toolCallId,
113
+ messages: input.messages,
114
+ }
115
+ );
116
+ return [
117
+ {
118
+ role: "assistant",
119
+ content: [
120
+ {
121
+ type: "tool-call",
122
+ toolName: toolCall.toolName,
123
+ toolCallId: toolCall.toolCallId,
124
+ input: toolCall.input,
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ role: "tool",
130
+ content: [
131
+ {
132
+ type: "tool-result",
133
+ toolName: toolCall.toolName,
134
+ toolCallId: toolCall.toolCallId,
135
+ output: { type: "text", value: toolResult as string },
136
+ },
137
+ ],
138
+ },
139
+ ];
113
140
  }
114
141
 
115
142
  return response.text;
@@ -1,20 +1,23 @@
1
1
  import {
2
2
  Logger,
3
3
  getEnv
4
- } from "./chunk-OL4RFXV4.mjs";
4
+ } from "./chunk-RHTLQKEJ.mjs";
5
5
  import {
6
6
  __export
7
7
  } from "./chunk-7P6ASYW6.mjs";
8
8
 
9
9
  // src/domain/core/config.ts
10
- import { z } from "zod";
10
+ import { z } from "zod/v4";
11
11
  var DEFAULT_TEMPERATURE = 0;
12
12
  var scenarioProjectConfigSchema = z.object({
13
13
  defaultModel: z.object({
14
14
  model: z.custom(),
15
15
  temperature: z.number().min(0).max(1).optional().default(DEFAULT_TEMPERATURE),
16
16
  maxTokens: z.number().optional()
17
- }).optional()
17
+ }).optional(),
18
+ headless: z.boolean().optional().default(
19
+ typeof process !== "undefined" ? !["false", "0"].includes(process.env.SCENARIO_HEADLESS || "false") : false
20
+ )
18
21
  }).strict();
19
22
  function defineConfig(config2) {
20
23
  return config2;
@@ -71,6 +74,12 @@ import {
71
74
  map
72
75
  } from "rxjs";
73
76
 
77
+ // src/events/event-alert-message-logger.ts
78
+ import * as fs2 from "fs";
79
+ import * as os from "os";
80
+ import * as path2 from "path";
81
+ import open from "open";
82
+
74
83
  // src/config/load.ts
75
84
  import fs from "node:fs/promises";
76
85
  import path from "node:path";
@@ -139,7 +148,7 @@ import process2 from "node:process";
139
148
  import { generate, parse } from "xksuid";
140
149
  var batchRunId;
141
150
  function generateThreadId() {
142
- return `thread_${generate()}`;
151
+ return `scenariothread_${generate()}`;
143
152
  }
144
153
  function generateScenarioRunId() {
145
154
  return `scenariorun_${generate()}`;
@@ -161,9 +170,9 @@ function getBatchRunId() {
161
170
  const week = String(getISOWeekNumber(now)).padStart(2, "0");
162
171
  const raw = `${parentProcessId}_${year}_w${week}`;
163
172
  const hash = crypto.createHash("sha256").update(raw).digest("hex").slice(0, 12);
164
- return batchRunId = `scenariobatchrun_${hash}`;
173
+ return batchRunId = `scenariobatch_${hash}`;
165
174
  }
166
- return batchRunId = `scenariobatchrun_${generate()}`;
175
+ return batchRunId = `scenariobatch_${generate()}`;
167
176
  }
168
177
  function getISOWeekNumber(date) {
169
178
  const tmp = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
@@ -178,8 +187,23 @@ function generateMessageId() {
178
187
  }
179
188
 
180
189
  // src/events/event-alert-message-logger.ts
181
- var EventAlertMessageLogger = class _EventAlertMessageLogger {
182
- static shownBatchIds = /* @__PURE__ */ new Set();
190
+ var EventAlertMessageLogger = class {
191
+ /**
192
+ * Creates a coordination file to prevent duplicate messages across processes.
193
+ * Returns true if this process should show the message (first one to create the file).
194
+ */
195
+ createCoordinationFile(type) {
196
+ try {
197
+ const batchId = getBatchRunId();
198
+ const tmpDir = os.tmpdir();
199
+ const fileName = `scenario-${type}-${batchId}`;
200
+ const filePath = path2.join(tmpDir, fileName);
201
+ fs2.writeFileSync(filePath, process.pid.toString(), { flag: "wx" });
202
+ return true;
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
183
207
  /**
184
208
  * Shows a fancy greeting message about simulation reporting status.
185
209
  * Only shows once per batch run to avoid spam.
@@ -188,21 +212,23 @@ var EventAlertMessageLogger = class _EventAlertMessageLogger {
188
212
  if (this.isGreetingDisabled()) {
189
213
  return;
190
214
  }
191
- if (_EventAlertMessageLogger.shownBatchIds.has(getBatchRunId())) {
215
+ if (!this.createCoordinationFile("greeting")) {
192
216
  return;
193
217
  }
194
- _EventAlertMessageLogger.shownBatchIds.add(getBatchRunId());
195
218
  this.displayGreeting();
196
219
  }
197
220
  /**
198
221
  * Shows a fancy message about how to watch the simulation.
199
222
  * Called when a run started event is received with a session ID.
200
223
  */
201
- handleWatchMessage(params) {
224
+ async handleWatchMessage(params) {
202
225
  if (this.isGreetingDisabled()) {
203
226
  return;
204
227
  }
205
- this.displayWatchMessage(params);
228
+ if (!this.createCoordinationFile(`watch-${params.scenarioSetId}`)) {
229
+ return;
230
+ }
231
+ await this.displayWatchMessage(params);
206
232
  }
207
233
  isGreetingDisabled() {
208
234
  return getEnv().SCENARIO_DISABLE_SIMULATION_REPORT_INFO === true;
@@ -213,54 +239,37 @@ var EventAlertMessageLogger = class _EventAlertMessageLogger {
213
239
  if (!env.LANGWATCH_API_KEY) {
214
240
  console.log(`
215
241
  ${separator}`);
216
- console.log("\u{1F680} LangWatch Simulation Reporting");
242
+ console.log("\u{1F3AD} Running Scenario Tests");
217
243
  console.log(`${separator}`);
218
- console.log("\u27A1\uFE0F API key not configured");
244
+ console.log("\u27A1\uFE0F LangWatch API key not configured");
219
245
  console.log(" Simulations will only output final results");
220
246
  console.log("");
221
247
  console.log("\u{1F4A1} To visualize conversations in real time:");
222
248
  console.log(" \u2022 Set LANGWATCH_API_KEY environment variable");
223
249
  console.log(" \u2022 Or configure apiKey in scenario.config.js");
224
250
  console.log("");
225
- console.log(`\u{1F4E6} Batch Run ID: ${getBatchRunId()}`);
226
- console.log("");
227
- console.log("\u{1F507} To disable these messages:");
228
- console.log(" \u2022 Set SCENARIO_DISABLE_SIMULATION_REPORT_INFO=true");
229
- console.log(`${separator}
230
- `);
231
- } else {
232
- console.log(`
233
- ${separator}`);
234
- console.log("\u{1F680} LangWatch Simulation Reporting");
235
- console.log(`${separator}`);
236
- console.log("\u2705 Simulation reporting enabled");
237
- console.log(` Endpoint: ${env.LANGWATCH_ENDPOINT}`);
238
- console.log(
239
- ` API Key: ${env.LANGWATCH_API_KEY.length > 0 ? "Configured" : "Not configured"}`
240
- );
241
- console.log("");
242
- console.log(`\u{1F4E6} Batch Run ID: ${getBatchRunId()}`);
243
- console.log("");
244
- console.log("\u{1F507} To disable these messages:");
245
- console.log(" \u2022 Set SCENARIO_DISABLE_SIMULATION_REPORT_INFO=true");
246
251
  console.log(`${separator}
247
252
  `);
248
253
  }
249
254
  }
250
- displayWatchMessage(params) {
255
+ async displayWatchMessage(params) {
251
256
  const separator = "\u2500".repeat(60);
252
257
  const setUrl = params.setUrl;
253
258
  const batchUrl = `${setUrl}/${getBatchRunId()}`;
254
259
  console.log(`
255
260
  ${separator}`);
256
- console.log("\u{1F440} Watch Your Simulation Live");
261
+ console.log("\u{1F3AD} Running Scenario Tests");
257
262
  console.log(`${separator}`);
258
- console.log("\u{1F310} Open in your browser:");
259
- console.log(` Scenario Set: ${setUrl}`);
260
- console.log(` Batch Run: ${batchUrl}`);
261
- console.log("");
263
+ console.log(`Follow it live: ${batchUrl}`);
262
264
  console.log(`${separator}
263
265
  `);
266
+ const projectConfig = await getProjectConfig();
267
+ if (!(projectConfig == null ? void 0 : projectConfig.headless)) {
268
+ try {
269
+ open(batchUrl);
270
+ } catch (_) {
271
+ }
272
+ }
264
273
  }
265
274
  };
266
275
 
@@ -461,9 +470,9 @@ var EventBus = class _EventBus {
461
470
  return { event, result };
462
471
  }),
463
472
  // Handle watch messages reactively
464
- tap(({ event, result }) => {
473
+ tap(async ({ event, result }) => {
465
474
  if (event.type === "SCENARIO_RUN_STARTED" /* RUN_STARTED */ && result.setUrl) {
466
- this.eventAlertMessageLogger.handleWatchMessage({
475
+ await this.eventAlertMessageLogger.handleWatchMessage({
467
476
  scenarioSetId: event.scenarioSetId,
468
477
  scenarioRunId: event.scenarioRunId,
469
478
  setUrl: result.setUrl
@@ -1,5 +1,5 @@
1
1
  // src/config/env.ts
2
- import { z } from "zod";
2
+ import { z } from "zod/v4";
3
3
 
4
4
  // src/config/log-levels.ts
5
5
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {